@memberjunction/core 6.1.0-edge.2 → 6.1.0-edge.3

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 (90) hide show
  1. package/LICENSE +180 -4
  2. package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts +120 -0
  3. package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts.map +1 -0
  4. package/dist/generic/JSONType-interfaces/IEntityConfiguration.js +6 -0
  5. package/dist/generic/JSONType-interfaces/IEntityConfiguration.js.map +1 -0
  6. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts +31 -0
  7. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts.map +1 -0
  8. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.js +6 -0
  9. package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.js.map +1 -0
  10. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts +75 -0
  11. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts.map +1 -0
  12. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.js +6 -0
  13. package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.js.map +1 -0
  14. package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.d.ts +19 -0
  15. package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.d.ts.map +1 -0
  16. package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.js +6 -0
  17. package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.js.map +1 -0
  18. package/dist/generic/authTypes.d.ts +69 -0
  19. package/dist/generic/authTypes.d.ts.map +1 -1
  20. package/dist/generic/baseEngine.d.ts +14 -0
  21. package/dist/generic/baseEngine.d.ts.map +1 -1
  22. package/dist/generic/baseEngine.js +53 -13
  23. package/dist/generic/baseEngine.js.map +1 -1
  24. package/dist/generic/baseEntity.d.ts +113 -20
  25. package/dist/generic/baseEntity.d.ts.map +1 -1
  26. package/dist/generic/baseEntity.js +351 -49
  27. package/dist/generic/baseEntity.js.map +1 -1
  28. package/dist/generic/baseInfo.d.ts.map +1 -1
  29. package/dist/generic/baseInfo.js +16 -4
  30. package/dist/generic/baseInfo.js.map +1 -1
  31. package/dist/generic/compositeKey.d.ts +30 -0
  32. package/dist/generic/compositeKey.d.ts.map +1 -1
  33. package/dist/generic/compositeKey.js +97 -0
  34. package/dist/generic/compositeKey.js.map +1 -1
  35. package/dist/generic/databaseProviderBase.d.ts.map +1 -1
  36. package/dist/generic/databaseProviderBase.js +9 -5
  37. package/dist/generic/databaseProviderBase.js.map +1 -1
  38. package/dist/generic/embeddedRecord.d.ts +123 -0
  39. package/dist/generic/embeddedRecord.d.ts.map +1 -0
  40. package/dist/generic/embeddedRecord.js +356 -0
  41. package/dist/generic/embeddedRecord.js.map +1 -0
  42. package/dist/generic/entityCompanion.d.ts +20 -2
  43. package/dist/generic/entityCompanion.d.ts.map +1 -1
  44. package/dist/generic/entityCompanion.js +17 -1
  45. package/dist/generic/entityCompanion.js.map +1 -1
  46. package/dist/generic/entityConfiguration.d.ts +131 -0
  47. package/dist/generic/entityConfiguration.d.ts.map +1 -0
  48. package/dist/generic/entityConfiguration.js +346 -0
  49. package/dist/generic/entityConfiguration.js.map +1 -0
  50. package/dist/generic/entityInfo.d.ts +274 -4
  51. package/dist/generic/entityInfo.d.ts.map +1 -1
  52. package/dist/generic/entityInfo.js +502 -14
  53. package/dist/generic/entityInfo.js.map +1 -1
  54. package/dist/generic/entitySavePlan.d.ts +40 -11
  55. package/dist/generic/entitySavePlan.d.ts.map +1 -1
  56. package/dist/generic/entitySavePlan.js +70 -2
  57. package/dist/generic/entitySavePlan.js.map +1 -1
  58. package/dist/generic/interfaces.d.ts +6 -24
  59. package/dist/generic/interfaces.d.ts.map +1 -1
  60. package/dist/generic/interfaces.js +6 -24
  61. package/dist/generic/interfaces.js.map +1 -1
  62. package/dist/generic/localCacheManager.d.ts.map +1 -1
  63. package/dist/generic/localCacheManager.js +10 -0
  64. package/dist/generic/localCacheManager.js.map +1 -1
  65. package/dist/generic/providerBase.d.ts +19 -0
  66. package/dist/generic/providerBase.d.ts.map +1 -1
  67. package/dist/generic/providerBase.js +71 -6
  68. package/dist/generic/providerBase.js.map +1 -1
  69. package/dist/generic/relatedRecordCollection.d.ts +25 -3
  70. package/dist/generic/relatedRecordCollection.d.ts.map +1 -1
  71. package/dist/generic/relatedRecordCollection.js +53 -7
  72. package/dist/generic/relatedRecordCollection.js.map +1 -1
  73. package/dist/generic/runQuery.d.ts +14 -0
  74. package/dist/generic/runQuery.d.ts.map +1 -1
  75. package/dist/generic/runQuery.js.map +1 -1
  76. package/dist/generic/saveEntityGraphOperation.js +1 -1
  77. package/dist/generic/saveEntityGraphOperation.js.map +1 -1
  78. package/dist/generic/securityInfo.d.ts.map +1 -1
  79. package/dist/generic/securityInfo.js +15 -5
  80. package/dist/generic/securityInfo.js.map +1 -1
  81. package/dist/index.d.ts +3 -0
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +3 -0
  84. package/dist/index.js.map +1 -1
  85. package/dist/views/runView.d.ts +23 -0
  86. package/dist/views/runView.d.ts.map +1 -1
  87. package/dist/views/runView.js +20 -0
  88. package/dist/views/runView.js.map +1 -1
  89. package/package.json +5 -4
  90. package/readme.md +20 -1
