v5.51.3: Event-driven metadata refresh, PostgreSQL dialect fixes, and Explorer accessibility
Event-driven metadata refresh, accessibility improvements, and host-controllable whiteboard rosters for the certified 5.x line. Plus PostgreSQL dialect, LLM JSON repair, metadata cache interval, and metadata sync fixes.
Improvements
Section titled “Improvements”- Explorer shell accessibility and focus containment improved (
@memberjunction/ng-explorer-core,@memberjunction/ng-shared-generic,@memberjunction/ng-whiteboard): fixes eight WCAG 2.1 A/AA findings across the Explorer shell and shared components. A “Skip to main content” link is now the first focusable element in the shell (ng-explorer-core), marking the routed content region withrole="main"(consuming applications that previously added a custom skip link should remove it on upgrade to avoid duplicate links). Accessible names were added to the global search input, mobile navigation toggle, and account/avatar button. Closed mobile nav drawers and search popups are markedinertwithvisibility: hiddenso controls inside them do not retain tab focus when closed. The command palette now traps tab focus while open and restores focus on close. A new--mj-focus-ring-colorCSS variable (ng-shared-generic) accompanies--mj-focus-ringfor validoutlinesyntax. Whiteboard single-character tool shortcuts (v h p r s t m w i c e) and key handlers (ng-whiteboard) are now scoped to host focus rather than listening globally across the document. - Whiteboard tool availability controlled via host ToolRoster (
@memberjunction/ng-whiteboard):RealtimeWhiteboardHostComponentgains an@Input() ToolRoster: readonly WhiteboardTool[] | nullproperty (defaulting tonullfor all tools). The roster restricts tool availability across the toolbar buttons, single-letter keyboard shortcuts, and canvas context menu “add widget” actions simultaneously. Selecting a disallowed tool automatically falls back toselector the first available roster entry.
Bug Fixes
Section titled “Bug Fixes”- Event-driven metadata refresh enforces schema and permission updates automatically (
@memberjunction/core,@memberjunction/generic-database-provider,@memberjunction/graphql-dataprovider): previously, server instances refreshed in-memory metadata only through periodic polling whose staleness check evaluated remote timestamps against cached dataset slots (a closed loop), delaying permission enforcement until process restart.ProviderBasenow tracks metadata dataset membership (registerMetadataDatasetMembership) and routes entity save, delete, and remote invalidation events to refresh member entities. Database providers debounce refreshes by 500ms and run a hard refresh, whileGraphQLDataProvidercoalesces checks into a jittered 15–45s window and re-pulls metadata only when genuinely stale. Single-flight guards prevent concurrent reload races, andGetDatasetStatusByNamenow queries authoritative SQL MAX/COUNT per item using stored and runtimeWhereClausefilters. - Engine derived state rebuilt on cross-server cache updates (
@memberjunction/core,@memberjunction/ai-engine-base): in multi-server deployments with Redis pub/sub enabled,BaseEngine.OnExternalCacheChangereplaced entity arrays from peer cache payloads without re-runningAdditionalLoading. Derived state in subclasses (such as model-vendor pairings inAIEngineBase) became empty while row counts appeared valid, causing AI model selection to fail with “No suitable model found”.AdditionalLoadingnow runs on both cache payload fast-paths and full reloads before property-change notifications are emitted.AIEngineBase.AdditionalLoadingwas also made idempotent and linear to prevent unbounded duplicate collection growth on long-lived processes. - LLM response JSON parse errors recovered deterministically (
@memberjunction/global,@memberjunction/ai-prompts,@memberjunction/ai-core-plus): unescaped quote characters inside markdown or diagram payloads embedded in JSON string fields previously caused agent responses to fail or exhaust retries.CleanJSONfence extraction now skips input already formatted as a JSON envelope, preventing embedded markdown fences inside string values from stripping the envelope. The repair chain error derivation (resolveTrueParseError) now passes the raw parse error directly to AI repair prompts rather than intermediate transform errors. A new deterministicRepairJSONEscaping()helper in@memberjunction/globalwalks failure offsets to escape broken quotes in microseconds without extra LLM completion tokens or content modifications. - Dataset items isolated in dedicated cache namespaces (
@memberjunction/core,@memberjunction/generic-database-provider):GetDatasetByNamecached dataset item rows using the same cache key builder asRunView. Because shipped dataset items haveNULLwhere clauses, dataset reads and unfiltered entity reads shared cache slots, serving deleted or mismatched rows (such as metadata datasets serving deleted query entities or truncated column sets). Each dataset item now receives a dedicated<dataset>/<item code>cache namespace, isolating dataset item reads from generalRunViewcache slots. - Metadata cache auto-refresh interval restored to configured schedule (
@memberjunction/server,@memberjunction/sqlserver-dataprovider):databaseSettings.metadataCacheRefreshInterval(configured in milliseconds, default 180,000 = 3 minutes) was passed undivided into SQL Server provider configuration expecting seconds, resulting insetIntervalscheduling refreshes every ~50 hours. The unit conversions were corrected inMJServercall sites andSQLServerDataProviderconfig, restoring the 3-minute auto-refresh cycle for metadata and user caches. - PostgreSQL keyword and function auto-quoting handling corrected (
@memberjunction/postgresql-dataprovider,@memberjunction/generic-database-provider): the PostgreSQL provider auto-quoted unrecognized capitalized words, turning valid ALL-CAPS function calls (e.g.PERCENTILE_CONT,DATE_TRUNC) or keywords (e.g.CURRENT_DATE,NULLS LAST) into quoted identifiers that PostgreSQL rejected. ALL-CAPS function calls followed by(are now left unquoted, and 87 PostgreSQL keywords were added to the provider allowlist. Additionally, applyingMaxRowscapping on PostgreSQL now appendsLIMIT Nas text for eligible simple queries instead of round-tripping throughnode-sql-parserAST generation, preserving statement text and avoiding unwanted re-quoting. - Transitive bridge views generated in database-native SQL dialects (
@memberjunction/db-auto-doc,@memberjunction/codegen-lib):DBAutoDocpreviously emitted bracket-quoted SQL Server syntax for transitive bridge views (TransitiveView.SQL), breaking view creation on PostgreSQL. Identifiers are now quoted according to the provider dialect (brackets for SQL Server, double quotes for PostgreSQL and Oracle, backticks for MySQL). CodeGen now generates PostgreSQLTransitiveViewbridge views usingCREATE OR REPLACE VIEWviaCodeGenDatabaseProvider.generateCreateOrReplaceViewSQL(falling back to drop-and-recreate when column lists change), and appends missingGObatch terminators after SQL Server view definitions in migration logs. - Metadata sync loads host packages and handles missing entity subclasses (
@memberjunction/metadata-sync):mj sync pushandmj sync pullnow load host Open App server packages (dynamicPackages.server) and generated entity packages (codeGeneration.packages), executing customSave()hooks, validations, and typed properties during sync.mj sync pullnow reads primary keys throughBaseEntity.Get()rather than typed properties (record.ID), preventing records with unregistered entity subclasses from evaluating primary keys asundefinedand collapsing into single overwritten records in write batches. - Credential updates protected against concurrent write races (
@memberjunction/credentials):CredentialEngine.getCredential()executed an un-serializedLastUsedAttimestamp save from a stale entity snapshot, which could overwrite and revert concurrentupdateCredential()writes that committed in between. All writes inCredentialEnginenow serialize through per-credential promise chains, ensuring background timestamp updates commit against current entity state without introducing lookup latency.