@memberjunction/core 6.2.0-edge.5 → 6.2.0-edge.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/generic/BaseEntitySaveQueue.d.ts +15 -0
  2. package/dist/generic/BaseEntitySaveQueue.d.ts.map +1 -1
  3. package/dist/generic/BaseEntitySaveQueue.js +21 -0
  4. package/dist/generic/BaseEntitySaveQueue.js.map +1 -1
  5. package/dist/generic/EntityFieldRules.d.ts.map +1 -1
  6. package/dist/generic/EntityFieldRules.js +1 -0
  7. package/dist/generic/EntityFieldRules.js.map +1 -1
  8. package/dist/generic/alternateBaseViews.d.ts +119 -0
  9. package/dist/generic/alternateBaseViews.d.ts.map +1 -0
  10. package/dist/generic/alternateBaseViews.js +223 -0
  11. package/dist/generic/alternateBaseViews.js.map +1 -0
  12. package/dist/generic/baseEngine.d.ts +171 -17
  13. package/dist/generic/baseEngine.d.ts.map +1 -1
  14. package/dist/generic/baseEngine.js +722 -117
  15. package/dist/generic/baseEngine.js.map +1 -1
  16. package/dist/generic/baseEngineRegistry.d.ts.map +1 -1
  17. package/dist/generic/baseEngineRegistry.js +2 -1
  18. package/dist/generic/baseEngineRegistry.js.map +1 -1
  19. package/dist/generic/baseEntity.d.ts +216 -21
  20. package/dist/generic/baseEntity.d.ts.map +1 -1
  21. package/dist/generic/baseEntity.js +969 -100
  22. package/dist/generic/baseEntity.js.map +1 -1
  23. package/dist/generic/baseRemotableOperation.d.ts +95 -6
  24. package/dist/generic/baseRemotableOperation.d.ts.map +1 -1
  25. package/dist/generic/baseRemotableOperation.js +112 -8
  26. package/dist/generic/baseRemotableOperation.js.map +1 -1
  27. package/dist/generic/contextTokens.d.ts +136 -0
  28. package/dist/generic/contextTokens.d.ts.map +1 -0
  29. package/dist/generic/contextTokens.js +319 -0
  30. package/dist/generic/contextTokens.js.map +1 -0
  31. package/dist/generic/databaseProviderBase.d.ts +103 -5
  32. package/dist/generic/databaseProviderBase.d.ts.map +1 -1
  33. package/dist/generic/databaseProviderBase.js +381 -52
  34. package/dist/generic/databaseProviderBase.js.map +1 -1
  35. package/dist/generic/embeddedRecord.d.ts +6 -0
  36. package/dist/generic/embeddedRecord.d.ts.map +1 -1
  37. package/dist/generic/embeddedRecord.js +15 -1
  38. package/dist/generic/embeddedRecord.js.map +1 -1
  39. package/dist/generic/entityCompanion.d.ts +7 -0
  40. package/dist/generic/entityCompanion.d.ts.map +1 -1
  41. package/dist/generic/entityCompanion.js +8 -0
  42. package/dist/generic/entityCompanion.js.map +1 -1
  43. package/dist/generic/entityInfo.d.ts +234 -9
  44. package/dist/generic/entityInfo.d.ts.map +1 -1
  45. package/dist/generic/entityInfo.js +592 -77
  46. package/dist/generic/entityInfo.js.map +1 -1
  47. package/dist/generic/entityValueFieldSecurity.d.ts +139 -0
  48. package/dist/generic/entityValueFieldSecurity.d.ts.map +1 -0
  49. package/dist/generic/entityValueFieldSecurity.js +250 -0
  50. package/dist/generic/entityValueFieldSecurity.js.map +1 -0
  51. package/dist/generic/externalDataSourceReadRouter.d.ts +10 -0
  52. package/dist/generic/externalDataSourceReadRouter.d.ts.map +1 -1
  53. package/dist/generic/externalDataSourceReadRouter.js +12 -0
  54. package/dist/generic/externalDataSourceReadRouter.js.map +1 -1
  55. package/dist/generic/fieldSecuritySQLGate.d.ts +359 -0
  56. package/dist/generic/fieldSecuritySQLGate.d.ts.map +1 -0
  57. package/dist/generic/fieldSecuritySQLGate.js +902 -0
  58. package/dist/generic/fieldSecuritySQLGate.js.map +1 -0
  59. package/dist/generic/indexRestrictedFields.d.ts +47 -0
  60. package/dist/generic/indexRestrictedFields.d.ts.map +1 -0
  61. package/dist/generic/indexRestrictedFields.js +129 -0
  62. package/dist/generic/indexRestrictedFields.js.map +1 -0
  63. package/dist/generic/interfaces.d.ts +64 -2
  64. package/dist/generic/interfaces.d.ts.map +1 -1
  65. package/dist/generic/interfaces.js.map +1 -1
  66. package/dist/generic/listMembershipFilter.d.ts +5 -0
  67. package/dist/generic/listMembershipFilter.d.ts.map +1 -0
  68. package/dist/generic/listMembershipFilter.js +23 -0
  69. package/dist/generic/listMembershipFilter.js.map +1 -0
  70. package/dist/generic/localCacheManager.d.ts +126 -14
  71. package/dist/generic/localCacheManager.d.ts.map +1 -1
  72. package/dist/generic/localCacheManager.js +348 -48
  73. package/dist/generic/localCacheManager.js.map +1 -1
  74. package/dist/generic/logging.d.ts.map +1 -1
  75. package/dist/generic/logging.js +7 -1
  76. package/dist/generic/logging.js.map +1 -1
  77. package/dist/generic/metadata.d.ts +3 -2
  78. package/dist/generic/metadata.d.ts.map +1 -1
  79. package/dist/generic/metadata.js +5 -2
  80. package/dist/generic/metadata.js.map +1 -1
  81. package/dist/generic/platformSQL.d.ts +54 -0
  82. package/dist/generic/platformSQL.d.ts.map +1 -1
  83. package/dist/generic/platformSQL.js +55 -0
  84. package/dist/generic/platformSQL.js.map +1 -1
  85. package/dist/generic/providerBase.d.ts +408 -57
  86. package/dist/generic/providerBase.d.ts.map +1 -1
  87. package/dist/generic/providerBase.js +1051 -310
  88. package/dist/generic/providerBase.js.map +1 -1
  89. package/dist/generic/queryInfo.d.ts +13 -0
  90. package/dist/generic/queryInfo.d.ts.map +1 -1
  91. package/dist/generic/queryInfo.js +13 -0
  92. package/dist/generic/queryInfo.js.map +1 -1
  93. package/dist/generic/queryInfoInterfaces.d.ts +8 -0
  94. package/dist/generic/queryInfoInterfaces.d.ts.map +1 -1
  95. package/dist/generic/queryTemplateContext.d.ts +49 -0
  96. package/dist/generic/queryTemplateContext.d.ts.map +1 -0
  97. package/dist/generic/queryTemplateContext.js +113 -0
  98. package/dist/generic/queryTemplateContext.js.map +1 -0
  99. package/dist/generic/recordChangeFieldSecurity.d.ts.map +1 -1
  100. package/dist/generic/recordChangeFieldSecurity.js +13 -2
  101. package/dist/generic/recordChangeFieldSecurity.js.map +1 -1
  102. package/dist/generic/relatedRecordCollection.d.ts +18 -0
  103. package/dist/generic/relatedRecordCollection.d.ts.map +1 -1
  104. package/dist/generic/relatedRecordCollection.js +55 -7
  105. package/dist/generic/relatedRecordCollection.js.map +1 -1
  106. package/dist/generic/remoteOperationDispatch.d.ts.map +1 -1
  107. package/dist/generic/remoteOperationDispatch.js +14 -2
  108. package/dist/generic/remoteOperationDispatch.js.map +1 -1
  109. package/dist/generic/runQuery.d.ts +42 -0
  110. package/dist/generic/runQuery.d.ts.map +1 -1
  111. package/dist/generic/runQuery.js +14 -0
  112. package/dist/generic/runQuery.js.map +1 -1
  113. package/dist/generic/runQuerySQLFilterImplementations.d.ts +33 -6
  114. package/dist/generic/runQuerySQLFilterImplementations.d.ts.map +1 -1
  115. package/dist/generic/runQuerySQLFilterImplementations.js +119 -29
  116. package/dist/generic/runQuerySQLFilterImplementations.js.map +1 -1
  117. package/dist/generic/saveEntityGraphOperation.d.ts.map +1 -1
  118. package/dist/generic/saveEntityGraphOperation.js +3 -0
  119. package/dist/generic/saveEntityGraphOperation.js.map +1 -1
  120. package/dist/generic/securityInfo.d.ts +11 -0
  121. package/dist/generic/securityInfo.d.ts.map +1 -1
  122. package/dist/generic/securityInfo.js +15 -0
  123. package/dist/generic/securityInfo.js.map +1 -1
  124. package/dist/generic/telemetryManager.d.ts +9 -0
  125. package/dist/generic/telemetryManager.d.ts.map +1 -1
  126. package/dist/generic/telemetryManager.js +5 -1
  127. package/dist/generic/telemetryManager.js.map +1 -1
  128. package/dist/generic/transportError.d.ts +41 -0
  129. package/dist/generic/transportError.d.ts.map +1 -0
  130. package/dist/generic/transportError.js +167 -0
  131. package/dist/generic/transportError.js.map +1 -0
  132. package/dist/generic/util.d.ts +12 -0
  133. package/dist/generic/util.d.ts.map +1 -1
  134. package/dist/generic/util.js +52 -3
  135. package/dist/generic/util.js.map +1 -1
  136. package/dist/index.d.ts +9 -0
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +9 -0
  139. package/dist/index.js.map +1 -1
  140. package/dist/views/runView.d.ts +102 -0
  141. package/dist/views/runView.d.ts.map +1 -1
  142. package/dist/views/runView.js +40 -0
  143. package/dist/views/runView.js.map +1 -1
  144. package/dist/views/viewProperties.d.ts +55 -0
  145. package/dist/views/viewProperties.d.ts.map +1 -0
  146. package/dist/views/viewProperties.js +258 -0
  147. package/dist/views/viewProperties.js.map +1 -0
  148. package/package.json +3 -3
