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_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.
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.
Gets all explorer navigation items including inactive ones.
Array of all ExplorerNavigationItem objects
Returns the currently loaded local metadata from within the instance
ProtectedAllowInternalGets whether metadata refresh is currently allowed
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
Which SQL platform this host speaks — selects the colocated provider's SQL/placeholder syntax.
Reuses the canonical DatabasePlatform from @memberjunction/sql-dialect.
Default schema where MJ entity tables/views live (e.g. "__mj").
Gets the current configuration data for this provider instance
Gets the current user's information including roles and permissions.
UserInfo object for the authenticated user
Gets the underlying SQL Server connection pool
The mssql ConnectionPool object
ProtectedDBRegex pattern matching known database default-value functions (non-UUID) for this provider's platform. SQL Server should match GETDATE, GETUTCDATE, SYSDATETIME, etc. PostgreSQL should match NOW, CURRENT_TIMESTAMP, clock_timestamp, etc. Case-insensitive, should match the full string with optional whitespace and parens.
The SQLDialect instance matching this provider's PlatformKey.
Use this whenever runtime code needs to emit dialect-specific SQL
(boolean literals, identifier quoting, casts, …) — it spares callers
from doing GetDialect(provider.PlatformKey) every time, and keeps
dialect resolution in one place. Resolves lazily and is cached so
repeated access is free.
Example:
const lit = provider.Dialect.BooleanLiteral(true); // '1' on SS, 'TRUE' on PG
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.
Checks if we're currently in a nested transaction (depth > 1)
For the SQLServerDataProvider the unique instance connection string which is used to identify, uniquely, a given connection is the following format: mssql://host:port/instanceName?/database instanceName is only inserted if it is provided in the options
Checks if we're currently in a transaction (at any depth)
Whether this provider currently has an active transaction. Subclasses
that track transaction state should override this. Used by callers
(e.g. runMaybeSerial) to decide whether to fan out concurrent saves
or run them sequentially. Defaults to false for providers that don't
expose this state.
Gets whether a transaction is currently active
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
ProtectedLocalThis property will return the prefix to use for local storage keys. This is useful if you have multiple instances of a provider running in the same environment and you want to keep their local storage keys separate. The default implementation returns an empty string, but subclasses can override this to return a unique string based on the connection or other distinct identifier.
Returns the active local storage provider, lazily creating an InMemoryLocalStorageProvider if none has been set.
This fulfills the abstract LocalStorageProvider requirement from
ProviderBase and is shared by all database providers
(SQL Server, PostgreSQL, and any future platforms).
ProtectedMetadataGets the MemberJunction core schema name (defaults to __mj if not configured)
ProtectedPlatformReturns the batch separator token for the underlying database platform by delegating to
the SQLDialect instance returned by getDialect().
SQL Server → 'GO', PostgreSQL → '' (no separator needed).
Auto-injected as the default batchSeparator in CreateSqlLogger.
Returns the database platform key for this provider. Override in subclasses. Defaults to 'sqlserver' for backward compatibility. Inherited from ProviderBase; redeclared here for DatabaseProviderBase consumers.
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
Maximum number of parameters a CRUD stored procedure (spCreate*/spUpdate*/spDelete*)
may declare on this database platform before CodeGen must emit a JSON-arg shape instead.
Infinity — SS supports up to 2,100 parameters per procedure, far beyond
any realistic CRUD sproc.90 — PG's hard FUNC_MAX_ARGS ceiling is 100 (compiled into the server,
not configurable on managed services like RDS/Aurora). 90 leaves headroom for column
adds without flipping sproc shape unexpectedly.Used by the shared useJsonArgShape predicate (CodeGen + provider call-site) to decide
whether a given sproc should emit as typed-args + _Clear companions (today's shape) or
as a single-JSONB-arg sproc with key-presence tri-state semantics. See
plans/json-arg-crud-sprocs.md and GitHub
issue #2552.
Gets 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
Gets the current savepoint names in the stack (for debugging) Returns a copy to prevent external modification
Gets the current transaction nesting depth 0 = no transaction, 1 = first level, 2+ = nested transactions
ProtectedTrustServer-side providers trust the local cache completely because it is kept in perfect sync via BaseEntity save/delete events and cross-server Redis pub/sub. No lightweight DB validation needed on cache hits.
ProtectedUUIDRegex pattern matching known database UUID/ID generation functions for this provider's platform. SQL Server should match NEWID, NEWSEQUENTIALID. PostgreSQL should match gen_random_uuid, uuid_generate_v4. Case-insensitive, should match the full string with optional whitespace and parens.
Gets only active explorer navigation items sorted by sequence. Results are cached for performance.
Array of active ExplorerNavigationItem objects
Protected_Internal method to log SQL statement to all active logging sessions. This is called automatically by ExecuteSQL methods. Protected so platform-specific providers can reference it (e.g., to bind as a callback).
The SQL query being executed
Optionalparameters: unknownParameters for the query
Optionaldescription: stringOptional description for this operation
OptionalignoreLogging: booleanIf true, this statement will not be logged
OptionalisMutation: booleanWhether this is a data mutation operation
OptionalsimpleSQLFallback: stringOptional simple SQL to use for loggers with logRecordChangeMetadata=false
OptionalcontextUser: UserInfoOptional user context for session filtering
ProtectedAdjustSQL Server-specific datetime field adjustments.
SQL Server's datetime2 and datetime types store values without timezone info.
The mssql driver creates Date objects using local timezone interpretation, which is
incorrect when the server stores UTC. This method adjusts dates back to UTC.
For datetimeoffset, the driver sometimes mishandles timezone info (detected via
NeedsDatetimeOffsetAdjustment()), requiring similar correction.
ProtectedapplyApplies in-memory pagination to query results based on StartRow and MaxRows parameters.
ProtectedassertGuards external-data-source reads against silently bypassing Row-Level Security. A remote system can't enforce MJ's RLS WHERE clauses, so if RLS would filter this user's rows we refuse the read with a clear error rather than returning unfiltered data. Users exempt from RLS (e.g. admins) get an empty clause and pass through — RLS wouldn't restrict them on an MJ-DB entity either. Called from the external RunView and Load dispatch points.
ProtectedassertRejects RunView params an external data source can't honor, rather than silently dropping them. Most important is AfterKey (keyset pagination): the external read path only supports offset paging, so a silently-dropped AfterKey would return the same page on every call — an infinite loop / duplicate processing in deep-pagination jobs. Aggregates and a non-empty UserSearchString likewise can't be evaluated remotely. Throws a clear error naming the param.
ProtectedauditCreates an audit log record for query execution (fire-and-forget).
Only logs if the query has AuditQueryRuns enabled or ForceAuditLog is set.
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: IMetadataProviderBegin an independent transaction for IS-A chain orchestration. Returns a new sql.Transaction object that is NOT linked to the provider's internal transaction state (used by TransactionGroup). Each IS-A chain gets its own transaction to avoid interference with other operations.
Begins a transaction for the current database connection.
ProtectedBuildBuilds and validates an aggregate SQL query from the provided aggregate expressions. Uses SQLExpressionValidator from @memberjunction/global for injection prevention. Uses QuoteIdentifier/QuoteSchemaAndView for dialect-neutral SQL generation.
Array of aggregate expressions to validate and build
Entity metadata for field reference validation
Schema name for the entity
Base view name for the entity
WHERE clause to apply (without the WHERE keyword)
Object with aggregateSQL string and any validation errors
ProtectedBuildBuilds a UNION ALL query that probes each child entity's BaseView for a record with the given primary key. Returns the first match (disjoint subtypes guarantee at most one result) unless used with overlapping subtypes.
Probes BaseView rather than BaseTable so the runtime SQL identity, which has SELECT only on views (not on the underlying tables), can execute it. All identifier and string-literal formatting is delegated to the dialect so the same generation logic produces correct SQL on any future platform.
ProtectedBuildBuilds 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
ProtectedBuildBuilds the ExecuteSQLOptions for a Delete operation.
ProtectedBuildBuilds the SQL to retrieve the "name" field value for a specific entity record. Uses QuoteIdentifier/QuoteSchemaAndView for dialect-neutral SQL generation.
The entity name
The record's primary key
The SQL query string, or null if the entity has no name field
ProtectedBuildBuilds SQL for hard-link (foreign key) dependency queries. Returns a UNION ALL query across all dependent entities.
The entity-level dependency metadata
The primary key of the record being checked
ProtectedBuildValidates that the entity and RunViewParams are compatible with keyset (AfterKey) pagination,
then returns the SQL predicate (<pk> > 'value' or <pk> < 'value') and the resolved
order-by direction.
See AfterKeyNotSupportedError for the validation rules and RunViewParams.AfterKey for the API contract.
ProtectedBuildBuilds a platform-specific non-paginated row limit clause appended at end of query.
SQL Server: returns '' (already handled by TOP in SELECT clause).
PostgreSQL: returns LIMIT N.
Default: returns empty string. PG overrides.
ProtectedBuildBuilds a platform-specific pagination clause.
SQL Server: OFFSET X ROWS FETCH NEXT Y ROWS ONLY
PostgreSQL: LIMIT Y OFFSET X
Builds a parameter placeholder for parameterized queries. Default: PG-style ($1, $2, ...). SQL Server overrides to @p0, @p1, etc.
Zero-based parameter index
ProtectedbuildBuild the SQL fragment that compares one EntityField against a user search term.
Resolution order:
{0} replaced
by the single-quote-escaped raw term. Caller is responsible for escaping
LIKE metacharacters in their format string if needed.ProtectedBuildBuilds the dialect-agnostic payload for a RecordChange row from the entity's old/new data and an optional restore context. Concrete providers consume the returned payload to render their dialect-specific SQL (SQL Server EXEC, PostgreSQL INSERT, etc.).
Returns null when there's nothing to log — i.e., an Update where
DiffObjects found no field-level changes. Creates and Deletes
are always logged (one side of oldData/newData is null).
The payload's recordID is whatever the caller passes in. PG's
inline CTE save/delete paths can pass an empty string and resolve
the actual RecordID expression in SQL (because the post-INSERT PK
isn't known in JS); the standalone BuildRecordChangeSQL path
passes a fully-resolved composite-key string.
Post-change data (null for deletes).
Pre-change data (null for creates).
Composite-key serialized RecordID, or empty for CTE callers.
Entity metadata (provides EntityID + field shapes for diff).
Change type. Create and Delete skip the change-key short-circuit.
Acting user (provides UserID).
OptionalrestoreContext: RestoreContextWhen non-null, populates source='Restore' plus the
lineage columns; otherwise source='Internal'.
OptionalquoteToEscape: stringQuote character for EscapeQuotesInProperties and
DiffObjects. Defaults to single quote.
ProtectedBuildImplements the abstract BuildRecordChangeSQL from DatabaseProviderBase. Delegates to GetLogRecordChangeSQL for T-SQL generation.
OptionalrestoreContext: RestoreContextProtectedBuildBuilds the ExecuteSQLOptions for a Save operation. SQL Server overrides to add connectionSource for IS-A shared transactions.
ProtectedBuildGenerates a single block of T-SQL for one sibling entity in the Record Change propagation batch. Uses SELECT...FOR JSON to get the full record, then conditionally inserts a Record Change entry via spCreateRecordChange_Internal.
ProtectedBuildThis function will generate SQL statements for all of the possible soft links that are not traditional foreign keys but exist in entities where there is a column that has the EntityIDFieldName set to a column name (not null). We need to get a list of all such soft link fields across ALL entities and then generate queries for each possible soft link in the same format as the hard links
ProtectedBuildProtectedBuildBuilds the SELECT COUNT(*) AS TotalRowCount FROM ... SQL used to compute the total
row count for paginated views. Returns null when the view isn't row-limited (no count
query needed).
Two PG-parity details this method encapsulates — both caught real regressions:
When to emit the count query. Returns non-null whenever rows are being limited
— either explicit pagination or MaxRows/UserViewMaxRows. Earlier code keyed off
topSQL.length > 0, which was SQL-Server-specific: PG's BuildTopClause returns
empty (PG uses LIMIT appended at end via BuildNonPaginatedLimitSQL, not TOP
in the SELECT). So the old condition missed every PG case where MaxRows was set
without explicit StartRow — Explorer's Entity list was one such case and showed
"100 of 100" (no pagination) instead of "100 of 299".
Quote the alias via QuoteIdentifier. PostgreSQL folds unquoted column aliases
to lowercase, so AS TotalRowCount returns a row keyed totalrowcount on PG. The
caller reads countResult[0].TotalRowCount (PascalCase) and gets undefined — the
count falls back to retData.length (the page size), so pagination breaks silently.
SQL Server is case-insensitive so it worked unquoted there. Quoting via
QuoteIdentifier produces "TotalRowCount" on PG and [TotalRowCount] on SQL
Server — both preserve case.
ProtectedbuildBuilds the WHERE clause for cache status check, using same logic as InternalRunView. Handles ExtraFilter, UserSearch, and Row-Level Security. Subclasses can override to add platform-specific SQL transformations (e.g., identifier quoting).
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: UserInfoProtectedcacheCaches query results if caching is enabled for the query. Currently a no-op (query caching is not active).
ProtectedCheckChecks whether a new record's field values pass the Create RLS filter. Builds a synthetic single-row subquery from entity field values, then tests the RLS filter against it.
ProtectedcheckChecks the paged cache for a specific page of query results. Returns a full RunQueryResult on hit, null on miss. Currently always returns null (query caching is not active).
ProtectedcheckChecks the query cache for existing results and returns them if valid. Currently always returns null (query caching is not active).
ProtectedCheckChecks whether an existing record passes the RLS filter for a given permission type. Executes: SELECT COUNT(*) AS cnt FROM view WHERE PK=value AND (RLS filter) Returns true if the record matches (cnt > 0), false otherwise.
Checks if local metadata is out of date and needs refreshing. Compares local timestamps with server timestamps.
OptionalproviderToUse: IMetadataProviderTrue if refresh is needed, false otherwise
ProtectedCheckChecks that the given user has read permissions on the specified entity. Throws if the user lacks CanRead permission.
The entity to check permissions for
The user whose permissions to check
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[]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.
ProtectedCoerceSQL Server per-field value transform. Coerces datetimeoffset to ISO
string for inline SET emission, and skips non-PK uniqueidentifier
values that look like SQL function calls (e.g. newid()) so the DB
default fires instead. PK-on-create with no explicit value is handled
upstream in GenericDatabaseProvider.GenerateSaveSQL's iteration loop.
Commit an IS-A chain transaction.
The sql.Transaction object returned from BeginISATransaction()
Commits the current transaction.
ProtectedCompleteFinalizes merge logging by updating the log record with completion status and creating deletion detail records. Uses BaseEntity with .Set() calls (no typed entity subclass imports).
OptionalcontextUser: UserInfoProtectedcomputeComputes the latest update date for a dataset item from its result rows and dataset metadata. Used by both the cache-hit and cache-miss paths in GetDatasetByName.
The result rows (from cache or SQL)
The field name to scan for latest date
The dataset item metadata row (contains DatasetItemUpdatedAt, DatasetUpdatedAt)
The latest date across all rows and dataset metadata
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: UserInfoConfigures the SQL Server data provider with connection settings and initializes the connection pool
Configuration data including connection string and options
OptionalproviderToUse: IMetadataProviderPromise
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.
Creates an audit log record in the MJ: Audit Logs entity. Uses BaseEntity with .Set() calls (no typed entity subclass imports needed - can't use those from MJCore anyway). Callers typically fire-and-forget.
The user performing the action
Optional authorization name to look up
The audit log type name (must exist in metadata)
'Success' or 'Failed'
Optional details (JSON string, description, etc.)
The entity ID being audited
Optional record ID being audited
Optional description for the audit log
Save options to pass to the entity Save() call
The saved audit log BaseEntity, or null on error
ProtectedCreateBuilds 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 SQL logging session that will capture all SQL operations to a file. Returns a disposable session object that must be disposed to stop logging.
Full path to the file where SQL statements will be logged
Optionaloptions: SqlLoggingOptionsOptional configuration for the logging session
Promise
// Basic usage
const session = await provider.CreateSqlLogger('./logs/metadata-sync.sql');
try {
// Perform operations that will be logged
await provider.ExecuteSQL('INSERT INTO ...');
} finally {
await session.dispose(); // Stop logging
}
// With migration formatting
const session = await provider.CreateSqlLogger('./migrations/changes.sql', {
formatAsMigration: true,
description: 'MetadataSync push operation'
});
Creates a new transaction group for managing database transactions. Must be implemented by subclasses to provide transaction support.
A new transaction group instance
Converts a diff/changes object into a human-readable description of what changed.
The output of DiffObjects()
OptionalmaxValueLength: numberMaximum length for displayed values before truncation
OptionalcutOffText: stringText to append when values are truncated
ProtectedcreateBuilds user search SQL for the given entity and search string.
Supports full-text search (if enabled) and field-by-field LIKE searching. For the LIKE path:
Deletes an entity record — the full orchestration flow shared by all DB providers.
Creates a changes object by comparing two JavaScript objects, identifying fields that have different values. Each property in the returned object represents a changed field, with the field name as the key.
The original data object to compare from
The new data object to compare to
Entity metadata used to validate fields and determine comparison logic
The quote character to escape in string values (typically "'")
A Record mapping field names to FieldChange objects, or null if either input is null/undefined. Only includes fields that have actually changed and are not read-only.
Dispose of this provider instance and clean up resources. This should be called when the provider is no longer needed.
Disposes all active SQL logging sessions. Useful for cleanup on provider shutdown.
ProtectedEncryptEncrypts field values before saving to the database.
This method handles field-level encryption for any entity with encrypted fields. It is called by platform-specific providers (SQL Server, PostgreSQL) before building their SQL parameters, ensuring encryption is handled generically.
For each field marked with Encrypt=true:
EncryptionKeyID is null → throws an error (misconfiguration)The entity being saved
Map of field info to current values — values are mutated in-place
OptionalcontextUser: UserInfoUser context for encryption operations
ProtectedEnqueueOverride to defer AI action tasks when a transaction is active. When inside a transaction, tasks are queued to _deferredTasks and processed after transaction commit (see processDeferredTasks).
ProtectedenrichOptional, additive post-query enrichment step. Resolves the QueryResultEnricherBase
registered under params.Enrichment.EnricherKey via the MJGlobal ClassFactory and awaits
it on the result rows, returning whatever (column-appended) rows it produces.
Fully decoupled + resilient by design:
null and we no-op,
returning the original rows.The loaded QueryInfo (when resolvable from the executed query's id) is passed through so an enricher can read the query's associated entity/fields.
the assembled, paginated result rows to enrich
the run params carrying the RunQueryEnrichment directive
the executed query entity (used to resolve its QueryInfo metadata)
OptionalcontextUser: UserInfothe request user, threaded through for isolation/audit
the enriched rows on success, or the original rows on any failure / no-op
O(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: UserInfoProtectedescapeEscape characters that have special meaning in SQL Server LIKE patterns.
The backslash itself must be escaped first so its replacement isn't reprocessed.
Pair with ESCAPE '\\' on the LIKE clause.
ProtectedEscapeRecursively escapes the specified quote character in all string properties of an object or array. Essential for preparing data to be embedded in SQL strings.
The object, array, or primitive value to process
The quote character to escape (typically single quote "'")
A new object/array with all string values having quotes properly escaped
ProtectedExecuteExecutes an ad-hoc SQL query directly, with security validation. SQL must be a SELECT or WITH (CTE) statement — mutations are rejected.
OptionalcontextUser: UserInfoProtectedExecuteExecutes an aggregate query and maps results back to the original expressions.
The SQL query to execute (from BuildAggregateSQL)
Original aggregate expression definitions
Any validation errors from BuildAggregateSQL
OptionalcontextUser: UserInfoUser context for query execution
Array of AggregateResult objects with execution time
Executes 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
ProtectedexecuteExecutes the query SQL and tracks execution time.
OptionalcontextUser: UserInfoThis method can be used to execute raw SQL statements outside of the MJ infrastructure. CAUTION - use this method with great care.
Optionaloptions: ExecuteSQLOptionsOptionalcontextUser: UserInfoExecutes multiple SQL queries in a single batch for optimal performance. All queries are combined into a single SQL statement and executed together. This is particularly useful for bulk operations where you need to execute many similar queries and want to minimize round trips to the database.
Array of SQL queries to execute
Optionalparameters: any[][]Optional array of parameter arrays, one for each query
Optionaloptions: ExecuteSQLBatchOptionsOptional execution options for logging and description
OptionalcontextUser: UserInfoPromise<any[][]> - Array of result arrays, one for each query
ProtectedexecuteOptionally wraps a view query with user view run logging. SQL Server overrides to use spCreateUserViewRunWithDetail. Default: returns null (no view run logging).
ProtectedextractProtectedextractExtracts the MAX value of a specified date field from result rows as an ISO string. Used for write-through caching of dataset item results.
The result rows
The field name to scan
ISO string of the max date, or current time if no dates found
ProtectedfindOptionalcontextUser: UserInfoDiscovers ALL IS-A child entities that have records with the given primary key. Used for overlapping subtype parents (AllowMultipleSubtypes = true) where multiple children can coexist.
The parent entity whose children to search
The primary key value to find in child tables
OptionalcontextUser: UserInfoOptional context user for audit/permission purposes
Array of child entity names found (empty if none)
Discovers which IS-A child entity, if any, has a record with the given primary key. Executes a single UNION ALL query across all child entity tables for maximum efficiency.
The parent entity whose children to search
The primary key value to find in child tables
OptionalcontextUser: UserInfoOptional context user for audit/permission purposes
The child entity name if found, or null if no child record exists
ProtectedformatFormats a CompositeKey value as a SQL literal for use in a keyset seek predicate.
This bypasses parameter binding because the entire WHERE clause is built as a string elsewhere in this provider (consistent with ExtraFilter / OrderBy handling). The seek value comes from server-side application code, not raw user input — but we still type- check and escape to defend against any caller passing a tainted value.
Strategy:
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: UserInfoProtectedGenerateGenerates a new UUID suitable for use as a primary key or unique identifier. Uses uuidv4() from @memberjunction/global. Subclasses may override to provide platform-specific ID generation if needed.
A new UUID string
ProtectedGenerateConcrete implementation of the abstract save-SQL builder defined on
DatabaseProviderBase. Iterates fields via the single IsSPParameter
predicate, applies provider-specific value coercion, encrypts, then
delegates parameter binding, result-capture wrapping, and record-change
wrapping to abstract hooks the provider subclass implements.
See plans/sp-save-builder-generic-layer-refactor.md (rev 4) for
the design and the rev-3 lesson that motivated this shape.
Gets information about all active SQL logging sessions. Useful for monitoring and debugging.
Array of session information objects
ProtectedGetRetrieves 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: IMetadataProviderProtectedgetExecutes a batched cache status check for multiple queries using their CacheValidationSQL.
OptionalcontextUser: UserInfoProtectedgetExecutes a batched cache status check for multiple views in a single SQL call. Uses multiple result sets to return status for each view efficiently.
OptionalcontextUser: UserInfoThis 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
ProtectedgetValidates columns for a dataset item and returns the column list string. Returns null if columns are invalid.
Returns the stored procedure / function name for a Create or Update operation. Pure metadata lookup — no SQL execution needed. SQL Server uses spCreate/spUpdate naming, PostgreSQL uses the same pattern.
The entity being saved
True for Create, false for Update
The SP/function name
Gets 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 a dataset by name, executing all item queries via ExecuteSQLBatch and aggregating results. Uses dialect-neutral quoting for all SQL construction.
ExecuteSQLBatch gives SQL Server true multi-result-set batching automatically, while PG (and the default) use parallel individual queries.
OptionalitemFilters: DatasetItemFilterType[]OptionalcontextUser: UserInfoOptionalproviderToUse: IMetadataProviderOptionalforceRefresh: booleanCreates 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 status information for a dataset by name: per-entity row count and latest update date. Uses ExecuteSQLBatch for per-item status queries.
OptionalitemFilters: DatasetItemFilterType[]OptionalcontextUser: UserInfoOptionalproviderToUse: IMetadataProviderProtectedgetGets IDs of records deleted since a given timestamp. Uses dialect-neutral quoting. Subclasses can override for parameterized queries.
OptionalcontextUser: UserInfoProtectedGetInternalGenerates the SQL statement for deleting an entity record
The entity to delete
The user performing the delete
The full SQL statement for deletion
ProtectedgetReturns the SQL Server dialect instance so that platform-specific tokens
(batch separator, quoted identifiers, etc.) are sourced from
@memberjunction/sql-dialect rather than hardcoded strings.
ProtectedGetReturns AI actions configured for the given entity and timing. Uses AIEngine metadata to find matching EntityAIAction records.
Returns a list of entity dependencies, basically metadata that tells you the links to this entity from all other entities.
Creates 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
ProtectedGetRecursively enumerates an entity's entire sub-tree from metadata. No DB queries — uses EntityInfo.ChildEntities which is populated from metadata.
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
ProtectedGetOptionalrestoreContext: RestoreContextRetrieves the change history for a specific record. Uses the vwRecordChanges view which exists in both SQL Server and PostgreSQL.
The entity name
The record's composite primary key
OptionalcontextUser: UserInfoOptional context user
Returns a list of record-level dependencies — records in other entities linked to the specified entity/record via foreign keys (hard links) or EntityIDFieldName soft links. Uses abstract SQL builders for dialect-specific query generation.
The entity name to check
The primary key(s) of the record
OptionalcontextUser: UserInfoOptional context user
ProtectedGetInitiates duplicate detection for a list of records. Uses BaseEntity to create a Duplicate Run record. Subclasses may override to provide additional functionality.
The duplicate detection request parameters
OptionalcontextUser: UserInfoThe acting user
A response indicating the duplicate detection status
Gets the favorite record ID if the record is a favorite for the given user, null otherwise.
OptionalcontextUser: UserInfoChecks if a record is marked as a favorite for a given user.
OptionalcontextUser: UserInfoProtectedgetResolves the list of EntityFieldInfo objects for a view query. Priority: params.Fields > view columns > all entity fields (wildcard).
ProtectedgetBuilds the SQL field list string for a view query, using dialect-neutral quoting. Returns '*' if no specific fields are resolved.
Generates the SQL Statement that will Save a record to the database.
This method is used by the Save() method of this class, but it is marked as public because it is also used by the SQLServerTransactionGroup to regenerate Save SQL if any values were changed by the transaction group due to transaction variables being set into the object.
The entity to generate save SQL for
Whether this is a new record (create) or existing record (update)
The user context for the operation
The full SQL statement for the save operation
Gets a specific SQL logging session by its ID. Returns the session if found, or undefined if not found.
The unique identifier of the session to retrieve
The SqlLoggingSession if found, undefined otherwise
ProtectedGetReturns provider-specific extra data to attach to a TransactionItem. SQL Server overrides to include { dataSource: this._pool }.
ProtectedgetGets rows updated/created since a given timestamp. Uses dialect-neutral quoting and TransformExternalSQLClause for OrderBy.
OptionalcontextUser: UserInfoProtectedHandleHandles entity actions (non-AI) for save, delete, or validate operations. Uses EntityActionEngineServer to discover and run active actions.
ProtectedHandleHandles Entity AI Actions for save or delete operations.
For "before save" actions: blocks (awaits) until complete. For "after save" actions: fires and forgets via QueueManager.
Subclasses that manage transactions can override to defer after-save tasks until after transaction commit (see SQLServerDataProvider).
ProtectedInternalLower-layer execution: resolves composition, processes templates, executes SQL. This is the single execution pathway used by both saved queries (via RunQuery upper layer) and transient test queries (via TestQuerySQL resolver).
Processing order:
OptionalcontextUser: UserInfoProtectedInternalRetrieves the display name for a single entity record. Uses BuildEntityRecordNameSQL for dialect-neutral SQL generation.
OptionalcontextUser: UserInfoProtectedInternalRetrieves display names for multiple entity records.
OptionalcontextUser: UserInfoProtectedInternalServer in-process transport for Remote Operations: resolves the registered operation by key
and runs it via ExecuteServer. Inherited by both SQL Server and PostgreSQL providers. The
client (GraphQL) provider overrides this to marshal over the wire instead.
ProtectedInternalBatch query execution — runs all queries in parallel.
OptionalcontextUser: UserInfoProtectedInternalFull query execution pipeline: resolve → validate → compose → template → cache check →
execute → paginate → audit → cache store. Platform providers inherit this; only
ExecuteSQL() is platform-specific.
OptionalcontextUser: UserInfoProtectedInternalShared InternalRunView implementation. Handles: view resolution, permissions, field selection, WHERE clause building (view + extra filter + user search + exclude + RLS), ORDER BY, pagination, aggregates, parallel query execution, post-processing, and audit logging.
OptionalcontextUser: 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.
ProtectedisCompares client cache status with server status to determine if cache is current. Checks both row count and maxUpdatedAt timestamp.
ProtectedisDetects infrastructure-level connection errors (timeout, refused, pool closed) as opposed to query-level errors (bad SQL, constraint violations). Delegates to the dialect's driver-specific error classification, with a fallback for POOL_CLOSED errors thrown by our own code.
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[]ProtectedIsChecks whether a given entity matches the target name, or is an ancestor of the target (i.e., the target is somewhere in its descendant sub-tree). Used to identify and skip the active branch during sibling propagation.
ProtectedIsA saved query is "external" when it resolves to a Query bound to an external data source. Used by the base RunQuery CacheLocal layer to defer external-query caching to InternalRunQuery's runExternalQueryWithCache. Non-throwing — resolves from cached metadata.
Checks whether a string value looks like a known database default-value function that is NOT a UUID generator for this provider's platform.
The string value to check
true if the value matches a known non-UUID database function pattern
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).
ProtectedisTrue if the field's column type is appropriate for text-pattern search. Non-text types are rejected because LIKE forces an implicit per-row CONVERT to nvarchar. Unbounded (MAX/ntext/text) columns are rejected because the LIKE path cannot seek them; FTX is the right tool for those.
Checks whether a string value looks like a database UUID generation function for this provider's platform.
The string value to check
true if the value matches a known UUID generation function pattern
Loads a single entity record by composite key, with optional relationship loading. Uses dialect-neutral quoting for all SQL construction.
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
ProtectedLogLogs a record change entry by diffing old/new data and executing provider-specific SQL to insert the record change. Concrete orchestration; SQL generation is delegated to BuildRecordChangeSQL.
The new record data (null for deletes)
The old record data (null for creates)
The entity name
The record ID (CompositeKey string)
The entity metadata
The change type
The acting user
OptionalrestoreContext: RestoreContextProtectedMapProtectedmergeMerges 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
ProtectedmergeFolds a saved view's stored WhereClause and OrderByClause into the RunView params before external dispatch. The external branch returns before the normal SQL path that applies them, so without this a UserView over an external entity would silently return unfiltered, unordered rows. The view's WhereClause is ANDed with any caller ExtraFilter; the view's OrderByClause is used only when the caller supplied no OrderBy. Returns params unchanged when there is no saved view.
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
Merges multiple records into a single surviving record. Full orchestration: transaction, field map update, dependency re-pointing, deletion, and merge logging.
The merge request with surviving record and records to merge
OptionalcontextUser: UserInfoThe acting user
Optional_options: EntityMergeOptionsOptional merge options
The merge result
Determines whether the database driver requires adjustment for datetimeoffset fields. This method performs an empirical test on first use to detect if the driver (e.g., mssql) incorrectly handles timezone information in datetimeoffset columns.
True if datetimeoffset values need timezone adjustment, false otherwise
const provider = new SQLServerDataProvider();
if (await provider.NeedsDatetimeOffsetAdjustment()) {
console.log('Driver requires datetimeoffset adjustment');
}
Some database drivers (notably TypeORM) incorrectly interpret SQL Server's datetimeoffset values as local time instead of respecting the timezone offset. This method detects this behavior by inserting a known UTC time and checking if it's retrieved correctly. The test is performed only once and the result is cached for performance.
ProtectedOnCalled after a successful delete. Intentionally synchronous — see OnAfterSaveExecute.
ProtectedOnCalled after a successful save (both direct and transaction-callback paths). Intentionally synchronous (fire-and-forget) — SQL Server overrides to dispatch after-save entity actions and AI actions without awaiting.
ProtectedOnCalled before the delete SQL is executed. SQL Server overrides to fire before-delete entity actions and AI actions.
ProtectedOnCalled before the save SQL is executed. SQL Server overrides this to fire before-save entity actions and AI actions.
ProtectedOnCalled after a save/delete SQL operation completes (success or failure) to resume refresh.
ProtectedOnCalled after a direct (non-transaction) save succeeds, before the result is returned to the caller and loaded into the entity via finalizeSave().
This hook can optionally return a Record<string, unknown> containing field values
that should be patched onto the SP result row before it is loaded into the entity.
This solves a timing problem: the SP result is captured before OnSaveCompleted runs,
so any data created by post-save hooks (e.g., geocoding writing to a JOINed table)
would be stale in the returned entity without this patch.
result[0] with the current view dataOnSaveCompleted runs post-save logic (geocoding, ISA propagation, etc.)result[0] via Object.assign(), overwriting stale valuesAfter geocoding updates RecordGeoCode, the new lat/lng are returned as patches
for the __mj_Latitude and __mj_Longitude virtual fields that come from the
RecordGeoCode JOIN in the entity's base view. Without this patch, those fields
would contain the pre-geocoding values until the next query.
null if no patches are needed (default behavior)await super.OnSaveCompleted(...) and merge its patches with yoursPatch fields to apply to the SP result, or null if no patches needed
ProtectedOnCalled before starting a save/delete SQL operation to pause background metadata refresh. SQL Server overrides to set _bAllowRefresh = false.
ProtectedOnProtectedpackageReturns a string that packages the parameter value for the stored procedure call. It will handle quoting the value based on the quoteString provided and will also handle null values by returning 'NULL' as a string. Finally, the prefix is used for unicode fields to add the 'N' prefix if needed.
ProtectedparseParses a timestamp string sent by the client.
Accepts both ISO 8601 strings (the canonical form) and all-digit strings
representing milliseconds since epoch. The numeric form has been observed
in the wild from clients whose cache layer round-tripped a Date through a
lossy serializer — new Date('1778004618383') returns Invalid Date,
but new Date(Number('1778004618383')) is a valid timestamp. Returning
null on unparseable input lets callers degrade to a stale-cache fallback
instead of throwing RangeError: Invalid time value on a downstream
.toISOString().
ProtectedPostOptionalorganicKeys: OrganicKeyMetadataRow[]OptionalorganicKeyRelatedEntities: OrganicKeyRelatedEntityMetadataRow[]ProtectedPostPost-processes rows: first applies platform-specific datetime adjustments
via the virtual AdjustDatetimeFields hook, then handles field-level
decryption for encrypted fields.
Subclasses should NOT override this method. Instead, override
AdjustDatetimeFields for platform-specific datetime corrections.
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
ProtectedPrePre-processing hook for RunView. Handles telemetry, validation, entity status check, and cache lookup.
The view parameters
OptionalcontextUser: UserInfoOptional user context
Pre-processing result with cache status and optional cached result
ProtectedPrePre-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: IMetadataProviderPublic backward-compatible wrapper that delegates to PostProcessRows (inherited from GenericDP). Used by SQLServerTransactionGroup which needs a public entry point for row processing.
PostProcessRows (GenericDP) handles: AdjustDatetimeFields → encryption decryption.
OptionalcontextUser: UserInfoProtectedprocessProcesses query parameters: resolves {{query:"..."}} composition tokens,
then applies Nunjucks template substitution for {{param}} tokens.
Delegates to RenderPipeline.Run for the composition → template pipeline. Paging is handled separately by the caller (InternalRunQuery).
Optionalparameters: Record<string, string>OptionalcontextUser: UserInfoProtectedPropagatePropagates record change entries to sibling branches of an IS-A hierarchy. Called after saving an entity with AllowMultipleSubtypes (overlapping subtypes). Collects SQL from BuildSiblingRecordChangeSQL for each sibling and executes as a batch.
The parent entity info
The changes JSON and description
The primary key value
The acting user ID
The child entity that initiated the save (to skip)
OptionalextraExecOptions: Record<string, unknown>Optional provider-specific execution options (e.g. connectionSource for SQL Server transactions)
Quotes a database identifier (table, column, view name) using the provider's dialect convention. SQL Server uses [brackets], PostgreSQL uses "double quotes".
The identifier to quote
Quotes a schema-qualified object name (e.g. schema.viewName) using the provider's dialect convention. SQL Server uses [schema].[view], PostgreSQL uses "schema"."view".
The schema name
The object name (table, view, etc.)
ProtectedRebuildRebuilds the O(1) entity lookup Maps from the current AllEntities array. Called automatically from UpdateLocalMetadata().
Metadata refresh invalidates this pool's shared view-column-order cache before re-running Config, so view changes from migrations/CodeGen (the same events that change metadata) are picked up by the subsequent rescan instead of serving a stale column order until restart.
OptionalproviderToUse: IMetadataProviderOverride RefreshIfNeeded to skip refresh when a transaction is active This prevents conflicts between metadata refresh operations and active transactions
Promise
Refreshes the remote metadata timestamps from the server. Updates the internal cache of remote timestamps.
OptionalproviderToUse: IMetadataProviderTrue if timestamps were successfully refreshed
Removes all cached metadata from local storage. Clears both timestamps and metadata collections.
ProtectedRenderRenders the SQL Server DECLARE/SET/EXEC binding for a save call.
Emits per-field uuid-suffixed variables to keep batched saves
(SQLServerTransactionGroup) collision-free. PKs on UPDATE are
tail-appended from entity.PrimaryKey.KeyValuePairs.
Emits _Clear companion args when a nullable column carrying a
non-NULL DB default is explicitly set to NULL — without it the SP
body's ISNULL(@Param, [Col]) (update) / default-substitution
(create) would silently keep the existing value. See
EntityFieldInfo.NeedsClearCompanion.
ProtectedRenderRenders the WHERE clause for a saved view, replacing template variables like {%UserView "viewId"%} with subquery SQL. Handles nested/recursive templates with circular reference detection.
Uses QuoteIdentifier/QuoteSchemaAndView for dialect-neutral SQL generation.
Optionalstack: string[]ProtectedresolveResolves a category path string to a QueryCategoryInfo ID.
ProtectedresolveResolves the cache TTL (in ms) for a RunView's entity, or undefined for MJ-DB entities
(which use event-based cache invalidation, not TTL). External-data-source entities MUST be
time-bounded — their remote data changes never emit BaseEntity events — so this returns the
data source's DefaultCacheTTLSeconds (via the external router) in ms, or 0 to signal "do
not cache" (TTL disabled on the source, or no router available to resolve one).
OptionalcontextUser: UserInfoProtectedResolveResolves 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.
ProtectedresolveResolves a query from RunQueryParams (by ID or Name+CategoryPath). Uses QueryEngine as the single source of truth for query metadata.
ProtectedResolveB45 override of the RunQuery cache-serve seam — enforce the SAME authorization on a
cache HIT that the miss path enforces. Resolution uses resolveQuery() (QueryEngine as
the single source of truth, with CategoryPath disambiguation of same-named queries — the
B46 pairing), and authorization is the FULL MJQueryEntityExtended.UserCanRun (roles +
entity CanRead + recursive composition) — exactly the check ValidateQueryForExecution
applies before a real execution. The base's roles-only check would let a user read cached
rows of a query whose underlying entities they cannot read.
Non-throwing by contract: any resolution error degrades to resolvable: false, which the
gate treats as "fall through to authorized execution" — one extra query run, never an
unauthorized serve and never a crashed cache path.
Optionaluser: UserInfoResolves 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.
Rollback an IS-A chain transaction.
The sql.Transaction object returned from BeginISATransaction()
Rolls back the current transaction.
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.
Execute a parameterized statement for a colocated vector provider against this connection.
Positional params bind to @p0..@pN (handled by ExecuteSQL's array path), so the
colocated provider emits @p0-style placeholders. Returns the recordset rows.
Optionalparams: readonly unknown[]ProtectedrunRuns a differential query and returns only changes since the client's cached state. Includes updated/created rows and deleted record IDs. Falls back to full query if hidden deletes are detected.
OptionalcontextUser: UserInfoOptionalcallerFields: string[]ProtectedrunExecutes an external-data-source query with TTL-based result caching keyed off the data source's DefaultCacheTTLSeconds. External queries can't be event-invalidated (their data lives on a remote system), so a time-bounded cache is the read-cost mitigation the plan calls for — notably for warehouses like Snowflake. The data source's TTL is the source of truth (a TTL of <= 0 disables caching for the source); field-drift warnings still apply to freshly-fetched rows. Ad-hoc SQL (params.SQL) is never cached.
OptionalcontextUser: UserInfoProtectedrunRuns a full query and stores the result in the server's LocalCacheManager. Used by RunViewsWithCacheCheck to populate the server cache for future requests.
OptionalcontextUser: UserInfoOptionalcallerFields: string[]ProtectedrunRuns a full view query and returns results with cache metadata.
OptionalcontextUser: UserInfoProtectedrunRuns a full query and returns results with cache metadata.
OptionalcontextUser: UserInfoOptionalqueryId: stringOptionalvalidationStamp: { maxUpdatedAt?: string; rowCount?: number }ProtectedRunRuns 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
Smart cache validation for batch RunQueries. For each query, if cacheStatus is provided, checks CacheValidationSQL to determine staleness. Returns 'current' if cache is valid, 'stale' with fresh data, or 'no_validation' if the query has no CacheValidationSQL configured.
OptionalcontextUser: UserInfoOptionalcontextUser: UserInfoRuns a report by looking up its SQL definition from vwReports and executing it. Both SQL Server and PostgreSQL share this logic — the only dialect difference is identifier quoting, handled by QuoteIdentifier/QuoteSchemaAndView.
Report parameters including ReportID
OptionalcontextUser: UserInfoOptional context user for permission/audit purposes
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 queriesRuns 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)
Smart cache validation for batch RunViews. For each view request, if cacheStatus is provided, checks if the cache is current by comparing MAX(__mj_UpdatedAt) and COUNT(*) with client's values. Returns 'current' if cache is valid (no data), 'stale' with fresh data, or 'differential' with only changed rows for entities that track record changes.
OptionalcontextUser: UserInfoSaves an entity record — the full orchestration flow shared by all DB providers.
Saves current metadata to local storage for caching. Serializes both timestamps and full metadata collections.
Batch form of SearchEntity. Fans the input list out to N
independent SearchEntity calls via Promise.all; result arrays come
back aligned by input order (result[i] holds the matches for params[i]).
On the server side, the per-entity passes are independent — running them
concurrently is a real wall-clock win when the caller wants results from
multiple entities. On the client side, GraphQLDataProvider overrides
this method to pack the whole batch into a single GraphQL round-trip
instead of issuing N parallel HTTP requests.
See IMetadataProvider.SearchEntities for the contract.
ProtectedsearchServer-side semantic ranking pass for ProviderBase.SearchEntity (and, by extension, the batched ProviderBase.SearchEntities).
The query embedding MUST be generated with the same model that produced
the indexed vectors — otherwise cosine scores compare apples to oranges
and rankings are garbage. We look up the EntityDocument's AIModelID via
AIEngine.Models to recover the driver class / APIName and call
EmbedText(model, text) directly. If the EntityDocument does not specify
a model (or the model isn't loaded) we fall back to
EmbedTextLocal (highest-power local model) only as a last resort.
Vector ranking runs against the in-process SimpleVectorServiceProvider,
which rehydrates the vector pool for entityDocumentId from
MJ: Entity Record Documents.VectorJSON rows.
Failures (no embedding model available, vector index miss) degrade to an empty result set so hybrid mode can still surface lexical matches.
Ranked search over one entity's records. See IMetadataProvider.SearchEntity for the contract and how this differs from EntityByName / FullTextSearch.
Implementation overview (concrete on ProviderBase, used as-is by every
server-side provider; GraphQLDataProvider overrides to proxy via GQL):
params.options.entityDocumentId
override or by looking up the active Search-category doc for the entity).IncludeInUserSearchAPI fields) and the semantic
pass (searchEntitiesSemanticPass, the protected template method
each concrete server provider implements).ComputeRRF() with optional per-list weights.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
Replaces the active local storage provider at runtime.
Use this to swap from the default in-memory provider to a Redis-backed provider (or any other ILocalStorageProvider) after reading application configuration.
The new storage provider to use for all caching operations.
import { RedisLocalStorageProvider } from '@memberjunction/redis-provider';
// During server startup, after config is loaded:
const redis = new RedisLocalStorageProvider({
url: process.env.REDIS_URL,
defaultTTLSeconds: 300
});
(Metadata.Provider as GenericDatabaseProvider).SetLocalStorageProvider(redis);
Creates or deletes a user favorite record for the specified entity record. Uses GetEntityObject and BaseEntity CRUD methods (no entity-specific type imports needed).
ProtectedshouldProtectedShouldDialect-agnostic predicate: should we write a RecordChange entry for this entity? Excludes the Record Changes entity itself to prevent recursion. Provider implementations should call this before invoking BuildRecordChangePayload or constructing dialect SQL.
ProtectedStartCreates the initial merge log record at the start of a merge operation. Uses BaseEntity with .Set() calls (no typed entity subclass imports available in MJCore).
OptionalcontextUser: UserInfoProtectedTransformTransforms a user-provided SQL clause (ExtraFilter, OrderBy, etc.) for platform compatibility. PostgreSQL overrides to quote mixed-case identifiers and convert bracket notation. Default: returns the clause unchanged.
ProtectedTransformTransforms 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
ProtectedTrimTruncates a string value to a maximum length, appending trailing characters if truncated.
ProtectedUpdateUpdates the local metadata cache with new data.
The new metadata to store locally
Returns true when CRUD sproc generation and call-construction for the
given entity + sproc verb should use a single JSON-arg shape (instead of
typed args + _Clear companions).
Convenience wrapper around the pure useJsonArgShape helper, applying
this provider's ProcedureParamLimit. CodeGen and runtime call-sites
can invoke this via the provider instance to keep sproc emit and sproc
invocation in lockstep.
ProtectedValidateValidates the result of a delete SQL execution by checking that the returned primary keys match the entity being deleted. SQL Server overrides to handle the multi-result-set case (CASCADE deletes).
ProtectedValidateValidates that a query can be executed by the given user. Checks both permissions and approval status. Permission failures throw an error. Non-approved status emits a console warning but allows execution to proceed, enabling query testing before formal approval.
The resolved MJQueryEntityExtended to validate
OptionalcontextUser: UserInfoThe user attempting to execute the query
ProtectedValidateValidates a user-provided SQL clause (WHERE, ORDER BY, etc.) to prevent SQL injection. Checks for forbidden keywords (INSERT, UPDATE, DELETE, EXEC, DROP, UNION, etc.) and dangerous patterns (comments, semicolons, xp_ prefix). String literals are stripped before validation to avoid false positives.
The SQL clause to validate
true if the clause is safe, false if it contains forbidden patterns
ProtectedwarnProtectedWrapWraps the bare SP-call binding with SQL Server's no-record-change save
shape: DECLARE ... ; SET ... ; EXEC [schema].sp callArgs. When
record-change tracking is on, this output is further wrapped by
WrapSaveCallWithRecordChange.
ProtectedWrapWraps the save SQL with SQL Server's
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.
StaticExecuteStatic method to execute a batch of SQL queries using a provided connection source. This allows the batch logic to be reused from external contexts like TransactionGroup. All queries are combined into a single SQL statement and executed together within the same connection/transaction context for optimal performance.
Either a sql.ConnectionPool, sql.Transaction, or sql.Request to use for execution
Array of SQL queries to execute
Optionalparameters: any[][]Optional array of parameter arrays, one for each query
OptionalcontextUser: UserInfoPromise<any[][]> - Array of result arrays, one for each query
StaticExecuteStatic helper method for executing SQL queries on an external connection pool. This method is designed to be used by generated code where a connection pool is passed in from the context. It returns results as arrays for consistency with the expected behavior in generated resolvers.
The mssql ConnectionPool to execute the query on
The SQL query to execute
Optionalparameters: anyOptional parameters for the query
OptionalcontextUser: UserInfoPromise<any[]> - Array of results (empty array if no results)
StaticInvalidateInvalidates the shared view-column-order cache for a connection pool, so the next Config()
against that pool re-reads sys.columns. Call after out-of-band DDL that creates or alters
views (migrations, direct SQL) when a metadata Refresh isn't also being performed —
Refresh() invalidates automatically.
StaticIsStatic convenience: checks all platforms' default-value functions. Prefer the instance method when you have a provider reference.
StaticIsStatic convenience: checks all platforms' UUID generation functions. Prefer the instance method when you have a provider reference.
StaticLogStatic method to log SQL statements from external sources like transaction groups. Gets the current provider instance from Metadata.Provider and delegates to the instance _logSqlStatement method.
The SQL query being executed
Optionalparameters: unknownParameters for the query
Optionaldescription: stringOptional description for this operation
OptionalisMutation: booleanWhether this is a data mutation operation
OptionalsimpleSQLFallback: stringOptional simple SQL to use for loggers with logRecordChangeMetadata=false
OptionalcontextUser: UserInfoOptional user context for session filtering
Protected 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.
SQL Server implementation of the MemberJunction data provider interfaces.
This class provides comprehensive database functionality including:
Example