@equationalapplications/expo-llm-wiki 2.1.0 → 2.3.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 CHANGED
@@ -19,36 +19,46 @@ Offline-first, SQLite-backed memory for LLM apps built with Expo. Handles FTS5 s
19
19
  ## How It Works
20
20
 
21
21
  ```mermaid
22
- flowchart LR
23
- subgraph API
22
+ flowchart TB
23
+ subgraph API["API Layer"]
24
24
  direction TB
25
25
  write["write(event)"]
26
26
  ingest["ingestDocument()"]
27
27
  librarian["runLibrarian()"]
28
28
  heal["runHeal()"]
29
+ read["read(entityId, query)"]
29
30
  end
30
31
 
31
- subgraph SQLite
32
+ subgraph LLMLayer["LLM Provider"]
33
+ LLM["LLMProvider.generateText()"]
34
+ end
35
+
36
+ subgraph SQLiteLayer["SQLite Database"]
32
37
  direction TB
33
38
  events[(events)]
34
- entries[("entries\n(facts)")]
39
+ entries[("entries<br/>(facts)")]
35
40
  tasks[(tasks)]
36
41
  end
37
42
 
38
- LLM["LLMProvider\n.generateText()"]
39
-
40
- read["read(entityId, query)"]
41
- FTS5(["FTS5 search"])
42
- Bundle(["MemoryBundle\nfacts · tasks · events"])
43
+ subgraph ReadPath["Read Path"]
44
+ FTS5(["FTS5 search"])
45
+ Bundle(["MemoryBundle<br/>facts · tasks · events"])
46
+ end
43
47
 
48
+ %% Write paths
44
49
  write --> events
45
50
  events -. "≥ threshold" .-> librarian
51
+
52
+ %% LLM calls
46
53
  librarian --> LLM
47
54
  heal --> LLM
48
55
  ingest --> LLM
56
+
57
+ %% Database writes
49
58
  LLM --> entries
50
59
  LLM --> tasks
51
-
60
+
61
+ %% Read path
52
62
  read --> FTS5
53
63
  FTS5 --> entries
54
64
  entries --> Bundle
@@ -56,6 +66,7 @@ flowchart LR
56
66
  events --> Bundle
57
67
  ```
58
68
 
69
+
59
70
  ## Installation
60
71
 
61
72
  In your Expo project:
@@ -97,6 +108,8 @@ const wiki = createWiki(db, {
97
108
  maxChunkLength: 6000, // optional, default: 6000 (char count, not bytes)
98
109
  chunkOverlap: 400, // optional, default: 400 (overlap between chunks in characters)
99
110
  chunkConcurrency: 1, // optional, default: 1 (parallel LLM calls per ingestDocument)
111
+ pruneRetainSoftDeletedFor: 7, // optional, default: 7 (days before hard-deleting soft-deleted rows)
112
+ pruneEventsAfter: 30, // optional, default: 30 (days before hard-deleting old events)
100
113
  },
101
114
  });
102
115
 
@@ -162,6 +175,34 @@ await wiki.runLibrarian('entity-123');
162
175
  await wiki.runHeal('entity-123');
163
176
  ```
164
177
 
178
+ ### Format Context
179
+
180
+ Convert a `MemoryBundle` into a string ready for LLM prompt injection:
181
+
182
+ ```typescript
183
+ import { formatContext } from 'expo-llm-wiki';
184
+
185
+ const bundle = await wiki.read('entity-123', 'weekend plans');
186
+ const context = formatContext(bundle, {
187
+ format: 'markdown', // 'markdown' (default) | 'plain'
188
+ maxFacts: 10, // default 10
189
+ maxTasks: 10, // default 10
190
+ maxEvents: 10, // default 10
191
+ includeConfidence: true, // default true — appends (certain/inferred/tentative)
192
+ includeTags: true, // default true — appends [tag1, tag2]
193
+ factWeights: {
194
+ confidence: 1.0, // default 1.0
195
+ accessCount: 0.3, // default 0.3 — log(1 + access_count) * weight
196
+ recency: 0.5, // default 0.5 — decays over 30d
197
+ },
198
+ });
199
+
200
+ // Inject into your system prompt:
201
+ const systemPrompt = `You are a helpful assistant.\n\n${context}`;
202
+ ```
203
+
204
+ Facts are ranked by a weighted score combining confidence tier, access frequency, and recency. Returns an empty string for an empty bundle.
205
+
165
206
  ### Forget
166
207
 
167
208
  ```typescript
