Skip to content

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.

  • 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 with role="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 marked inert with visibility: hidden so 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-color CSS variable (ng-shared-generic) accompanies --mj-focus-ring for valid outline syntax. 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): RealtimeWhiteboardHostComponent gains an @Input() ToolRoster: readonly WhiteboardTool[] | null property (defaulting to null for 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 to select or the first available roster entry.
  • 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. ProviderBase now 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, while GraphQLDataProvider coalesces checks into a jittered 15–45s window and re-pulls metadata only when genuinely stale. Single-flight guards prevent concurrent reload races, and GetDatasetStatusByName now queries authoritative SQL MAX/COUNT per item using stored and runtime WhereClause filters.
  • 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.OnExternalCacheChange replaced entity arrays from peer cache payloads without re-running AdditionalLoading. Derived state in subclasses (such as model-vendor pairings in AIEngineBase) became empty while row counts appeared valid, causing AI model selection to fail with “No suitable model found”. AdditionalLoading now runs on both cache payload fast-paths and full reloads before property-change notifications are emitted. AIEngineBase.AdditionalLoading was 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. CleanJSON fence 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 deterministic RepairJSONEscaping() helper in @memberjunction/global walks 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): GetDatasetByName cached dataset item rows using the same cache key builder as RunView. Because shipped dataset items have NULL where 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 general RunView cache 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 in setInterval scheduling refreshes every ~50 hours. The unit conversions were corrected in MJServer call sites and SQLServerDataProvider config, 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, applying MaxRows capping on PostgreSQL now appends LIMIT N as text for eligible simple queries instead of round-tripping through node-sql-parser AST 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): DBAutoDoc previously 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 PostgreSQL TransitiveView bridge views using CREATE OR REPLACE VIEW via CodeGenDatabaseProvider.generateCreateOrReplaceViewSQL (falling back to drop-and-recreate when column lists change), and appends missing GO batch terminators after SQL Server view definitions in migration logs.
  • Metadata sync loads host packages and handles missing entity subclasses (@memberjunction/metadata-sync): mj sync push and mj sync pull now load host Open App server packages (dynamicPackages.server) and generated entity packages (codeGeneration.packages), executing custom Save() hooks, validations, and typed properties during sync. mj sync pull now reads primary keys through BaseEntity.Get() rather than typed properties (record.ID), preventing records with unregistered entity subclasses from evaluating primary keys as undefined and collapsing into single overwritten records in write batches.
  • Credential updates protected against concurrent write races (@memberjunction/credentials): CredentialEngine.getCredential() executed an un-serialized LastUsedAt timestamp save from a stale entity snapshot, which could overwrite and revert concurrent updateCredential() writes that committed in between. All writes in CredentialEngine now serialize through per-credential promise chains, ensuring background timestamp updates commit against current entity state without introducing lookup latency.