@equationalapplications/core-llm-wiki 4.0.0 → 4.1.0
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/README.md +35 -0
- package/dist/index.d.mts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +66 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +66 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -302,6 +302,41 @@ wikiMemory.clearVectorCache();
|
|
|
302
302
|
|
|
303
303
|
The cache is also automatically invalidated on any mutation (`runLibrarian`, `runHeal`, `runPrune`, `runReembed`, `ingestDocument`, `importDump`, `forget`).
|
|
304
304
|
|
|
305
|
+
## Entity Status
|
|
306
|
+
|
|
307
|
+
`WikiMemory` exposes the in-flight job state for a single entity through two complementary APIs.
|
|
308
|
+
|
|
309
|
+
### `getEntityStatus(entityId)`
|
|
310
|
+
|
|
311
|
+
Synchronous point-in-time snapshot:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const status = wikiMemory.getEntityStatus('user-42');
|
|
315
|
+
// { ingesting: boolean, librarian: boolean, heal: boolean }
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
Use this when you only need the current value (e.g. inside a request handler).
|
|
319
|
+
|
|
320
|
+
### `subscribeEntityStatus(entityId, callback)`
|
|
321
|
+
|
|
322
|
+
Push-based change notification — the callback fires synchronously once with the current status, then again on every transition where any of the three booleans flips. There is no polling and no duplicate snapshots.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const unsubscribe = wikiMemory.subscribeEntityStatus('user-42', (status) => {
|
|
326
|
+
console.log(status); // { ingesting, librarian, heal }
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Later:
|
|
330
|
+
unsubscribe(); // idempotent — safe to call more than once
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Notes:
|
|
334
|
+
|
|
335
|
+
- The first invocation happens **before** `subscribeEntityStatus` returns. Treat it as the initial render value.
|
|
336
|
+
- Each emission may be a fresh object literal. Do not rely on referential equality between callbacks; equality of the three booleans is the contract.
|
|
337
|
+
- A throwing callback is caught (logged via `console.error`) and does not block other subscribers or the underlying job.
|
|
338
|
+
- Subscriptions are scoped to a single `entityId`. There is no wildcard or "all entities" form.
|
|
339
|
+
|
|
305
340
|
## Security
|
|
306
341
|
|
|
307
342
|
`@equationalapplications/core-llm-wiki` enforces multiple security layers:
|
package/dist/index.d.mts
CHANGED
|
@@ -337,6 +337,7 @@ declare class WikiMemory {
|
|
|
337
337
|
private options;
|
|
338
338
|
private activeMaintenanceJobs;
|
|
339
339
|
private activeIngestJobs;
|
|
340
|
+
private statusSubscribers;
|
|
340
341
|
private miniSearch;
|
|
341
342
|
private miniSearchEntryIdsByEntity;
|
|
342
343
|
/**
|
|
@@ -392,6 +393,8 @@ declare class WikiMemory {
|
|
|
392
393
|
private _isAnyMaintenanceActiveWithSuffix;
|
|
393
394
|
/** Returns true if any ingest job is active for the given entity. */
|
|
394
395
|
private _isIngestActiveFor;
|
|
396
|
+
private _copyEntityStatus;
|
|
397
|
+
private _notifyStatusSubscribers;
|
|
395
398
|
private _validatePruneDuration;
|
|
396
399
|
runPrune(entityId: string, options?: {
|
|
397
400
|
retainSoftDeletedFor?: number | null;
|
|
@@ -445,6 +448,17 @@ declare class WikiMemory {
|
|
|
445
448
|
failed: number;
|
|
446
449
|
}>;
|
|
447
450
|
getEntityStatus(entityId: string): EntityStatus;
|
|
451
|
+
/**
|
|
452
|
+
* Subscribe to {@link EntityStatus} changes for a single entity. The callback
|
|
453
|
+
* is invoked synchronously once with the current status before this method
|
|
454
|
+
* returns, then again on every transition where any of `ingesting`,
|
|
455
|
+
* `librarian`, or `heal` flips. No polling, no duplicate snapshots.
|
|
456
|
+
*
|
|
457
|
+
* Returns an idempotent unsubscribe function.
|
|
458
|
+
*
|
|
459
|
+
* See also {@link getEntityStatus} for a synchronous point-in-time read.
|
|
460
|
+
*/
|
|
461
|
+
subscribeEntityStatus(entityId: string, callback: (status: EntityStatus) => void): () => void;
|
|
448
462
|
clearVectorCache(): void;
|
|
449
463
|
private _getFullBundle;
|
|
450
464
|
exportDump(entityIds?: string[]): Promise<MemoryDump>;
|
package/dist/index.d.ts
CHANGED
|
@@ -337,6 +337,7 @@ declare class WikiMemory {
|
|
|
337
337
|
private options;
|
|
338
338
|
private activeMaintenanceJobs;
|
|
339
339
|
private activeIngestJobs;
|
|
340
|
+
private statusSubscribers;
|
|
340
341
|
private miniSearch;
|
|
341
342
|
private miniSearchEntryIdsByEntity;
|
|
342
343
|
/**
|
|
@@ -392,6 +393,8 @@ declare class WikiMemory {
|
|
|
392
393
|
private _isAnyMaintenanceActiveWithSuffix;
|
|
393
394
|
/** Returns true if any ingest job is active for the given entity. */
|
|
394
395
|
private _isIngestActiveFor;
|
|
396
|
+
private _copyEntityStatus;
|
|
397
|
+
private _notifyStatusSubscribers;
|
|
395
398
|
private _validatePruneDuration;
|
|
396
399
|
runPrune(entityId: string, options?: {
|
|
397
400
|
retainSoftDeletedFor?: number | null;
|
|
@@ -445,6 +448,17 @@ declare class WikiMemory {
|
|
|
445
448
|
failed: number;
|
|
446
449
|
}>;
|
|
447
450
|
getEntityStatus(entityId: string): EntityStatus;
|
|
451
|
+
/**
|
|
452
|
+
* Subscribe to {@link EntityStatus} changes for a single entity. The callback
|
|
453
|
+
* is invoked synchronously once with the current status before this method
|
|
454
|
+
* returns, then again on every transition where any of `ingesting`,
|
|
455
|
+
* `librarian`, or `heal` flips. No polling, no duplicate snapshots.
|
|
456
|
+
*
|
|
457
|
+
* Returns an idempotent unsubscribe function.
|
|
458
|
+
*
|
|
459
|
+
* See also {@link getEntityStatus} for a synchronous point-in-time read.
|
|
460
|
+
*/
|
|
461
|
+
subscribeEntityStatus(entityId: string, callback: (status: EntityStatus) => void): () => void;
|
|
448
462
|
clearVectorCache(): void;
|
|
449
463
|
private _getFullBundle;
|
|
450
464
|
exportDump(entityIds?: string[]): Promise<MemoryDump>;
|
package/dist/index.js
CHANGED
|
@@ -418,6 +418,7 @@ var _WikiMemory = class _WikiMemory {
|
|
|
418
418
|
constructor(db, options) {
|
|
419
419
|
this.activeMaintenanceJobs = /* @__PURE__ */ new Set();
|
|
420
420
|
this.activeIngestJobs = /* @__PURE__ */ new Set();
|
|
421
|
+
this.statusSubscribers = /* @__PURE__ */ new Map();
|
|
421
422
|
this.miniSearch = new MiniSearch__default.default({
|
|
422
423
|
fields: ["title", "body", "tags"],
|
|
423
424
|
storeFields: ["entity_id"],
|
|
@@ -822,6 +823,24 @@ After running the migration SQL, restart your application.`
|
|
|
822
823
|
}
|
|
823
824
|
return false;
|
|
824
825
|
}
|
|
826
|
+
_copyEntityStatus(s) {
|
|
827
|
+
return { ingesting: s.ingesting, librarian: s.librarian, heal: s.heal };
|
|
828
|
+
}
|
|
829
|
+
_notifyStatusSubscribers(entityId) {
|
|
830
|
+
const set = this.statusSubscribers.get(entityId);
|
|
831
|
+
if (!set || set.size === 0) return;
|
|
832
|
+
for (const entry of Array.from(set)) {
|
|
833
|
+
if (!set.has(entry)) continue;
|
|
834
|
+
const next = this.getEntityStatus(entityId);
|
|
835
|
+
if (entry.last.ingesting === next.ingesting && entry.last.librarian === next.librarian && entry.last.heal === next.heal) continue;
|
|
836
|
+
entry.last = this._copyEntityStatus(next);
|
|
837
|
+
try {
|
|
838
|
+
entry.callback(this._copyEntityStatus(next));
|
|
839
|
+
} catch (err) {
|
|
840
|
+
console.error(`[WikiMemory.subscribeEntityStatus] callback error for entityId="${entityId}" during transition emission`, err);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
825
844
|
_validatePruneDuration(value, name) {
|
|
826
845
|
if (value !== null && value !== void 0 && (typeof value !== "number" || !isFinite(value) || value < 0)) {
|
|
827
846
|
throw new Error(`Invalid ${name}: must be a non-negative finite number or null`);
|
|
@@ -1564,7 +1583,11 @@ After running the migration SQL, restart your application.`
|
|
|
1564
1583
|
const jobKey = this._librarianKey(entityId);
|
|
1565
1584
|
if (!this.activeMaintenanceJobs.has(jobKey) && !this.activeMaintenanceJobs.has(this._pruneKey(entityId)) && !this._isReembedActive(entityId) && !this._isImportActiveFor(entityId) && !this._isForgetActiveFor(entityId)) {
|
|
1566
1585
|
this.activeMaintenanceJobs.add(jobKey);
|
|
1567
|
-
this.
|
|
1586
|
+
this._notifyStatusSubscribers(entityId);
|
|
1587
|
+
this.runLibrarianThenMaybeHeal(entityId, count).catch(console.error).finally(() => {
|
|
1588
|
+
this.activeMaintenanceJobs.delete(jobKey);
|
|
1589
|
+
this._notifyStatusSubscribers(entityId);
|
|
1590
|
+
});
|
|
1568
1591
|
}
|
|
1569
1592
|
}
|
|
1570
1593
|
}
|
|
@@ -1583,6 +1606,7 @@ After running the migration SQL, restart your application.`
|
|
|
1583
1606
|
const healKey = this._healKey(entityId);
|
|
1584
1607
|
if (!this.activeMaintenanceJobs.has(healKey)) {
|
|
1585
1608
|
this.activeMaintenanceJobs.add(healKey);
|
|
1609
|
+
this._notifyStatusSubscribers(entityId);
|
|
1586
1610
|
try {
|
|
1587
1611
|
await this._doRunHeal(entityId);
|
|
1588
1612
|
await this.db.runAsync(`
|
|
@@ -1592,6 +1616,7 @@ After running the migration SQL, restart your application.`
|
|
|
1592
1616
|
`, [entityId, currentEventCount, currentEventCount]);
|
|
1593
1617
|
} finally {
|
|
1594
1618
|
this.activeMaintenanceJobs.delete(healKey);
|
|
1619
|
+
this._notifyStatusSubscribers(entityId);
|
|
1595
1620
|
}
|
|
1596
1621
|
}
|
|
1597
1622
|
}
|
|
@@ -1783,10 +1808,12 @@ The following document anchors are provided for contradiction detection only. Do
|
|
|
1783
1808
|
throw new WikiBusyError("forget", entityId);
|
|
1784
1809
|
}
|
|
1785
1810
|
this.activeMaintenanceJobs.add(jobKey);
|
|
1811
|
+
this._notifyStatusSubscribers(entityId);
|
|
1786
1812
|
try {
|
|
1787
1813
|
await this._doRunLibrarian(entityId);
|
|
1788
1814
|
} finally {
|
|
1789
1815
|
this.activeMaintenanceJobs.delete(jobKey);
|
|
1816
|
+
this._notifyStatusSubscribers(entityId);
|
|
1790
1817
|
}
|
|
1791
1818
|
}
|
|
1792
1819
|
async runHeal(entityId) {
|
|
@@ -1807,10 +1834,12 @@ The following document anchors are provided for contradiction detection only. Do
|
|
|
1807
1834
|
throw new WikiBusyError("forget", entityId);
|
|
1808
1835
|
}
|
|
1809
1836
|
this.activeMaintenanceJobs.add(jobKey);
|
|
1837
|
+
this._notifyStatusSubscribers(entityId);
|
|
1810
1838
|
try {
|
|
1811
1839
|
await this._doRunHeal(entityId);
|
|
1812
1840
|
} finally {
|
|
1813
1841
|
this.activeMaintenanceJobs.delete(jobKey);
|
|
1842
|
+
this._notifyStatusSubscribers(entityId);
|
|
1814
1843
|
}
|
|
1815
1844
|
}
|
|
1816
1845
|
async runReembed(entityId, opts) {
|
|
@@ -1950,6 +1979,40 @@ The following document anchors are provided for contradiction detection only. Do
|
|
|
1950
1979
|
heal: this.activeMaintenanceJobs.has(this._healKey(entityId))
|
|
1951
1980
|
};
|
|
1952
1981
|
}
|
|
1982
|
+
/**
|
|
1983
|
+
* Subscribe to {@link EntityStatus} changes for a single entity. The callback
|
|
1984
|
+
* is invoked synchronously once with the current status before this method
|
|
1985
|
+
* returns, then again on every transition where any of `ingesting`,
|
|
1986
|
+
* `librarian`, or `heal` flips. No polling, no duplicate snapshots.
|
|
1987
|
+
*
|
|
1988
|
+
* Returns an idempotent unsubscribe function.
|
|
1989
|
+
*
|
|
1990
|
+
* See also {@link getEntityStatus} for a synchronous point-in-time read.
|
|
1991
|
+
*/
|
|
1992
|
+
subscribeEntityStatus(entityId, callback) {
|
|
1993
|
+
const initial = this.getEntityStatus(entityId);
|
|
1994
|
+
let set = this.statusSubscribers.get(entityId);
|
|
1995
|
+
if (!set) {
|
|
1996
|
+
set = /* @__PURE__ */ new Set();
|
|
1997
|
+
this.statusSubscribers.set(entityId, set);
|
|
1998
|
+
}
|
|
1999
|
+
const entry = { callback, last: this._copyEntityStatus(initial) };
|
|
2000
|
+
set.add(entry);
|
|
2001
|
+
try {
|
|
2002
|
+
callback(this._copyEntityStatus(initial));
|
|
2003
|
+
} catch (err) {
|
|
2004
|
+
console.error(`[WikiMemory.subscribeEntityStatus] callback error for entityId="${entityId}" during initial emission`, err);
|
|
2005
|
+
}
|
|
2006
|
+
let active = true;
|
|
2007
|
+
return () => {
|
|
2008
|
+
if (!active) return;
|
|
2009
|
+
active = false;
|
|
2010
|
+
const s = this.statusSubscribers.get(entityId);
|
|
2011
|
+
if (!s) return;
|
|
2012
|
+
s.delete(entry);
|
|
2013
|
+
if (s.size === 0) this.statusSubscribers.delete(entityId);
|
|
2014
|
+
};
|
|
2015
|
+
}
|
|
1953
2016
|
clearVectorCache() {
|
|
1954
2017
|
this.vectorCache.clear();
|
|
1955
2018
|
}
|
|
@@ -2475,6 +2538,7 @@ The following document anchors are provided for contradiction detection only. Do
|
|
|
2475
2538
|
throw new WikiBusyError("forget", entityId);
|
|
2476
2539
|
}
|
|
2477
2540
|
this.activeIngestJobs.add(jobKey);
|
|
2541
|
+
this._notifyStatusSubscribers(entityId);
|
|
2478
2542
|
try {
|
|
2479
2543
|
const { chunks, truncated } = chunkText(params.documentChunk, maxChunkLength, chunkOverlap);
|
|
2480
2544
|
if (chunks.length === 0) {
|
|
@@ -2546,6 +2610,7 @@ ${chunk}`;
|
|
|
2546
2610
|
return { truncated, chunks: chunks.length };
|
|
2547
2611
|
} finally {
|
|
2548
2612
|
this.activeIngestJobs.delete(jobKey);
|
|
2613
|
+
this._notifyStatusSubscribers(entityId);
|
|
2549
2614
|
}
|
|
2550
2615
|
}
|
|
2551
2616
|
};
|