@@ -1,6 +1,6 @@
1
1
  import { BaseEntity, BaseEntityEvent } from "./baseEntity.js";
2
2
  import { EntityDependency, EntityDocumentTypeInfo, EntityInfo, RecordDependency, RecordMergeRequest, RecordMergeResult } from "./entityInfo.js";
3
- import { IMetadataProvider, MetadataSweepResult, ProviderConfigDataBase, MetadataInfo, ILocalStorageProvider, IFileSystemProvider, DatasetResultType, DatasetStatusResultType, DatasetItemFilterType, EntityRecordNameInput, EntityRecordNameResult, ProviderType, PotentialDuplicateRequest, PotentialDuplicateResponse, EntityMergeOptions, AllMetadata, IRunViewProvider, RunViewResult, IRunQueryProvider, RunQueryResult, RunViewWithCacheCheckParams, FullTextSearchParams, FullTextSearchResult, SearchEntityParams, EntitySearchResult, IRemoteOperationProvider, RemoteOpInvokeOptions, RemoteOpResult } from "./interfaces.js";
3
+ import { IMetadataProvider, MetadataSweepResult, ProviderConfigDataBase, MetadataInfo, ILocalStorageProvider, IFileSystemProvider, DatasetResultType, DatasetStatusResultType, DatasetItemFilterType, EntityRecordNameInput, EntityRecordNameResult, ProviderType, PotentialDuplicateRequest, PotentialDuplicateResponse, EntityMergeOptions, GetRecordDependenciesOptions, AllMetadata, IRunViewProvider, RunViewResult, IRunQueryProvider, RunQueryResult, RunViewWithCacheCheckParams, FullTextSearchParams, FullTextSearchResult, SearchEntityParams, EntitySearchResult, IRemoteOperationProvider, RemoteOpInvokeOptions, RemoteOpResult } from "./interfaces.js";
4
4
  import { ScoredCandidate } from "./scoring/ReciprocalRankFusion.js";
5
5
  import { RunQueryParams } from "./runQuery.js";
6
6
  import { CachedRunViewResult } from "./localCacheManager.js";
@@ -15,6 +15,7 @@ import { CompositeKey } from "./compositeKey.js";
15
15
  import { ExplorerNavigationItem } from "./explorerNavigationItem.js";
16
16
  import { RunViewParams } from "../views/runView.js";
17
17
  import { DatabasePlatform, PlatformSQL } from "./platformSQL.js";
18
+ import { ContextToken } from "./contextTokens.js";
18
19
  /**
19
20
  * Creates a new instance of AllMetadata from a simple object.
20
21
  * Handles deserialization and proper instantiation of all metadata classes.
@@ -51,6 +52,12 @@ export interface QueryCacheAuthorization {
51
52
  categoryPath?: string;
52
53
  /** Resolved query name — for log messages when the request was by ID only. */
53
54
  queryName?: string;
55
+ /**
56
+ * The context tokens the resolved query's template reads, with every query it composes
57
+ * (`FindQueryContextTokens`). Their values join the fingerprint, so a result that depends on
58
+ * the user or the date is never served to another user or on another day.
59
+ */
60
+ contextTokens?: ContextToken[];
54
61
  /**
55
62
  * Resolved query ID. With `queryName`, the identity the RunQuery fingerprint is built from, so
56
63
  * a request by ID, by name, or by both reaches the same slot.
@@ -62,6 +69,12 @@ export interface QueryCacheAuthorization {
62
69
  * provider cannot compute one (a client, whose slots the server validates instead).
63
70
  */
64
71
  definition?: QueryDefinitionSignature;
72
+ /**
73
+ * The user's field-security profile (`FieldSecuritySQLGate.CatalogDenialProfileKey`) when the
74
+ * run drops denied columns from its rows; it joins the fingerprint, so users with different
75
+ * denials never share a slot. Undefined when the rows are the same for every authorized user.
76
+ */
77
+ fieldSecurityKey?: string;
65
78
  }
