@memberjunction/core 5.44.0 → 5.45.1
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/dist/generic/baseEngine.d.ts +146 -10
- package/dist/generic/baseEngine.d.ts.map +1 -1
- package/dist/generic/baseEngine.js +286 -54
- package/dist/generic/baseEngine.js.map +1 -1
- package/dist/generic/databaseProviderBase.d.ts.map +1 -1
- package/dist/generic/databaseProviderBase.js +16 -0
- package/dist/generic/databaseProviderBase.js.map +1 -1
- package/dist/generic/entityInfo.d.ts +12 -0
- package/dist/generic/entityInfo.d.ts.map +1 -1
- package/dist/generic/entityInfo.js +12 -0
- package/dist/generic/entityInfo.js.map +1 -1
- package/dist/generic/externalDataSourceReadRouter.d.ts +63 -0
- package/dist/generic/externalDataSourceReadRouter.d.ts.map +1 -0
- package/dist/generic/externalDataSourceReadRouter.js +19 -0
- package/dist/generic/externalDataSourceReadRouter.js.map +1 -0
- package/dist/generic/externalDataSourceTypes.d.ts +68 -0
- package/dist/generic/externalDataSourceTypes.d.ts.map +1 -0
- package/dist/generic/externalDataSourceTypes.js +14 -0
- package/dist/generic/externalDataSourceTypes.js.map +1 -0
- package/dist/generic/localCacheManager.d.ts +1 -1
- package/dist/generic/localCacheManager.d.ts.map +1 -1
- package/dist/generic/localCacheManager.js +21 -2
- package/dist/generic/localCacheManager.js.map +1 -1
- package/dist/generic/logging.d.ts.map +1 -1
- package/dist/generic/logging.js +4 -1
- package/dist/generic/logging.js.map +1 -1
- package/dist/generic/providerBase.d.ts +35 -0
- package/dist/generic/providerBase.d.ts.map +1 -1
- package/dist/generic/providerBase.js +83 -3
- package/dist/generic/providerBase.js.map +1 -1
- package/dist/generic/securityInfo.d.ts +48 -0
- package/dist/generic/securityInfo.d.ts.map +1 -1
- package/dist/generic/securityInfo.js +27 -0
- package/dist/generic/securityInfo.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
|
@@ -5,7 +5,7 @@ import { ComputeRRF } from "./scoring/ReciprocalRankFusion.js";
|
|
|
5
5
|
import { LocalCacheManager } from "./localCacheManager.js";
|
|
6
6
|
import { ApplicationInfo } from "../generic/applicationInfo.js";
|
|
7
7
|
import { AuditLogTypeInfo, AuthorizationInfo, AuthorizationRoleInfo, RoleInfo, RowLevelSecurityFilterInfo, UserInfo } from "./securityInfo.js";
|
|
8
|
-
import { MJGlobal, NormalizeUUID, UUIDsEqual } from "@memberjunction/global";
|
|
8
|
+
import { MJGlobal, MJEventType, NormalizeUUID, UUIDsEqual } from "@memberjunction/global";
|
|
9
9
|
import { TelemetryManager } from "./telemetryManager.js";
|
|
10
10
|
import { LogError, LogStatus, LogStatusEx } from "./logging.js";
|
|
11
11
|
import { QueryCategoryInfo, QueryFieldInfo, QueryInfo, QueryPermissionInfo, QueryEntityInfo, QueryParameterInfo, QueryDependencyInfo, SQLDialectInfo, QuerySQLInfo } from "./queryInfo.js";
|
|
@@ -196,8 +196,16 @@ export class ProviderBase {
|
|
|
196
196
|
* the RunViewParams batch. While a request is in-flight, concurrent
|
|
197
197
|
* identical calls share the same promise. After resolution the entry
|
|
198
198
|
* lingers so that near-sequential identical calls return immediately.
|
|
199
|
+
*
|
|
200
|
+
* Entries record the (lowercased) entity names their params touch so that
|
|
201
|
+
* a BaseEntity save/delete/remote-invalidate can drop them — a lingered
|
|
202
|
+
* result is a cache, and without write-invalidation a caller re-running
|
|
203
|
+
* the identical view within the linger window after a save would receive
|
|
204
|
+
* PRE-save rows (observed live: an engine's debounced post-save refreshes
|
|
205
|
+
* for multiple sequential saves — only the first save's data ever arrived).
|
|
199
206
|
*/
|
|
200
207
|
this._inflightViews = new Map();
|
|
208
|
+
this._lingerInvalidationWired = false;
|
|
201
209
|
this._clientFingerprintMemo = new WeakMap();
|
|
202
210
|
this._cachedVisibleExplorerNavigationItems = null;
|
|
203
211
|
}
|
|
@@ -252,6 +260,62 @@ export class ProviderBase {
|
|
|
252
260
|
* instead of accumulating result arrays in memory.
|
|
253
261
|
*/
|
|
254
262
|
static { this.MaxLingerEntries = 500; }
|
|
263
|
+
/**
|
|
264
|
+
* Lazily subscribes (once per provider instance) to BaseEntity events so
|
|
265
|
+
* in-flight/lingered RunView entries touching a written entity are dropped.
|
|
266
|
+
* Deleting an IN-FLIGHT entry is deliberate: late joiners must not share a
|
|
267
|
+
* query that may have read pre-commit data; the original caller still gets
|
|
268
|
+
* its own result, and the orphaned promise's stash step no-ops safely.
|
|
269
|
+
*/
|
|
270
|
+
ensureInflightViewInvalidation() {
|
|
271
|
+
if (this._lingerInvalidationWired) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
this._lingerInvalidationWired = true;
|
|
275
|
+
MJGlobal.Instance.GetEventListener(false).subscribe((event) => {
|
|
276
|
+
if (event.event !== MJEventType.ComponentEvent || event.eventCode !== BaseEntity.BaseEventCode) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const entityEvent = event.args;
|
|
280
|
+
if (!entityEvent) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
if (entityEvent.type !== 'save' && entityEvent.type !== 'delete' && entityEvent.type !== 'remote-invalidate') {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const entityName = (entityEvent.baseEntity?.EntityInfo?.Name ?? entityEvent.entityName)?.trim().toLowerCase();
|
|
287
|
+
if (entityName) {
|
|
288
|
+
this.invalidateInflightViewsForEntity(entityName);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Drops every in-flight/lingered RunView entry whose params touch the given
|
|
294
|
+
* entity (lowercased name). Called on BaseEntity save/delete/remote-invalidate.
|
|
295
|
+
*/
|
|
296
|
+
invalidateInflightViewsForEntity(lowerEntityName) {
|
|
297
|
+
for (const [key, entry] of this._inflightViews) {
|
|
298
|
+
if (entry.entityNames?.has(lowerEntityName)) {
|
|
299
|
+
this._inflightViews.delete(key);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Collects the lowercased entity names a RunViewParams batch touches, for
|
|
305
|
+
* write-invalidation of dedup/linger entries. Params addressed by ViewID /
|
|
306
|
+
* ViewName only (no EntityName) are not tracked — they keep today's
|
|
307
|
+
* linger behavior rather than pay a metadata resolution here.
|
|
308
|
+
*/
|
|
309
|
+
collectParamEntityNames(params) {
|
|
310
|
+
let names;
|
|
311
|
+
for (const p of params) {
|
|
312
|
+
const n = p.EntityName?.trim().toLowerCase();
|
|
313
|
+
if (n) {
|
|
314
|
+
(names ??= new Set()).add(n);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return names;
|
|
318
|
+
}
|
|
255
319
|
/******** ABSTRACT SECTION ****************************************************************** */
|
|
256
320
|
/**
|
|
257
321
|
* When true, cached RunView/RunQuery results are returned immediately on a
|
|
@@ -566,7 +630,8 @@ export class ProviderBase {
|
|
|
566
630
|
}
|
|
567
631
|
throw err;
|
|
568
632
|
});
|
|
569
|
-
this.
|
|
633
|
+
this.ensureInflightViewInvalidation();
|
|
634
|
+
this._inflightViews.set(key, { promise, entityNames: this.collectParamEntityNames(params) });
|
|
570
635
|
const results = await promise;
|
|
571
636
|
return results.map(r => this.ShallowCopyResult(r));
|
|
572
637
|
}
|
|
@@ -860,7 +925,8 @@ export class ProviderBase {
|
|
|
860
925
|
this._inflightViews.delete(key);
|
|
861
926
|
throw err;
|
|
862
927
|
});
|
|
863
|
-
this.
|
|
928
|
+
this.ensureInflightViewInvalidation();
|
|
929
|
+
this._inflightViews.set(key, { promise, entityNames: this.collectParamEntityNames(params) });
|
|
864
930
|
const results = await promise;
|
|
865
931
|
return results.map(r => this.ShallowCopyResult(r));
|
|
866
932
|
}
|
|
@@ -1270,6 +1336,15 @@ export class ProviderBase {
|
|
|
1270
1336
|
* @param contextUser - Optional user context for permissions (required server-side)
|
|
1271
1337
|
* @returns The query results
|
|
1272
1338
|
*/
|
|
1339
|
+
/**
|
|
1340
|
+
* Whether a saved query is bound to an external data source. The base returns false;
|
|
1341
|
+
* providers that support external data sources override this (consulting query metadata)
|
|
1342
|
+
* so the outer RunQuery CacheLocal layer can defer to InternalRunQuery's own external
|
|
1343
|
+
* TTL caching. Synchronous + non-throwing: resolves from cached metadata only.
|
|
1344
|
+
*/
|
|
1345
|
+
IsExternalQuery(_params) {
|
|
1346
|
+
return false;
|
|
1347
|
+
}
|
|
1273
1348
|
async RunQuery(params, contextUser) {
|
|
1274
1349
|
// Shallow-clone for symmetry with RunView — pipeline must never mutate caller objects
|
|
1275
1350
|
params = { ...params };
|
|
@@ -1286,6 +1361,11 @@ export class ProviderBase {
|
|
|
1286
1361
|
const queryCacheEngaged = params.CacheLocal === true
|
|
1287
1362
|
&& !params.SQL
|
|
1288
1363
|
&& (!!params.QueryID || !!params.QueryName)
|
|
1364
|
+
// External-data-source queries own their own TTL caching inside InternalRunQuery
|
|
1365
|
+
// (runExternalQueryWithCache, keyed to the source's DefaultCacheTTLSeconds). This
|
|
1366
|
+
// outer CacheLocal layer would otherwise write a SECOND, divergent slot with no/foreign
|
|
1367
|
+
// TTL (CacheLocalTTL) — a stale-forever hazard since external data can't be event-invalidated.
|
|
1368
|
+
&& !this.IsExternalQuery(params)
|
|
1289
1369
|
&& LocalCacheManager.Instance.IsInitialized;
|
|
1290
1370
|
let queryFingerprint;
|
|
1291
1371
|
if (queryCacheEngaged) {
|