@@ -176,6 +217,38 @@ await wiki.forget('entity-123', { clearAll: true }); // wipe entity
176
217
 
177
218
  Throws `Error` if `sourceRef` or `sourceHash` is provided but invalid. Soft-deletes are idempotent — calling again with the same parameters returns `{ deleted: { entries: 0; tasks: 0 } }`.
178
219
 
220
+ ### Check for Changes
221
+
222
+ Skip re-ingest if a document's content hasn't changed since the last ingest:
223
+
224
+ ```typescript
225
+ const changed = await wiki.hasChanged('entity-123', 'preferences.md', sha256(content));
226
+ if (changed) {
227
+ await wiki.ingestDocument('entity-123', { sourceRef: 'preferences.md', sourceHash: sha256(content), documentChunk: content });
228
+ }
229
+ ```
230
+
231
+ Returns `true` if the document has never been ingested, all prior ingest results were forgotten, or the stored hash differs from the supplied one. Returns `false` if the stored hash matches exactly.
232
+
233
+ Throws `Error` if `sourceRef` or `sourceHash` is invalid (same rules as `ingestDocument`).
234
+
235
+ ### Prune (Hard Delete)
236
+
237
+ Hard-delete aged soft-deleted entries/tasks and old events to reclaim storage:
238
+
239
+ ```typescript
240
+ const result = await wiki.runPrune('entity-123', {
241
+ retainSoftDeletedFor: 7, // days — hard-delete entries/tasks soft-deleted > 7d ago; null to skip
242
+ retainEventsFor: 30, // days since created_at — hard-delete old events; null to skip
243
+ vacuum: false, // set true to VACUUM (slow on mobile, rewrites entire DB)
244
+ });
245
+ // result: { entries: number; tasks: number; events: number }
246
+ ```
247
+
248
+ Defaults: `retainSoftDeletedFor = config.pruneRetainSoftDeletedFor ?? 7`, `retainEventsFor = config.pruneEventsAfter ?? 30`, `vacuum = false`.
249
+
250
+ Throws `WikiBusyError` if librarian, heal, ingest, or another prune is in-flight for the same entity. `ingestDocument`, `runLibrarian`, and `runHeal` reciprocally throw `WikiBusyError` if a prune is in-flight.
251
+
179
252
  ---
180
253
 
181
254
  ## React / Expo Component API