66
79
  export declare const AllMetadataArrays: ({
67
80
  key: string;
@@ -123,6 +136,7 @@ export type EntityMetadataRow = BaseMetadataRow & {
123
136
  EntityRelationships?: unknown[];
124
137
  EntitySettings?: unknown[];
125
138
  EntityOrganicKeys?: unknown[];
139
+ EntityAlternateBaseViews?: unknown[];
126
140
  };
127
141
  export type EntityFieldMetadataRow = BaseMetadataRow & {
128
142
  ID: string;
@@ -401,10 +415,11 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
401
415
  */
402
416
  private _metadataReadsJoinTransaction;
403
417
  /**
404
- * The MJ_Metadata dataset items (code + entity name) as of the last full load. The remote
418
+ * The MJ_Metadata dataset items (code + entity name + row count) as of the last load. The remote
405
419
  * timestamps ({@link GetLatestMetadataUpdates}) are keyed by entity name while a dataset
406
420
  * read is scoped by item code, so this is the bridge that lets a staleness check be turned
407
- * into a scoped reload (see {@link PartialRefreshItemCodes}).
421
+ * into a scoped reload (see {@link PartialRefreshItemCodes}) and lets {@link adoptServerMetadata}
422
+ * verify that the received dataset items' row counts match `_latestRemoteMetadataTimestamps`.
408
423
  */
409
424
  private _metadataDatasetItems;
410
425
  /**
@@ -812,11 +827,41 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
812
827
  * @returns Array of view results (shallow-copied Results per caller)
813
828
  */
814
829
  RunViews<T = any>(params: RunViewParams[], contextUser?: UserInfo): Promise<RunViewResult<T>[]>;
830
+ /**
831
+ * Stashes resolved RunViews results on `_inflightViews` for `DedupLingerMs` and schedules
832
+ * cleanup via an isolated static `WeakRef` timer helper so the timer closure's V8 lexical
833
+ * context never retains `this`, `promise`, or `results` on short-lived per-request providers.
834
+ */
835
+ private stashInflightLingerResult;
836
+ private static scheduleInflightLingerExpiry;
815
837
  /**
816
838
  * The original RunViews execution pipeline (pre-processing, cache,
817
839
  * internal execution, post-processing).
818
840
  */
819
841
  private executeRunViewsPipeline;
842
+ /**
843
+ * Puts each refused view's result back at its own index of the caller's batch, with the
844
+ * admitted views' results filling the remaining indexes in order.
845
+ */
846
+ private static interleaveRefusedResults;
847
+ /**
848
+ * Keeps the `TotalRowCount` contract on every way out of the pipeline: a caller that passed
849
+ * `SkipTotalRowCount` gets `undefined`, never a number. Cache hits, the smart-cache check and
850
+ * the GraphQL wire each rebuild a result and each have a reason to fill in a row count, so the
851
+ * contract is applied once here rather than at each of those sites.
852
+ */
853
+ private static clearSkippedTotalRowCount;
854
+ /**
855
+ * Runs a RunViews batch of the views the field-security predicate gate admitted, and applies
856
+ * the `TotalRowCount` contract to whichever stage produced each result.
857
+ */
858
+ private runAdmittedViews;
859
+ /**
860
+ * The cache, database and post-processing stages of a RunViews batch, for the views the
861
+ * field-security predicate gate admitted. `params` and every index map in `preResult`
862
+ * describe the same admitted views.
863
+ */
864
+ private runAdmittedViewStages;
820
865
  /**
821
866
  * Enqueues a RunViews call for coalescing. Multiple calls arriving within
822
867
  * CoalesceWindowMs are merged into a single mega-batch, executed once, and
@@ -890,6 +935,34 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
890
935
  * - entities where server caching is disallowed
891
936
  */
892
937
  protected runViewCacheEligible(param: RunViewParams): boolean;
938
+ /**
939
+ * True when the caller explicitly requested a non-empty `Fields` projection on a plain-object
940
+ * (`ResultType !== 'entity_object'`) read.
941
+ */
942
+ protected hasNarrowFieldsOverride(param: RunViewParams): boolean;
943
+ /**
944
+ * True when a cache-miss read should widen `Fields` to the full entity superset AND populate
945
+ * `LocalCacheManager` with that superset.
946
+ *
947
+ * On the server (`TrustLocalCacheCompletely === true`), cache-allowed entities (`AllowCaching === true`)
948
+ * widen on miss by default so the Fields-agnostic superset slot serves any future `Fields` subset
949
+ * (`server-cache` S1 -> S2 -> S3), UNLESS the caller explicitly opts out via `CacheLocal: false` or
950
+ * `BypassCache: true` (in which case narrow SQL `SELECT` pushdown is preserved end-to-end without
951
+ * polluting the cache). On the client (`TrustLocalCacheCompletely === false`), only `CacheLocal: true`
952
+ * widens and caches.
953
+ */
954
+ protected shouldWidenAndPopulateCache(param: RunViewParams): boolean;
955
+ /**
956
+ * True when the request targets an `AlternateBaseView` (explicitly on `param.AlternateBaseView`
957
+ * or pinned on `param.ViewEntity`) whose `CachePolicy` (or an ancestor's `CachePolicy` in its
958
+ * `ParentID` chain) is `'Bypass'` (`IsBypassCache === true`), when the requested alternate
959
+ * base view does not resolve to a usable view on the entity, or when a saved User View is
960
+ * referenced only by `ViewID`/`ViewName` without a loaded `ViewEntity` (fail-closed: we cannot
961
+ * synchronously inspect `DisplayState.alternateBaseView` until the view is loaded).
962
+ */
963
+ protected isAlternateBaseViewCacheBypassed(param: RunViewParams): boolean;
964
+ /** True when a run carries view properties (even an empty map): the browser sends it to the server as it is. */
965
+ private static carriesViewProperties;
893
966
  /** True when the entity is a CodeGen materialized-query wrapper (materialized_vw*) whose snapshot is
894
967
  * refreshed out-of-band — the same one IsServerCacheAllowedForEntity excludes from the server cache. */
895
968
  protected isMaterializedWrapperEntity(param: RunViewParams): boolean;
@@ -1120,6 +1193,13 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1120
1193
  * the miss path also checked entity read permissions).
1121
1194
  */
1122
1195
  protected ResolveQueryCacheAuthorization(params: RunQueryParams, user?: UserInfo): QueryCacheAuthorization;
1196
+ /**
1197
+ * The fingerprint segment for what the server sets on a run: `ServerParameters`, and the
1198
+ * `Context.*` values the query reads, worked out for this user, zone and day. Empty when the
1199
+ * run has neither, so the fingerprint is exactly what it was before either existed.
1200
+ * @returns null when the query reads `Context.Now`: such a result is never reused
1201
+ */
1202
+ private queryCacheServerSegment;
1123
1203
  RunQuery(params: RunQueryParams, contextUser?: UserInfo): Promise<RunQueryResult>;
1124
1204
  /**
1125
1205
  * Executes a query from a `QueryExecutionSpec` — the lower-layer interface-based entry point.
@@ -1151,6 +1231,12 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1151
1231
  * @param callerName
1152
1232
  */
1153
1233
  protected EntityStatusCheck(params: RunViewParams, callerName: string, contextUser?: UserInfo): Promise<void>;
1234
+ /**
1235
+ * Emits a `[Deprecated]` warning when a cache-hit RunView served rows for a deprecated
1236
+ * `AlternateBaseView` (or an alternate view whose ancestor in `ParentID` is deprecated).
1237
+ * Cache misses log deprecation once inside `ResolveAlternateBaseView` during execution.
1238
+ */
1239
+ protected logDeprecatedAlternateBaseViewIfAny(params: RunViewParams, callerName: string): void;
1154
1240
  /**
1155
1241
  * Result from PreRunView hook containing cache status and optional cached result
1156
1242
  */
@@ -1166,6 +1252,11 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1166
1252
  * to project cache-miss DB results back down to the requested shape.
1167
1253
  */
1168
1254
  callerRequestedFields?: string[] | null;
1255
+ /**
1256
+ * True when PreRunView determined that a cache-miss result holds a full superset
1257
+ * projection eligible to populate the Fields-agnostic cache slot in PostRunView.
1258
+ */
1259
+ populateCache?: boolean;
1169
1260
  };
1170
1261
  /**
1171
1262
  * Result from PreRunViews hook containing cache status for batch operations
@@ -1196,12 +1287,25 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1196
1287
  * fingerprint string for every batch item.
1197
1288
  */
1198
1289
  fingerprintMap?: Map<number, string>;
1290
+ /**
1291
+ * Param indexes whose cache-miss result holds a full superset projection eligible to
1292
+ * populate the Fields-agnostic cache slot in PostRunViews.
1293
+ */
1294
+ populateCacheSet?: Set<number>;
1199
1295
  /**
1200
1296
  * Pre-resolved local cache entries keyed by client cache fingerprint, populated
1201
1297
  * during prepareSmartCacheCheckParams so executeSmartCacheCheck can reuse the
1202
1298
  * already-parsed results for 'current' items without a second IndexedDB read.
1203
1299
  */
1204
1300
  preResolvedCache?: Map<string, CachedRunViewResult | null>;
1301
+ /**
1302
+ * Views the field-security predicate gate refused, keyed by their index in the caller's
1303
+ * batch, each with its `Success: false` result. Present only when at least one view was
1304
+ * refused. Every other field of this result — the index maps, `cachedResults`,
1305
+ * `uncachedParams`, `smartCacheCheckParams` — then describes the ADMITTED views only,
1306
+ * indexed by their position among them.
1307
+ */
1308
+ refusedResults?: Map<number, RunViewResult>;
1205
1309
  };
