@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.
- package/LICENSE +180 -4
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts +120 -0
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts.map +1 -0
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.js +6 -0
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.js.map +1 -0
- package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts +31 -0
- package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.d.ts.map +1 -0
- package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.js +6 -0
- package/dist/generic/JSONType-interfaces/IEntityFieldConfiguration.js.map +1 -0
- package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts +75 -0
- package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.d.ts.map +1 -0
- package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.js +6 -0
- package/dist/generic/JSONType-interfaces/IEntityRelationshipConfiguration.js.map +1 -0
- package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.d.ts +19 -0
- package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.d.ts.map +1 -0
- package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.js +6 -0
- package/dist/generic/JSONType-interfaces/IFileStorageProviderConfiguration.js.map +1 -0
- package/dist/generic/authTypes.d.ts +69 -0
- package/dist/generic/authTypes.d.ts.map +1 -1
- package/dist/generic/baseEngine.d.ts +14 -0
- package/dist/generic/baseEngine.d.ts.map +1 -1
- package/dist/generic/baseEngine.js +53 -13
- package/dist/generic/baseEngine.js.map +1 -1
- package/dist/generic/baseEntity.d.ts +113 -20
- package/dist/generic/baseEntity.d.ts.map +1 -1
- package/dist/generic/baseEntity.js +351 -49
- package/dist/generic/baseEntity.js.map +1 -1
- package/dist/generic/baseInfo.d.ts.map +1 -1
- package/dist/generic/baseInfo.js +16 -4
- package/dist/generic/baseInfo.js.map +1 -1
- package/dist/generic/compositeKey.d.ts +30 -0
- package/dist/generic/compositeKey.d.ts.map +1 -1
- package/dist/generic/compositeKey.js +97 -0
- package/dist/generic/compositeKey.js.map +1 -1
- package/dist/generic/databaseProviderBase.d.ts.map +1 -1
- package/dist/generic/databaseProviderBase.js +9 -5
- package/dist/generic/databaseProviderBase.js.map +1 -1
- package/dist/generic/embeddedRecord.d.ts +123 -0
- package/dist/generic/embeddedRecord.d.ts.map +1 -0
- package/dist/generic/embeddedRecord.js +356 -0
- package/dist/generic/embeddedRecord.js.map +1 -0
- package/dist/generic/entityCompanion.d.ts +20 -2
- package/dist/generic/entityCompanion.d.ts.map +1 -1
- package/dist/generic/entityCompanion.js +17 -1
- package/dist/generic/entityCompanion.js.map +1 -1
- package/dist/generic/entityConfiguration.d.ts +131 -0
- package/dist/generic/entityConfiguration.d.ts.map +1 -0
- package/dist/generic/entityConfiguration.js +346 -0
- package/dist/generic/entityConfiguration.js.map +1 -0
- package/dist/generic/entityInfo.d.ts +274 -4
- package/dist/generic/entityInfo.d.ts.map +1 -1
- package/dist/generic/entityInfo.js +502 -14
- package/dist/generic/entityInfo.js.map +1 -1
- package/dist/generic/entitySavePlan.d.ts +40 -11
- package/dist/generic/entitySavePlan.d.ts.map +1 -1
- package/dist/generic/entitySavePlan.js +70 -2
- package/dist/generic/entitySavePlan.js.map +1 -1
- package/dist/generic/interfaces.d.ts +6 -24
- package/dist/generic/interfaces.d.ts.map +1 -1
- package/dist/generic/interfaces.js +6 -24
- package/dist/generic/interfaces.js.map +1 -1
- package/dist/generic/localCacheManager.d.ts.map +1 -1
- package/dist/generic/localCacheManager.js +10 -0
- package/dist/generic/localCacheManager.js.map +1 -1
- package/dist/generic/providerBase.d.ts +19 -0
- package/dist/generic/providerBase.d.ts.map +1 -1
- package/dist/generic/providerBase.js +71 -6
- package/dist/generic/providerBase.js.map +1 -1
- package/dist/generic/relatedRecordCollection.d.ts +25 -3
- package/dist/generic/relatedRecordCollection.d.ts.map +1 -1
- package/dist/generic/relatedRecordCollection.js +53 -7
- package/dist/generic/relatedRecordCollection.js.map +1 -1
- package/dist/generic/runQuery.d.ts +14 -0
- package/dist/generic/runQuery.d.ts.map +1 -1
- package/dist/generic/runQuery.js.map +1 -1
- package/dist/generic/saveEntityGraphOperation.js +1 -1
- package/dist/generic/saveEntityGraphOperation.js.map +1 -1
- package/dist/generic/securityInfo.d.ts.map +1 -1
- package/dist/generic/securityInfo.js +15 -5
- package/dist/generic/securityInfo.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/views/runView.d.ts +23 -0
- package/dist/views/runView.d.ts.map +1 -1
- package/dist/views/runView.js +20 -0
- package/dist/views/runView.js.map +1 -1
- package/package.json +5 -4
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|