Protected_Result from PreRunQueries hook containing cache status for batch operations
Protected_Result from PreRunQuery hook containing cache status and optional cached result
Protected_Result from PreRunView hook containing cache status and optional cached result
OptionalcachedResult?: RunViewResultOptionalcallerRequestedFields?: string[]The caller's original Fields list (lowercased), captured before PreRunView widened params.Fields to all entity fields for cache-superset storage. Non-null ONLY when that widening actually happened — PostRunView uses it to project cache-miss DB results back down to the requested shape.
Optionalfingerprint?: stringOptionaltelemetryEventId?: stringProtected_Result from PreRunViews hook containing cache status for batch operations
OptionalcachedResults?: RunViewResult[]OptionalcacheStatusMap?: Map<OptionalcallerFieldsMap?: Map<number, string[]>Per-param-index caller Fields lists (lowercased), captured before PreRunViews widened params.Fields to all entity fields for cache-superset storage. An index is present ONLY when that widening actually happened — PostRunViews uses it to project cache-miss DB results back down to the requested shape.
OptionalfingerprintMap?: Map<number, string>Per-param-index cache fingerprints computed during PreRunViews — carried forward so PostRunViews doesn't recompute the RLS where-clause and fingerprint string for every batch item.
OptionalsmartCacheCheckParams?: RunViewWithCacheCheckParams[]When CacheLocal is enabled, contains the cache check params to send to server
OptionaltelemetryEventId?: stringOptionaluncachedParams?: RunViewParams[]OptionaluseSmartCacheCheck?: booleanWhen CacheLocal is enabled, indicates we should use smart cache check
Protected Static_StaticClientRandom jitter added on top of ClientMemberRefreshWindowMinMs each time the window is armed, so the fleet of connected browsers never runs its checks in the same instant.
StaticClientMinimum client-side coalescing window after a metadata member write, in milliseconds. See MetadataMemberRefreshDelayMs for why this is tens of seconds and jittered.
StaticCoalesceWhen enabled, concurrent RunViews calls arriving within the same microtask (or within CoalesceWindowMs) are merged into a single mega-batch before hitting the network. This dramatically reduces the number of HTTP round-trips during startup when multiple engines independently call RunViews in parallel.
Set to 0 to disable coalescing. Default 10ms — enough to capture all engines that fire in the same tick, without adding perceptible delay.
StaticDedupHow long (ms) a resolved RunViews result stays available for instant replay. Set to 0 to disable the linger window (in-flight dedup still applies). Default 5 000 ms.
StaticMaxSafety cap on the number of linger entries held simultaneously. The linger window is a latency optimization — under extreme churn (more distinct query keys than this resolving within one window) new resolutions skip lingering instead of accumulating result arrays in memory.
StaticMetadataCoalescing window, in milliseconds, for metadata refreshes triggered by writes to the entities that compose this provider's metadata. A single administrative action produces a burst (enabling field security writes one permission row per field/role pair, each raising its own event) — one refresh per burst, not one per row. The window also gives an enclosing entity transaction time to COMMIT before the re-read: every event in the burst re-arms the timer, so the refresh runs no earlier than this long after the LAST write.
StaticMinMinimum interval (ms) between metadata refresh checks to prevent redundant network calls when Config()/RefreshIfNeeded() fire in quick succession (e.g., multiple engines during startup). Does NOT affect forced Refresh() calls. Default: 30 000 ms.
StaticServerMaximum row count for auto-caching on the server side. When
TrustLocalCacheCompletely is true and a RunView result has no
ExtraFilter, no OrderBy, and the result count is at or below this
threshold, the result is automatically stored in LocalCacheManager
even without an explicit CacheLocal flag.
This captures small reference/lookup tables that are repeatedly queried by multiple clients while avoiding caching large ad-hoc result sets. Invalidation is handled by the standard BaseEntity event-driven upsert (safe because unfiltered caches can be updated in-place).
Set to 0 to disable auto-caching. Default 250.
StaticVerboseOpt-in verbose logging for the real-time cache-invalidation subscription. Off by default — these
messages fire on every cross-server save/delete and flood the console. Set to true (e.g. from
the console: GraphQLDataProvider.VerboseCacheInvalidationLogging = true) only when debugging
cache-invalidation / cross-server sync behavior.
Gets the AI client for executing AI operations through GraphQL. The client is lazily initialized on first access.
The GraphQLAIClient instance
Gets all explorer navigation items including inactive ones.
Array of all ExplorerNavigationItem objects
Returns the currently loaded local metadata from within the instance
ProtectedAllowDetermines if a refresh is currently allowed or not. Subclasses should return FALSE if they are performing operations that should prevent refreshes. This helps avoid metadata refreshes during critical operations.
Gets all application metadata in the system.
Array of ApplicationInfo objects representing all applications
Gets all audit log types defined for tracking system activities.
Array of AuditLogTypeInfo objects
Gets the flat collection of authorization-role assignments.
Consumed lazily by AuthorizationInfo.Roles — consumers should
prefer accessing roles through AuthorizationInfo.Roles rather than
filtering this array directly.
Array of AuthorizationRoleInfo join-table objects
Gets all authorization definitions in the system.
Array of AuthorizationInfo objects defining permissions
Gets the configuration data that was provided to the provider.
The provider configuration including schema filters
Gets the current user's information including roles and permissions.
UserInfo object for the authenticated user
This getter is not implemented for the GraphQLDataProvider class.
Gets all entity metadata in the system.
Array of EntityInfo objects representing all entities
Returns the filesystem provider for the current environment. Default implementation returns null (no filesystem access). Server-side providers should override this to return a NodeFileSystemProvider.
The connection string for each GraphQLProvider instance is simply the URL for the GraphQL endpoint. This is because each GraphQLDataProvider instance can be configured with a different URL and each URL is a unique combination of host/port/etc.
Gets the latest metadata timestamps from local cache. Used for comparison with remote timestamps.
Array of locally cached metadata timestamps
Gets the latest metadata timestamps from the remote server. Used to determine if local cache is out of date.
Array of metadata timestamp information
Gets all library definitions in the system.
Array of LibraryInfo objects representing code libraries
ProtectedLocalThe GraphQLDataProvider uses a prefix for local storage that is equal to the URL of the GraphQL endpoint. This is because the GraphQLDataProvider can be configured multiple times with different URLs and each configuration will have its own local storage. This is useful when you want to have multiple connections to different servers and you don't want the local storage to be shared between them. The URL is normalized to remove special characters and replace anything other than alphanumeric characters with an underscore.
Gets the local storage provider implementation. Must be implemented by subclasses to provide environment-specific storage.
Local storage provider instance
ProtectedMetadataProtectedMetadataThe client's window is long and RANDOMIZED where the server's is a short debounce,
because the economics are inverted. MJ_Metadata's members are not only permission
entities — they include entities ordinary users and agents write routinely
(MJ: Dashboards, MJ: Queries and its children, MJ: Libraries), and the server
broadcasts every save to every connected browser. With the server-style 500ms window,
one dashboard save would make EVERY session run a staleness check and — since the member
table's timestamp genuinely moved — re-pull the multi-megabyte metadata graph, all
within the same half-second: at 200 sessions, gigabytes of egress per routine save.
A window of 15–45s (uniform jitter per arming) caps each browser at one status check and
at most one pull per window regardless of org-wide write rate, and spreads those pulls
so they cannot stampede the server. Client metadata freshness is a UX nicety, not an
enforcement surface — the server enforces from its OWN metadata on its unchanged ~1–2s
path, so a browser rendering a just-revoked column for up to a window is display-only
(the wire strips it regardless).
ProtectedMetadataCoalesce instead of debounce: with a window this long, re-arming on every event would let steady org-wide write activity postpone the refresh forever. Joining the armed window guarantees at most one refresh per window at any write rate.
Returns the database platform key for this provider. Override in subclasses to return the appropriate platform. Defaults to 'sqlserver' for backward compatibility.
ProtectedPreProtectedPreProtectedPreOptionalcachedResult?: RunViewResultOptionalcallerRequestedFields?: string[]The caller's original Fields list (lowercased), captured before PreRunView widened params.Fields to all entity fields for cache-superset storage. Non-null ONLY when that widening actually happened — PostRunView uses it to project cache-miss DB results back down to the requested shape.
Optionalfingerprint?: stringOptionaltelemetryEventId?: stringProtectedPreOptionalcachedResults?: RunViewResult[]OptionalcacheStatusMap?: Map<OptionalcallerFieldsMap?: Map<number, string[]>Per-param-index caller Fields lists (lowercased), captured before PreRunViews widened params.Fields to all entity fields for cache-superset storage. An index is present ONLY when that widening actually happened — PostRunViews uses it to project cache-miss DB results back down to the requested shape.
OptionalfingerprintMap?: Map<number, string>Per-param-index cache fingerprints computed during PreRunViews — carried forward so PostRunViews doesn't recompute the RLS where-clause and fingerprint string for every batch item.
OptionalsmartCacheCheckParams?: RunViewWithCacheCheckParams[]When CacheLocal is enabled, contains the cache check params to send to server
OptionaltelemetryEventId?: stringOptionaluncachedParams?: RunViewParams[]OptionaluseSmartCacheCheck?: booleanWhen CacheLocal is enabled, indicates we should use smart cache check
ProtectedQueryGets all security roles defined in the system.
Array of RoleInfo objects representing all roles
Gets all row-level security filters defined in the system.
Array of RowLevelSecurityFilterInfo objects for data access control
Current WebSocket connection state (synchronous snapshot).
Observable of the WebSocket (graphql-ws) connection state. Used by connectivity monitors as the primary signal for server reachability, with /healthcheck polling as a fallback when 'disconnected' is emitted.
Whether this provider can execute a multi-record unit of work atomically, in-process.
Defaults to false — the correct answer for every provider that is not talking directly to a
database, most importantly the client-side GraphQLDataProvider. DatabaseProviderBase
overrides this to true and supplies DatabaseProviderBase.BeginEntityTransaction.
BaseEntity reads this to decide whether a multi-node save graph runs locally inside a
transaction or is routed to the server as a single unit of work. Defaulting to false is the
safe direction: a provider that has not opted in never has non-atomic work mistaken for
atomic work.
ProtectedTrustWhen true, cached RunView/RunQuery results are returned immediately on a cache hit without any server-side validation round-trip.
Server-side providers (DatabaseProviderBase and its subclasses) override
this to return true because the cache is kept in perfect sync via
BaseEntity save/delete events and cross-server Redis pub/sub — the DB
validation query is unnecessary overhead.
Client-side providers (e.g. GraphQLDataProvider) keep the default false
so that the lightweight smart cache check (maxUpdatedAt + rowCount) is
still performed against the server before trusting the browser cache.
Gets only active explorer navigation items sorted by sequence. Results are cached for performance.
Array of active ExplorerNavigationItem objects
StaticInstanceReturns the singleton instance of GraphQLDataProvider. Uses the Global Object Store to guarantee a single instance across the entire process, even if bundlers duplicate this module.
ProtectedApplyStrips fields the user cannot read from plain-object result rows.
This is the primary read-time control for list results, and it runs on BOTH cache hits and cache misses — the property the rest of the cache design is arranged around. Because it reads live metadata, a permission change takes effect on the next metadata refresh with no result-cache invalidation: the cached full-width superset stays valid and only the projection changes.
It composes with two per-request, never-cached narrowings that avoid pulling columns a
restricted service account has no business holding: the simple-path SELECT-list
intersection, and Load()'s allowed-column SELECT.
NEVER applied to entity_object results. Those become BaseEntity instances whose
fields round-trip through GenerateSaveSQL, which iterates ALL IsSPParameter fields
reading field.Value — not just dirty ones. A stripped field would therefore be written
back as a real NULL on the user's next save: silent data loss. Entity objects keep their
values in server memory exactly as encrypted fields do; the trust boundary is the API
output, which the GraphQL layer enforces separately.
OptionalcontextUser: UserInfoProtectedApplyApplies the PostRunView hook chain to a result that was served from cache, mutating
result in place so the caller's reference reflects the chain's output.
PostRunView is the OUTPUT half of the enforcement seam (data masking / audit). Hooks
receive contextUser, so masking is PER-USER, while the cache slot is shared across
users — there is no correct way to apply masking once at write time on behalf of a
reader who has not arrived yet. A hit that skips the chain therefore returns rows the
miss path would have masked.
This previously appeared to work by accident: PostRunView writes the cache BEFORE running the hooks, so a hook that masked rows in place was writing through into the cached objects — which both made later hits look masked and baked one user's masking decision into a shared slot. Freeze-on-write removes that write-through, which is what makes running the chain here necessary rather than merely tidier.
result in place is safeCache-hit results are FRESH wrapper objects built per hit by PreRunView/PreRunViews —
only .Results points at shared cache state. A hook that returns a replacement (the
required pattern now that rows are frozen) is copied onto that per-hit wrapper, so it
can never write back into the cache.
GetDataHooks is a memoized store read (~30ns), but await-ing the async chain costs
a microtask (~750ns) — comparable to the entire cache lookup this rides on. The
overwhelmingly common case is zero registered hooks, so check first and skip the await.
OptionalcontextUser: UserInfoProtectedApplyStrips or narrows the MJ: Record Changes payload columns a user may not read, using the
denied set of the entity each row is ABOUT rather than of Record Changes itself.
A sibling of ApplyFieldSecurityProjection rather than part of it, because that
method short-circuits on EnableFieldLevelSecurity for the entity named in the RunView
params — which here is MJ: Record Changes, whose flag is off in every default deployment.
Everything about the per-row denied set, the payload treatment, and the fail-closed decision
lives in RecordChangeFieldSecurityProjector; this is only the RunView wiring.
Runs at all four RunView projection points, matching the main projection: both cache-hit paths and both cache-miss paths. The cache-hit path is not optional — it is the exact path the original cross-user leak runs through, an unrestricted user warming a full-width slot that a restricted user then hits.
NEVER applied to entity_object results, for the reason the main projection is exempt plus
one specific to this one. The main reason transfers directly: an entity object's fields
round-trip through GenerateSaveSQL, which reads EVERY IsSPParameter field's value rather
than only dirty ones, so a withheld ChangesDescription would be written back as a real
NULL and a narrowed ChangesJSON as the narrowed payload — destroying audit history instead
of merely hiding it. Record Changes rows genuinely are saved through the entity layer
(replay writes Status/ErrorLog, users write Comments), so this is not hypothetical.
The additional reason is that the exemption cannot become a hole: the GraphQL RunView
resolver coerces entity_object to simple on the wire, so an entity_object Record
Changes result is by construction server-internal — and server-internal code holding full
values in memory is the documented trust boundary (FLS guide §3.4), exactly as for
encrypted fields.
OptionalcontextUser: UserInfoProtectedApplyTurns fields the SERVER withheld into genuine key-absence on a response payload, so the hydration paths mark them EntityField.NotLoaded rather than loading a null over them.
This is needed because deleting the key server-side is not sufficient by itself: GraphQL
emits every SELECTED field, so a withheld field the client asked for arrives as an explicit
null that is indistinguishable from a genuine one. Rewriting it back to absence here is
what preserves the "key-absence means not-loaded, never means null" contract end to end.
Two sources, in priority order:
ReadableFields___ — authoritative. It describes the request that
actually ran, so it is correct even when this client's metadata is stale, and it stays
correct once metadata filtering (issue #3485) means the client may not hold the
permission rules at all. Anything not on that list is withheld, whatever value arrived.The transport key itself is always removed — it is not an entity field, and leaving it on
the payload would trip SetMany's field-not-found warning during hydration.
ProtectedAssertRejects a RunView whose ExtraFilter, OrderBy, or Aggregates expressions reference a
field the user cannot read.
Output projection alone is security theater. A user denied Salary can send
ExtraFilter: "Salary > 200000" or OrderBy: "Salary DESC" and reconstruct the values
from which rows come back and in what order — the column never appears in a result, so
every output-stripping point reports "secure." Aggregates are the same channel in a purer
form: Aggregates: [{expression: 'MIN(Salary)'}] under a narrow filter returns a denied
field's exact values directly. Predicate validation is a first-class enforcement point,
not a belt-and-braces afterthought. Together these cover every caller-authored expression
surface (UserSearchString is handled by excluding denied fields from the searched set,
not by rejection — see below).
Lives at the provider layer rather than in the GraphQL resolver (where the plan first
placed it) because every RunView funnels through here — the batch path, server-internal
agents and actions running under a restricted contextUser, and the resolver alike. One
gate, no path left uncovered.
The error deliberately does not say whether the field is missing or merely forbidden — see ProviderBase.FieldSecurityDenialMessage.
OptionalcontextUser: UserInfoBackground validation for the stale-while-revalidate fast-start pattern. Checks if local metadata is still current; if stale, fetches fresh metadata and atomically swaps it in. The app continues operating on cached data during this process — no blocking.
OptionalproviderToUse: IMetadataProviderProtectedBuildBuilds dataset filters based on the provider configuration. Ensures MJ Core schema is always included and never excluded.
Array of filters to apply when loading metadata
Stores a dataset in the local cache. If itemFilters are provided, the combination of datasetName and the filters are used to build a key and determine a match in the cache
ProtectedcacheSECURITY — decide whether the shared cache must be BYPASSED for a RunView that targets
a saved VIEW rather than a named entity (no EntityName), under a context user.
The cache-hit path returns BEFORE the DB provider's read-permission gate
(CheckUserReadPermissions). The primary gate keys off the entity resolved from
params.EntityName, so a ViewID-/ViewName-only request (the Explorer-standard shape for a
saved view) yields no entity there and the gate is disarmed — a read-denied user could be
served rows a permitted user warmed for the same ViewID. The vw: fingerprint segment makes
the two users' requests collide on exactly one slot, so the leak is clean.
Returns true when the cache must be skipped for this call (fail-closed):
ViewEntity supplied and its entity resolves → apply the normal CanRead gate on it
(allow caching for a permitted user; deny for a read-denied one).ViewEntity absent/unresolvable but ViewID/ViewName present → fail closed: the view's
real entity (hence the user's permission) is only known after the async MJ: User Views
lookup that the cache-hit path deliberately skips, so we cannot safely consult the cache.
Returns false when there is no context user, when EntityName is set (the normal gate owns
that path), or when no view identifier is present at all (nothing to gate).OptionalcontextUser: UserInfoProtectedCancelCancels any pending debounced metadata refresh. Call during teardown (logout, provider disposal) so a timer armed just before teardown doesn't fire a refresh against a connection that no longer has a valid session.
Checks if local metadata is out of date and needs refreshing. Compares local timestamps with server timestamps.
OptionalproviderToUse: IMetadataProviderOptionalbypassMinCheckInterval: booleanWhen true, skips the MinRefreshCheckIntervalMs throttle. Event-driven callers pass true: they hold positive evidence that a metadata member entity was just written, and the throttle otherwise answers "fresh" for any check arriving within the window of the previous one — which would silently drop the second of two permission changes made less than the window apart.
True if refresh is needed, false otherwise
If the specified datasetName is cached, this method will clear the cache. If itemFilters are provided, the combination of datasetName and the filters are used to determine a match in the cache
OptionalitemFilters: DatasetItemFilterType[]Subscribe to client tool requests for a specific agent session. The returned Observable emits ClientToolRequestNotification objects when the server-side agent wants to invoke a browser-side tool.
The agent session ID to filter requests for
Observable that emits tool request notifications
ProtectedCloneThe reuse-global fast path now builds a shared shell instead — see CreateSharedMetadataShell. The metadata graph is immutable after Config, so re-instantiating every Info object (~1s of synchronous constructor work for a ~600-entity install) bought no isolation the shell doesn't already provide. Subclass OVERRIDES of this method are still honored on the fast path (see CopyMetadataFromGlobalProvider) for backward compatibility; new customizations should override CreateSharedMetadataShell instead.
ProtectedComputeThe CLIENT's field-security cache key: a canonical list of the fields this user MAY read.
Keyed on the ALLOWED set rather than the denied set for two reasons. Once metadata ships
to browsers filtered to what a user may see (#3485),
a denied field will not appear in the client's field list at all — so a denied-set key
would be empty and would silently stop segmenting. The allowed list also resolves the
f:* ambiguity in the client's projection segment, where "full width" means different
columns for different users.
Returns '' when the entity has field security off or the user is denied nothing, so unrestricted users keep byte-identical fingerprints and shared slots.
Only takes effect once the client's metadata refreshes. A client on stale metadata computes a stale key; the backstop is that the server strips denied columns from every fresh fetch regardless.
ProtectedComputeThe fetch-widening field list for a cache-eligible request: always every entity field. One slot per (entity, filter, order) serves every caller regardless of the field subset they asked for, and field security narrows per request at read time via ApplyFieldSecurityProjection.
ProtectedComputeThe field-security segment for a LocalCacheManager RunView fingerprint — the one place the client/server asymmetry is decided, so the two tiers cannot drift.
SERVER → no segment. Its slots are full-width and shared by every user; per-request narrowing happens at read time in ApplyFieldSecurityProjection, which runs on every hit and every miss. A segment here would fragment one shared slot into one per permission class and protect nothing the projection does not already handle.
CLIENT → the allowed-list key. Its slots are stored exactly as the server returned
them (already narrowed on the wire) and are not projected on read, so slot identity has to
carry the field set. Without it, a user whose access is tightened keeps being served their
persisted IndexedDB slot: the currency check compares maxUpdatedAt and rowCount only,
neither of which notices a column, so the server answers "current" and the browser keeps
showing a column that was just taken away.
Empty/undefined for unrestricted users on both tiers, so their fingerprints stay byte-identical and keep sharing slots.
ProtectedComputeComputes the per-user Row-Level-Security WHERE clause that InternalRunView will append to this query's SQL for the given user, so it can be folded into the cache fingerprint. RLS-scoped reads return a different result set than unscoped reads of the same entity+filter; without including the RLS clause in the cache key, a scoped user could be served a cached unscoped result set (a data leak).
Returns '' when the user is exempt from RLS on this entity (the common case), which makes the resulting fingerprint byte-identical to the pre-RLS format — preserving normal cache sharing.
Uses this (the active provider) to resolve the entity, never the global Metadata, so the
correct per-provider/per-tenant metadata is consulted.
OptionalcontextUser: UserInfoThis method configures the class instance. If separateConnection is false or not provided, the global/static variables are set that means that the Config() call will affect all callers to the GraphQLDataProvider including via wrappers like the Metadata class. If separateConnection is true, then the instance variables are set and only this instance of the GraphQLDataProvider will be affected by the Config() call.
OptionalproviderToUse: IMetadataProviderOptionalseparateConnection: booleanOptionalforceRefreshSessionId: booleanProtectedConvertThis method will convert back any fields that start with mj_ back to _mj so that the entity object can properly update itself with the data that was returned from the server
ProtectedConvertConverts dataset item filters into a unique string key for caching.
Array of filters to convert
JSON-formatted string representing the filters
ProtectedCopyAdopts the global provider's metadata for this instance without reloading it from the server: shares the (immutable post-Config) metadata arrays by reference via CreateSharedMetadataShell and builds this instance's entity lookup maps.
ProtectedCreateProtectedCreateBuilds this instance's AllMetadata as a thin shell over another provider's already-loaded metadata: every metadata array is a PER-INSTANCE shallow copy whose elements are the SHARED Info object instances, and CurrentUser remains this instance's own.
Why sharing the instances is safe — and why this replaced the former deep clone (CloneAllMetadata) on the reuse-global fast path: the metadata graph is immutable after Config. Refreshes swap the WHOLE AllMetadata object (UpdateLocalMetadata), never mutate the Info objects in place, so the only per-instance datum inside the graph is CurrentUser — which this shell keeps independent. The deep clone cost ~1s of event-loop-blocking constructor work per provider on every server request (MemberJunction/MJ#3083); the shell is ~20 array-of-pointer copies (microseconds).
Why the array containers are copied rather than aliased: an in-place
.sort()/.push()/.splice() by request-scoped code then stays local to
that provider — matching the clone era's isolation for the common accidental
mutation class — instead of reordering the global graph for every other
in-flight request. Only the top-level AllMetadata collections get this
per-instance protection: everything below them is shared, including the
nested arrays owned by Info objects (entity.Fields,
entity.RelatedEntities, application.ApplicationEntities, ...) — an
in-place mutation of those is process-wide. Property writes on the shared
Info objects themselves are likewise visible process-wide (as they always
were on the client's global provider): treat Info objects and everything
they own as read-only; copy before sorting.
Override precedence: if a subclass overrides BOTH this method and the deprecated CloneAllMetadata, the CloneAllMetadata override wins on the fast path (see CopyMetadataFromGlobalProvider) — the conservative back-compat choice, since pre-#3083 subclasses could only have customized adoption through CloneAllMetadata. Remove the CloneAllMetadata override to activate a CreateSharedMetadataShell override.
Creates a new transaction group for managing database transactions. Must be implemented by subclasses to provide transaction support.
A new transaction group instance
Public method to dispose of WebSocket resources Call this when shutting down the provider or on logout
ProtectedensureO(1) entity lookup by ID (UUID-normalized). Falls back to linear search if the internal Map hasn't been built yet.
O(1) entity lookup by name (case-insensitive, trimmed). Falls back to linear search if the internal Map hasn't been built yet.
ProtectedEntityUsed to check to see if the entity in question is active or not If it is not active, it will throw an exception or log a warning depending on the status of the entity being either Deprecated or Disabled.
OptionalcontextUser: UserInfoProtectedeventWhether the write described by entityEvent happened against the backend THIS provider's
metadata comes from. In a multi-provider process (a client connected to several MJ servers,
a server connected to several databases) a write on one backend must not refresh another's
metadata. Deliberately fails OPEN — when the event does not identify its provider, or a
connection string is unavailable, the answer is "yes": a spurious refresh is a bounded
cost, a suppressed one is a stale-permissions window.
Executes the GQL query with the provided variables. If the token is expired, it will attempt to refresh the token and then re-execute the query. If the token is expired and the refresh fails, it will throw an error.
Executes a GraphQL query/mutation with real upload progress tracking via XMLHttpRequest.
OptionalonProgress: (event: { loaded: number; percent: number; total: number }) => voidExecutes a query from a QueryExecutionSpec — the lower-layer interface-based entry point.
Runs the full pipeline: composition resolution → Nunjucks template processing → SQL execution.
Subclasses (GenericDatabaseProvider) provide the concrete implementation via InternalExecuteQueryFromSpec.
The execution spec describing the query, parameters, and inline dependencies
OptionalcontextUser: UserInfoOptional user context for permissions (required server-side)
Query results including data rows and execution metadata
ProtectedextractDiscovers ALL IS-A child entities that have records matching the given PK. Used for overlapping subtype parents (AllowMultipleSubtypes = true). Calls the server-side FindISAChildEntities resolver via GraphQL.
The parent entity to check children for
The primary key value to search for in child tables
OptionalcontextUser: UserInfoOptional context user (unused on client, present for interface parity)
Array of child entity names found (empty if none)
Discovers which IS-A child entity has a record matching the given PK. Calls the server-side FindISAChildEntity resolver via GraphQL.
The parent entity to check children for
The primary key value to search for in child tables
OptionalcontextUser: UserInfoOptional context user (unused on client, present for interface parity)
The child entity name if found, or null if no child record exists
Force-dispose the current WebSocket client so the next subscription creates a fresh connection. Called by ServerConnectivityService after /healthcheck confirms the server is back online.
Performs a full-text search across all entities that have FullTextSearchEnabled=true. Uses the existing RunView + UserSearchString infrastructure which routes through the database-native FTS capabilities (SQL Server FREETEXT functions, PostgreSQL tsvector).
This is the default implementation that works across all database providers. Each provider's createViewUserSearchSQL() method handles the platform-specific SQL generation.
OptionalcontextUser: UserInfoProtectedGetRetrieves all metadata from the server and constructs typed instances. Uses the MJ_Metadata dataset for efficient bulk loading.
OptionalproviderToUse: IMetadataProviderOptionalforceRefresh: booleanComplete metadata collection with all relationships
Gets a database by name, if required, and caches it in a format available to the client (e.g. IndexedDB, LocalStorage, File, etc). The cache method is Provider specific If itemFilters are provided, the combination of datasetName and the filters are used to determine a match in the cache
OptionalitemFilters: DatasetItemFilterType[]OptionalcontextUser: UserInfoOptionalproviderToUse: IMetadataProviderThis routine gets the local cached version of a given datasetName/itemFilters combination, it does NOT check the server status first and does not fall back on the server if there isn't a local cache version of this dataset/itemFilters combination
OptionalitemFilters: DatasetItemFilterType[]Asynchronous lookup of a cached entity record name. Returns the cached name if available, or undefined if not cached. Use this for synchronous contexts (like template rendering) where you can't await GetEntityRecordName().
The name of the entity
The primary key value(s) for the record
OptionalloadIfNeeded: booleanIf set to true, will load from database if not already cached
The cached display name, or undefined if not in cache
ProtectedGetGets the current user information from the provider. Must be implemented by subclasses to return user-specific data.
Current user information including roles and permissions
Retrieves all of the data context data for the specified data context ID.
Retrieves the data context item data for the specified data context item ID.
Returns a dataset by name
OptionalitemFilters: DatasetItemFilterType[]Creates a unique key for the given datasetName and itemFilters combination coupled with the instance connection string to ensure uniqueness when 2+ connections exist
OptionalitemFilters: DatasetItemFilterType[]Retrieves the date status information for a dataset and all its items from the server. This method will match the datasetName and itemFilters to the server's dataset and item filters to determine a match
OptionalitemFilters: DatasetItemFilterType[]Returns a read-only copy of all currently set dynamic headers.
Returns a list of entity dependencies, basically metadata that tells you the links to this entity from all other entities.
ProtectedgetOptionalcontextUser: UserInfoCreates a new instance of a BaseEntity subclass for the specified entity and automatically calls NewRecord() to initialize it. This method serves as the core implementation for entity instantiation in the MemberJunction framework.
The name of the entity to create (must exist in metadata)
OptionalcontextUser: UserInfoOptional user context for permissions and audit tracking
Promise resolving to the newly created entity instance with NewRecord() called
Creates a new instance of a BaseEntity subclass and loads an existing record using the provided key. This overload provides a convenient way to instantiate and load in a single operation.
The name of the entity to create (must exist in metadata)
CompositeKey containing the primary key value(s) for the record to load
OptionalcontextUser: UserInfoOptional user context for permissions and audit tracking
Promise resolving to the entity instance with the specified record loaded
Gets the display name for a single entity record with caching. Uses the entity's IsNameField or falls back to 'Name' field if available.
The name of the entity
The primary key value(s) for the record
OptionalcontextUser: UserInfoOptional user context for permissions
OptionalforceRefresh: booleanIf true, bypasses cache and queries database
The display name of the record or null if not found
Gets display names for multiple entity records in a single operation with caching. More efficient than multiple GetEntityRecordName calls.
Array of entity/key pairs to lookup
OptionalcontextUser: UserInfoOptional user context for permissions
OptionalforceRefresh: booleanIf true, bypasses cache and queries database for all records
Array of results with names and status for each requested record
ProtectedGetRetrieves the latest metadata update timestamps from the server.
OptionalproviderToUse: IMetadataProviderArray of metadata update information
Returns the timestamp of the local cached version of a given datasetName or null if there is no local cache for the specified dataset
the name of the dataset to check
OptionalitemFilters: DatasetItemFilterType[]optional filters to apply to the dataset
OptionalforceRefreshSessionId: booleanReturns a list of dependencies - records that are linked to the specified Entity/KeyValuePairs combination. A dependency is as defined by the relationships in the database. The MemberJunction metadata that is used for this simply reflects the foreign key relationships that exist in the database. The CodeGen tool is what detects all of the relationships and generates the metadata that is used by MemberJunction. The metadata in question is within the EntityField table and specifically the RelatedEntity and RelatedEntityField columns. In turn, this method uses that metadata and queries the database to determine the dependencies. To get the list of entity dependencies you can use the utility method GetEntityDependencies(), which doesn't check for dependencies on a specific record, but rather gets the metadata in one shot that can be used for dependency checking.
the name of the entity to check
Returns a list of record IDs that are possible duplicates of the specified record.
object containing many properties used in fetching records and determining which ones to return
OptionalcontextUser: UserInfoChecks if a specific record is marked as a favorite by the user.
The ID of the user to check
The name of the entity
The primary key value(s) for the record
True if the record is a favorite, false otherwise
ProtectedgetRetrieves the stored session ID from the LocalStorageProvider if available. If no session ID is found, returns null. The session ID is stored using the same storage mechanism as other persistent data with a key specific to the current URL to ensure uniqueness across different server connections.
The stored session ID or null if not found
ProtectedgetProtectedhandleStatic fan-out callback: a BaseEntity save/delete (or a remote-invalidate from another
server) touched lowerEntityName. If that entity is one of the entities this provider's
metadata is BUILT FROM, the metadata this provider is serving — and, on the server, the
metadata every per-request provider adopts from it — is now stale, so schedule a debounced
refresh. Permission metadata is the load-bearing case: field-level security is enforced
FROM metadata at every enforcement point, so a rule an administrator just tightened is
simply not enforced until this re-read happens.
ProtectedInternalSpec-based query execution is not supported by this provider.
Optional_contextUser: UserInfoProtectedInternalInternal provider-specific implementation to get a single entity record name from database. Subclasses must implement this to query the database.
The name of the entity
The primary key value(s) for the record
The display name of the record or null if not found
ProtectedInternalInternal provider-specific implementation to get multiple entity record names from database. Subclasses must implement this to query the database in batch.
Array of entity/key pairs to lookup
Array of results with names and status for each requested record
ProtectedInternalClient-side transport for a Remote Operation: marshals the operation key + JSON input over the
generic ExecuteRemoteOperation GraphQL mutation, and parses the JSON output back. The server
resolves and executes the operation in-process. Overrides the no-op default on ProviderBase;
key validation still runs in ProviderBase.RouteOperation before this is called.
ProtectedInternalInternal implementation of RunQueries that subclasses must provide. This method should ONLY contain the batch query execution logic - no pre/post processing. The base class handles all orchestration (telemetry, caching).
Array of query parameters
OptionalcontextUser: UserInfoOptional user context for permissions
ProtectedInternalProtectedInternalOptionalcontextUser: UserInfoProtectedInternalInternal implementation of RunViews that subclasses must provide. This method should ONLY contain the batch data fetching logic - no pre/post processing. The base class handles all orchestration (telemetry, caching, transformation).
Array of view parameters
OptionalcontextUser: UserInfoOptional user context for permissions
ProtectedinvalidateDrops every in-flight/lingered RunView entry whose params touch the given entity (lowercased name). Called on BaseEntity save/delete/remote-invalidate.
Determines if a given datasetName/itemFilters combination is cached locally or not
OptionalitemFilters: DatasetItemFilterType[]This routine checks to see if the local cache version of a given datasetName/itemFilters combination is up to date with the server or not
OptionalitemFilters: DatasetItemFilterType[]ProtectedIsWhether a saved query is bound to an external data source. The base returns false; providers that support external data sources override this (consulting query metadata) so the outer RunQuery CacheLocal layer can defer to InternalRunQuery's own external TTL caching. Synchronous + non-throwing: resolves from cached metadata only.
ProtectedisTrue when the entity is a CodeGen materialized-query wrapper (materialized_vw*) whose snapshot is refreshed out-of-band — the same one IsServerCacheAllowedForEntity excludes from the server cache.
ProtectedIsChecks whether server-side caching is allowed for the entity in the given RunViewParams. Returns false for entities that have TrustServerCacheCompletely = false, or for Record Changes which is always exempt (rows are created via raw SQL side-effects, not BaseEntity.Save(), so cache invalidation events never fire).
ProtectedLoadLoads metadata from local storage if available. Deserializes and reconstructs typed metadata objects.
Checks if local metadata is obsolete compared to remote metadata. Compares timestamps and row counts to detect changes.
Optionaltype: stringOptional specific metadata type to check
True if local metadata is out of date
ProtectedmergeMerges cached and fresh results for RunViews, maintaining original order.
The pre-processing result with cache info
OptionalcachedResults?: RunViewResult[]OptionalcacheStatusMap?: Map<OptionalcallerFieldsMap?: Map<number, string[]>Per-param-index caller Fields lists (lowercased), captured before PreRunViews widened params.Fields to all entity fields for cache-superset storage. An index is present ONLY when that widening actually happened — PostRunViews uses it to project cache-miss DB results back down to the requested shape.
OptionalfingerprintMap?: Map<number, string>Per-param-index cache fingerprints computed during PreRunViews — carried forward so PostRunViews doesn't recompute the RLS where-clause and fingerprint string for every batch item.
OptionalsmartCacheCheckParams?: RunViewWithCacheCheckParams[]When CacheLocal is enabled, contains the cache check params to send to server
OptionaltelemetryEventId?: stringOptionaluncachedParams?: RunViewParams[]OptionaluseSmartCacheCheck?: booleanWhen CacheLocal is enabled, indicates we should use smart cache check
The fresh results from InternalRunViews
Combined results in original order
ProtectedmergeMerges cached and fresh results for RunQueries, maintaining original order.
The pre-processing result with cache info
The fresh results from InternalRunQueries
Combined results in original order
This method will merge two or more records based on the request provided. The RecordMergeRequest type you pass in specifies the record that will survive the merge, the records to merge into the surviving record, and an optional field map that can update values in the surviving record, if desired. The process followed is:
The return value from this method contains detailed information about the execution of the process. In addition, all attempted merges are logged in the RecordMergeLog and RecordMergeDeletionLog tables.
OptionalcontextUser: UserInfoOptionaloptions: EntityMergeOptionsProtectedNormalizeNormalizes non-entity ('simple') result rows so Date and numeric columns hold real
Dates and numbers on EVERY tier, matching what the generated entity types declare.
Before this existed, the value a simple read returned for a DATETIME column depended on
where the code happened to run: a fresh server-side query yields real Date objects (the
driver parses them and AdjustDatetimeFields timezone-adjusts them), a server-side Redis
cache hit yields ISO strings (JSON.parse with no reviver), and a browser client over
GraphQL yields ISO strings (rows are JSON.stringify'd on the wire). Same call, three
shapes. MJ's contract is a unified programming interface on both sides of the wire, so the
one representation the platform's own generated types declare — Date — is enforced here,
at the one choke point every provider's RunView pipeline flows through.
It makes date and number VALUES match the generated types; it does not make a caller's T
honest in general. A Status column typed as a closed union still holds whatever string the
database held, and plain rows never have entity methods. If you need the type to be fully
true, use ResultType: 'entity_object'.
The field-key lists are computed once per view from EntityInfo, not per cell. Rows already
in the right shape — the common server-side case, where the driver returned Dates — are
detected and the ORIGINAL array is kept untouched: same array identity, same row objects,
zero copying. A row is shallow-copied only when a cell actually converts, and that copy is
load-bearing: on a cache hit the rows handed back can be the cache's OWN objects (the
in-memory server store holds them by reference), so converting in place would write Dates
into the cache entry itself and corrupt it for serialization and for later readers.
Per-cell rules:
Date instances pass through untouched, so the pass is idempotent on every path.NULL/undefined cells are left alone rather than becoming epoch-1970 dates.Invalid Date, which renders
as that literal string and destroys the evidence of what the database actually held.Number.MAX_SAFE_INTEGER stays a string: the PostgreSQL
provider deliberately returns unsafe-range BIGINTs as strings to avoid precision loss,
and Number('9007199254740993') "succeeds" while silently corrupting the value.View-based runs (ViewID/ViewName with neither EntityName nor a loaded ViewEntity)
skip normalization: resolving the entity would take an async User Views read this late in
the pipeline. Pass EntityName alongside the view identifier to get normalized rows.
ProtectedPostOptionalorganicKeys: OrganicKeyMetadataRow[]OptionalorganicKeyRelatedEntities: OrganicKeyRelatedEntityMetadataRow[]OptionalfieldPermissions: EntityFieldPermissionMetadataRow[]ProtectedPostBase class post-processor that all sub-classes should call after they finish their RunView process
OptionalcontextUser: UserInfoProtectedPostBase class utilty method that should be called after each sub-class handles its internal RunViews() process before returning results This handles the optional conversion of simple objects to entity objects for each requested view depending on if the params requests a result_type === 'entity_object'
OptionalcontextUser: UserInfoProtectedPostPost-processing hook for RunQueries (batch). Handles telemetry end.
Array of query results
Array of query parameters
The pre-processing result
OptionalcontextUser: UserInfoOptional user context
ProtectedPostPost-processing hook for RunQuery. Handles cache storage and telemetry end.
The query result
The query parameters
The pre-processing result
OptionalcontextUser: UserInfoOptional user context
ProtectedPostPost-processing hook for RunView. Handles result transformation, cache storage, and telemetry end.
The view result
The view parameters
The pre-processing result
OptionalcachedResult?: RunViewResultOptionalcallerRequestedFields?: string[]The caller's original Fields list (lowercased), captured before PreRunView widened params.Fields to all entity fields for cache-superset storage. Non-null ONLY when that widening actually happened — PostRunView uses it to project cache-miss DB results back down to the requested shape.
Optionalfingerprint?: stringOptionaltelemetryEventId?: stringOptionalcontextUser: UserInfoOptional user context
ProtectedPostPost-processing hook for RunViews (batch). Handles result transformation, cache storage, and telemetry end.
Array of view results
Array of view parameters
The pre-processing result
OptionalcachedResults?: RunViewResult[]OptionalcacheStatusMap?: Map<OptionalcallerFieldsMap?: Map<number, string[]>Per-param-index caller Fields lists (lowercased), captured before PreRunViews widened params.Fields to all entity fields for cache-superset storage. An index is present ONLY when that widening actually happened — PostRunViews uses it to project cache-miss DB results back down to the requested shape.
OptionalfingerprintMap?: Map<number, string>Per-param-index cache fingerprints computed during PreRunViews — carried forward so PostRunViews doesn't recompute the RLS where-clause and fingerprint string for every batch item.
OptionalsmartCacheCheckParams?: RunViewWithCacheCheckParams[]When CacheLocal is enabled, contains the cache check params to send to server
OptionaltelemetryEventId?: stringOptionaluncachedParams?: RunViewParams[]OptionaluseSmartCacheCheck?: booleanWhen CacheLocal is enabled, indicates we should use smart cache check
OptionalcontextUser: UserInfoOptional user context
ProtectedPreOptionalcontextUser: UserInfoProtectedPreBase class implementation for handling pre-processing of RunViews() each sub-class should call this within their RunViews() method implementation
OptionalcontextUser: UserInfoProtectedPrePre-processing hook for RunQueries (batch). Handles telemetry for batch query operations.
Array of query parameters
OptionalcontextUser: UserInfoOptional user context
Pre-processing result
ProtectedPrePre-processing hook for RunQuery. Handles telemetry and cache lookup.
The query parameters
OptionalcontextUser: UserInfoOptional user context
Pre-processing result with cache status and optional cached result
ProtectedPreOptionalcontextUser: UserInfoProtectedPrePre-processing hook for RunViews (batch). Handles telemetry, validation, and cache lookup for multiple views.
Array of view parameters
OptionalcontextUser: UserInfoOptional user context
Pre-processing result with cache status for each view
Synchronous pre-validation of cached metadata before engine startup.
On a warm load we serve the metadata graph from IndexedDB so the app can boot without pulling MBs of metadata from the server. Before engines run, this method makes one batched timestamp round-trip to confirm the snapshot is still current:
Cost on the warm-current path is one batched timestamp fetch (~50–200 ms depending on RTT). On the warm-stale path we additionally pay the full metadata fetch but avoid serving stale data to the UI in the first place.
Caller contract: invoke this before StartupManager.Startup().
OptionalproviderToUse: IMetadataProviderProtectedRebuildRebuilds the O(1) entity lookup Maps from the current AllEntities array. Called automatically from UpdateLocalMetadata().
Refreshes all metadata from the server. Respects the AllowRefresh flag from subclasses.
OptionalproviderToUse: IMetadataProviderTrue if refresh was initiated or allowed
ProtectedRefreshHow this client refreshes after one of the entities its metadata is built from changes
(see ProviderBase.handleMetadataMemberEntityEvent — membership comes from the MJ_Metadata
dataset definition, not a list). A browser must not re-pull the full metadata graph on
every such write, so instead of the base class's hard Refresh this runs the staleness
check: RefreshIfNeeded compares server timestamps and refetches only when genuinely
stale, so a spurious event costs one cheap status round-trip. The check-interval throttle
is bypassed because by the time the coalescing window fires, the caller holds positive
evidence at least one member entity was written since the window was armed — the throttle
would otherwise silently drop it. Volume is governed by the window above, not the
throttle.
Refreshes the CurrentUser from the server and updates local metadata in place. Useful on warm boot or when user roles/permissions change dynamically without entity schema changes.
Refreshes metadata only if needed based on timestamp comparison. Combines check and refresh into a single operation.
OptionalproviderToUse: IMetadataProviderOptionalbypassMinCheckInterval: booleanPassed through to CheckToSeeIfRefreshNeeded; event-driven callers set true so the throttle cannot eat a check they have positive evidence for.
True if refresh was successful or not needed
Refreshes the remote metadata timestamps from the server. Updates the internal cache of remote timestamps.
OptionalproviderToUse: IMetadataProviderTrue if timestamps were successfully refreshed
ProtectedregisterRecords which entities compose this provider's metadata, from the loaded MJ_Metadata dataset result, and registers this instance with the static event fan-out so writes to any of them schedule a debounced metadata refresh. Called from GetAllMetadata on every successful load, so the set tracks the dataset definition as it changes.
Removes a previously set dynamic header. The header will no longer be included in subsequent GraphQL requests.
The header name to remove
Removes all cached metadata from local storage. Clears both timestamps and metadata collections.
ProtectedResolveResolves any PlatformSQL values in RunViewParams to plain strings for the active platform. Mutates the params object in place so downstream InternalRunView implementations always receive plain string values for ExtraFilter and OrderBy.
ProtectedResolveThe RunQuery cache-serve seam (B45/B46) — resolves a RunQuery request against this provider's query metadata and answers, in ONE computation performed BEFORE fingerprinting:
categoryPath: the RESOLVED query's canonical full category path. This becomes a
distinguishing fingerprint segment (B46) so two same-named queries in different
categories can never collide onto one cache slot. When the request is unresolvable the
caller falls back to the CALLER-STATED params.CategoryPath (still distinguishing,
just not canonicalized).resolvable: whether metadata could resolve the request at all. Runtime-created
queries are typically NOT resolvable from the base metadata cache (it does not refresh
in-process) — the gate then applies the warmer tie-break instead.authorized: whether user may run the resolved query. Meaningful only when
resolvable is true.The BASE implementation resolves from the metadata Queries cache and enforces the
ROLES-ONLY QueryInfo.UserCanRun — the strongest check available at this layer.
Providers with richer query metadata MUST override this to enforce the SAME authorization
their miss path enforces (GenericDatabaseProvider overrides with
MJQueryEntityExtended.UserCanRun, which adds entity CanRead + recursive composition
checks — the exact check ValidateQueryForExecution applies on a cache miss). The
invariant this seam exists to hold: a cache HIT must never be easier to read than a
cache MISS (B45 was precisely that asymmetry — the TTL gate checked roles only while
the miss path also checked entity read permissions).
Optionaluser: UserInfoProtectedResolveThe entity a RunView targets, resolved with NO I/O — for gates that run on the result path,
where an async RunView.GetEntityNameFromRunViewParams (which issues a User Views query for
a bare ViewID) would be a query per result set.
Covers the two shapes every caller in this repository uses: an explicit EntityName, and a
loaded ViewEntity. A request carrying only ViewID/ViewName with neither is not
resolvable here and returns undefined — the same shape RunView's own row normalization
already declines to handle for the same reason, and the same one
cacheDeniedForViewOnlyRequest exists to fail closed for on the cache path.
Resolves a PlatformSQL value to the appropriate SQL string for this provider's platform. If the value is a plain string, it is returned as-is (backward compatible). If the value is a PlatformSQL object, the platform-specific variant is used if available, otherwise the default variant is used.
Routes a typed Remote Operation by key to its implementation (see IRemoteOperationProvider).
This is the public power-tool transport seam. Prefer the typed
BaseRemotableOperation.Execute() entry point in application code — RouteOperation is the
stringly-typed escape hatch for dynamic dispatch / generic tooling, not for building
significant systems. Server providers override InternalRouteOperation to execute the
operation in-process; the client (GraphQL) provider overrides it to marshal over the wire.
Only registered, active (and, when AI-authored, approved) operations are routable, and every
call is authorized on the server side.
Stable registry key of the operation (e.g. RecordProcess.RunNow).
The operation's typed input payload.
Optionaloptions: RemoteOpInvokeOptionsOptional invocation options (mode, progress callback, user, provider, fingerprint).
The operation result; never throws for logical failures — check Success/ErrorMessage.
ProtectedRunExecutes an ad-hoc SQL query via the ExecuteAdhocQuery GraphQL resolver. The server validates the SQL (SELECT/WITH only) and executes on a read-only connection.
OptionalmaxRows: numberOptionaltimeoutSeconds: numberOptionalstartRow: numberProtectedRunRuns all registered PostRunView hooks against a single result, returning the (possibly mutated) result.
Protected (not private) for the same reason as RunPreRunViewHooks above: a subclass pipeline that returns view rows WITHOUT passing through PostRunView/PostRunViews — e.g. the RunViewsWithCacheCheck smart-cache path — MUST apply these hooks to the rows it returns. PostRunView is the OUTPUT half of the enforcement seam (data masking / audit); a path that skips it returns rows the hooked paths would have masked.
OptionalcontextUser: UserInfoProtectedRunRuns all registered PreRunView hooks against a single RunViewParams, returning the (possibly mutated) params.
Protected (not private) on purpose: any subclass pipeline that executes view queries WITHOUT passing through PreRunView/PreRunViews — e.g. the RunViewsWithCacheCheck smart-cache path in GenericDatabaseProvider — MUST apply these hooks itself. Hooks are an enforcement seam (tenant scoping middleware injects filters here); a query path that skips them silently returns rows the hooked paths would have filtered out.
OptionalcontextUser: UserInfoRuns multiple queries based on the provided parameters. This method orchestrates the full execution flow for batch query operations.
Array of query parameters
OptionalcontextUser: UserInfoOptional user context for permissions (required server-side)
Array of query results
RunQueriesWithCacheCheck - Smart cache validation for batch RunQueries. For each query, if cacheStatus is provided, the server checks if the cache is current using the Query's CacheValidationSQL. If current, returns status='current' with no data. If stale, returns status='stale' with fresh data.
Array of RunQuery requests with optional cache status
OptionalcontextUser: UserInfoOptional user context
Response containing results for each query in the batch
OptionalcontextUser: UserInfoOptionalCategoryID: stringOptionalCategoryPath: stringOptionalcontextUser: UserInfoOptionalParameters: Record<string, any>OptionalMaxRows: numberOptionalStartRow: numberOptionalEnrichment: RunQueryEnrichmentOptionalDataSource: "Live" | "Materialized"OptionalCategoryID: stringOptionalCategoryPath: stringOptionalcontextUser: UserInfoOptionalParameters: Record<string, any>OptionalMaxRows: numberOptionalStartRow: numberOptionalEnrichment: RunQueryEnrichmentOptionalDataSource: "Live" | "Materialized"Runs a view based on the provided parameters. This method orchestrates the full execution flow: pre-processing, cache check, internal execution, post-processing, and cache storage.
The view parameters
OptionalcontextUser: UserInfoOptional user context for permissions (required server-side)
The view results
ProtectedrunSingle source of truth for whether a RunView call participates in the local cache (both READ and WRITE). Pre/Post hooks for the singular and batch paths must all use this predicate — historically each site recomputed it inline and they drifted (PostRunViews wrote BypassCache results into the cache, poisoning the Fields-agnostic superset slot with narrow rows).
Ineligible:
BypassCache — caller explicitly wants true DB state, no cache interactionAfterKey — keyset pages are single-use AND the fingerprint doesn't include
the seek key, so caching a page would poison the entity+filter slotResultType 'count_only' — returns no rows; caching its empty Results under
a fingerprint that excludes ResultType would poison row queriesDataSource: 'Materialized' — the snapshot is rebuilt OUT-OF-BAND by the scheduled refresh
(direct SQL, no BaseEntity save), so the entity's normal event-driven cache invalidation never
fires for it; a cached materialized result would be served indefinitely stale after a refresh.
Bypass caching entirely for materialized reads. (The ds:materialized fingerprint segment still
keeps the short-lived dedup/linger layer from cross-serving Live vs Materialized in-flight reads.)ProtectedrunWrite-side eligibility for the smart-cache-check (stamped) path. On the trusting SERVER this is exactly runViewCacheEligible — the server cache is kept fresh by BaseEntity events, so a server-cache-disallowed entity must never be slotted. On a CLIENT the slot is instead written with a maxUpdatedAt stamp and DB-revalidated per request, so the server Trust/event gate does NOT apply: a server-cache-disallowed entity (Trust=0 'MJ: Audit Logs', Record Changes, other caching-disabled) is still safely client-cacheable when stamped — folding runViewCacheEligible's server gate onto this path regressed that (integration check client-cache.C12). Materialized reads stay excluded on both (the out-of-band snapshot swap the stamp can't observe).
Runs multiple views based on the provided parameters. Wraps the execution pipeline with request deduplication and a linger window so that concurrent (and near-sequential) identical calls share a single server round-trip. Every caller receives a shallow-copied Results array to protect against cross-caller mutations (push/sort/splice).
Array of view parameters
OptionalcontextUser: UserInfoOptional user context for permissions (required server-side)
Array of view results (shallow-copied Results per caller)
RunViewsWithCacheCheck - Smart cache validation for batch RunViews. For each view, if cacheStatus is provided, the server checks if the cache is current. If current, returns status='current' with no data. If stale, returns status='stale' with fresh data.
Array of RunView requests with optional cache status
OptionalcontextUser: UserInfoOptional user context
Response containing results for each view in the batch
Saves current metadata to local storage for caching. Serializes both timestamps and full metadata collections.
ProtectedscheduleSchedules this provider's metadata refresh after a write to a metadata member entity. Delay and re-arm semantics come from MetadataMemberRefreshDelayMs and MetadataMemberRefreshRearmsOnNewEvents — debounce on the server, long jittered coalescing window on clients. The refresh targets THIS instance — the provider that loaded the dataset owns the metadata built from it; short-lived per-request providers never load the dataset (they adopt the global's metadata as a shared shell), so on the server only the process-global provider ever gets here.
Ranked search over many entities in one GraphQL round-trip.
Both the singular SearchEntity and the batched SearchEntities
forms on the client route through the same plural GQL endpoint — one
request, one response, regardless of how many entities are in params.
The full ranking (lexical + semantic + RRF blend + permission filter)
runs server-side against the backing database provider; the client just
unpacks the already-ranked, permission-filtered result groups and
returns them aligned by input order.
Overrides the inherited Promise.all-based fan-out on ProviderBase
because the client has no embedder, no vector pool, and no business
doing the work locally — N round-trips to N entities would be silly when
one batched payload returns the same answer.
ProtectedsearchUnreachable on the client — SearchEntity and SearchEntities are both
overridden above to proxy through one batched GraphQL round-trip, so the
inherited template-method orchestration never invokes this. Implementing
the abstract method as a no-op is purely a type-system requirement.
Singular form — convenience wrapper that calls the batched
SearchEntities with a single-element params list. Same one
GraphQL round-trip; same server-side ranking. Returning results[0]
is safe even for invalid inputs because SearchEntities returns an
empty array per slot in that case.
Stores a record name in the cache for later synchronous retrieval via GetCachedRecordName(). Called automatically by BaseEntity after Load(), LoadFromData(), and Save() operations.
The name of the entity
The primary key value(s) for the record
The display name to cache
Sets a dynamic header that will be included in all subsequent GraphQL requests. Dynamic headers survive token refreshes and client re-creation.
This is useful for passing per-session context (e.g., organization selection) that the server needs on every request but isn't part of the auth token.
The header name (e.g., 'x-organization-id')
The header value
Sets or removes a record's favorite status for a user.
The ID of the user
The name of the entity
The primary key value(s) for the record
True to mark as favorite, false to remove
User context for permissions (required)
ProtectedshouldOptionalcontextUser: UserInfoGeneric subscription method for GraphQL subscriptions
The GraphQL subscription query
Optionalvariables: anyVariables to pass to the subscription
Observable that emits subscription data
Live dispatcher lifecycle frames for one workflow run (durable task graph), keyed by the graph's parent task ID.
This is the push channel the run console rides: TaskStarted / TaskCompleted /
TaskFailed / TaskBlocked / TaskSkipped / TaskAwaitingHuman / GraphSettled, plus the
debugger frames (GateDecision, ClaimChanged, PassCompleted, GraphPaused,
GraphResumed, BreakpointHit, NodeProgress). Addressed by graph rather than by session on
purpose — a durable graph outlives the tab that submitted it, so "watch this workflow run"
survives a refresh and works for whoever the server authorizes (the server filter is
owner-scoped and fails closed).
Frames are advisory; rows are truth. Delivery is best-effort and in-process on the server,
so consumers should treat a frame as a trigger to render and reconcile from MJ: Tasks rows
on attach, on reconnect, and on GraphSettled — never as a substitute for them.
The stream is shared and refcounted per graph: ten components watching one run cost one
WebSocket subscription. Errors and completions tear the shared entry down so the next call
re-subscribes fresh (the same posture PushStatusUpdates takes).
ProtectedTransformProtectedTransformTransforms the result set from simple objects to entity objects if needed.
The RunViewParams used for the request
The RunViewResult returned from the request
OptionalcontextUser: UserInfoThe user context for permissions
Unsubscribes from cache invalidation events. Called during cleanup/logout.
ProtectedUpdateUpdates the local metadata cache with new data.
The new metadata to store locally
Protected StaticarrayConverts an ArrayBuffer to a base64-encoded string. Used for compressed metadata storage/retrieval.
Protected Staticbase64Converts a base64-encoded string to an ArrayBuffer. Used for compressed metadata storage/retrieval.
StaticclearClears all MJ client-side caches that are tied to a user session.
Call this on logout to ensure that a subsequent login as a different user does not see stale metadata or cached data rows from the previous session.
Specifically clears:
MJ_Metadata IndexedDB database (entity definitions, RunView/RunQuery/Dataset caches)preservedKeyslocalStorage keys to keep across logout (e.g. theme preference). Defaults to an empty set.
StaticExecuteStatic version of the ExecuteGQL method that will use the global instance of the GraphQLDataProvider and execute the specified query with the provided variables. If the token is expired, it will attempt to refresh the token and then re-execute the query. If the token is expired and the refresh fails, it will throw an error.
StaticExecuteExecutes a GraphQL query/mutation with real upload progress tracking via XMLHttpRequest. Useful for large payload mutations such as file uploads where byte-level progress reporting is required.
OptionalonProgress: (event: { loaded: number; percent: number; total: number }) => voidStaticFieldThe single wording for "you cannot use this field," modeled on SQL Server's posture of never disclosing whether an object is missing or merely inaccessible.
Naming the field is safe — the caller supplied it, so it tells them nothing they did not already know. Naming the REASON is not: confirming "this field exists and is restricted" turns any predicate into an oracle for probing which columns a deployment considers sensitive. The ambiguity also keeps this message correct after #3485 tiers metadata and restricted fields stop shipping to clients at all, at which point "does not exist" becomes literally true from the client's vantage point.
StaticRefreshProtected StaticUnionReturns the caller's requested fields (lowercased) unioned with the entity's
primary key field names. Platform contract: when Fields is explicitly
specified, results ALWAYS include the primary key(s) — the direct SQL path has
always done this, differential smart-cache merges require it, and entity
linking in UIs depends on it. Applying the same union at every projection site
keeps result shapes identical across cached, non-cached, and smart-cache paths.
The GraphQLDataProvider class is a data provider for MemberJunction that implements the IEntityDataProvider, IMetadataProvider, IRunViewProvider, IRunQueryProvider interfaces and connects to the MJAPI server using GraphQL. This class is used to interact with the server to get and save data, as well as to get metadata about the entities and fields in the system.