1206
1310
  /**
1207
1311
  * Result from PreRunQuery hook containing cache status and optional cached result
@@ -1263,7 +1367,7 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1263
1367
  */
1264
1368
  protected ComputeRunViewRLSWhereClause(params: RunViewParams, contextUser?: UserInfo): string;
1265
1369
  /**
1266
- * The CLIENT's field-security cache key: a canonical list of the fields this user MAY read.
1370
+ * The field-security cache key for one entity: a canonical list of the fields the user MAY read.
1267
1371
  *
1268
1372
  * Keyed on the ALLOWED set rather than the denied set for two reasons. Once metadata ships
1269
1373
  * to browsers filtered to what a user may see ([#3485](https://github.com/MemberJunction/MJ/issues/3485)),
@@ -1275,36 +1379,90 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1275
1379
  * Returns '' when the entity has field security off or the user is denied nothing, so
1276
1380
  * unrestricted users keep byte-identical fingerprints and shared slots.
1277
1381
  *
1278
- * Only takes effect once the client's metadata refreshes. A client on stale metadata
1279
- * computes a stale key; the backstop is that the server strips denied columns from every
1280
- * fresh fetch regardless.
1382
+ * Reads the denied set through {@link ResolveRequestDeniedReadFields}, so the key and the
1383
+ * SELECT list a database provider builds for the same request describe the same columns.
1384
+ *
1385
+ * @param params - The request; also the memo the denied set is shared through
1386
+ * @param contextUser - The request's user; defaults to {@link CurrentUser}
1387
+ */
1388
+ protected ComputeClientFLSAllowedKey(params: RunViewParams, contextUser?: UserInfo): string;
1389
+ /**
1390
+ * The field-security segment for a {@link LocalCacheManager} RunView fingerprint, on both
1391
+ * tiers: the slot a request reads and fills belongs to the user's permission class.
1392
+ *
1393
+ * - **The entity's allowed columns** ({@link ComputeClientFLSAllowedKey}). A database
1394
+ * provider selects only the columns the reader may see, so a restricted reader's fill is
1395
+ * narrower than an unrestricted one's, and the two must never share a slot. A browser
1396
+ * stores rows exactly as the server returned them and never projects on read, so its
1397
+ * slots need the same identity. It also keeps user searches apart: which rows match a
1398
+ * search depends on which columns the reader may search.
1399
+ * - **For a saved view, the user's denials on every entity.** A view's stored clause can
1400
+ * reach other entities (nested view filters), and those clauses are only screened when the
1401
+ * database runs them, on a miss. Keying the slot on the whole denial profile means a hit
1402
+ * is only ever served from a fill by a user whose clauses screen identically.
1403
+ *
1404
+ * Empty for a user denied nothing, so unrestricted users keep byte-identical fingerprints
1405
+ * and share slots. Read-time projection ({@link ApplyFieldSecurityProjection}) still runs on
1406
+ * every hit and miss, as a backstop.
1407
+ *
1408
+ * @param params - The request
1409
+ * @param contextUser - The request's user; defaults to {@link CurrentUser}. On a server
1410
+ * this must be the request's user, never the process user.
1411
+ */
1412
+ protected ComputeRunViewFLSFingerprintKey(params: RunViewParams, contextUser?: UserInfo): string;
1413
+ /**
1414
+ * Every field `user` may not read, across all entities: one `entityID:field,field` entry per
1415
+ * entity on which the user is denied something, sorted. '' for a user denied nothing.
1416
+ *
1417
+ * @param user - The user whose denials to describe
1418
+ */
1419
+ protected ComputeFieldSecurityProfileKey(user: UserInfo): string;
1420
+ /**
1421
+ * The fingerprint of the server-cache slot a RunView reads and fills for `contextUser`:
1422
+ * the request, this provider's connection, the user's row-level-security clause and the
1423
+ * user's field-security key. Every server path that reads or writes a RunView slot builds
1424
+ * its fingerprint here, so none of them can leave a segment out — a path that omitted the
1425
+ * user's segments would read or fill another permission class's slot.
1426
+ *
1427
+ * @param params - The request
1428
+ * @param contextUser - The request's user; defaults to {@link CurrentUser}
1281
1429
  */
1282
- protected ComputeClientFLSAllowedKey(params: RunViewParams): string;
1430
+ ComputeRunViewCacheFingerprint(params: RunViewParams, contextUser?: UserInfo): string;
1283
1431
  /**
1284
- * The field-security segment for a {@link LocalCacheManager} RunView fingerprint — the one
1285
- * place the client/server asymmetry is decided, so the two tiers cannot drift.
1432
+ * The field-security key a browser sends with a cached slot's status, and the one a server
1433
+ * compares it with: a hash of {@link ComputeRunViewFLSFingerprintKey}, or '' for a user
1434
+ * denied nothing.
1286
1435
  *
1287
- * **SERVER → no segment.** Its slots are full-width and shared by every user; per-request
1288
- * narrowing happens at read time in {@link ApplyFieldSecurityProjection}, which runs on
1289
- * every hit and every miss. A segment here would fragment one shared slot into one per
1290
- * permission class and protect nothing the projection does not already handle.
1436
+ * The currency check otherwise compares only `maxUpdatedAt` and `rowCount`, and neither
1437
+ * changes when a user loses a column. A browser whose metadata has not refreshed yet keeps
1438
+ * its old slot (same fingerprint) and would be told "current" while still showing the
1439
+ * column; with this key the server sees the mismatch and answers "stale" with fresh rows.
1291
1440
  *
1292
- * **CLIENT → the allowed-list key.** Its slots are stored exactly as the server returned
1293
- * them (already narrowed on the wire) and are not projected on read, so slot identity has to
1294
- * carry the field set. Without it, a user whose access is tightened keeps being served their
1295
- * persisted IndexedDB slot: the currency check compares `maxUpdatedAt` and `rowCount` only,
1296
- * neither of which notices a column, so the server answers "current" and the browser keeps
1297
- * showing a column that was just taken away.
1441
+ * @param params - The request
1442
+ * @param contextUser - The request's user; defaults to {@link CurrentUser}
1443
+ */
1444
+ protected ComputeFieldSecurityCacheStatusKey(params: RunViewParams, contextUser?: UserInfo): string;
1445
+ /**
1446
+ * The fields `user` may not read on `entity` for this one request, resolved once and reused by
1447
+ * everything that asks again for the same request (`params` object), user object and entity.
1448
+ *
1449
+ * The cache key and the SELECT list are built at different moments of a request. Read
1450
+ * independently, a metadata refresh landing between the two would key a slot for one
1451
+ * permission class and fill it with another's columns. Sharing one answer per request rules
1452
+ * that out. Empty when field security does not apply.
1298
1453
  *
1299
- * Empty/undefined for unrestricted users on both tiers, so their fingerprints stay
1300
- * byte-identical and keep sharing slots.
1454
+ * @param params - The request; the memo is keyed on this object
1455
+ * @param entity - The entity being read
1456
+ * @param user - The request's user
1301
1457
  */