@@ -223,10 +296,10 @@ await execute('entity-123', {
223
296
 
224
297
  ### `useWikiMaintenance()`
225
298
 
226
- Shared `isPending` — true if either operation is in-flight:
299
+ Shared `isPending` — true if any operation is in-flight. See [extended form below](#usewikimaintenance-extended) for `runPrune`:
227
300
 
228
301
  ```typescript
229
- const { runLibrarian, runHeal, isPending, error } = useWikiMaintenance();
302
+ const { runLibrarian, runHeal, runPrune, isPending, error } = useWikiMaintenance();
230
303
 
231
304
  await runLibrarian('entity-123');
232
305
  await runHeal('entity-123');
@@ -257,6 +330,43 @@ const result = await execute('entity-123', { entryId: 'fact_abc' });
257
330
  // result.deleted.entries — rows soft-deleted
258
331
  ```
259
332
 
333
+ ### `useWikiHasChanged()`
334
+
335
+ ```typescript
336
+ const { execute, lastResult, isPending, error } = useWikiHasChanged();
337
+ // lastResult: boolean | null
338
+
339
+ const changed = await execute('entity-123', 'preferences.md', sha256(content));
340
+ ```
341
+
342
+ ### `useWikiMaintenance()` (extended)
343
+
344
+ `runPrune` is now available alongside `runLibrarian` and `runHeal`. Shared `isPending` is true if any operation is in-flight. `lastResult` is a discriminated union — check `.operation` to narrow the type:
345
+
346
+ ```typescript
347
+ const { runLibrarian, runHeal, runPrune, lastResult, isPending, error } = useWikiMaintenance();
348
+
349
+ await runLibrarian('entity-123');
350
+ // lastResult: { operation: 'librarian', result: void }
351
+
352
+ await runHeal('entity-123');
353
+ // lastResult: { operation: 'heal', result: void }
354
+
355
+ const counts = await runPrune('entity-123', { retainSoftDeletedFor: 7, retainEventsFor: 30 });
356
+ // counts: { entries: number; tasks: number; events: number }
357
+ // lastResult: { operation: 'prune', result: { entries: number; tasks: number; events: number } }
358
+
359
+ if (lastResult?.operation === 'prune') {
360
+ console.log(lastResult.result.entries); // type-safe access to prune counts
361
+ }
362
+ ```
363
+
364
+ The exported `MaintenanceResult` type can be imported for typed consumers:
365
+
366
+ ```typescript
367
+ import type { MaintenanceResult } from 'expo-llm-wiki/react';
368
+ ```
369
+
260
370
  All mutation hooks follow the same pattern (`TResult` is specific per hook):
261
371
 
262
372
  ```typescript
@@ -4,6 +4,7 @@ interface WikiConfig {
4
4
  tablePrefix?: string;
5
5
  maxFtsResults?: number;
6
6
  pruneEventsAfter?: number;
7
+ pruneRetainSoftDeletedFor?: number;
7
8
  autoLibrarianThreshold?: number;
8
9
  autoHealThreshold?: number;
9
10
  orphanAfterDays?: number | null;
@@ -101,15 +102,28 @@ interface FormattedMemoryDump {
101
102
  content: string;
102
103
  }>;
103
104
  }
105
+ interface FormatContextOptions {
106
+ format?: 'markdown' | 'plain';
107
+ maxFacts?: number;
108
+ maxTasks?: number;
109
+ maxEvents?: number;
110
+ includeConfidence?: boolean;
111
+ includeTags?: boolean;
112
+ factWeights?: {
113
+ confidence?: number;
114
+ accessCount?: number;
115
+ recency?: number;
116
+ };
117
+ }
104
118
  interface EntityStatus {
105
119
  ingesting: boolean;
106
120
  librarian: boolean;
107
121
  heal: boolean;
108
122
  }
109
123
  declare class WikiBusyError extends Error {
110
- readonly operation: 'ingest' | 'librarian' | 'heal';
124
+ readonly operation: 'ingest' | 'librarian' | 'heal' | 'prune';
111
125
  readonly entityId: string;
112
- constructor(operation: 'ingest' | 'librarian' | 'heal', entityId: string);
126
+ constructor(operation: 'ingest' | 'librarian' | 'heal' | 'prune', entityId: string);
113
127
  }
114
128
 
115
129
  declare class WikiMemory {
@@ -123,6 +137,18 @@ declare class WikiMemory {
123
137
  private _warnCrossEntityCollision;
124
138
  constructor(db: SQLite.SQLiteDatabase, options: WikiOptions);
125
139
  setup(): Promise<void>;
140
+ hasChanged(entityId: string, sourceRef: string, sourceHash: string): Promise<boolean>;
141
+ private _pruneKey;
142
+ private _validatePruneDuration;
143
+ runPrune(entityId: string, options?: {
144
+ retainSoftDeletedFor?: number | null;
145
+ retainEventsFor?: number | null;
146
+ vacuum?: boolean;
147
+ }): Promise<{
148
+ entries: number;
149
+ tasks: number;
150
+ events: number;
151
+ }>;
126
152
  private formatSearchQuery;
127
153
  read(entityId: string, query: string): Promise<MemoryBundle>;
128
154
  getMemoryBundle(entityId: string): Promise<MemoryBundle>;
@@ -163,4 +189,4 @@ declare class WikiMemory {
163
189
  }>;
164
190
  }
165
191
 
166
- export { type EntityStatus as E, type FormattedMemoryDump as F, type LLMProvider as L, type MemoryDump as M, type WikiOptions as W, WikiMemory as a, type ExtractedFact as b, type ExtractedTask as c, type MemoryBundle as d, WikiBusyError as e, type WikiCheckpoint as f, type WikiConfig as g, type WikiEvent as h, type WikiFact as i, type WikiTask as j };
192
+ export { type EntityStatus as E, type FormattedMemoryDump as F, type LLMProvider as L, type MemoryDump as M, type WikiOptions as W, type MemoryBundle as a, type FormatContextOptions as b, WikiMemory as c, type ExtractedFact as d, type ExtractedTask as e, WikiBusyError as f, type WikiCheckpoint as g, type WikiConfig as h, type WikiEvent as i, type WikiFact as j, type WikiTask as k };
@@ -4,6 +4,7 @@ interface WikiConfig {
4
4
  tablePrefix?: string;
5
5
  maxFtsResults?: number;
6
6
  pruneEventsAfter?: number;
7
+ pruneRetainSoftDeletedFor?: number;
7
8
  autoLibrarianThreshold?: number;
8
9
  autoHealThreshold?: number;
9
10
  orphanAfterDays?: number | null;
@@ -101,15 +102,28 @@ interface FormattedMemoryDump {
101
102
  content: string;
102
103
  }>;
103
104
  }
105
+ interface FormatContextOptions {
106
+ format?: 'markdown' | 'plain';
107
+ maxFacts?: number;
108
+ maxTasks?: number;
109
+ maxEvents?: number;
110
+ includeConfidence?: boolean;
111
+ includeTags?: boolean;
112
+ factWeights?: {
113
+ confidence?: number;
114
+ accessCount?: number;
115
+ recency?: number;
116
+ };
117
+ }
104
118
  interface EntityStatus {
105
119
  ingesting: boolean;
106
120
  librarian: boolean;
107
121
  heal: boolean;
108
122
  }
109
123
  declare class WikiBusyError extends Error {
110
- readonly operation: 'ingest' | 'librarian' | 'heal';
124
+ readonly operation: 'ingest' | 'librarian' | 'heal' | 'prune';
111
125
  readonly entityId: string;
112
- constructor(operation: 'ingest' | 'librarian' | 'heal', entityId: string);
126
+ constructor(operation: 'ingest' | 'librarian' | 'heal' | 'prune', entityId: string);
113
127
  }
114
128
 
115
129
  declare class WikiMemory {
@@ -123,6 +137,18 @@ declare class WikiMemory {
123
137
  private _warnCrossEntityCollision;
124
138
  constructor(db: SQLite.SQLiteDatabase, options: WikiOptions);
125
139
  setup(): Promise<void>;
140
+ hasChanged(entityId: string, sourceRef: string, sourceHash: string): Promise<boolean>;
141
+ private _pruneKey;
142
+ private _validatePruneDuration;
143
+ runPrune(entityId: string, options?: {
144
+ retainSoftDeletedFor?: number | null;
145
+ retainEventsFor?: number | null;
146
+ vacuum?: boolean;
147
+ }): Promise<{
148
+ entries: number;
149
+ tasks: number;
150
+ events: number;
151
+ }>;
126
152
  private formatSearchQuery;
127
153
  read(entityId: string, query: string): Promise<MemoryBundle>;
128
154
  getMemoryBundle(entityId: string): Promise<MemoryBundle>;
@@ -163,4 +189,4 @@ declare class WikiMemory {
163
189
  }>;
164
190
  }
165
191
 
166
- export { type EntityStatus as E, type FormattedMemoryDump as F, type LLMProvider as L, type MemoryDump as M, type WikiOptions as W, WikiMemory as a, type ExtractedFact as b, type ExtractedTask as c, type MemoryBundle as d, WikiBusyError as e, type WikiCheckpoint as f, type WikiConfig as g, type WikiEvent as h, type WikiFact as i, type WikiTask as j };
192
+ export { type EntityStatus as E, type FormattedMemoryDump as F, type LLMProvider as L, type MemoryDump as M, type WikiOptions as W, type MemoryBundle as a, type FormatContextOptions as b, WikiMemory as c, type ExtractedFact as d, type ExtractedTask as e, WikiBusyError as f, type WikiCheckpoint as g, type WikiConfig as h, type WikiEvent as i, type WikiFact as j, type WikiTask as k };
package/dist/index.d.mts CHANGED
@@ -1,9 +1,11 @@
1
1
  import * as SQLite from 'expo-sqlite';
2
- import { M as MemoryDump, F as FormattedMemoryDump, W as WikiOptions, a as WikiMemory } from './WikiMemory-BDVn-TJf.mjs';
3
- export { E as EntityStatus, b as ExtractedFact, c as ExtractedTask, L as LLMProvider, d as MemoryBundle, e as WikiBusyError, f as WikiCheckpoint, g as WikiConfig, h as WikiEvent, i as WikiFact, j as WikiTask } from './WikiMemory-BDVn-TJf.mjs';
2
+ import { M as MemoryDump, F as FormattedMemoryDump, a as MemoryBundle, b as FormatContextOptions, W as WikiOptions, c as WikiMemory } from './WikiMemory-ChQmVyvA.mjs';
3
+ export { E as EntityStatus, d as ExtractedFact, e as ExtractedTask, L as LLMProvider, f as WikiBusyError, g as WikiCheckpoint, h as WikiConfig, i as WikiEvent, j as WikiFact, k as WikiTask } from './WikiMemory-ChQmVyvA.mjs';
4
4
 
5
5
  declare function formatMemoryDump(dump: MemoryDump): FormattedMemoryDump;
6
6
 
7
+ declare function formatContext(bundle: MemoryBundle, options?: FormatContextOptions): string;
8
+
7
9
  declare function createWiki(db: SQLite.SQLiteDatabase, options: WikiOptions): WikiMemory;
8
10
 
9
- export { FormattedMemoryDump, MemoryDump, WikiMemory, WikiOptions, createWiki, formatMemoryDump };
11
+ export { FormatContextOptions, FormattedMemoryDump, MemoryBundle, MemoryDump, WikiMemory, WikiOptions, createWiki, formatContext, formatMemoryDump };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import * as SQLite from 'expo-sqlite';
2
- import { M as MemoryDump, F as FormattedMemoryDump, W as WikiOptions, a as WikiMemory } from './WikiMemory-BDVn-TJf.js';
3
- export { E as EntityStatus, b as ExtractedFact, c as ExtractedTask, L as LLMProvider, d as MemoryBundle, e as WikiBusyError, f as WikiCheckpoint, g as WikiConfig, h as WikiEvent, i as WikiFact, j as WikiTask } from './WikiMemory-BDVn-TJf.js';
2
+ import { M as MemoryDump, F as FormattedMemoryDump, a as MemoryBundle, b as FormatContextOptions, W as WikiOptions, c as WikiMemory } from './WikiMemory-ChQmVyvA.js';
3
+ export { E as EntityStatus, d as ExtractedFact, e as ExtractedTask, L as LLMProvider, f as WikiBusyError, g as WikiCheckpoint, h as WikiConfig, i as WikiEvent, j as WikiFact, k as WikiTask } from './WikiMemory-ChQmVyvA.js';
4
4
 
5
5
  declare function formatMemoryDump(dump: MemoryDump): FormattedMemoryDump;
6
6
 
7
+ declare function formatContext(bundle: MemoryBundle, options?: FormatContextOptions): string;
8
+
7
9
  declare function createWiki(db: SQLite.SQLiteDatabase, options: WikiOptions): WikiMemory;
8
10
 
9
- export { FormattedMemoryDump, MemoryDump, WikiMemory, WikiOptions, createWiki, formatMemoryDump };
11
+ export { FormatContextOptions, FormattedMemoryDump, MemoryBundle, MemoryDump, WikiMemory, WikiOptions, createWiki, formatContext, formatMemoryDump };