@@ -13,7 +13,7 @@ import { LibraryInfo } from "./libraryInfo.js";
13
13
  import { CompositeKey } from "./compositeKey.js";
14
14
  import { ExplorerNavigationItem } from "./explorerNavigationItem.js";
15
15
  import { Metadata } from "./metadata.js";
16
- import { RunView } from "../views/runView.js";
16
+ import { RunView, IsMaterializedDataSource } from "../views/runView.js";
17
17
  import { IsPlatformSQL } from "./platformSQL.js";
18
18
  import { GetDataHooks } from "./dataHooks.js";
19
19
  import { TransformSimpleObjectToEntityObject } from "./util.js";
@@ -996,15 +996,51 @@ export class ProviderBase {
996
996
  * the seek key, so caching a page would poison the entity+filter slot
997
997
  * - `ResultType 'count_only'` — returns no rows; caching its empty Results under
998
998
  * a fingerprint that excludes ResultType would poison row queries
999
+ * - `DataSource: 'Materialized'` — the snapshot is rebuilt OUT-OF-BAND by the scheduled refresh
1000
+ * (direct SQL, no BaseEntity save), so the entity's normal event-driven cache invalidation never
1001
+ * fires for it; a cached materialized result would be served indefinitely stale after a refresh.
1002
+ * Bypass caching entirely for materialized reads. (The `ds:materialized` fingerprint segment still
1003
+ * keeps the short-lived dedup/linger layer from cross-serving Live vs Materialized in-flight reads.)
999
1004
  * - entities where server caching is disallowed
1000
1005
  */
1001
1006
  runViewCacheEligible(param) {
1002
1007
  return !param.BypassCache &&
1003
1008
  !param.AfterKey &&
1009
+ !IsMaterializedDataSource(param.DataSource) &&
1004
1010
  param.ResultType !== 'count_only' &&
1005
1011
  (param.CacheLocal === true || this.TrustLocalCacheCompletely) &&
1006
1012
  this.IsServerCacheAllowedForEntity(param);
1007
1013
  }
1014
+ /** True when the entity is a CodeGen materialized-query wrapper (materialized_vw*) whose snapshot is
1015
+ * refreshed out-of-band — the same one IsServerCacheAllowedForEntity excludes from the server cache. */
1016
+ isMaterializedWrapperEntity(param) {
1017
+ if (!param.EntityName) {
1018
+ return false;
1019
+ }
1020
+ const entity = this.EntityByName(param.EntityName);
1021
+ return !!(entity?.VirtualEntity && entity.BaseView && entity.BaseView.toLowerCase().startsWith('materialized_vw'));
1022
+ }
1023
+ /**
1024
+ * Write-side eligibility for the smart-cache-check (stamped) path. On the trusting SERVER this is
1025
+ * exactly runViewCacheEligible — the server cache is kept fresh by BaseEntity events, so a
1026
+ * server-cache-disallowed entity must never be slotted. On a CLIENT the slot is instead written with
1027
+ * a maxUpdatedAt stamp and DB-revalidated per request, so the server Trust/event gate does NOT apply:
1028
+ * a server-cache-disallowed entity (Trust=0 'MJ: Audit Logs', Record Changes, other caching-disabled)
1029
+ * is still safely client-cacheable when stamped — folding runViewCacheEligible's server gate onto this
1030
+ * path regressed that (integration check client-cache.C12). Materialized reads stay excluded on both
1031
+ * (the out-of-band snapshot swap the stamp can't observe).
1032
+ */
1033
+ runViewCacheEligibleForWrite(param) {
1034
+ if (this.TrustLocalCacheCompletely) {
1035
+ return this.runViewCacheEligible(param);
1036
+ }
1037
+ return !param.BypassCache &&
1038
+ !param.AfterKey &&
1039
+ !IsMaterializedDataSource(param.DataSource) &&
1040
+ param.ResultType !== 'count_only' &&
1041
+ param.CacheLocal === true &&
1042
+ !this.isMaterializedWrapperEntity(param);
1043
+ }
1008
1044
  /**
1009
1045
  * SECURITY — decide whether the shared cache must be BYPASSED for a RunView that targets
1010
1046
  * a saved VIEW rather than a named entity (no `EntityName`), under a context user.
@@ -1515,6 +1551,13 @@ export class ProviderBase {
1515
1551
  // outer CacheLocal layer would otherwise write a SECOND, divergent slot with no/foreign
1516
1552
  // TTL (CacheLocalTTL) — a stale-forever hazard since external data can't be event-invalidated.
1517
1553
  && !this.IsExternalQuery(params)
1554
+ // Materialized query results are excluded for the SAME reason: the snapshot IS the cache
1555
+ // and its freshness is governed by the refresh cycle, not by BaseEntity events, so it can't
1556
+ // be event-invalidated. Layering the outer CacheLocal TTL slot on top would serve rows older
1557
+ // than the latest materialized refresh AND would risk a Live-vs-Materialized slot collision
1558
+ // (the fingerprint carries no DataSource segment). Keeping materialized out of this layer
1559
+ // means the only staleness is the refresh cadence the snapshot already advertises.
1560
+ && !IsMaterializedDataSource(params.DataSource)
1518
1561
  && LocalCacheManager.Instance.IsInitialized;
1519
1562
  let queryFingerprint;
1520
1563
  if (queryCacheEngaged) {
@@ -2113,7 +2156,13 @@ export class ProviderBase {
2113
2156
  // field list to `*` in the FINGERPRINT only, which cannot affect fetching.
2114
2157
  param.Fields = entity.Fields.map(f => f.Name);
2115
2158
  }
2116
- if (param.CacheLocal && LocalCacheManager.Instance.IsInitialized) {
2159
+ // Gate on runViewCacheEligible (NOT raw param.CacheLocal): the smart-cache-check path is a
2160
+ // second, independent cache transport, and gating it on CacheLocal alone re-admits the exact
2161
+ // params runViewCacheEligible excludes — DataSource:'Materialized' (out-of-band refreshed, no
2162
+ // BaseEntity event), count_only, AfterKey, BypassCache, cache-disallowed entities. On a client
2163
+ // (!TrustLocalCacheCompletely) runViewCacheEligible already implies CacheLocal===true, so this is
2164
+ // strictly a tightening — normal cacheable slots are unaffected.
2165
+ if (this.runViewCacheEligibleForWrite(param) && LocalCacheManager.Instance.IsInitialized) {
2117
2166
  cacheable.push({ paramIndex: i, fingerprint: this.clientCacheFingerprint(param) });
2118
2167
  }
2119
2168
  }
@@ -2289,8 +2338,9 @@ export class ProviderBase {
2289
2338
  // Get entity info for primary key field name
2290
2339
  const entity = this.EntityByName(param.EntityName);
2291
2340
  const primaryKeyFieldName = entity?.FirstPrimaryKey?.Name || 'ID';
2292
- // Apply differential update to cache
2293
- if (param.CacheLocal && checkResult.differentialData && LocalCacheManager.Instance.IsInitialized) {
2341
+ // Apply differential update to cache (runViewCacheEligible, not raw CacheLocal — see the
2342
+ // cacheable-gate note in prepareSmartCacheCheckParams; keeps Materialized/count_only/etc. out).
2343
+ if (this.runViewCacheEligibleForWrite(param) && checkResult.differentialData && LocalCacheManager.Instance.IsInitialized) {
2294
2344
  const merged = await LocalCacheManager.Instance.ApplyDifferentialUpdate(fingerprint, param, checkResult.differentialData.updatedRows, checkResult.differentialData.deletedRecordIDs, primaryKeyFieldName, checkResult.maxUpdatedAt || new Date().toISOString(), checkResult.rowCount || 0, checkResult.aggregateResults, // Pass fresh aggregate results (can't be differentially computed)
2295
2345
  this);
2296
2346
  if (merged) {
@@ -2343,8 +2393,10 @@ export class ProviderBase {
2343
2393
  UserViewRunID: '',
2344
2394
  AggregateResults: checkResult.aggregateResults // Include fresh aggregate results
2345
2395
  };
2346
- // Update the local cache with fresh data (don't await - fire and forget for performance)
2347
- if (param.CacheLocal && checkResult.maxUpdatedAt && LocalCacheManager.Instance.IsInitialized) {
2396
+ // Update the local cache with fresh data (don't await - fire and forget for performance).
2397
+ // runViewCacheEligible, not raw CacheLocal see the cacheable-gate note; a first-time
2398
+ // Materialized read reaches this 'stale' branch with fresh data and would otherwise be cached.
2399
+ if (this.runViewCacheEligibleForWrite(param) && checkResult.maxUpdatedAt && LocalCacheManager.Instance.IsInitialized) {
2348
2400
  const fingerprint = this.clientCacheFingerprint(param);
2349
2401
  // Note: We don't await here to avoid blocking the response
2350
2402
  // Cache update happens in background
@@ -2750,6 +2802,16 @@ export class ProviderBase {
2750
2802
  // set to true, we still skip caching for this entity.
2751
2803
  if (entity.Name === 'MJ: Record Changes')
2752
2804
  return false;
2805
+ // Same rationale for MATERIALIZED QUERY entities: their wrapper view (materialized_vw<CodeName>) is
2806
+ // rebuilt OUT-OF-BAND by the scheduled materialization refresh (a direct-SQL atomic table swap),
2807
+ // which fires no BaseEntity.Save event for this entity — so a cached read would be served the
2808
+ // pre-refresh snapshot indefinitely. Identify them by BOTH the CodeGen wrapper-view naming convention
2809
+ // AND the VirtualEntity flag (CodeGen mints these as virtual entities) — the conjunction avoids
2810
+ // over-matching a real, event-invalidated entity that merely happens to be named materialized_vw*.
2811
+ // (Base-view materializations reuse the SOURCE entity and are handled by the DataSource:'Materialized'
2812
+ // bypass in runViewCacheEligible; this covers the query-materialization Live-read path.)
2813
+ if (entity.VirtualEntity && entity.BaseView && entity.BaseView.toLowerCase().startsWith('materialized_vw'))
2814
+ return false;
2753
2815
  return entity.TrustServerCacheCompletely !== false;
2754
2816
  }
2755
2817
  shouldAutoCache(params, result) {
@@ -3817,6 +3879,9 @@ export class ProviderBase {
3817
3879
  await newObject.Config(actualContextUser);
3818
3880
  // Initialize IS-A parent entity composition chain before any data operations
3819
3881
  await newObject.InitializeParentEntity();
3882
+ // Construct declared embedded peers (no NewRecord/Load yet) so NewRecord()
3883
+ // can provision required FKs synchronously and Load() can fill them.
3884
+ await newObject.InitializeEmbeddedRecords();
3820
3885
  if (actualLoadKey) {
3821
3886
  // Load existing record
3822
3887
  const loadResult = await newObject.InnerLoad(actualLoadKey);