1302
- protected ComputeRunViewFLSFingerprintKey(params: RunViewParams): string | undefined;
1458
+ protected ResolveRequestDeniedReadFields(params: RunViewParams, entity: EntityInfo, user: UserInfo | undefined): Set<string>;
1459
+ private _requestDeniedReadFields;
1303
1460
  /**
1304
1461
  * The fetch-widening field list for a cache-eligible request: always every entity field.
1305
- * One slot per (entity, filter, order) serves every caller regardless of the field subset
1306
- * they asked for, and field security narrows per request at read time via
1307
- * {@link ApplyFieldSecurityProjection}.
1462
+ * One slot per (entity, filter, order, permission class) serves every caller regardless of
1463
+ * the field subset they asked for. Field security is not applied here: a database provider
1464
+ * leaves the user's denied columns out of the SELECT it builds from this list, and the slot
1465
+ * key carries the user's class ({@link ComputeRunViewFLSFingerprintKey}).
1308
1466
  */
1309
1467
  protected ComputeRunViewFetchFields(entity: EntityInfo, params?: RunViewParams): string[];
1310
1468
  /**
@@ -1338,28 +1496,44 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1338
1496
  * @returns Pre-processing result with cache status and optional cached result
1339
1497
  */
1340
1498
  /**
1341
- * Rejects a RunView whose `ExtraFilter`, `OrderBy`, or `Aggregates` expressions reference a
1342
- * field the user cannot read.
1499
+ * Rejects a RunView whose caller-authored SQL reads a field the user cannot read: `ExtraFilter`,
1500
+ * `OrderBy`, `OverrideExcludeFilter`, every `Aggregates[].expression`, and — when the request
1501
+ * carries a loaded `ViewEntity` — that view's stored `WhereClause` and `OrderByClause`.
1343
1502
  *
1344
1503
  * Output projection alone is security theater. A user denied `Salary` can send
1345
1504
  * `ExtraFilter: "Salary > 200000"` or `OrderBy: "Salary DESC"` and reconstruct the values
1346
1505
  * from which rows come back and in what order — the column never appears in a result, so
1347
1506
  * every output-stripping point reports "secure." Aggregates are the same channel in a purer
1348
1507
  * form: `Aggregates: [{expression: 'MIN(Salary)'}]` under a narrow filter returns a denied
1349
- * field's exact values directly. Predicate validation is a first-class enforcement point,
1350
- * not a belt-and-braces afterthought. Together these cover every caller-authored expression
1351
- * surface (`UserSearchString` is handled by excluding denied fields from the searched set,
1352
- * not by rejection — see below).
1508
+ * field's exact values directly. `UserSearchString` is handled by excluding denied fields from
1509
+ * the searched set, not by rejection.
1353
1510
  *
1354
- * Lives at the provider layer rather than in the GraphQL resolver (where the plan first
1355
- * placed it) because every RunView funnels through here — the batch path, server-internal
1356
- * agents and actions running under a restricted `contextUser`, and the resolver alike. One
1357
- * gate, no path left uncovered.
1511
+ * Lives at the provider layer because every RunView funnels through here — the batch path,
1512
+ * server-internal agents and actions running under a restricted `contextUser`, and the
1513
+ * resolver alike. A database provider checks the clauses it actually runs a second time, once
1514
+ * a saved view is resolved, so a request that names only a `ViewID` is covered too.
1358
1515
  *
1359
- * The error deliberately does not say whether the field is missing or merely forbidden —
1360
- * see {@link ProviderBase.FieldSecurityDenialMessage}.
1516
+ * The reading of each clause, and the error it raises, is {@link FieldSecuritySQLGate}'s.
1361
1517
  */
1362
1518
  protected AssertPredicatesRespectFieldSecurity(params: RunViewParams, contextUser?: UserInfo): void;
1519
+ /**
1520
+ * Throws a {@link FieldSecurityError} when any of `clauses` — SQL fragments that run against
1521
+ * `entity`'s view — reads a field `user` cannot read, reads whole rows of an entity the user is
1522
+ * denied fields on, or cannot be read well enough to tell. Non-string and blank entries are
1523
+ * skipped. A no-op, with nothing scanned, for a user denied nothing on any entity.
1524
+ *
1525
+ * The shared entry point for every clause slot a provider runs: RunView parameters, a saved
1526
+ * view's stored clauses, dataset item filters, user-search text spliced as SQL.
1527
+ */
1528
+ protected AssertClausesRespectFieldSecurityForEntity(entity: EntityInfo, clauses: Iterable<unknown>, user: UserInfo): void;
1529
+ /**
1530
+ * {@link AssertClausesRespectFieldSecurityForEntity} for ORDER BY fragments, which also refuses
1531
+ * a sort by column position for a user denied a field on `entity`
1532
+ * ({@link FieldSecuritySQLGate.FindSortViolation}).
1533
+ */
1534
+ protected AssertSortRespectsFieldSecurityForEntity(entity: EntityInfo, orderBys: Iterable<unknown>, user: UserInfo): void;
1535
+ /** Throws the field-security error for the first of `fragments` that `find` refuses. */
1536
+ private assertFragmentsAllowed;
1363
1537
  /**
1364
1538
  * The single wording for "you cannot use this field," modeled on SQL Server's posture of
1365
1539
  * never disclosing whether an object is missing or merely inaccessible.
@@ -1376,15 +1550,13 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1376
1550
  /**
1377
1551
  * Strips fields the user cannot read from plain-object result rows.
1378
1552
  *
1379
- * This is the primary read-time control for list results, and it runs on BOTH cache hits
1380
- * and cache misses — the property the rest of the cache design is arranged around. Because
1381
- * it reads live metadata, a permission change takes effect on the next metadata refresh
1382
- * with no result-cache invalidation: the cached full-width superset stays valid and only
1383
- * the projection changes.
1384
- *
1385
- * It composes with two per-request, never-cached narrowings that avoid pulling columns a
1386
- * restricted service account has no business holding: the `simple`-path SELECT-list
1387
- * intersection, and `Load()`'s allowed-column SELECT.
1553
+ * The backstop behind slot segmentation: cache slots are keyed by the reader's permission
1554
+ * class ({@link ComputeRunViewFLSFingerprintKey}) and filled with only that class's columns,
1555
+ * and this still runs on BOTH cache hits and cache misses, checking every row. A slot can hold
1556
+ * more than its class may read (an `entity_object` fill selects every column, and a slot keyed
1557
+ * just before a permission change keeps its old width), and this is what keeps such a slot
1558
+ * from reaching a reader. It reads live metadata, so a tightened permission applies here from
1559
+ * the next metadata refresh.
1388
1560
  *
1389
1561
  * NEVER applied to `entity_object` results. Those become `BaseEntity` instances whose
1390
1562
  * fields round-trip through `GenerateSaveSQL`, which iterates ALL `IsSPParameter` fields
@@ -1396,13 +1568,30 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1396
1568
  protected ApplyFieldSecurityProjection<T>(rows: T[], params: RunViewParams, contextUser?: UserInfo): T[];
1397
1569
  /**
1398
1570
  * Row-loop half of {@link ApplyFieldSecurityProjection}, split out so the policy above reads
1399
- * as policy. Mirrors {@link ProjectRowsToFields}' matching semantics (trim + lowercase) and
1400
- * its no-op probe: when the first row carries none of the denied keys, the whole result is
1401
- * returned untouched rather than rebuilt row by row.
1571
+ * as policy. Mirrors {@link ProjectRowsToFields}' matching semantics (trim + lowercase).
1572
+ *
1573
+ * Every row is checked on its own: rows in one result need not share a shape (a slot can mix
1574
+ * rows written by different paths), so no row stands in for the others. A row carrying no
1575
+ * denied key is kept as is, and when none do the input array itself is returned.
1576
+ *
1577
+ * The per-key work (trim, lowercase, set lookup) is done once per key SHAPE rather than once
1578
+ * per row: rows with the same keys in the same order share one {@link RowProjectionPlan}. A
1579
+ * result is almost always a single shape, so a restricted user's 10k-row result pays for one
1580
+ * row's worth of key matching plus a copy per row.
1402
1581
  *
1403
1582
  * @param deniedLowercase field names to omit, already lowercased
1404
1583
  */
