@memberjunction/core 6.2.0-edge.3 → 6.2.0-edge.5
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/BaseEntitySaveQueue.d.ts +7 -0
- package/dist/generic/BaseEntitySaveQueue.d.ts.map +1 -1
- package/dist/generic/BaseEntitySaveQueue.js +22 -4
- package/dist/generic/BaseEntitySaveQueue.js.map +1 -1
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts +33 -0
- package/dist/generic/JSONType-interfaces/IEntityConfiguration.d.ts.map +1 -1
- package/dist/generic/RegisterForStartup.d.ts +44 -0
- package/dist/generic/RegisterForStartup.d.ts.map +1 -1
- package/dist/generic/RegisterForStartup.js +124 -48
- package/dist/generic/RegisterForStartup.js.map +1 -1
- package/dist/generic/baseEngine.d.ts +222 -7
- package/dist/generic/baseEngine.d.ts.map +1 -1
- package/dist/generic/baseEngine.js +555 -37
- package/dist/generic/baseEngine.js.map +1 -1
- package/dist/generic/baseEngineSweeper.d.ts +35 -0
- package/dist/generic/baseEngineSweeper.d.ts.map +1 -0
- package/dist/generic/baseEngineSweeper.js +97 -0
- package/dist/generic/baseEngineSweeper.js.map +1 -0
- package/dist/generic/baseEntity.d.ts +45 -1
- package/dist/generic/baseEntity.d.ts.map +1 -1
- package/dist/generic/baseEntity.js +308 -187
- package/dist/generic/baseEntity.js.map +1 -1
- package/dist/generic/baseInfo.d.ts +31 -3
- package/dist/generic/baseInfo.d.ts.map +1 -1
- package/dist/generic/baseInfo.js +138 -40
- package/dist/generic/baseInfo.js.map +1 -1
- package/dist/generic/databaseProviderBase.d.ts +57 -19
- package/dist/generic/databaseProviderBase.d.ts.map +1 -1
- package/dist/generic/databaseProviderBase.js +203 -80
- package/dist/generic/databaseProviderBase.js.map +1 -1
- package/dist/generic/entityConfiguration.d.ts +8 -2
- package/dist/generic/entityConfiguration.d.ts.map +1 -1
- package/dist/generic/entityConfiguration.js +9 -0
- package/dist/generic/entityConfiguration.js.map +1 -1
- package/dist/generic/entityEventBatch.d.ts +124 -0
- package/dist/generic/entityEventBatch.d.ts.map +1 -0
- package/dist/generic/entityEventBatch.js +194 -0
- package/dist/generic/entityEventBatch.js.map +1 -0
- package/dist/generic/entityInfo.d.ts +103 -8
- package/dist/generic/entityInfo.d.ts.map +1 -1
- package/dist/generic/entityInfo.js +376 -60
- package/dist/generic/entityInfo.js.map +1 -1
- package/dist/generic/infoProviderResolution.d.ts +16 -0
- package/dist/generic/infoProviderResolution.d.ts.map +1 -0
- package/dist/generic/infoProviderResolution.js +17 -0
- package/dist/generic/infoProviderResolution.js.map +1 -0
- package/dist/generic/interfaces.d.ts +143 -3
- package/dist/generic/interfaces.d.ts.map +1 -1
- package/dist/generic/interfaces.js.map +1 -1
- package/dist/generic/localCacheManager.d.ts +505 -56
- package/dist/generic/localCacheManager.d.ts.map +1 -1
- package/dist/generic/localCacheManager.js +1185 -175
- package/dist/generic/localCacheManager.js.map +1 -1
- package/dist/generic/logging.d.ts +15 -0
- package/dist/generic/logging.d.ts.map +1 -1
- package/dist/generic/logging.js +17 -0
- package/dist/generic/logging.js.map +1 -1
- package/dist/generic/metadata.d.ts +1 -1
- package/dist/generic/metadata.d.ts.map +1 -1
- package/dist/generic/metadata.js.map +1 -1
- package/dist/generic/mjapiUrl.d.ts +21 -0
- package/dist/generic/mjapiUrl.d.ts.map +1 -0
- package/dist/generic/mjapiUrl.js +25 -0
- package/dist/generic/mjapiUrl.js.map +1 -0
- package/dist/generic/providerBase.d.ts +301 -5
- package/dist/generic/providerBase.d.ts.map +1 -1
- package/dist/generic/providerBase.js +750 -168
- package/dist/generic/providerBase.js.map +1 -1
- package/dist/generic/queryInfo.d.ts +10 -9
- package/dist/generic/queryInfo.d.ts.map +1 -1
- package/dist/generic/queryInfo.js +60 -33
- package/dist/generic/queryInfo.js.map +1 -1
- package/dist/generic/scoring/ReciprocalRankFusion.d.ts.map +1 -1
- package/dist/generic/scoring/ReciprocalRankFusion.js +10 -3
- package/dist/generic/scoring/ReciprocalRankFusion.js.map +1 -1
- package/dist/generic/securityInfo.d.ts +11 -6
- package/dist/generic/securityInfo.d.ts.map +1 -1
- package/dist/generic/securityInfo.js +15 -10
- package/dist/generic/securityInfo.js.map +1 -1
- package/dist/generic/telemetryManager.d.ts.map +1 -1
- package/dist/generic/telemetryManager.js +4 -1
- package/dist/generic/telemetryManager.js.map +1 -1
- package/dist/generic/transactionGroup.d.ts +27 -0
- package/dist/generic/transactionGroup.d.ts.map +1 -1
- package/dist/generic/transactionGroup.js +58 -5
- package/dist/generic/transactionGroup.js.map +1 -1
- package/dist/generic/util.d.ts.map +1 -1
- package/dist/generic/util.js +3 -0
- package/dist/generic/util.js.map +1 -1
- package/dist/generic/wellKnownUserSource.d.ts +3 -2
- package/dist/generic/wellKnownUserSource.d.ts.map +1 -1
- package/dist/generic/wellKnownUserSource.js +18 -9
- package/dist/generic/wellKnownUserSource.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
- package/readme.md +29 -4
|
@@ -3,6 +3,14 @@ import { AggregateResult, DatasetItemFilterType, DatasetResultType, IMetadataPro
|
|
|
3
3
|
import { AggregateExpression, RunViewParams } from "../views/runView.js";
|
|
4
4
|
import { BaseEntityEvent } from "./baseEntity.js";
|
|
5
5
|
import { CompositeKey } from "./compositeKey.js";
|
|
6
|
+
/**
|
|
7
|
+
* One change for {@link LocalCacheManager.ApplyRowChanges}: `Row` adds or replaces the row with
|
|
8
|
+
* `Key`; `Row: null` removes it.
|
|
9
|
+
*/
|
|
10
|
+
export interface CachedRowChange {
|
|
11
|
+
Key: CompositeKey;
|
|
12
|
+
Row: Record<string, unknown> | null;
|
|
13
|
+
}
|
|
6
14
|
/**
|
|
7
15
|
* The type of cache entry: dataset, runview, or runquery
|
|
8
16
|
*/
|
|
@@ -90,6 +98,14 @@ export interface CachedRunViewData {
|
|
|
90
98
|
* Backward-compatible: entries without this field are served normally (no regression).
|
|
91
99
|
*/
|
|
92
100
|
schemaHash?: string;
|
|
101
|
+
/**
|
|
102
|
+
* The newest `__mj_UpdatedAt` (ISO string) across the entity's metadata row and its field rows,
|
|
103
|
+
* written alongside {@link schemaHash}. When two processes sharing a cache disagree on the hash,
|
|
104
|
+
* this says which of them holds the newer metadata, so the older one stops treating the newer
|
|
105
|
+
* one's slots as stale. Absent on entries from writers that predate it or whose metadata
|
|
106
|
+
* carries no timestamps.
|
|
107
|
+
*/
|
|
108
|
+
schemaStamp?: string;
|
|
93
109
|
/**
|
|
94
110
|
* Set when the slot was written with {@link CacheWriteOptions.ProviderInternalScaffolding}.
|
|
95
111
|
* Persisted so that in-place slot maintenance (`storeCachedResults`, driven by BaseEntity
|
|
@@ -124,12 +140,50 @@ export interface CachedRunViewResult {
|
|
|
124
140
|
* permanently disabling post-migration drift detection for that slot.
|
|
125
141
|
*/
|
|
126
142
|
schemaHash?: string;
|
|
143
|
+
/** Surfaced for the same carry-forward reason as {@link schemaHash}. See {@link CachedRunViewData.schemaStamp}. */
|
|
144
|
+
schemaStamp?: string;
|
|
127
145
|
/**
|
|
128
146
|
* Surfaced from the stored payload so in-place maintenance can carry the freeze exemption
|
|
129
147
|
* forward. See {@link CachedRunViewData.providerInternalScaffolding}.
|
|
130
148
|
*/
|
|
131
149
|
providerInternalScaffolding?: boolean;
|
|
132
150
|
}
|
|
151
|
+
/** A cached RunQuery result, as {@link LocalCacheManager.GetRunQueryResult} returns it. */
|
|
152
|
+
export interface CachedRunQuerySlot {
|
|
153
|
+
/**
|
|
154
|
+
* The cached result rows — shared and deep-frozen under a reference-sharing storage
|
|
155
|
+
* provider, exactly like {@link CachedRunViewData.results}. Do not mutate.
|
|
156
|
+
*/
|
|
157
|
+
results: unknown[];
|
|
158
|
+
maxUpdatedAt: string;
|
|
159
|
+
rowCount: number;
|
|
160
|
+
queryId?: string;
|
|
161
|
+
/** User who executed the authorized miss that produced this slot (B43 tie-breaker). */
|
|
162
|
+
warmedForUserID?: string;
|
|
163
|
+
/** The definition hash the slot was written under; undefined for slots written without one. */
|
|
164
|
+
definitionHash?: string;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* What a saved query's cached results were computed from. A RunQuery slot stores the hash and the
|
|
168
|
+
* stamp it was written under; a read that expects a different hash is a miss. Computed by the
|
|
169
|
+
* database provider from the query metadata it executes (`QueryEngine`), so every process that
|
|
170
|
+
* runs the query agrees on it once it holds the same definition.
|
|
171
|
+
*/
|
|
172
|
+
export interface QueryDefinitionSignature {
|
|
173
|
+
/** The query the signature describes. Its RunQuery slots are indexed and held by this ID. */
|
|
174
|
+
QueryID: string;
|
|
175
|
+
/**
|
|
176
|
+
* Hash of everything that changes the query's results: its SQL for the platform, the columns
|
|
177
|
+
* that change how that SQL executes, its parameter definitions, and the hashes of the queries
|
|
178
|
+
* it composes.
|
|
179
|
+
*/
|
|
180
|
+
Hash: string;
|
|
181
|
+
/**
|
|
182
|
+
* The newest `__mj_UpdatedAt` (ISO string) across the rows the hash covers. When two processes
|
|
183
|
+
* sharing a cache disagree on the hash, this says which of them holds the newer definition.
|
|
184
|
+
*/
|
|
185
|
+
Stamp?: string;
|
|
186
|
+
}
|
|
133
187
|
/**
|
|
134
188
|
* Per-write options for the cache write methods.
|
|
135
189
|
*/
|
|
@@ -174,16 +228,29 @@ export interface LocalCacheManagerConfig {
|
|
|
174
228
|
*/
|
|
175
229
|
maxPercentOfCachePerEntity: number;
|
|
176
230
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
|
|
186
|
-
|
|
231
|
+
* Ceiling on the size of any ONE cache entry. An entry estimated larger than the
|
|
232
|
+
* ceiling is not cached at all — the write is skipped (logged, data still returned
|
|
233
|
+
* to the caller uncached). Applies to RunView and RunQuery entries.
|
|
234
|
+
*
|
|
235
|
+
* The ceiling exists because an entry that cannot be RETAINED within the budget is
|
|
236
|
+
* worse than no entry: it evicts other entries to make room and is itself evicted on
|
|
237
|
+
* the next store, so the cache churns and nothing is ever served from it.
|
|
238
|
+
*
|
|
239
|
+
* Three modes:
|
|
240
|
+
* - `'auto'` (default): the ceiling is DERIVED from the budget — everything except a
|
|
241
|
+
* reserve of {@link SINGLE_ENTRY_RESERVE_PERCENT} held back for the rest of the
|
|
242
|
+
* cache. This is deliberately not a small fixed fraction: a fixed fraction sized for
|
|
243
|
+
* a small tenant makes the one entity worth caching (the biggest one) permanently
|
|
244
|
+
* uncacheable, which is the failure this mode exists to avoid. The only entries
|
|
245
|
+
* `'auto'` declines are entries that genuinely cannot be retained — and the decline
|
|
246
|
+
* message says which `maxMemoryMB` would admit them.
|
|
247
|
+
* - a positive number: an explicit percentage of `maxSizeBytes`, for an operator who
|
|
248
|
+
* wants a stricter ceiling than `'auto'`. Clamped to
|
|
249
|
+
* {@link HARD_MAX_ENTRY_PERCENT} — no configuration may admit an entry that leaves
|
|
250
|
+
* the cache less free space than one eviction cycle needs.
|
|
251
|
+
* - `0`: no ceiling at all (the original, unguarded behaviour — not recommended).
|
|
252
|
+
*/
|
|
253
|
+
maxEntryPercentOfCache: number | 'auto';
|
|
187
254
|
/**
|
|
188
255
|
* Interval in milliseconds for the periodic eviction sweep.
|
|
189
256
|
* Catches entries that should have been evicted (TTL expired) but weren't
|
|
@@ -329,6 +396,25 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
329
396
|
* (e.g., TransactionGroup batch deletes).
|
|
330
397
|
*/
|
|
331
398
|
private _fingerprintLocks;
|
|
399
|
+
/** Logged once when a local TTL is configured against a shared store. See {@link runEvictionSweep}. */
|
|
400
|
+
private _sharedTTLWarned;
|
|
401
|
+
/** Open entity-event batches. See {@link BeginEntityEventBatch}. */
|
|
402
|
+
private _entityEventBatches;
|
|
403
|
+
/** Save/delete events a batch took when they were raised. See {@link WasTakenByEntityEventBatch}. */
|
|
404
|
+
private _eventsTakenByBatch;
|
|
405
|
+
/**
|
|
406
|
+
* Write holds on entities whose shared slots were found under a different schema, keyed by
|
|
407
|
+
* lowercased entity name. While a hold applies, {@link SetRunViewResult} skips or rate-limits
|
|
408
|
+
* writes of that entity's slots. See {@link handleSchemaMismatch} and {@link mayWriteUnderSchemaHold}.
|
|
409
|
+
*/
|
|
410
|
+
private _schemaWriteHolds;
|
|
411
|
+
/**
|
|
412
|
+
* How often, at most, a process writes an entity's shared slots while it keeps finding that
|
|
413
|
+
* entity's slots under a schema different from its own. See {@link handleSchemaMismatch}.
|
|
414
|
+
*/
|
|
415
|
+
static readonly SCHEMA_MISMATCH_WRITE_INTERVAL_MS = 60000;
|
|
416
|
+
/** Schema mismatches already reported, so each entity/hash pair is logged once per process. */
|
|
417
|
+
private _reportedSchemaMismatches;
|
|
332
418
|
private readonly REGISTRY_KEY;
|
|
333
419
|
/**
|
|
334
420
|
* Reverse index from entity name to the set of RunView cache fingerprints
|
|
@@ -341,11 +427,15 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
341
427
|
* Initialize the cache manager with a storage provider.
|
|
342
428
|
* This should be called during app startup after the storage provider is available.
|
|
343
429
|
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
430
|
+
* Safe to call repeatedly: the cache is initialized once, but **a `config` passed by a later
|
|
431
|
+
* caller is still applied**. That matters because a database provider initializes this manager
|
|
432
|
+
* from inside its own `Config()` — before the host has a chance to pass anything — so a host
|
|
433
|
+
* that hands its settings to `StartupManager` would otherwise have them silently dropped, and
|
|
434
|
+
* every `cacheSettings` knob would be inert. The host's explicit settings win
|
|
435
|
+
* over the provider's implicit initialization, whichever runs first.
|
|
346
436
|
*
|
|
347
437
|
* @param storageProvider - The local storage provider to use for persistence
|
|
348
|
-
* @param config - Optional configuration overrides
|
|
438
|
+
* @param config - Optional configuration overrides, applied even when already initialized
|
|
349
439
|
* @returns A promise that resolves when initialization is complete
|
|
350
440
|
*/
|
|
351
441
|
Initialize(storageProvider: ILocalStorageProvider, config?: Partial<LocalCacheManagerConfig>): Promise<void>;
|
|
@@ -370,6 +460,17 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
370
460
|
* Internal initialization logic - only called once by the first caller
|
|
371
461
|
*/
|
|
372
462
|
private doInitialize;
|
|
463
|
+
private _initializedListeners;
|
|
464
|
+
/**
|
|
465
|
+
* Runs `listener` once the cache manager is initialized — immediately if it already is.
|
|
466
|
+
* Lets code that ran too early (an engine loaded before startup initialized the cache) finish
|
|
467
|
+
* its cache wiring later instead of never.
|
|
468
|
+
*
|
|
469
|
+
* @param listener - Called once; errors are logged and do not affect other listeners
|
|
470
|
+
*/
|
|
471
|
+
WhenInitialized(listener: () => void): void;
|
|
472
|
+
private runInitializedListener;
|
|
473
|
+
private notifyInitialized;
|
|
373
474
|
/**
|
|
374
475
|
* Returns whether the cache manager has been initialized
|
|
375
476
|
*/
|
|
@@ -379,7 +480,8 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
379
480
|
*/
|
|
380
481
|
get Config(): LocalCacheManagerConfig;
|
|
381
482
|
/**
|
|
382
|
-
* Updates the configuration at runtime
|
|
483
|
+
* Updates the configuration at runtime, and re-arms the eviction sweep so a changed interval
|
|
484
|
+
* (or a disabled cache) takes effect immediately instead of at the next restart.
|
|
383
485
|
*/
|
|
384
486
|
UpdateConfig(config: Partial<LocalCacheManagerConfig>): void;
|
|
385
487
|
/**
|
|
@@ -438,6 +540,23 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
438
540
|
* in-place mutation (`canUseImmediateMutation`); this closes the same gap in the raw
|
|
439
541
|
* provider cache. (B42)
|
|
440
542
|
*
|
|
543
|
+
* Maintaining the order in place was built and then withdrawn, so the payoff is on record rather
|
|
544
|
+
* than left to be rediscovered. It is possible for a narrow subset — plain column terms over
|
|
545
|
+
* numeric/date/boolean fields with no NULLs present, where JavaScript and SQL provably agree —
|
|
546
|
+
* but the subset is where the value dies, not the implementation. Of 918 RunView param literals
|
|
547
|
+
* in this repo, 289 carry an ORDER BY, 112 are also unfiltered and uncapped (the only ones a slot
|
|
548
|
+
* could maintain), and roughly 26 fall in that type subset, several of which order on a
|
|
549
|
+
* uniqueidentifier and are refused anyway. The survivors are about a dozen configuration and
|
|
550
|
+
* admin lists — API keys, credentials, view types, schema info — saved rarely, so the saving is a
|
|
551
|
+
* handful of small indexed reads per day. High-write entities gain nothing: every ordered read of
|
|
552
|
+
* MJ: AI Agent Runs, MJ: Conversation Details and MJ: AI Prompt Runs is FILTERED, so their slots
|
|
553
|
+
* land in the invalidate branch regardless. Against that, the cost is a comparator that must keep
|
|
554
|
+
* agreeing with SQL collation and NULL placement across dialects forever, whose failure mode is a
|
|
555
|
+
* silently reordered list in a slot other readers treat as authoritative. Widening it to strings —
|
|
556
|
+
* by far the largest bucket, `Name` — means owning collation per dialect and per column.
|
|
557
|
+
*
|
|
558
|
+
* If you are about to attempt this, the measurement above is the argument not to.
|
|
559
|
+
*
|
|
441
560
|
* @param parts - the fingerprint already split on '|'
|
|
442
561
|
*/
|
|
443
562
|
protected hasOrderBy(parts: string[]): boolean;
|
|
@@ -547,14 +666,82 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
547
666
|
*/
|
|
548
667
|
protected isSubsetFingerprint(fingerprint: string): boolean;
|
|
549
668
|
/**
|
|
550
|
-
*
|
|
551
|
-
* (
|
|
552
|
-
*
|
|
669
|
+
* Compares a cached RunView entry's schema hash against this process's current field list for
|
|
670
|
+
* the entity (a mismatch follows a migration + CodeGen that added, removed or reordered
|
|
671
|
+
* columns). Entries without a hash, and entities this process cannot resolve, never mismatch.
|
|
553
672
|
* @param fingerprint - The cache fingerprint
|
|
554
673
|
* @param data - The cached data to validate
|
|
555
|
-
* @returns
|
|
556
|
-
*/
|
|
557
|
-
private
|
|
674
|
+
* @returns The mismatch, or null when the entry may be served
|
|
675
|
+
*/
|
|
676
|
+
private findSchemaMismatch;
|
|
677
|
+
/** The mismatch subject for an entity's RunView slots. */
|
|
678
|
+
private static runViewSubject;
|
|
679
|
+
/** The mismatch subject for a query's RunQuery slots. */
|
|
680
|
+
private static runQuerySubject;
|
|
681
|
+
/**
|
|
682
|
+
* True only when both stamps are present and the cached entry's is strictly newer than this
|
|
683
|
+
* process's. Equal or missing stamps leave the order unknown, which counts as not newer.
|
|
684
|
+
*/
|
|
685
|
+
private static isNewerSchemaStamp;
|
|
686
|
+
/**
|
|
687
|
+
* Decides what a signature mismatch does to the slot: an entity schema mismatch on a RunView
|
|
688
|
+
* slot, or a query definition mismatch on a RunQuery slot. The entry is never served; the
|
|
689
|
+
* question is whether the slot is deleted, which on a shared store may also tell every peer to
|
|
690
|
+
* reload.
|
|
691
|
+
*
|
|
692
|
+
* - Process-local store, or a slot-maintenance read: delete it (`drop`). No other process reads a
|
|
693
|
+
* local store. A maintenance read happens because a row changed, and a slot that cannot take
|
|
694
|
+
* the change in place must go, whichever process wrote it.
|
|
695
|
+
* - Shared store, serving read: leave it, and hold back this process's writes of the subject's
|
|
696
|
+
* slots. Deleting a RunView slot publishes `removed`, every peer engine holding the slot
|
|
697
|
+
* reloads and rewrites it under its own hash, and processes on either side of a migration keep
|
|
698
|
+
* doing that to each other. The caller reads the database instead.
|
|
699
|
+
* - When the slot is known to come from newer metadata, this process is the one out of date.
|
|
700
|
+
* It stops writing the subject's slots until its own hash changes.
|
|
701
|
+
* - Otherwise it writes the subject's slots at most once per
|
|
702
|
+
* {@link SCHEMA_MISMATCH_WRITE_INTERVAL_MS} while mismatches keep appearing. That still
|
|
703
|
+
* replaces a slot left by older metadata, but a peer running code that deletes mismatched
|
|
704
|
+
* slots (any release before this check stopped deleting them) gets one slot to delete per
|
|
705
|
+
* interval instead of one per read. The hold lapses after an interval with no mismatch.
|
|
706
|
+
* For RunView slots the first replacement waits one interval. For RunQuery slots it is
|
|
707
|
+
* immediate: no release ever deleted a RunQuery slot over its definition and the category
|
|
708
|
+
* is not published, so an immediate replacement cannot feed a loop, and waiting would make
|
|
709
|
+
* every server run an edited query against the database for a full interval.
|
|
710
|
+
*/
|
|
711
|
+
private handleSchemaMismatch;
|
|
712
|
+
/** The log text for a mismatch that left the slot in place and took a write hold. */
|
|
713
|
+
private static describeHeldMismatch;
|
|
714
|
+
/** The key of a subject's write hold. RunView and RunQuery holds share one map. */
|
|
715
|
+
private static holdKey;
|
|
716
|
+
/** Records or extends the write hold for a mismatch seen on a shared store. */
|
|
717
|
+
private holdSchemaWrites;
|
|
718
|
+
/** Logs a mismatch once per subject and hash pair per process. */
|
|
719
|
+
private reportSchemaMismatch;
|
|
720
|
+
/**
|
|
721
|
+
* Whether a slot of `subject` may be written now, given its write hold (see
|
|
722
|
+
* {@link handleSchemaMismatch}). A permitted write under a rate-limiting hold uses up the
|
|
723
|
+
* interval. The hold is dropped once this process's hash changes (a metadata refresh, or a
|
|
724
|
+
* query edit reaching this process) or, for a rate-limiting hold, once an interval passes with
|
|
725
|
+
* no mismatch.
|
|
726
|
+
*/
|
|
727
|
+
private mayWriteUnderSchemaHold;
|
|
728
|
+
/**
|
|
729
|
+
* The newest `__mj_UpdatedAt` among `rows` as an ISO string, `''` for an empty set, or
|
|
730
|
+
* `undefined` when the rows carry no such column (so the caller can fall back to its own stamp).
|
|
731
|
+
* Mirrors how the RunView fill path stamps a slot.
|
|
732
|
+
*/
|
|
733
|
+
static MaxUpdatedAtOfRows(rows: ReadonlyArray<unknown>): string | undefined;
|
|
734
|
+
/**
|
|
735
|
+
* Storage write options for a RunView slot: the entity name as index group, so a shared
|
|
736
|
+
* provider can find the slot from any process, and the time left before the slot's own expiry
|
|
737
|
+
* (external-data-source entities), so a rewrite does not extend it.
|
|
738
|
+
*
|
|
739
|
+
* @param fingerprint - The slot key
|
|
740
|
+
* @param expiresAt - Absolute expiry (ms since epoch), when the slot has one
|
|
741
|
+
*/
|
|
742
|
+
private runViewWriteOptions;
|
|
743
|
+
/** Storage write options for any registry entry; only RunView slots are indexed. */
|
|
744
|
+
private writeOptionsForEntry;
|
|
558
745
|
/**
|
|
559
746
|
* Adds a fingerprint to the entity→fingerprint reverse index.
|
|
560
747
|
* Called when a RunView result is cached.
|
|
@@ -571,11 +758,14 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
571
758
|
*/
|
|
572
759
|
GetFingerprintsForEntity(entityName: string): ReadonlySet<string>;
|
|
573
760
|
/**
|
|
574
|
-
* Resolves cached fingerprints for an entity,
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
761
|
+
* Resolves cached fingerprints for an entity: this process's own index, plus — on a storage
|
|
762
|
+
* provider shared between processes — the slots other processes wrote.
|
|
763
|
+
*
|
|
764
|
+
* A save on server A has to reach slots server B wrote after A booted, because nothing else
|
|
765
|
+
* will: B only hears about A's save through the slots A rewrites. A shared provider therefore
|
|
766
|
+
* answers from its per-entity index ({@link ILocalStorageProvider.GetIndexGroupKeys}) on every
|
|
767
|
+
* call. A provider without that index is process-local or cannot enumerate cheaply, and keeps
|
|
768
|
+
* the older behaviour: the category is listed only when the local index knows nothing.
|
|
579
769
|
*/
|
|
580
770
|
private resolveFingerprintsForEntity;
|
|
581
771
|
/**
|
|
@@ -593,6 +783,118 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
593
783
|
* @param entityEvent - The BaseEntity event payload
|
|
594
784
|
*/
|
|
595
785
|
protected HandleBaseEntityEvent(entityEvent: BaseEntityEvent): Promise<void>;
|
|
786
|
+
/**
|
|
787
|
+
* Opens a batch for `owner` (normally a provider), or enters the one already open. Until the
|
|
788
|
+
* matching {@link EndEntityEventBatch}, saves and deletes raised by entities that use `owner`
|
|
789
|
+
* as their provider are recorded instead of applied; closing the batch rewrites each affected
|
|
790
|
+
* slot once. `DatabaseProviderBase.BeginEntityTransaction` opens one for every transaction, so
|
|
791
|
+
* callers rarely need this directly — {@link RunInEntityEventBatch} is the wrapper for bulk
|
|
792
|
+
* writers that do not use a transaction.
|
|
793
|
+
*
|
|
794
|
+
* Every call must be paired with exactly one {@link EndEntityEventBatch}.
|
|
795
|
+
*/
|
|
796
|
+
BeginEntityEventBatch(owner: object): void;
|
|
797
|
+
/**
|
|
798
|
+
* Replaces a RunView slot wholesale while holding the cross-process lock for it.
|
|
799
|
+
*
|
|
800
|
+
* The event-driven path already reads-modifies-writes under that lock; a writer that replaces
|
|
801
|
+
* the same slot outside it can land between a peer's read and its write, and whichever `SET`
|
|
802
|
+
* lands second wins — the engine sweep's fresh rows lost, or the peer's delta lost, and either
|
|
803
|
+
* way the losing state is published to the fleet.
|
|
804
|
+
*/
|
|
805
|
+
ReplaceRunViewResultLocked(fingerprint: string, params: RunViewParams, results: unknown[], maxUpdatedAt: string, totalRowCount: number, provider?: IMetadataProvider): Promise<boolean>;
|
|
806
|
+
/** Milliseconds left on a slot's expiry, or undefined when it has none. @internal */
|
|
807
|
+
private remainingTTLForSlot;
|
|
808
|
+
/**
|
|
809
|
+
* Abandons `owner`'s batch at any depth and invalidates every slot it touched.
|
|
810
|
+
*
|
|
811
|
+
* For an owner that cannot settle its batch normally: an independent provider instance being
|
|
812
|
+
* released with a transaction still open, or a transaction handle reset after a failure. The
|
|
813
|
+
* buffered rows may or may not have reached the database, so the affected slots are invalidated
|
|
814
|
+
* rather than written, and the entities stop counting as pending — a batch left open makes
|
|
815
|
+
* every cached read of its entities miss, for the life of the process.
|
|
816
|
+
*
|
|
817
|
+
* Never throws. Safe when no batch is open.
|
|
818
|
+
*/
|
|
819
|
+
AbandonEntityEventBatch(owner: object): Promise<void>;
|
|
820
|
+
/**
|
|
821
|
+
* Leaves one level of `owner`'s batch. Closing the outermost level applies the batch: with
|
|
822
|
+
* every level successful, each affected slot is rewritten once with all its changes; if any
|
|
823
|
+
* level failed (a rollback), the affected slots are invalidated instead, so every server reloads
|
|
824
|
+
* them from the database. Never throws.
|
|
825
|
+
*
|
|
826
|
+
* @param owner - The object passed to {@link BeginEntityEventBatch}
|
|
827
|
+
* @param succeeded - False when the work this level covered was rolled back or failed
|
|
828
|
+
*/
|
|
829
|
+
EndEntityEventBatch(owner: object, succeeded: boolean): Promise<void>;
|
|
830
|
+
/**
|
|
831
|
+
* Claims a named lease on the storage provider for `ttlMs`: on a shared store only one process
|
|
832
|
+
* gets it per period; on a private store the caller always does. A lease that cannot be
|
|
833
|
+
* checked is reported as not claimed.
|
|
834
|
+
*/
|
|
835
|
+
TryAcquireSharedLease(name: string, ttlMs: number): Promise<boolean>;
|
|
836
|
+
/** One lease attempt: claimed, held by another process, or the store could not be asked. */
|
|
837
|
+
private claimLease;
|
|
838
|
+
/**
|
|
839
|
+
* Extends a lease this process already holds, so long-running work keeps its turn. Falls back
|
|
840
|
+
* to re-claiming when the store has no dedicated renewal, which is a no-op for anyone else's
|
|
841
|
+
* lease because the claim is `SET NX`. Never throws.
|
|
842
|
+
*
|
|
843
|
+
* @returns true when the lease is still (or again) held by this process.
|
|
844
|
+
*/
|
|
845
|
+
RenewSharedLease(name: string, ttlMs: number): Promise<boolean>;
|
|
846
|
+
/** Ends a lease claimed with {@link TryAcquireSharedLease}. Never throws. */
|
|
847
|
+
ReleaseSharedLease(name: string): Promise<void>;
|
|
848
|
+
/**
|
|
849
|
+
* Waits until this process claims the lease, polling every `pollMs`, for at most `maxWaitMs`.
|
|
850
|
+
* @returns Whether the lease was claimed, and how long the wait took.
|
|
851
|
+
*/
|
|
852
|
+
WaitForSharedLease(name: string, ttlMs: number, maxWaitMs: number, pollMs?: number): Promise<{
|
|
853
|
+
Acquired: boolean;
|
|
854
|
+
WaitedMs: number;
|
|
855
|
+
}>;
|
|
856
|
+
/** True while a batch is open for `owner`. */
|
|
857
|
+
IsBatchingEntityEvents(owner: object | null | undefined): boolean;
|
|
858
|
+
/**
|
|
859
|
+
* True when a batch took `entityEvent` as it was raised, so closing that batch maintains every
|
|
860
|
+
* slot indexed for the entity. A listener that reaches its cache write only after an `await`
|
|
861
|
+
* asks this instead of {@link IsBatchingEntityEvents}: by then the batch may have closed, and
|
|
862
|
+
* a write of its own would rewrite the slot once per event.
|
|
863
|
+
*/
|
|
864
|
+
WasTakenByEntityEventBatch(entityEvent: BaseEntityEvent): boolean;
|
|
865
|
+
/**
|
|
866
|
+
* Runs `work` inside an entity-event batch for `owner` and applies the batch when it finishes
|
|
867
|
+
* (as a failure when `work` throws). Use for bulk writes that do not already run in a
|
|
868
|
+
* transaction.
|
|
869
|
+
*/
|
|
870
|
+
RunInEntityEventBatch<T>(owner: object, work: () => Promise<T>): Promise<T>;
|
|
871
|
+
/**
|
|
872
|
+
* Records a save/delete in the batch open for the entity's provider.
|
|
873
|
+
* @returns True when the event was taken by a batch (or needs no cache work at all).
|
|
874
|
+
*/
|
|
875
|
+
private bufferIfBatching;
|
|
876
|
+
/**
|
|
877
|
+
* True when `fingerprint`'s entity has changes waiting in an open batch. Such a slot is behind
|
|
878
|
+
* what the unit of work has written, so reads treat it as a miss (the database answers,
|
|
879
|
+
* including the uncommitted rows for a reader inside the transaction) and fills are not stored.
|
|
880
|
+
*/
|
|
881
|
+
private isPendingInBatch;
|
|
882
|
+
/**
|
|
883
|
+
* The change a save/delete event describes, captured now — the entity object can change (or,
|
|
884
|
+
* after a delete, be reset) before a batch closes. Null when the event needs no cache work.
|
|
885
|
+
*/
|
|
886
|
+
private captureEntityChange;
|
|
887
|
+
/** Applies a closed batch; see {@link EndEntityEventBatch}. */
|
|
888
|
+
private applyEntityEventBatch;
|
|
889
|
+
/** Invalidates every entity a batch touched — for a batch that failed or was abandoned. */
|
|
890
|
+
private invalidateBatchEntities;
|
|
891
|
+
/** Applies one entity's buffered changes to every slot indexed for it, each slot once. */
|
|
892
|
+
private applyEntityChanges;
|
|
893
|
+
/**
|
|
894
|
+
* One slot, several changes: the same decisions {@link processEntityEventForFingerprint} makes
|
|
895
|
+
* per event, made once for the whole list.
|
|
896
|
+
*/
|
|
897
|
+
private applyEntityChangesToSlot;
|
|
596
898
|
/**
|
|
597
899
|
* Handles remote-invalidate events that include recordData (the saved entity as JSON).
|
|
598
900
|
* Updates all cached RunView results for the entity without a server round-trip.
|
|
@@ -637,6 +939,20 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
637
939
|
* Decomposed from HandleBaseEntityEvent for clarity and testability.
|
|
638
940
|
*/
|
|
639
941
|
private processEntityEventForFingerprint;
|
|
942
|
+
/**
|
|
943
|
+
* Handles a save/delete that could not be applied to a slot this process indexed — in practice
|
|
944
|
+
* because the slot expired or was removed by someone else.
|
|
945
|
+
*
|
|
946
|
+
* Rewriting the slot is how peer servers hear about the change: their engines hold the rows in
|
|
947
|
+
* memory and never read the slot again, so a slot that expired while they were running leaves
|
|
948
|
+
* nothing to rewrite and the change would never reach them. Invalidating instead publishes a
|
|
949
|
+
* `removed` event on a shared provider, which makes every peer engine tracking the slot reload
|
|
950
|
+
* it from the database. It also drops the dead fingerprint from this process's index.
|
|
951
|
+
*
|
|
952
|
+
* @param fingerprint - The slot the event was applied to
|
|
953
|
+
* @param maintained - What the in-place upsert/remove reported
|
|
954
|
+
*/
|
|
955
|
+
private invalidateIfNotMaintained;
|
|
640
956
|
/**
|
|
641
957
|
* Map from cache fingerprint (or category for category_cleared events) to
|
|
642
958
|
* registered {@link CacheChangedEvent} callbacks. Callbacks are invoked when
|
|
@@ -675,9 +991,8 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
675
991
|
* fingerprint. Called by infrastructure code (e.g., {@link RedisLocalStorageProvider})
|
|
676
992
|
* when another server modifies a cached entry.
|
|
677
993
|
*
|
|
678
|
-
* For `category_cleared` events, dispatches to ALL registered
|
|
679
|
-
*
|
|
680
|
-
* which contains the category name).
|
|
994
|
+
* For `category_cleared` events on the RunView category, dispatches to ALL registered
|
|
995
|
+
* callbacks (they all watch RunView slots); a clear of any other category reaches none.
|
|
681
996
|
*
|
|
682
997
|
* Errors in individual callbacks are caught and logged via {@link LogError}
|
|
683
998
|
* to prevent one bad callback from blocking others.
|
|
@@ -827,6 +1142,14 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
827
1142
|
* @returns The schema hash string, or undefined if the entity can't be resolved
|
|
828
1143
|
*/
|
|
829
1144
|
ComputeSchemaHash(provider: IMetadataProvider | undefined, entityName: string): string | undefined;
|
|
1145
|
+
/**
|
|
1146
|
+
* The entity's schema hash (see {@link ComputeSchemaHash}) together with its schema stamp: the
|
|
1147
|
+
* newest `__mj_UpdatedAt` across the entity row and its field rows, as an ISO string, or
|
|
1148
|
+
* undefined when the metadata carries no timestamps.
|
|
1149
|
+
*/
|
|
1150
|
+
private computeSchemaSignature;
|
|
1151
|
+
/** Milliseconds since the epoch for a metadata timestamp (a Date or a string), or 0 when unreadable. */
|
|
1152
|
+
private static timestampMs;
|
|
830
1153
|
/**
|
|
831
1154
|
* Deep-freezes an about-to-be-stored cache payload when — and only when — the active
|
|
832
1155
|
* storage provider hands out live object references
|
|
@@ -892,6 +1215,8 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
892
1215
|
* @returns The cached results, maxUpdatedAt, rowCount (derived), and aggregateResults, or null if not found
|
|
893
1216
|
*/
|
|
894
1217
|
GetRunViewResult(fingerprint: string): Promise<CachedRunViewResult | null>;
|
|
1218
|
+
/** {@link GetRunViewResult} without the open-batch check; slot maintenance reads through this. */
|
|
1219
|
+
private readRunViewSlot;
|
|
895
1220
|
/**
|
|
896
1221
|
* Batched retrieval for many cached RunView results in a single underlying
|
|
897
1222
|
* IndexedDB transaction (or Redis MGET, or one in-memory pass — depends on
|
|
@@ -920,10 +1245,35 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
920
1245
|
* recording the appropriate hit/miss + access-tracking side effects.
|
|
921
1246
|
*
|
|
922
1247
|
* Also validates the schema hash (if present) to detect structurally stale
|
|
923
|
-
* cache entries after schema migrations. If the entity's field list
|
|
924
|
-
*
|
|
1248
|
+
* cache entries after schema migrations. If the entity's field list differs
|
|
1249
|
+
* from the one the entry was cached under, null is returned; whether the slot
|
|
1250
|
+
* is also deleted depends on `purpose` (see {@link handleSchemaMismatch}).
|
|
925
1251
|
*/
|
|
926
1252
|
private materializeCachedRunViewResult;
|
|
1253
|
+
/**
|
|
1254
|
+
* Records a slot this process READ, so a later save can still find it once it has expired.
|
|
1255
|
+
*
|
|
1256
|
+
* An engine loads its rows once and never reads its slot again — that is the design, not a
|
|
1257
|
+
* defect: the slot is a boot shortcut and a propagation channel, and correctness comes from
|
|
1258
|
+
* events. Giving slots a finite expiry opened a window those events could not cross. When a
|
|
1259
|
+
* slot expires under running replicas, a save finds nothing to rewrite and publishes nothing,
|
|
1260
|
+
* so every peer engine keeps serving what it loaded.
|
|
1261
|
+
*
|
|
1262
|
+
* The fix — invalidate a slot that cannot be maintained, which publishes `removed` — only
|
|
1263
|
+
* fires if the saving process still holds the fingerprint, and only the process that WROTE the
|
|
1264
|
+
* slot did. A server that booted warm read it instead, and a read indexed nothing; behind a
|
|
1265
|
+
* load balancer, which server takes the save is arbitrary. A warm read is the only trace such a
|
|
1266
|
+
* process has of that slot, so it is indexed too.
|
|
1267
|
+
*
|
|
1268
|
+
* Gated on the provider exposing a shared per-entity index, which is exactly the case
|
|
1269
|
+
* {@link resolveFingerprintsForEntity} answers from that index — with no category-scan
|
|
1270
|
+
* fallback. On a process-local store there is nothing to gain (no peers to notify, and every
|
|
1271
|
+
* slot in it was written by this process, so it is already indexed) and something to lose: a
|
|
1272
|
+
* non-empty index SUPPRESSES that fallback scan, which is what finds persisted slots the index
|
|
1273
|
+
* does not know about.
|
|
1274
|
+
* @internal
|
|
1275
|
+
*/
|
|
1276
|
+
private indexSlotThisProcessDidNotWrite;
|
|
927
1277
|
/**
|
|
928
1278
|
* Invalidates a cached RunView result.
|
|
929
1279
|
*
|
|
@@ -959,6 +1309,14 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
959
1309
|
* @returns The merged results after applying the differential update, or null if cache not found
|
|
960
1310
|
*/
|
|
961
1311
|
ApplyDifferentialUpdate(fingerprint: string, params: RunViewParams, updatedRows: unknown[], deletedRecordIDs: string[], primaryKeyFieldNames: string | string[], newMaxUpdatedAt: string, serverRowCount?: number, aggregateResults?: AggregateResult[], provider?: IMetadataProvider): Promise<CachedRunViewResult | null>;
|
|
1312
|
+
/**
|
|
1313
|
+
* Runs an in-place slot rewrite under {@link withFingerprintLock}. When the lock itself fails
|
|
1314
|
+
* (a shared lock another server held too long), reports the rewrite as not done rather than
|
|
1315
|
+
* throwing, so the caller's fallback — invalidate the slot, making every server reload —
|
|
1316
|
+
* applies.
|
|
1317
|
+
*/
|
|
1318
|
+
private maintainSlotLocked;
|
|
1319
|
+
private withFingerprintLock;
|
|
962
1320
|
/**
|
|
963
1321
|
* Upserts a single entity in a cached RunView result.
|
|
964
1322
|
* Used by BaseEngine for immediate cache sync when an entity is saved.
|
|
@@ -969,12 +1327,10 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
969
1327
|
* (e.g., 3 deletes from a TransactionGroup), each read-modify-write cycle
|
|
970
1328
|
* must complete before the next one starts for the same fingerprint.
|
|
971
1329
|
* Different fingerprints run concurrently with no contention.
|
|
972
|
-
|
|
973
|
-
private withFingerprintLock;
|
|
974
|
-
/**
|
|
1330
|
+
*
|
|
975
1331
|
* @param fingerprint - The cache fingerprint to update
|
|
976
1332
|
* @param entityData - The entity data as a plain object (use entity.GetAll())
|
|
977
|
-
* @param
|
|
1333
|
+
* @param key - The record's primary key
|
|
978
1334
|
* @param newMaxUpdatedAt - New maxUpdatedAt timestamp (from entity's __mj_UpdatedAt)
|
|
979
1335
|
* @returns true if cache was updated, false if cache not found or update failed
|
|
980
1336
|
*/
|
|
@@ -989,6 +1345,23 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
989
1345
|
* @returns true if cache was updated, false if cache not found or update failed
|
|
990
1346
|
*/
|
|
991
1347
|
RemoveSingleEntity(fingerprint: string, key: CompositeKey, newMaxUpdatedAt: string): Promise<boolean>;
|
|
1348
|
+
/**
|
|
1349
|
+
* Applies several row changes to one cached RunView result in a single read-modify-write:
|
|
1350
|
+
* each change with a `Row` adds or replaces that row, each with `Row: null` removes it, in
|
|
1351
|
+
* order. The slot is written once, and not at all when only removals of absent rows were asked.
|
|
1352
|
+
* All keys must name the same primary-key fields.
|
|
1353
|
+
*
|
|
1354
|
+
* @returns true if the slot now reflects the changes, false if it was not cached or the update failed
|
|
1355
|
+
*/
|
|
1356
|
+
ApplyRowChanges(fingerprint: string, changes: ReadonlyArray<CachedRowChange>, newMaxUpdatedAt: string): Promise<boolean>;
|
|
1357
|
+
/**
|
|
1358
|
+
* Rows keyed by a cheap delimiter-joined PK string (no per-row CompositeKey/KeyValuePair
|
|
1359
|
+
* allocation); matching is consistent because {@link cheapKeyFromCompositeKey} uses the same
|
|
1360
|
+
* format. Rows missing a PK value are dropped.
|
|
1361
|
+
*/
|
|
1362
|
+
private indexRowsByKey;
|
|
1363
|
+
/** Applies `changes` to `rows` in order. @returns True when anything changed. */
|
|
1364
|
+
private applyChangesToRowMap;
|
|
992
1365
|
/**
|
|
993
1366
|
* Stores updated results array back to the cache and updates the registry.
|
|
994
1367
|
* Shared by UpsertSingleEntity and RemoveSingleEntity to avoid duplication.
|
|
@@ -1034,26 +1407,38 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
1034
1407
|
* @param rowCount - Optional row count (defaults to results.length if not provided)
|
|
1035
1408
|
* @param queryId - Optional query ID for reference
|
|
1036
1409
|
* @param ttlMs - Optional TTL in milliseconds (for cache expiry tracking)
|
|
1410
|
+
* @param warmedForUserID - The user whose authorized execution produced the rows
|
|
1411
|
+
* @param definition - The query definition the rows were computed under. Stored with the slot so
|
|
1412
|
+
* a read under a different definition misses (see {@link GetRunQueryResult}), and used to index
|
|
1413
|
+
* the slot by query ID (see {@link InvalidateRunQueryResultsForQuery}). On a shared store the
|
|
1414
|
+
* write is skipped while the query's slots are under a write hold (see
|
|
1415
|
+
* `handleSchemaMismatch`).
|
|
1037
1416
|
*/
|
|
1038
|
-
SetRunQueryResult(fingerprint: string, queryName: string, results: unknown[], maxUpdatedAt: string, rowCount?: number, queryId?: string, ttlMs?: number, warmedForUserID?: string): Promise<void>;
|
|
1417
|
+
SetRunQueryResult(fingerprint: string, queryName: string, results: unknown[], maxUpdatedAt: string, rowCount?: number, queryId?: string, ttlMs?: number, warmedForUserID?: string, definition?: QueryDefinitionSignature): Promise<void>;
|
|
1418
|
+
/**
|
|
1419
|
+
* Store options for a RunQuery slot: its expiry, and the query ID as the index group, so a
|
|
1420
|
+
* shared store can list every slot of one query whichever process wrote it.
|
|
1421
|
+
*/
|
|
1422
|
+
private static runQueryWriteOptions;
|
|
1039
1423
|
/**
|
|
1040
1424
|
* Retrieves a cached RunQuery result.
|
|
1041
1425
|
*
|
|
1042
1426
|
* @param fingerprint - The cache fingerprint
|
|
1043
|
-
* @
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1427
|
+
* @param expectedDefinition - The query definition the caller would execute on a miss. When
|
|
1428
|
+
* given, a slot written under a different definition (or one that records none) is a miss.
|
|
1429
|
+
* On a process-local store the slot is also deleted; on a shared store it is left in place and
|
|
1430
|
+
* this process's writes of the query's slots are held (see `handleSchemaMismatch`). Omit it
|
|
1431
|
+
* where the caller cannot compute a definition (a client, whose slots the server validates).
|
|
1432
|
+
* @returns The cached results, maxUpdatedAt, rowCount, queryId and definition hash, or null if
|
|
1433
|
+
* not found, expired, or written under another definition
|
|
1434
|
+
*/
|
|
1435
|
+
GetRunQueryResult(fingerprint: string, expectedDefinition?: QueryDefinitionSignature): Promise<CachedRunQuerySlot | null>;
|
|
1436
|
+
/**
|
|
1437
|
+
* Compares a RunQuery slot's definition hash with the definition the caller expects. A slot
|
|
1438
|
+
* that records no hash was written before slots carried one, so nothing says which SQL produced
|
|
1439
|
+
* its rows; it counts as a mismatch with an unknown order.
|
|
1440
|
+
*/
|
|
1441
|
+
private static findDefinitionMismatch;
|
|
1057
1442
|
/**
|
|
1058
1443
|
* Invalidates a cached RunQuery result.
|
|
1059
1444
|
*
|
|
@@ -1064,19 +1449,39 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
1064
1449
|
* Invalidates all cached RunQuery results for a specific query.
|
|
1065
1450
|
* Useful when a query's underlying data changes and all related caches should be cleared.
|
|
1066
1451
|
*
|
|
1452
|
+
* Matches this process's registry by name only, so it misses slots written by other processes
|
|
1453
|
+
* on a shared store and slots written under a former name.
|
|
1454
|
+
*
|
|
1067
1455
|
* @param queryName - The query name to invalidate
|
|
1456
|
+
* @deprecated Use {@link InvalidateRunQueryResultsForQuery}, which finds a query's slots by ID
|
|
1457
|
+
* in every process that shares the store.
|
|
1068
1458
|
*/
|
|
1069
1459
|
InvalidateQueryCaches(queryName: string): Promise<void>;
|
|
1460
|
+
/**
|
|
1461
|
+
* Deletes every cached RunQuery result of one query: the slots this process wrote, and on a
|
|
1462
|
+
* shared store every slot any process wrote with the query's ID (see {@link SetRunQueryResult}).
|
|
1463
|
+
* Slots keyed by the query's name alone, or written before slots were indexed, are not found;
|
|
1464
|
+
* a definition check on read (see {@link GetRunQueryResult}) keeps them from being served.
|
|
1465
|
+
* Never throws.
|
|
1466
|
+
*
|
|
1467
|
+
* @param queryId - The query whose slots to delete
|
|
1468
|
+
* @returns The number of slots deleted
|
|
1469
|
+
*/
|
|
1470
|
+
InvalidateRunQueryResultsForQuery(queryId: string): Promise<number>;
|
|
1471
|
+
/** The keys of one query's RunQuery slots: this process's registry, plus the shared index. */
|
|
1472
|
+
private findRunQueryKeysForQuery;
|
|
1070
1473
|
/**
|
|
1071
1474
|
* Gets the cache status (fingerprint data) for a RunQuery result.
|
|
1072
1475
|
* Used for smart cache validation with the server.
|
|
1073
1476
|
*
|
|
1074
1477
|
* @param fingerprint - The cache fingerprint
|
|
1075
|
-
* @returns The cache status with maxUpdatedAt and
|
|
1478
|
+
* @returns The cache status with maxUpdatedAt, rowCount and the definition hash the slot was
|
|
1479
|
+
* written under, or null if not found/expired
|
|
1076
1480
|
*/
|
|
1077
1481
|
GetRunQueryCacheStatus(fingerprint: string): Promise<{
|
|
1078
1482
|
maxUpdatedAt: string;
|
|
1079
1483
|
rowCount: number;
|
|
1484
|
+
definitionHash?: string;
|
|
1080
1485
|
} | null>;
|
|
1081
1486
|
/**
|
|
1082
1487
|
* Returns all cache entries for dashboard display.
|
|
@@ -1133,6 +1538,14 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
1133
1538
|
* Records an access to a cache entry (updates lastAccessedAt and accessCount).
|
|
1134
1539
|
*/
|
|
1135
1540
|
private recordAccess;
|
|
1541
|
+
/**
|
|
1542
|
+
* True when the registry must stay in this process: the storage is shared with other
|
|
1543
|
+
* processes, so a persisted registry would be one key every server overwrites with its own
|
|
1544
|
+
* view (and publishes to every peer on each write), and a booting server would adopt another
|
|
1545
|
+
* server's accounting. Entries written by other processes are found through the provider's
|
|
1546
|
+
* per-entity index instead ({@link resolveFingerprintsForEntity}).
|
|
1547
|
+
*/
|
|
1548
|
+
private registryIsProcessLocalOnly;
|
|
1136
1549
|
/**
|
|
1137
1550
|
* Loads the registry from storage.
|
|
1138
1551
|
*/
|
|
@@ -1164,16 +1577,50 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
1164
1577
|
*/
|
|
1165
1578
|
private estimateResultsSize;
|
|
1166
1579
|
/**
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
*
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1580
|
+
* The largest single entry this cache will store, in bytes, or 0 when no ceiling
|
|
1581
|
+
* applies (`maxEntryPercentOfCache: 0`).
|
|
1582
|
+
*
|
|
1583
|
+
* `'auto'` derives it from the budget — the whole budget less
|
|
1584
|
+
* {@link SINGLE_ENTRY_RESERVE_PERCENT} — so the ceiling grows with the memory the
|
|
1585
|
+
* operator gave the cache instead of being a small fixed fraction that the biggest
|
|
1586
|
+
* entity can never fit under. An explicit percentage is honoured as a stricter
|
|
1587
|
+
* ceiling, clamped to {@link HARD_MAX_ENTRY_PERCENT} so no configuration can admit an
|
|
1588
|
+
* entry that leaves the cache with less free space than one eviction pass needs.
|
|
1589
|
+
*/
|
|
1590
|
+
get MaxEntrySizeBytes(): number;
|
|
1591
|
+
/**
|
|
1592
|
+
* The per-entry ceiling as a percentage of the budget, with `'auto'` resolved and an
|
|
1593
|
+
* explicit configuration clamped. 0 or below means "no ceiling".
|
|
1594
|
+
*/
|
|
1595
|
+
private get effectiveMaxEntryPercent();
|
|
1596
|
+
/**
|
|
1597
|
+
* Returns true when a single entry of the given estimated size exceeds
|
|
1598
|
+
* {@link MaxEntrySizeBytes} and must not be cached. See the config property's doc
|
|
1599
|
+
* comment for the rationale — in short, an entry that cannot be RETAINED within the
|
|
1600
|
+
* budget would be evicted again on the next store, so caching it is churn. An entry
|
|
1601
|
+
* that fits is cached however large it is: the cases that most need caching are the
|
|
1602
|
+
* large ones.
|
|
1173
1603
|
*/
|
|
1174
1604
|
private exceedsMaxEntrySize;
|
|
1605
|
+
/**
|
|
1606
|
+
* Renders the decline of an oversized entry as something an operator can act on: what
|
|
1607
|
+
* the ceiling is, and the `cacheSettings.maxMemoryMB` that would admit this entry.
|
|
1608
|
+
* The old message reported raw byte counts and a percentage, which named the symptom
|
|
1609
|
+
* but not the lever — and the entries it declines are exactly the expensive queries
|
|
1610
|
+
* whose absence from the cache costs the most.
|
|
1611
|
+
*/
|
|
1612
|
+
private describeOversizedEntry;
|
|
1175
1613
|
/**
|
|
1176
1614
|
* Evicts entries if needed to make room for new data.
|
|
1615
|
+
*
|
|
1616
|
+
* Frees the DEFICIT — how far over budget the cache would be with the incoming entry —
|
|
1617
|
+
* rounded up to {@link EVICTION_THRASH_FLOOR_PERCENT} of the budget so a run of stores
|
|
1618
|
+
* near the budget does not evict on every one. It deliberately does not free the
|
|
1619
|
+
* incoming entry's whole size: for a large entry arriving into a mostly empty cache the
|
|
1620
|
+
* deficit is small, and freeing the entry's full size instead emptied the cache to make
|
|
1621
|
+
* room that was already there. That over-eviction is what made a large entry look
|
|
1622
|
+
* inherently destructive, and it is why the per-entry ceiling used to be set low enough
|
|
1623
|
+
* to exclude every large entry.
|
|
1177
1624
|
*/
|
|
1178
1625
|
private evictIfNeeded;
|
|
1179
1626
|
/**
|
|
@@ -1211,5 +1658,7 @@ export declare class LocalCacheManager extends BaseSingleton<LocalCacheManager>
|
|
|
1211
1658
|
* or entries for entities that are over their per-entity cap.
|
|
1212
1659
|
*/
|
|
1213
1660
|
private runEvictionSweep;
|
|
1661
|
+
/** Drops one entry from the registry and the fingerprint index, leaving the store alone. @internal */
|
|
1662
|
+
private forgetRegistryEntry;
|
|
1214
1663
|
}
|
|
1215
1664
|
//# sourceMappingURL=localCacheManager.d.ts.map
|