1405
1584
  private static omitFieldsFromRows;
1585
+ /** One row of {@link omitFieldsFromRows}: the row itself when it carries no denied key, else a copy without them. */
1586
+ private static omitFieldsFromRow;
1587
+ /**
1588
+ * The plan for a row with exactly these keys, built on first sight of the shape. The last plan
1589
+ * used is tried first; otherwise plans are found by the joined key list. Either way the plan's
1590
+ * own key list is compared with the row's before it is used, so two different shapes can
1591
+ * never share a plan.
1592
+ */
1593
+ private static rowProjectionPlanFor;
1594
+ private static sameKeys;
1406
1595
  /**
1407
1596
  * Strips or narrows the `MJ: Record Changes` payload columns a user may not read, using the
1408
1597
  * denied set of the entity each row is ABOUT rather than of Record Changes itself.
@@ -1413,6 +1602,9 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1413
1602
  * Everything about the per-row denied set, the payload treatment, and the fail-closed decision
1414
1603
  * lives in {@link RecordChangeFieldSecurityProjector}; this is only the RunView wiring.
1415
1604
  *
1605
+ * A request whose entity cannot be resolved without I/O (only a `ViewID` or `ViewName`) is
1606
+ * projected too, so a saved view over Record Changes is not a way around it.
1607
+ *
1416
1608
  * Runs at all four RunView projection points, matching the main projection: both cache-hit
1417
1609
  * paths and both cache-miss paths. The cache-hit path is not optional — it is the exact path
1418
1610
  * the original cross-user leak runs through, an unrestricted user warming a full-width slot
@@ -1448,11 +1640,34 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1448
1640
  /**
1449
1641
  * Pre-processing hook for RunViews (batch).
1450
1642
  * Handles telemetry, validation, and cache lookup for multiple views.
1643
+ * A view the field-security predicate gate refuses is left out of every later stage and
1644
+ * reported in `refusedResults`; the cache status describes the admitted views only.
1451
1645
  * @param params - Array of view parameters
1452
1646
  * @param contextUser - Optional user context
1453
- * @returns Pre-processing result with cache status for each view
1647
+ * @returns Pre-processing result with cache status for each admitted view
1454
1648
  */
1455
1649
  protected PreRunViews(params: RunViewParams[], contextUser?: UserInfo): Promise<typeof this._preRunViewsResultType>;
1650
+ /**
1651
+ * Runs each view's PreRunView hooks, replacing `params[i]` with the hooked params, then the
1652
+ * field-security predicate gate — the same contract as the single-view path, except that a
1653
+ * refusal fails only its own view: it becomes that view's `Success: false` result, carrying
1654
+ * the refusal's ambiguous message, so the views batched with it still run. A batch is often
1655
+ * several independent reads (a dashboard's panels), and one unreadable column in one of them
1656
+ * must not blank the rest; the refusal is still reported, at the refused view's index. Any
1657
+ * other error from the gate is thrown and fails the batch.
1658
+ * @returns The refused views' results keyed by batch index, or `undefined` when none was refused.
1659
+ */
1660
+ private runBatchHooksAndPredicateGate;
1661
+ /** `error` as a failed view result when it is a field-security refusal; otherwise rethrows it. */
1662
+ protected static fieldSecurityRefusalAsResult<T = unknown>(error: unknown): RunViewResult<T>;
1663
+ /**
1664
+ * The cache stage of PreRunViews for the views the predicate gate admitted. An empty batch
1665
+ * (every view refused) is reported as fully cached, so the pipeline returns without a
1666
+ * database call and still ends the batch's telemetry event.
1667
+ */
1668
+ private lookUpAdmittedViews;
1669
+ /** The server's trusted-cache lookup for a batch: serves hits, and lists the misses to run. */
1670
+ private lookUpBatchCache;
1456
1671
  /**
1457
1672
  * Prepares smart cache check parameters for RunViews when CacheLocal is enabled.
1458
1673
  * Instead of returning cached data immediately, this builds params to send to the server
@@ -1502,6 +1717,22 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1502
1717
  * @param preResult - The pre-processing result
1503
1718
  * @param contextUser - Optional user context
1504
1719
  */
1720
+ /**
1721
+ * Hook allowing server database providers to substitute pre-decryption (ciphertext-preserving)
1722
+ * rows before storing view results in {@link LocalCacheManager} / Redis, ensuring decrypted
1723
+ * plaintext secrets are never persisted in local or shared caches.
1724
+ *
1725
+ * Client-side providers inherit this default identity implementation.
1726
+ */
1727
+ protected PrepareRowsForCacheStorage(rows: unknown[], _entityInfo?: EntityInfo): unknown[];
1728
+ /**
1729
+ * Hook allowing server database providers to decrypt ciphertext-at-rest rows retrieved from
1730
+ * {@link LocalCacheManager} for in-process callers (or selectively preserve restricted
1731
+ * ciphertext when `preserveRestrictedApiCiphertext` is `true`).
1732
+ *
1733
+ * Client-side providers inherit this default identity implementation.
1734
+ */
1735
+ protected MaterializeCachedRowsForCaller(rows: Record<string, unknown>[], _entityInfo?: EntityInfo, _contextUser?: UserInfo, _preserveRestrictedApiCiphertext?: boolean): Promise<Record<string, unknown>[]>;
1505
1736
  protected PostRunView(result: RunViewResult, params: RunViewParams, preResult: typeof this._preRunViewResultType, contextUser?: UserInfo): Promise<void>;
1506
1737
  /**
1507
1738
  * Post-processing hook for RunViews (batch).
@@ -1512,6 +1743,16 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1512
1743
  * @param contextUser - Optional user context
1513
1744
  */
1514
1745
  protected PostRunViews(results: RunViewResult[], params: RunViewParams[], preResult: typeof this._preRunViewsResultType, contextUser?: UserInfo): Promise<void>;
1746
+ /**
1747
+ * Works out a stored view's property values ({@link RunViewParams.Properties},
1748
+ * {@link RunViewParams.ServerProperties}, and each property's default) before the run's cache
1749
+ * lookup, so the result-cache fingerprint keys on the values the run will actually use. Called
1750
+ * on the run's own params copy, after the PreRunView hooks. Must never throw: a run whose
1751
+ * properties would be refused is left for the provider's run path to refuse with its message.
1752
+ *
1753
+ * Does nothing here. A database provider, which resolves token defaults on the server, overrides it.
1754
+ */
1755
+ protected PrepareRunViewProperties(_params: RunViewParams, _contextUser?: UserInfo): Promise<void>;
1515
1756
  /**
1516
1757
  * Runs all registered PreRunView hooks against a single RunViewParams,
1517
1758
  * returning the (possibly mutated) params.
@@ -1711,6 +1952,7 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1711
1952
  * Resolves the {@link EntityInfo} normalization should read field types from, using only
1712
1953
  * synchronously available information on the params.
1713
1954
  */
1955
+ protected resolveEntityForRunViewParams(param: RunViewParams): EntityInfo | undefined;
1714
1956
  private resolveEntityForNormalization;
1715
1957
  /**
1716
1958
  * The row keys each field of the given TSType can appear under, one entry per field.
@@ -1753,13 +1995,23 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1753
1995
  protected ReloadMetadata(hardRefresh: boolean, providerToUse?: IMetadataProvider, scopedFirst?: boolean): Promise<boolean>;
1754
1996
  /**
1755
1997
  * Adopts a metadata snapshot just loaded from the server: swaps it in, records that the
1756
- * local copy now matches the server's timestamps, and persists it to local storage.
1998
+ * local copy now matches the server's timestamps (verified per item against the received
1999
+ * dataset row counts), and persists it to local storage.
1757
2000
  *
1758
2001
  * The swap goes through UpdateLocalMetadata — a single property assignment, atomic in
1759
2002
  * JavaScript, so readers see the old snapshot until the new one is complete — which keeps
1760
2003
  * it consistent with LoadLocalMetadataFromStorage() and lets subclasses override it.
1761
2004
  */
1762
2005
  private adoptServerMetadata;
2006
+ /**
2007
+ * Verifies that each item in `_latestRemoteMetadataTimestamps` matches the row count actually
2008
+ * received in the loaded `MJ_Metadata` dataset (or `AllMetadata` collections) before advancing
2009
+ * `_latestLocalMetadataTimestamps`. If any item has a mismatched row count (e.g., because a
2010
+ * server cache slot returned stale rows while the SQL status probe reported newer counts),
2011
+ * logs a warning and preserves the unadvanced local timestamp for that item so the next
2012
+ * staleness check (`LocalMetadataObsolete`) detects the discrepancy and self-heals.
2013
+ */
2014
+ private reconcileAdoptedMetadataTimestamps;
1763
2015
  /**
1764
2016
  * Decides whether the current staleness can be served by a scoped reload of the saved-query
1765
2017
  * family. Compares local and remote timestamps item by item (the same test as
@@ -1865,14 +2117,30 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1865
2117
  * customized adoption through CloneAllMetadata. Remove the CloneAllMetadata
1866
2118
  * override to activate a CreateSharedMetadataShell override.
1867
2119
  */
2120
+ /**
2121
+ * Builds this instance's AllMetadata as a zero-copy prototype-delegated shell over
2122
+ * another provider's already-loaded metadata (`Object.create(shared)`), keeping
2123
+ * `CurrentUser` as an own property on the shell instance.
2124
+ *
2125
+ * Why zero-copy prototype delegation is safe: the metadata graph (`AllMetadata` and its
2126
+ * arrays) is treated as immutable after `Config`. Refreshes swap the entire `AllMetadata`
2127
+ * reference via `UpdateLocalMetadata` rather than mutating the previous `AllMetadata`
2128
+ * instance in place, so in-flight shells continue reading a consistent snapshot while
2129
+ * avoiding 19 array shallow-copies on every per-request provider creation.
2130
+ */
1868
2131
  protected CreateSharedMetadataShell(shared: AllMetadata): AllMetadata;
1869
2132
  /**
1870
2133
  * Adopts the global provider's metadata for this instance without reloading it
1871
2134
  * from the server: shares the (immutable post-Config) metadata arrays by
1872
- * reference via {@link CreateSharedMetadataShell} and builds this instance's
2135
+ * reference via {@link CreateSharedMetadataShell} and shares or builds this instance's
1873
2136
  * entity lookup maps.
1874
2137
  */
1875
2138
  protected CopyMetadataFromGlobalProvider(): boolean;
2139
+ /**
2140
+ * Re-adopts the immutable read-only metadata catalog shell and lookup maps from
2141
+ * `Metadata.Provider` after the global provider completes a metadata refresh.
2142
+ */
2143
+ SyncMetadataFromGlobalProvider(): boolean;
1876
2144
  /**
1877
2145
  * Builds dataset filters based on the provider configuration.
1878
2146
  * Ensures MJ Core schema is always included and never excluded.
@@ -1917,7 +2185,7 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
1917
2185
  * metadata post-processing, replacing repeated O(N*M) filter scans.
1918
2186
  */
1919
2187
  private groupByNormalizedUUID;
1920
- protected PostProcessEntityMetadata(entities: EntityMetadataRow[], fields: EntityFieldMetadataRow[], fieldValues: EntityFieldValueMetadataRow[], permissions: EntityChildMetadataRow[], relationships: EntityChildMetadataRow[], settings: EntityChildMetadataRow[], organicKeys?: OrganicKeyMetadataRow[], organicKeyRelatedEntities?: OrganicKeyRelatedEntityMetadataRow[], fieldPermissions?: EntityFieldPermissionMetadataRow[]): EntityInfo[];
2188
+ protected PostProcessEntityMetadata(entities: EntityMetadataRow[], fields: EntityFieldMetadataRow[], fieldValues: EntityFieldValueMetadataRow[], permissions: EntityChildMetadataRow[], relationships: EntityChildMetadataRow[], settings: EntityChildMetadataRow[], organicKeys?: OrganicKeyMetadataRow[], organicKeyRelatedEntities?: OrganicKeyRelatedEntityMetadataRow[], fieldPermissions?: EntityFieldPermissionMetadataRow[], alternateBaseViews?: EntityChildMetadataRow[]): EntityInfo[];
1921
2189
  /**
1922
2190
  * Gets the configuration data that was provided to the provider.
1923
2191
  * @returns The provider configuration including schema filters
@@ -2040,6 +2308,27 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
2040
2308
  * RefreshIfNeeded load outside Config's reload single-flight). For the same reason it gets
2041
2309
  * `false`, not the owner's rejection, when the owner's check throws.
2042
2310
  * @returns True if refresh is needed, false otherwise
2311
+ *
2312
+ * @remarks
2313
+ * This is a **freshness question, not a load**, so it never throws on an unreachable
2314
+ * server. {@link ReadRemoteMetadataTimestamps} reports whether the server was reached at
2315
+ * all, and when it was NOT the honest answer here depends on whether we hold anything to
2316
+ * serve:
2317
+ *
2318
+ * - **We hold local metadata** → answer `false` ("do not tear this down"). The cached
2319
+ * snapshot may be a few seconds stale; a rejected status query is no reason to throw
2320
+ * it away, and every caller of this method treats `false` as "carry on with what you
2321
+ * have".
2322
+ * - **We hold nothing** → answer `true`, so the caller attempts a real load against the
2323
+ * actual metadata fetch. That load logs its own failure and leaves the graph empty;
2324
+ * `SetupGraphQLClient` turns an empty graph into an error carrying it, while a caller
2325
+ * that only checks {@link Config}'s return value sees `true`.
2326
+ *
2327
+ * Before this, an unreachable status query propagated an exception out of
2328
+ * {@link Config} and the Explorer's single boot catch turned it into a non-retryable
2329
+ * "System Error" — the whole app dead over a freshness check it did not need an answer
2330
+ * to. The two warm callers ({@link backgroundValidateAndRefresh},
2331
+ * {@link preValidateAndRefresh}) already caught it; {@link Config} did not.
2043
2332
  */
2044
2333
  CheckToSeeIfRefreshNeeded(providerToUse?: IMetadataProvider, bypassMinCheckInterval?: boolean): Promise<boolean>;
2045
2334
  /**
@@ -2097,9 +2386,11 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
2097
2386
  * 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
2098
2387
  * 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.
2099
2388
  * @param entityName the name of the entity to check
2100
- * @param KeyValuePairs the values of the primary key of the record to check
2389
+ * @param CompositeKey the values of the primary key of the record to check
2390
+ * @param contextUser optional user context (or options when called with 3 arguments)
2391
+ * @param options optional filtering options for dependency discovery
2101
2392
  */
2102
- abstract GetRecordDependencies(entityName: string, CompositeKey: CompositeKey, contextUser?: UserInfo): Promise<RecordDependency[]>;
2393
+ abstract GetRecordDependencies(entityName: string, CompositeKey: CompositeKey, contextUser?: UserInfo | GetRecordDependenciesOptions, options?: GetRecordDependenciesOptions): Promise<RecordDependency[]>;
2103
2394
  /**
2104
2395
  * Returns a list of record IDs that are possible duplicates of the specified record.
2105
2396
  *
@@ -2164,6 +2455,11 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
2164
2455
  * @returns
2165
2456
  */
2166
2457
  IsDatasetCacheUpToDate(datasetName: string, itemFilters?: DatasetItemFilterType[]): Promise<boolean>;
2458
+ /**
2459
+ * Normalizes a dataset `LatestUpdateDate` to a valid `Date`, or `null` when absent or invalid
2460
+ * (for example, when a dataset or dataset-status call fails and returns `LatestUpdateDate: null`).
2461
+ */
2462
+ private validDatasetDate;
2167
2463
  /**
2168
2464
  * Compares the per-entity row counts of a cached dataset against the server's status.
2169
2465
  *
@@ -2288,15 +2584,68 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
2288
2584
  get LatestLocalMetadata(): MetadataInfo[];
2289
2585
  /**
2290
2586
  * Retrieves the latest metadata update timestamps from the server.
2291
- * @returns Array of metadata update information
2587
+ * @returns Array of metadata update information, or `undefined` when the server could not
2588
+ * be asked (transport failure, timeout, gateway error) or answered unsuccessfully.
2589
+ *
2590
+ * @remarks
2591
+ * This is the ONE network call behind every staleness check, and it is a question about
2592
+ * freshness — never the metadata itself. A rejected status query therefore returns
2593
+ * `undefined` (an absent answer) rather than propagating: its callers already treat a
2594
+ * falsy result as "could not tell", while an exception out of here travelled unguarded
2595
+ * through {@link RefreshRemoteMetadataTimestamps} → {@link CheckToSeeIfRefreshNeeded} →
2596
+ * {@link Config} and killed the caller's whole bootstrap.
2597
+ *
2598
+ * The failure is logged at error level, not swallowed quietly: "we are running on a
2599
+ * cached snapshot because the server did not answer" is something an operator needs to
2600
+ * see in the console.
2292
2601
  */
2293
2602
  protected GetLatestMetadataUpdates(providerToUse?: IMetadataProvider): Promise<MetadataInfo[]>;
2603
+ /**
2604
+ * The metadata-status read, with **why** it failed preserved.
2605
+ *
2606
+ * Two failures look the same to {@link GetLatestMetadataUpdates} — both give `undefined` —
2607
+ * but they call for opposite responses, so callers that can act on the difference read it
2608
+ * here instead:
2609
+ *
2610
+ * - `Reached: false` — the request never got an answer (transport failure, timeout, 504).
2611
+ * Transient. Nothing can be concluded about freshness, and a full metadata load would hit
2612
+ * the same wall, so the right move is to keep serving what we have.
2613
+ * - `Reached: true` with no `Timestamps` — the server answered `Success: false`. A
2614
+ * *permanent* condition (the dataset is missing or misconfigured), not a network blip.
2615
+ * Degrading here would serve a stale cache forever; the historical behaviour of treating
2616
+ * it as "obsolete" and attempting the real load is correct, because `GetDatasetByName`
2617
+ * may well still work.
2618
+ * - A rejection that is NOT a transport failure — an authorization denial ("this API key
2619
+ * requires the dataset:read scope"), a validation rejection, a programming error — is
2620
+ * the server (or our own code) answering, not the wire failing. It propagates exactly
2621
+ * as it did before this method existed. {@link IsTransportError} draws the line. The
2622
+ * integration tier's FLS fixture depends on this: it builds a reader whose scope rule
2623
+ * has not propagated yet and retries `Config()` until the denial stops — a denial
2624
+ * swallowed into `Reached: false` left that reader with an empty snapshot for good.
2625
+ *
2626
+ * Conflating these is how a broken status endpoint — or a refused identity — would become
2627
+ * permanently invisible.
2628
+ */
2629
+ protected ReadRemoteMetadataTimestamps(providerToUse?: IMetadataProvider): Promise<{
2630
+ Reached: boolean;
2631
+ Timestamps: MetadataInfo[] | undefined;
2632
+ }>;
2633
+ /** Shape a dataset-status answer into the timestamp list, or `undefined` if it failed. */
2634
+ private mapDatasetStatusToTimestamps;
2294
2635
  /**
2295
2636
  * Refreshes the remote metadata timestamps from the server.
2296
2637
  * Updates the internal cache of remote timestamps.
2297
2638
  * @returns True if timestamps were successfully refreshed
2298
2639
  */
2299
2640
  RefreshRemoteMetadataTimestamps(providerToUse?: IMetadataProvider): Promise<boolean>;
2641
+ /**
2642
+ * Whether the most recent {@link RefreshRemoteMetadataTimestamps} actually REACHED the
2643
+ * server, regardless of whether it came back with usable timestamps.
2644
+ *
2645
+ * Starts `true` so that nothing reads "unreachable" before a read has been attempted.
2646
+ * See {@link ReadRemoteMetadataTimestamps} for why the distinction exists.
2647
+ */
2648
+ private _lastRemoteTimestampReadReached;
2300
2649
  /**
2301
2650
  * Checks if local metadata is obsolete compared to remote metadata.
2302
2651
  * Compares timestamps and row counts to detect changes.
@@ -2311,6 +2660,8 @@ export declare abstract class ProviderBase implements IMetadataProvider, IRunVie
2311
2660
  protected UpdateLocalMetadata(res: AllMetadata): void;
2312
2661
  /**
2313
2662
  * Rebuilds the O(1) entity lookup Maps from the current AllEntities array.
2663
+ * Allocates fresh Map instances so any per-request provider sharing a previous
2664
+ * snapshot's maps is unaffected by a subsequent refresh.
2314
2665
  * Called automatically from UpdateLocalMetadata().
2315
2666
  */
2316
2667
  protected RebuildEntityMaps(): void;