@mastra/libsql 1.25.2-alpha.1 → 1.26.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core/dist/knowledge-compat/index.d.ts +594 -0
- package/dist/_types/@internal_core/package.json +1 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-message-history.md +15 -8
- package/dist/docs/references/reference-core-mastra-class.md +12 -4
- package/dist/docs/references/reference-storage-retention.md +21 -18
- package/dist/index.cjs +2048 -292
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2049 -293
- package/dist/index.js.map +1 -1
- package/dist/shared/file-transaction-gate.d.ts +28 -0
- package/dist/shared/file-transaction-gate.d.ts.map +1 -0
- package/dist/shared/reset-after-busy-client.d.ts +34 -0
- package/dist/shared/reset-after-busy-client.d.ts.map +1 -0
- package/dist/storage/db/index.d.ts +14 -6
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/db/run-fencing.d.ts +41 -0
- package/dist/storage/db/run-fencing.d.ts.map +1 -0
- package/dist/storage/db/utils.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/index.d.ts +30 -7
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +21 -7
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/workflows/index.d.ts +21 -6
- package/dist/storage/domains/workflows/index.d.ts.map +1 -1
- package/dist/storage/factory-storage.d.ts.map +1 -1
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/retention.d.ts +3 -3
- package/dist/storage/retention.d.ts.map +1 -1
- package/dist/vector/index.d.ts.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,15 +1,360 @@
|
|
|
1
1
|
import { createClient } from "@libsql/client";
|
|
2
2
|
import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
|
|
3
|
-
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FactoryStorage, FavoritesStorage, HarnessStorage,
|
|
3
|
+
import { AGENTS_SCHEMA, AGENT_VERSIONS_SCHEMA, AgentsStorage, BackgroundTasksStorage, BlobStore, ChannelsStorage, DATASETS_SCHEMA, DATASET_ITEMS_SCHEMA, DATASET_VERSIONS_SCHEMA, DatasetsStorage, EXPERIMENTS_SCHEMA, EXPERIMENT_RESULTS_SCHEMA, ExperimentsStorage, FAVORITES_SCHEMA, FactoryStorage, FavoritesStorage, HarnessStorage, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA, KnowledgeConflictError, KnowledgeNotFoundError, KnowledgeStorage, MCPClientsStorage, MCPServersStorage, MCP_CLIENTS_SCHEMA, MCP_CLIENT_VERSIONS_SCHEMA, MCP_SERVERS_SCHEMA, MCP_SERVER_VERSIONS_SCHEMA, MastraCompositeStore, MemoryStorage, NotificationsStorage, OBSERVATIONAL_MEMORY_TABLE_SCHEMA, ObservabilityStorage, PROMPT_BLOCKS_SCHEMA, PROMPT_BLOCK_VERSIONS_SCHEMA, PromptBlocksStorage, RUN_FENCING_TABLE_SCHEMAS, RunFenceConflictError, SCORERS_SCHEMA, SCORER_DEFINITIONS_SCHEMA, SCORER_DEFINITION_VERSIONS_SCHEMA, SKILLS_SCHEMA, SKILL_BLOBS_SCHEMA, SKILL_VERSIONS_SCHEMA, SPAN_SCHEMA, SchedulesStorage, ScorerDefinitionsStorage, ScoresStorage, SkillsStorage, TABLE_AGENTS, TABLE_AGENT_VERSIONS, TABLE_BACKGROUND_TASKS, TABLE_CHANNEL_CONFIG, TABLE_CHANNEL_INSTALLATIONS, TABLE_DATASETS, TABLE_DATASET_ITEMS, TABLE_DATASET_VERSIONS, TABLE_EXPERIMENTS, TABLE_EXPERIMENT_RESULTS, TABLE_FAVORITES, TABLE_HARNESS_SESSIONS, TABLE_KNOWLEDGE_ACTIVITY, TABLE_KNOWLEDGE_MENTIONS, TABLE_KNOWLEDGE_NODES, TABLE_KNOWLEDGE_RECORDS, TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, TABLE_MCP_CLIENTS, TABLE_MCP_CLIENT_VERSIONS, TABLE_MCP_SERVERS, TABLE_MCP_SERVER_VERSIONS, TABLE_MEMORY_RUN_FENCES, TABLE_MESSAGES, TABLE_NOTIFICATIONS, TABLE_PROMPT_BLOCKS, TABLE_PROMPT_BLOCK_VERSIONS, TABLE_RESOURCES, TABLE_SCHEDULES, TABLE_SCHEDULE_TRIGGERS, TABLE_SCHEMAS, TABLE_SCORERS, TABLE_SCORER_DEFINITIONS, TABLE_SCORER_DEFINITION_VERSIONS, TABLE_SKILLS, TABLE_SKILL_BLOBS, TABLE_SKILL_VERSIONS, TABLE_SPANS, TABLE_THREADS, TABLE_THREAD_STATE, TABLE_TOOL_PROVIDER_CONNECTIONS, TABLE_WORKFLOW_DEFINITIONS, TABLE_WORKFLOW_RUN_OWNERS, TABLE_WORKFLOW_SNAPSHOT, TABLE_WORKSPACES, TABLE_WORKSPACE_VERSIONS, THREAD_STATE_SCHEMA, TOOL_PROVIDER_CONNECTIONS_SCHEMA, ThreadStateStorage, ToolProviderConnectionsStorage, TraceStatus, UniqueViolationError, WORKSPACES_SCHEMA, WORKSPACE_VERSIONS_SCHEMA, WorkflowDefinitionsStorage, WorkflowsStorage, WorkspacesStorage, calculatePagination, canonicalizeKnowledgeScope, createKnowledgeUlid, createStorageErrorId, createVectorErrorId, ensureDate, executeRetentionPrune, getSqlType, hasErrorCode, isKnowledgeScopeVisible, isRunFenceConflictError, knowledgeScopeKey, knowledgeSemanticDocumentId, knowledgeSemanticIdempotencyKey, listTracesArgsSchema, matchesExpectedWorkflowStatus, matchesRunFence, mergeWorkflowStepResult, normalizePerPage, normalizeScheduleTarget, parseKnowledgeNodeCursor, parseKnowledgeWikilinks, resolveListOrderBy, resolveRetentionTargets, resolveRunFence, retentionCutoffMs, runRetentionBatches, safelyParseJSON, toTraceSpans, transformScoreRow, validateStorageMetadataFilter } from "@mastra/core/storage";
|
|
4
4
|
import { parseFieldKey, parseSqlIdentifier } from "@mastra/core/utils";
|
|
5
5
|
import { MastraVector, validateTopK, validateUpsertInput } from "@mastra/core/vector";
|
|
6
|
+
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
7
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
6
8
|
import { BaseFilterTranslator } from "@mastra/core/vector/filter";
|
|
7
9
|
import { realpath } from "fs/promises";
|
|
8
|
-
import { basename, dirname, isAbsolute, join, resolve } from "path";
|
|
9
10
|
import { MastraBase } from "@mastra/core/base";
|
|
11
|
+
import { randomUUID } from "crypto";
|
|
12
|
+
import { coreFeatures } from "@mastra/core/features";
|
|
10
13
|
import { MessageList } from "@mastra/core/agent";
|
|
11
14
|
import { saveScorePayloadSchema } from "@mastra/core/evals";
|
|
12
15
|
import { skillSnapshotFieldValuesEqual } from "@mastra/core/storage/domains/skills";
|
|
16
|
+
//#region src/shared/file-transaction-gate.ts
|
|
17
|
+
/** Interactive transactions open in this process, per database file. */
|
|
18
|
+
const openTransactions = /* @__PURE__ */ new Map();
|
|
19
|
+
/** The database file a local `file:` url points at, or `undefined` for anything else. */
|
|
20
|
+
function localDatabaseFileKey(url) {
|
|
21
|
+
if (!url.startsWith("file:") || url.includes(":memory:")) return void 0;
|
|
22
|
+
const path = url.slice(5).split("?")[0];
|
|
23
|
+
return path ? resolve(path.replace(/^\/\/(?=\/)/, "")) : void 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a local `file:` client so its calls wait for interactive transactions
|
|
27
|
+
* that other clients in this process hold open on the same database file,
|
|
28
|
+
* instead of blocking the thread.
|
|
29
|
+
*
|
|
30
|
+
* `@libsql/client` runs local statements synchronously. When one client holds
|
|
31
|
+
* a write transaction open across an `await` and another client writes the same
|
|
32
|
+
* file, SQLite's busy handler spins on the event loop's only thread for
|
|
33
|
+
* `busy_timeout`, so the transaction holding the lock can't resume and the write
|
|
34
|
+
* fails with `SQLITE_BUSY`. Waiting lets the transaction finish first.
|
|
35
|
+
*
|
|
36
|
+
* Only calls on other clients wait; a client already orders its own writes
|
|
37
|
+
* behind its transactions (see `withClientWriteLock`). A call waits at most
|
|
38
|
+
* `maxWaitMs` (the busy timeout), then goes ahead as it would without the gate,
|
|
39
|
+
* so code that awaits another client from inside its own transaction can't hang.
|
|
40
|
+
* Clients in other processes are unaffected and still rely on `busy_timeout`.
|
|
41
|
+
*/
|
|
42
|
+
function gateLocalFileClient(client, key, { maxWaitMs = 5e3 } = {}) {
|
|
43
|
+
let gated;
|
|
44
|
+
const othersOpen = () => [...openTransactions.get(key) ?? []].filter((open) => open.owner !== gated);
|
|
45
|
+
const waitForOthers = () => {
|
|
46
|
+
if (othersOpen().length === 0) return;
|
|
47
|
+
return (async () => {
|
|
48
|
+
let timer;
|
|
49
|
+
const gaveUp = new Promise((r) => timer = setTimeout(r, maxWaitMs, "timeout"));
|
|
50
|
+
try {
|
|
51
|
+
for (let open = othersOpen(); open.length > 0; open = othersOpen()) if (await Promise.race([Promise.all(open.map((o) => o.done)), gaveUp]) === "timeout") return false;
|
|
52
|
+
return true;
|
|
53
|
+
} finally {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
}
|
|
56
|
+
})();
|
|
57
|
+
};
|
|
58
|
+
const call = (run) => {
|
|
59
|
+
const waiting = waitForOthers();
|
|
60
|
+
return waiting ? waiting.then(run) : run();
|
|
61
|
+
};
|
|
62
|
+
const transaction = async (open, mode) => {
|
|
63
|
+
const deadline = Date.now() + maxWaitMs;
|
|
64
|
+
for (let waiting = waitForOthers(); waiting; waiting = Date.now() < deadline ? waitForOthers() : void 0) if (!await waiting) break;
|
|
65
|
+
let finish;
|
|
66
|
+
const entry = {
|
|
67
|
+
owner: gated,
|
|
68
|
+
done: new Promise((r) => finish = r)
|
|
69
|
+
};
|
|
70
|
+
let set = openTransactions.get(key);
|
|
71
|
+
if (!set) openTransactions.set(key, set = /* @__PURE__ */ new Set());
|
|
72
|
+
set.add(entry);
|
|
73
|
+
const end = () => {
|
|
74
|
+
if (!set.delete(entry)) return;
|
|
75
|
+
if (set.size === 0 && openTransactions.get(key) === set) openTransactions.delete(key);
|
|
76
|
+
finish();
|
|
77
|
+
};
|
|
78
|
+
let tx;
|
|
79
|
+
try {
|
|
80
|
+
tx = await open(mode);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
end();
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
return new Proxy(tx, { get(target, prop) {
|
|
86
|
+
switch (prop) {
|
|
87
|
+
case "execute":
|
|
88
|
+
case "batch":
|
|
89
|
+
case "executeMultiple": {
|
|
90
|
+
const method = Reflect.get(target, prop);
|
|
91
|
+
return async (...args) => {
|
|
92
|
+
try {
|
|
93
|
+
return await method.apply(target, args);
|
|
94
|
+
} finally {
|
|
95
|
+
if (target.closed) end();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
case "commit":
|
|
100
|
+
case "rollback": {
|
|
101
|
+
const method = Reflect.get(target, prop);
|
|
102
|
+
return async () => {
|
|
103
|
+
try {
|
|
104
|
+
await method.call(target);
|
|
105
|
+
} finally {
|
|
106
|
+
end();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
case "close": {
|
|
111
|
+
const method = Reflect.get(target, prop);
|
|
112
|
+
return () => {
|
|
113
|
+
try {
|
|
114
|
+
method.call(target);
|
|
115
|
+
} finally {
|
|
116
|
+
end();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
default: {
|
|
121
|
+
const value = Reflect.get(target, prop);
|
|
122
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} });
|
|
126
|
+
};
|
|
127
|
+
gated = new Proxy(client, { get(target, prop) {
|
|
128
|
+
switch (prop) {
|
|
129
|
+
case "transaction": {
|
|
130
|
+
const open = Reflect.get(target, prop);
|
|
131
|
+
return (mode) => transaction(open.bind(target), mode);
|
|
132
|
+
}
|
|
133
|
+
case "execute":
|
|
134
|
+
case "batch":
|
|
135
|
+
case "executeMultiple":
|
|
136
|
+
case "migrate": {
|
|
137
|
+
const method = Reflect.get(target, prop);
|
|
138
|
+
return (...args) => call(() => method.apply(target, args));
|
|
139
|
+
}
|
|
140
|
+
default: {
|
|
141
|
+
const value = Reflect.get(target, prop);
|
|
142
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} });
|
|
146
|
+
return gated;
|
|
147
|
+
}
|
|
148
|
+
/** Applies {@link gateLocalFileClient} when `url` is a local database file; returns `client` otherwise. */
|
|
149
|
+
function gateIfLocalFile(client, url, options) {
|
|
150
|
+
const key = localDatabaseFileKey(url);
|
|
151
|
+
return key ? gateLocalFileClient(client, key, options) : client;
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
//#region src/shared/reset-after-busy-client.ts
|
|
155
|
+
function isBusy(error) {
|
|
156
|
+
return error?.code === "SQLITE_BUSY";
|
|
157
|
+
}
|
|
158
|
+
function firstOpen(hold) {
|
|
159
|
+
while (hold && !hold.open) hold = hold.outer;
|
|
160
|
+
return hold;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Wraps a pooled local `file:` client so a statement refused with `SQLITE_BUSY`
|
|
164
|
+
* doesn't leave its connection unusable.
|
|
165
|
+
*
|
|
166
|
+
* libsql leaves a write statement whose run failed busy in progress until it is
|
|
167
|
+
* garbage collected (tursodatabase/libsql-js#237). Until then no transaction on
|
|
168
|
+
* that connection can commit ("cannot commit transaction - SQL statements in
|
|
169
|
+
* progress"), and an autocommit write on it stays uncommitted while holding the
|
|
170
|
+
* database's write lock. The pool hands the same connection out again, so
|
|
171
|
+
* retrying the write doesn't help.
|
|
172
|
+
*
|
|
173
|
+
* After a busy failure the wrapper reopens the client's connections, which
|
|
174
|
+
* finalizes the stuck statement. Reopening closes every connection, including
|
|
175
|
+
* borrowed ones, so it waits until no call or transaction made through the
|
|
176
|
+
* wrapper is in flight. New calls wait for the reset rather than join the calls
|
|
177
|
+
* it waits for, so a client that is never idle still gets reset. A call made
|
|
178
|
+
* while its caller holds an open transaction, or from inside another call,
|
|
179
|
+
* doesn't wait: the reset waits for what its caller holds, so waiting would
|
|
180
|
+
* deadlock. Reopened connections start from the pool's connection options,
|
|
181
|
+
* so `afterReset` runs right after to restore any per-connection settings
|
|
182
|
+
* applied after opening, and calls that waited for the reset go on once it
|
|
183
|
+
* settles, even if it fails. A call waits at most `maxWaitMs` (default 5s, the
|
|
184
|
+
* busy timeout) so a long-held transaction can't stall every later call; after
|
|
185
|
+
* that it goes ahead on the current pool. A transaction SQLite rolled back on its
|
|
186
|
+
* own is released as soon as the failing call returns. It must use the client it was given, not the
|
|
187
|
+
* wrapper: calls through the wrapper would wait for the reset it is part of.
|
|
188
|
+
* Every other member passes through untouched.
|
|
189
|
+
*/
|
|
190
|
+
function resetConnectionsAfterBusy(client, { afterReset, maxWaitMs = 5e3 } = {}) {
|
|
191
|
+
let inFlight = 0;
|
|
192
|
+
let pendingReset;
|
|
193
|
+
const holds = new AsyncLocalStorage();
|
|
194
|
+
const finishReset = () => {
|
|
195
|
+
const reset = pendingReset;
|
|
196
|
+
pendingReset = void 0;
|
|
197
|
+
reset?.finish();
|
|
198
|
+
};
|
|
199
|
+
const settle = () => {
|
|
200
|
+
inFlight--;
|
|
201
|
+
if (client.closed) return finishReset();
|
|
202
|
+
if (!pendingReset || inFlight > 0) return;
|
|
203
|
+
client.reconnect();
|
|
204
|
+
if (!afterReset) return finishReset();
|
|
205
|
+
const reset = pendingReset;
|
|
206
|
+
Promise.resolve().then(afterReset).catch(() => {}).finally(() => {
|
|
207
|
+
if (pendingReset === reset) finishReset();
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
const noteBusy = (error) => {
|
|
211
|
+
if (!isBusy(error) || pendingReset) return;
|
|
212
|
+
let finish;
|
|
213
|
+
pendingReset = {
|
|
214
|
+
done: new Promise((resolve) => finish = resolve),
|
|
215
|
+
finish
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const enter = (callerHolds) => {
|
|
219
|
+
if (!pendingReset || callerHolds || client.closed) {
|
|
220
|
+
inFlight++;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
return (async () => {
|
|
224
|
+
let timer;
|
|
225
|
+
const gaveUp = new Promise((resolve) => timer = setTimeout(resolve, maxWaitMs, "timeout"));
|
|
226
|
+
try {
|
|
227
|
+
while (pendingReset && !client.closed) if (await Promise.race([pendingReset.done, gaveUp]) === "timeout") break;
|
|
228
|
+
} finally {
|
|
229
|
+
clearTimeout(timer);
|
|
230
|
+
}
|
|
231
|
+
inFlight++;
|
|
232
|
+
})();
|
|
233
|
+
};
|
|
234
|
+
const run = async (call) => {
|
|
235
|
+
const hold = {
|
|
236
|
+
open: true,
|
|
237
|
+
outer: firstOpen(holds.getStore())
|
|
238
|
+
};
|
|
239
|
+
const waiting = enter(hold.outer !== void 0);
|
|
240
|
+
if (waiting) await waiting;
|
|
241
|
+
try {
|
|
242
|
+
return await holds.run(hold, call);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
noteBusy(error);
|
|
245
|
+
throw error;
|
|
246
|
+
} finally {
|
|
247
|
+
hold.open = false;
|
|
248
|
+
settle();
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
const transaction = (open, mode) => {
|
|
252
|
+
const hold = {
|
|
253
|
+
open: true,
|
|
254
|
+
outer: firstOpen(holds.getStore())
|
|
255
|
+
};
|
|
256
|
+
const waiting = enter(hold.outer !== void 0);
|
|
257
|
+
holds.enterWith(hold);
|
|
258
|
+
return openTransaction(open, mode, hold, waiting);
|
|
259
|
+
};
|
|
260
|
+
const openTransaction = async (open, mode, hold, waiting) => {
|
|
261
|
+
let tx;
|
|
262
|
+
try {
|
|
263
|
+
if (waiting) await waiting;
|
|
264
|
+
tx = await open(mode);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
hold.open = false;
|
|
267
|
+
noteBusy(error);
|
|
268
|
+
settle();
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
const end = () => {
|
|
272
|
+
if (!hold.open) return;
|
|
273
|
+
hold.open = false;
|
|
274
|
+
settle();
|
|
275
|
+
};
|
|
276
|
+
return new Proxy(tx, { get(target, prop) {
|
|
277
|
+
switch (prop) {
|
|
278
|
+
case "execute":
|
|
279
|
+
case "batch":
|
|
280
|
+
case "executeMultiple": {
|
|
281
|
+
const method = Reflect.get(target, prop);
|
|
282
|
+
return async (...args) => {
|
|
283
|
+
try {
|
|
284
|
+
return await method.apply(target, args);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
noteBusy(error);
|
|
287
|
+
if (target.closed) try {
|
|
288
|
+
target.close();
|
|
289
|
+
} finally {
|
|
290
|
+
end();
|
|
291
|
+
}
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
case "commit":
|
|
297
|
+
case "rollback": {
|
|
298
|
+
const method = Reflect.get(target, prop);
|
|
299
|
+
return async () => {
|
|
300
|
+
try {
|
|
301
|
+
await method.call(target);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
noteBusy(error);
|
|
304
|
+
throw error;
|
|
305
|
+
} finally {
|
|
306
|
+
end();
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
case "close": {
|
|
311
|
+
const method = Reflect.get(target, prop);
|
|
312
|
+
return () => {
|
|
313
|
+
try {
|
|
314
|
+
method.call(target);
|
|
315
|
+
} finally {
|
|
316
|
+
end();
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
default: {
|
|
321
|
+
const value = Reflect.get(target, prop);
|
|
322
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} });
|
|
326
|
+
};
|
|
327
|
+
return new Proxy(client, { get(target, prop) {
|
|
328
|
+
switch (prop) {
|
|
329
|
+
case "transaction": {
|
|
330
|
+
const open = Reflect.get(target, prop);
|
|
331
|
+
return (mode) => transaction(open.bind(target), mode);
|
|
332
|
+
}
|
|
333
|
+
case "execute":
|
|
334
|
+
case "batch":
|
|
335
|
+
case "executeMultiple":
|
|
336
|
+
case "migrate": {
|
|
337
|
+
const method = Reflect.get(target, prop);
|
|
338
|
+
return (...args) => run(() => method.apply(target, args));
|
|
339
|
+
}
|
|
340
|
+
case "close": {
|
|
341
|
+
const method = Reflect.get(target, prop);
|
|
342
|
+
return () => {
|
|
343
|
+
try {
|
|
344
|
+
method.call(target);
|
|
345
|
+
} finally {
|
|
346
|
+
finishReset();
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
default: {
|
|
351
|
+
const value = Reflect.get(target, prop);
|
|
352
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
} });
|
|
356
|
+
}
|
|
357
|
+
//#endregion
|
|
13
358
|
//#region src/shared/single-connection-client.ts
|
|
14
359
|
/**
|
|
15
360
|
* Whether `@libsql/client` backs this database with exactly one connection.
|
|
@@ -518,7 +863,7 @@ var LibSQLVector = class extends MastraVector {
|
|
|
518
863
|
this.turso = isSingleConnectionDatabase({
|
|
519
864
|
url,
|
|
520
865
|
syncUrl
|
|
521
|
-
}) ? gateSingleConnectionClient(client) : client;
|
|
866
|
+
}) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: 5e3 }), url, { maxWaitMs: 5e3 }) : client;
|
|
522
867
|
this.maxRetries = maxRetries;
|
|
523
868
|
this.initialBackoffMs = initialBackoffMs;
|
|
524
869
|
if (!Number.isInteger(vectorTopKOverFetchMultiplier) || vectorTopKOverFetchMultiplier < 1) throw new Error("vectorTopKOverFetchMultiplier must be a positive integer");
|
|
@@ -1109,6 +1454,110 @@ var LibSQLVector = class extends MastraVector {
|
|
|
1109
1454
|
}
|
|
1110
1455
|
};
|
|
1111
1456
|
//#endregion
|
|
1457
|
+
//#region src/storage/db/write-lock.ts
|
|
1458
|
+
/**
|
|
1459
|
+
* Per-client write serialization.
|
|
1460
|
+
*
|
|
1461
|
+
* `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
|
|
1462
|
+
* SQLite still admits one writer at a time: an interactive
|
|
1463
|
+
* `client.transaction('write')` holds `BEGIN` open across every
|
|
1464
|
+
* `await tx.execute(...)`, and any other write on the same database in that
|
|
1465
|
+
* window contends on the file lock and can fail with `SQLITE_BUSY` once
|
|
1466
|
+
* `busy_timeout` expires.
|
|
1467
|
+
*
|
|
1468
|
+
* This is dormant under the default engine but the evented engine runs many
|
|
1469
|
+
* concurrent workflow snapshot writes per agent run, so a write issued by an
|
|
1470
|
+
* unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
|
|
1471
|
+
*
|
|
1472
|
+
* Serializing every write on a given client closes that window: writes — both
|
|
1473
|
+
* autocommit statements and full interactive transactions — run one at a time,
|
|
1474
|
+
* so none can interleave with an open transaction. Reads are intentionally not
|
|
1475
|
+
* gated; WAL readers never observe a partial write and must not queue behind a
|
|
1476
|
+
* long-running writer.
|
|
1477
|
+
*
|
|
1478
|
+
* `:memory:` databases and embedded replicas get a single pooled connection
|
|
1479
|
+
* instead; see `shared/single-connection-client.ts`, which gates *all* calls
|
|
1480
|
+
* (reads included) behind open transactions for those clients.
|
|
1481
|
+
*/
|
|
1482
|
+
const clientWriteChains = /* @__PURE__ */ new WeakMap();
|
|
1483
|
+
/**
|
|
1484
|
+
* Runs `fn` after every previously-enqueued write on `client` has settled, and
|
|
1485
|
+
* returns its result. The chain advances regardless of whether `fn` resolves or
|
|
1486
|
+
* rejects, so one failed write never wedges the queue.
|
|
1487
|
+
*/
|
|
1488
|
+
function withClientWriteLock(client, fn) {
|
|
1489
|
+
const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
|
|
1490
|
+
clientWriteChains.set(client, result.then(() => void 0, () => void 0));
|
|
1491
|
+
return result;
|
|
1492
|
+
}
|
|
1493
|
+
//#endregion
|
|
1494
|
+
//#region src/storage/db/run-fencing.ts
|
|
1495
|
+
/**
|
|
1496
|
+
* Epoch milliseconds on the database clock. `julianday('now')` rather than
|
|
1497
|
+
* `unixepoch('subsec')`, which needs SQLite 3.42 on the server.
|
|
1498
|
+
*/
|
|
1499
|
+
const DB_NOW_MS = `CAST(ROUND((julianday('now') - 2440587.5) * 86400000) AS INTEGER)`;
|
|
1500
|
+
/**
|
|
1501
|
+
* Runs `fn` in a write transaction, behind every other write on `client`.
|
|
1502
|
+
* `'write'` opens the transaction with `BEGIN IMMEDIATE`, which takes the
|
|
1503
|
+
* database's write lock up front, so nothing written between a read and a
|
|
1504
|
+
* write inside `fn` can come from another connection.
|
|
1505
|
+
*/
|
|
1506
|
+
function inWriteTransaction(client, fn) {
|
|
1507
|
+
return withClientWriteLock(client, async () => {
|
|
1508
|
+
const tx = await client.transaction("write");
|
|
1509
|
+
try {
|
|
1510
|
+
const result = await fn(tx);
|
|
1511
|
+
await tx.commit();
|
|
1512
|
+
return result;
|
|
1513
|
+
} catch (error) {
|
|
1514
|
+
if (!tx.closed) await tx.rollback();
|
|
1515
|
+
throw error;
|
|
1516
|
+
} finally {
|
|
1517
|
+
if (!tx.closed) tx.close();
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* The check half of a fenced write; call it first inside the write's
|
|
1523
|
+
* transaction. The transaction already holds the database's write lock, so
|
|
1524
|
+
* a claim can't commit between this check and the write.
|
|
1525
|
+
*/
|
|
1526
|
+
async function assertRunFence(tx, { claimsTable, fence, operation }) {
|
|
1527
|
+
const row = (await tx.execute({
|
|
1528
|
+
sql: `SELECT generation, ownerId FROM ${claimsTable} WHERE runId = ?`,
|
|
1529
|
+
args: [fence.runId]
|
|
1530
|
+
})).rows[0];
|
|
1531
|
+
if (!matchesRunFence(row ? {
|
|
1532
|
+
generation: Number(row.generation),
|
|
1533
|
+
ownerId: String(row.ownerId)
|
|
1534
|
+
} : null, fence)) throw new RunFenceConflictError(fence, operation);
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Runs `write` behind the client's write lock: directly when there is no
|
|
1538
|
+
* fence to check, or in a write transaction behind the check when there is.
|
|
1539
|
+
* `write` must issue every statement through the writer it is given; on a
|
|
1540
|
+
* single-connection client, a call on the client itself would wait for the
|
|
1541
|
+
* open transaction forever.
|
|
1542
|
+
*/
|
|
1543
|
+
function withRunFence(client, check, write) {
|
|
1544
|
+
if (!check) return withClientWriteLock(client, () => write({
|
|
1545
|
+
execute: (statement) => client.execute(statement),
|
|
1546
|
+
batch: (statements) => client.batch(statements, "write")
|
|
1547
|
+
}));
|
|
1548
|
+
return inWriteTransaction(client, async (tx) => {
|
|
1549
|
+
await assertRunFence(tx, check);
|
|
1550
|
+
return write({
|
|
1551
|
+
execute: (statement) => tx.execute(statement),
|
|
1552
|
+
batch: async (statements) => {
|
|
1553
|
+
const results = [];
|
|
1554
|
+
for (const statement of statements) results.push(await tx.execute(statement));
|
|
1555
|
+
return results;
|
|
1556
|
+
}
|
|
1557
|
+
});
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
//#endregion
|
|
1112
1561
|
//#region src/storage/db/utils.ts
|
|
1113
1562
|
const MAX_DEPTH = 100;
|
|
1114
1563
|
/**
|
|
@@ -1201,6 +1650,7 @@ function createExecuteWriteOperationWithRetry({ logger, maxRetries, initialBacko
|
|
|
1201
1650
|
while (attempts < maxRetries) try {
|
|
1202
1651
|
return await operationFn();
|
|
1203
1652
|
} catch (error) {
|
|
1653
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
1204
1654
|
logger.debug(`LibSQLStore: Error caught in retry loop for ${operationDescription}`, {
|
|
1205
1655
|
errorType: error.constructor.name,
|
|
1206
1656
|
errorCode: error.code,
|
|
@@ -1361,43 +1811,6 @@ function transformFromSqlRow({ tableName, sqlRow }) {
|
|
|
1361
1811
|
return result;
|
|
1362
1812
|
}
|
|
1363
1813
|
//#endregion
|
|
1364
|
-
//#region src/storage/db/write-lock.ts
|
|
1365
|
-
/**
|
|
1366
|
-
* Per-client write serialization.
|
|
1367
|
-
*
|
|
1368
|
-
* `@libsql/client` >= 0.18.0 pools connections for local `file:` databases, but
|
|
1369
|
-
* SQLite still admits one writer at a time: an interactive
|
|
1370
|
-
* `client.transaction('write')` holds `BEGIN` open across every
|
|
1371
|
-
* `await tx.execute(...)`, and any other write on the same database in that
|
|
1372
|
-
* window contends on the file lock and can fail with `SQLITE_BUSY` once
|
|
1373
|
-
* `busy_timeout` expires.
|
|
1374
|
-
*
|
|
1375
|
-
* This is dormant under the default engine but the evented engine runs many
|
|
1376
|
-
* concurrent workflow snapshot writes per agent run, so a write issued by an
|
|
1377
|
-
* unrelated domain (e.g. creating a dataset experiment) can fail spuriously.
|
|
1378
|
-
*
|
|
1379
|
-
* Serializing every write on a given client closes that window: writes — both
|
|
1380
|
-
* autocommit statements and full interactive transactions — run one at a time,
|
|
1381
|
-
* so none can interleave with an open transaction. Reads are intentionally not
|
|
1382
|
-
* gated; WAL readers never observe a partial write and must not queue behind a
|
|
1383
|
-
* long-running writer.
|
|
1384
|
-
*
|
|
1385
|
-
* `:memory:` databases and embedded replicas get a single pooled connection
|
|
1386
|
-
* instead; see `shared/single-connection-client.ts`, which gates *all* calls
|
|
1387
|
-
* (reads included) behind open transactions for those clients.
|
|
1388
|
-
*/
|
|
1389
|
-
const clientWriteChains = /* @__PURE__ */ new WeakMap();
|
|
1390
|
-
/**
|
|
1391
|
-
* Runs `fn` after every previously-enqueued write on `client` has settled, and
|
|
1392
|
-
* returns its result. The chain advances regardless of whether `fn` resolves or
|
|
1393
|
-
* rejects, so one failed write never wedges the queue.
|
|
1394
|
-
*/
|
|
1395
|
-
function withClientWriteLock(client, fn) {
|
|
1396
|
-
const result = (clientWriteChains.get(client) ?? Promise.resolve()).then(fn, fn);
|
|
1397
|
-
clientWriteChains.set(client, result.then(() => void 0, () => void 0));
|
|
1398
|
-
return result;
|
|
1399
|
-
}
|
|
1400
|
-
//#endregion
|
|
1401
1814
|
//#region src/storage/db/index.ts
|
|
1402
1815
|
/**
|
|
1403
1816
|
* Default SQLite `busy_timeout` (ms) for local LibSQL connections. Chosen to
|
|
@@ -1498,10 +1911,10 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1498
1911
|
/**
|
|
1499
1912
|
* Internal insert implementation without retry logic.
|
|
1500
1913
|
*/
|
|
1501
|
-
async doInsert({ tableName, record }) {
|
|
1914
|
+
async doInsert({ tableName, record, check }) {
|
|
1502
1915
|
const filteredRecord = await this.filterRecordToKnownColumns(tableName, record);
|
|
1503
1916
|
if (Object.keys(filteredRecord).length === 0) return;
|
|
1504
|
-
await
|
|
1917
|
+
await withRunFence(this.client, check, (writer) => writer.execute(prepareStatement({
|
|
1505
1918
|
tableName,
|
|
1506
1919
|
record: filteredRecord
|
|
1507
1920
|
})));
|
|
@@ -1512,6 +1925,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1512
1925
|
* @param args - The insert arguments
|
|
1513
1926
|
* @param args.tableName - The name of the table to insert into
|
|
1514
1927
|
* @param args.record - The record to insert (key-value pairs)
|
|
1928
|
+
* @param args.check - The run fence the insert must carry, if any
|
|
1515
1929
|
*/
|
|
1516
1930
|
insert(args) {
|
|
1517
1931
|
return this.executeWriteOperationWithRetry(() => this.doInsert(args), `insert into table ${args.tableName}`);
|
|
@@ -1786,7 +2200,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1786
2200
|
* @param args.tableName - The name of the table to create
|
|
1787
2201
|
* @param args.schema - The schema definition for the table columns
|
|
1788
2202
|
*/
|
|
1789
|
-
async createTable({ tableName, schema, compositePrimaryKey }) {
|
|
2203
|
+
async createTable({ tableName, schema, compositePrimaryKey, executor = this.client }) {
|
|
1790
2204
|
try {
|
|
1791
2205
|
const parsedTableName = parseSqlIdentifier(tableName, "table name");
|
|
1792
2206
|
if (compositePrimaryKey) {
|
|
@@ -1804,7 +2218,7 @@ var LibSQLDB = class extends MastraBase {
|
|
|
1804
2218
|
if (tableName === TABLE_WORKFLOW_SNAPSHOT) tableConstraints.push("UNIQUE (workflow_name, run_id)");
|
|
1805
2219
|
if (tableName === TABLE_SPANS) tableConstraints.push("UNIQUE (spanId, traceId)");
|
|
1806
2220
|
const sql = `CREATE TABLE IF NOT EXISTS ${parsedTableName} (\n ${[...columnDefinitions, ...tableConstraints].join(",\n ")}\n)`;
|
|
1807
|
-
await
|
|
2221
|
+
await executor.execute(sql);
|
|
1808
2222
|
this.logger.debug(`LibSQLDB: Created table ${tableName}`);
|
|
1809
2223
|
if (tableName === TABLE_SPANS) await this.migrateSpansTable();
|
|
1810
2224
|
} catch (error) {
|
|
@@ -6047,20 +6461,678 @@ var HarnessLibSQL = class HarnessLibSQL extends HarnessStorage {
|
|
|
6047
6461
|
}
|
|
6048
6462
|
};
|
|
6049
6463
|
//#endregion
|
|
6464
|
+
//#region ../../packages/_internal-core/dist/knowledge-compat/index.js
|
|
6465
|
+
const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
|
|
6466
|
+
/** Hard cap on scope nodes returned by one `listScopeNodes` read. */
|
|
6467
|
+
const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
|
|
6468
|
+
function knowledgeScopeNodeFilterKey(input) {
|
|
6469
|
+
return JSON.stringify([
|
|
6470
|
+
input.withinAddress ?? null,
|
|
6471
|
+
input.addresses ? [...input.addresses].sort() : null,
|
|
6472
|
+
input.ids ? [...input.ids].sort() : null
|
|
6473
|
+
]);
|
|
6474
|
+
}
|
|
6475
|
+
function createKnowledgeScopeNodeCursor(scope, input) {
|
|
6476
|
+
return encodeURIComponent(JSON.stringify({
|
|
6477
|
+
version: 1,
|
|
6478
|
+
type: "scope",
|
|
6479
|
+
name: scope.name,
|
|
6480
|
+
id: scope.id,
|
|
6481
|
+
filter: knowledgeScopeNodeFilterKey(input)
|
|
6482
|
+
}));
|
|
6483
|
+
}
|
|
6484
|
+
/** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
|
|
6485
|
+
function parseListKnowledgeScopeNodesInput(input = {}) {
|
|
6486
|
+
const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
|
|
6487
|
+
const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
|
|
6488
|
+
if (!input.cursor) return {
|
|
6489
|
+
limit,
|
|
6490
|
+
after: null
|
|
6491
|
+
};
|
|
6492
|
+
let value;
|
|
6493
|
+
try {
|
|
6494
|
+
value = JSON.parse(decodeURIComponent(input.cursor));
|
|
6495
|
+
} catch {
|
|
6496
|
+
throw new Error("Invalid Knowledge scope node cursor.");
|
|
6497
|
+
}
|
|
6498
|
+
const parsed = value && typeof value === "object" ? value : {};
|
|
6499
|
+
if (parsed.version !== 1 || parsed.type !== "scope" || typeof parsed.name !== "string" || typeof parsed.id !== "string" || parsed.filter !== knowledgeScopeNodeFilterKey(input)) throw new Error("Knowledge scope node cursor does not match this query.");
|
|
6500
|
+
return {
|
|
6501
|
+
limit,
|
|
6502
|
+
after: {
|
|
6503
|
+
name: parsed.name,
|
|
6504
|
+
id: parsed.id
|
|
6505
|
+
}
|
|
6506
|
+
};
|
|
6507
|
+
}
|
|
6508
|
+
/** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
|
|
6509
|
+
function pageKnowledgeScopeNodes(rows, limit, input) {
|
|
6510
|
+
const scopes = rows.slice(0, limit);
|
|
6511
|
+
const last = scopes.at(-1);
|
|
6512
|
+
return {
|
|
6513
|
+
scopes,
|
|
6514
|
+
nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
|
|
6515
|
+
};
|
|
6516
|
+
}
|
|
6517
|
+
/** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
|
|
6518
|
+
function knowledgeScopeMembersLimit(limit) {
|
|
6519
|
+
if (limit === void 0 || !Number.isFinite(limit)) return 500;
|
|
6520
|
+
return Math.min(Math.max(Math.trunc(limit), 1), 500);
|
|
6521
|
+
}
|
|
6522
|
+
/** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
|
|
6523
|
+
function parseListKnowledgeScopeMembersInput(input) {
|
|
6524
|
+
const limit = knowledgeScopeMembersLimit(input.limit);
|
|
6525
|
+
if (!input.cursor) return {
|
|
6526
|
+
limit,
|
|
6527
|
+
after: null
|
|
6528
|
+
};
|
|
6529
|
+
let value;
|
|
6530
|
+
try {
|
|
6531
|
+
value = JSON.parse(decodeURIComponent(input.cursor));
|
|
6532
|
+
} catch {
|
|
6533
|
+
throw new Error("Invalid Knowledge scope member cursor.");
|
|
6534
|
+
}
|
|
6535
|
+
const parsed = value && typeof value === "object" ? value : {};
|
|
6536
|
+
const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
|
|
6537
|
+
if (parsed.version !== 1 || parsed.type !== "scope-member" || parsed.scopeNodeId !== input.scopeNodeId || typeof parsed.name !== "string" || typeof parsed.id !== "string" || Number.isNaN(updatedAt.getTime())) throw new Error("Knowledge scope member cursor does not match this query.");
|
|
6538
|
+
return {
|
|
6539
|
+
limit,
|
|
6540
|
+
after: {
|
|
6541
|
+
updatedAt,
|
|
6542
|
+
name: parsed.name,
|
|
6543
|
+
id: parsed.id
|
|
6544
|
+
}
|
|
6545
|
+
};
|
|
6546
|
+
}
|
|
6547
|
+
/** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
|
|
6548
|
+
function pageKnowledgeScopeMembers(rows, limit, input) {
|
|
6549
|
+
const members = rows.slice(0, limit);
|
|
6550
|
+
const last = members.at(-1);
|
|
6551
|
+
const hasMore = rows.length > limit;
|
|
6552
|
+
return {
|
|
6553
|
+
members,
|
|
6554
|
+
hasMore,
|
|
6555
|
+
nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
|
|
6556
|
+
version: 1,
|
|
6557
|
+
type: "scope-member",
|
|
6558
|
+
scopeNodeId: input.scopeNodeId,
|
|
6559
|
+
updatedAt: last.updatedAt.toISOString(),
|
|
6560
|
+
name: last.name,
|
|
6561
|
+
id: last.id
|
|
6562
|
+
})) : null
|
|
6563
|
+
};
|
|
6564
|
+
}
|
|
6565
|
+
const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
|
|
6566
|
+
const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
|
|
6567
|
+
const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
|
|
6568
|
+
const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
|
|
6569
|
+
const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
|
|
6570
|
+
const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
|
|
6571
|
+
const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
|
|
6572
|
+
const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
|
|
6573
|
+
const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
|
|
6574
|
+
const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
|
|
6575
|
+
const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
|
|
6576
|
+
const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
|
|
6577
|
+
const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
|
|
6578
|
+
const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
|
|
6579
|
+
const KNOWLEDGE_TABLE_NAMES = [
|
|
6580
|
+
TABLE_KNOWLEDGE_NODES$1,
|
|
6581
|
+
TABLE_KNOWLEDGE_RECORDS$1,
|
|
6582
|
+
TABLE_KNOWLEDGE_MENTIONS$1,
|
|
6583
|
+
TABLE_KNOWLEDGE_ACTIVITY$1,
|
|
6584
|
+
TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
|
|
6585
|
+
TABLE_KNOWLEDGE_NODE_SCOPES,
|
|
6586
|
+
TABLE_KNOWLEDGE_RECORD_SCOPES,
|
|
6587
|
+
TABLE_KNOWLEDGE_SCOPE_GRANTS,
|
|
6588
|
+
TABLE_KNOWLEDGE_ACCESS_STATE,
|
|
6589
|
+
TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
|
|
6590
|
+
TABLE_KNOWLEDGE_NODE_ADDRESSES,
|
|
6591
|
+
TABLE_KNOWLEDGE_IMPORT_STATE,
|
|
6592
|
+
TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
6593
|
+
TABLE_KNOWLEDGE_PROPOSALS
|
|
6594
|
+
];
|
|
6595
|
+
/** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
|
|
6596
|
+
const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
|
|
6597
|
+
/** Tables and indexes published v1 adapters created for every app, whether or not it used Knowledge. */
|
|
6598
|
+
const PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
6599
|
+
TABLE_KNOWLEDGE_NODES$1,
|
|
6600
|
+
TABLE_KNOWLEDGE_RECORDS$1,
|
|
6601
|
+
TABLE_KNOWLEDGE_MENTIONS$1,
|
|
6602
|
+
TABLE_KNOWLEDGE_ACTIVITY$1,
|
|
6603
|
+
TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
|
|
6604
|
+
...RETIRED_KNOWLEDGE_TABLE_NAMES
|
|
6605
|
+
]);
|
|
6606
|
+
const PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES = /* @__PURE__ */ new Set([
|
|
6607
|
+
"idx_knowledge_nodes_identity",
|
|
6608
|
+
"idx_knowledge_nodes_scope",
|
|
6609
|
+
"idx_knowledge_records_node_latest",
|
|
6610
|
+
"idx_knowledge_records_thread_latest",
|
|
6611
|
+
"idx_knowledge_mentions_record",
|
|
6612
|
+
"idx_knowledge_activity_latest",
|
|
6613
|
+
"idx_knowledge_outbox_idempotency",
|
|
6614
|
+
"idx_knowledge_outbox_claim"
|
|
6615
|
+
]);
|
|
6616
|
+
const KNOWLEDGE_V2_NODES_SCHEMA = {
|
|
6617
|
+
id: {
|
|
6618
|
+
type: "text",
|
|
6619
|
+
nullable: false,
|
|
6620
|
+
primaryKey: true
|
|
6621
|
+
},
|
|
6622
|
+
name: {
|
|
6623
|
+
type: "text",
|
|
6624
|
+
nullable: false
|
|
6625
|
+
},
|
|
6626
|
+
kind: {
|
|
6627
|
+
type: "text",
|
|
6628
|
+
nullable: true
|
|
6629
|
+
},
|
|
6630
|
+
isScope: {
|
|
6631
|
+
type: "boolean",
|
|
6632
|
+
nullable: false
|
|
6633
|
+
},
|
|
6634
|
+
metadata: {
|
|
6635
|
+
type: "jsonb",
|
|
6636
|
+
nullable: true
|
|
6637
|
+
},
|
|
6638
|
+
version: {
|
|
6639
|
+
type: "integer",
|
|
6640
|
+
nullable: false
|
|
6641
|
+
},
|
|
6642
|
+
createdAt: {
|
|
6643
|
+
type: "timestamp",
|
|
6644
|
+
nullable: false
|
|
6645
|
+
},
|
|
6646
|
+
updatedAt: {
|
|
6647
|
+
type: "timestamp",
|
|
6648
|
+
nullable: false
|
|
6649
|
+
},
|
|
6650
|
+
deletedAt: {
|
|
6651
|
+
type: "timestamp",
|
|
6652
|
+
nullable: true
|
|
6653
|
+
},
|
|
6654
|
+
deletedBy: {
|
|
6655
|
+
type: "text",
|
|
6656
|
+
nullable: true
|
|
6657
|
+
},
|
|
6658
|
+
type: {
|
|
6659
|
+
type: "text",
|
|
6660
|
+
nullable: true
|
|
6661
|
+
},
|
|
6662
|
+
canonicalName: {
|
|
6663
|
+
type: "text",
|
|
6664
|
+
nullable: true
|
|
6665
|
+
},
|
|
6666
|
+
content: {
|
|
6667
|
+
type: "text",
|
|
6668
|
+
nullable: true
|
|
6669
|
+
},
|
|
6670
|
+
description: {
|
|
6671
|
+
type: "text",
|
|
6672
|
+
nullable: true
|
|
6673
|
+
},
|
|
6674
|
+
scope: {
|
|
6675
|
+
type: "jsonb",
|
|
6676
|
+
nullable: true
|
|
6677
|
+
},
|
|
6678
|
+
scopeKey: {
|
|
6679
|
+
type: "text",
|
|
6680
|
+
nullable: true
|
|
6681
|
+
},
|
|
6682
|
+
mergedInto: {
|
|
6683
|
+
type: "text",
|
|
6684
|
+
nullable: true
|
|
6685
|
+
}
|
|
6686
|
+
};
|
|
6687
|
+
const KNOWLEDGE_V2_RECORDS_SCHEMA = {
|
|
6688
|
+
id: {
|
|
6689
|
+
type: "text",
|
|
6690
|
+
nullable: false,
|
|
6691
|
+
primaryKey: true
|
|
6692
|
+
},
|
|
6693
|
+
node: {
|
|
6694
|
+
type: "text",
|
|
6695
|
+
nullable: false,
|
|
6696
|
+
references: {
|
|
6697
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6698
|
+
column: "id"
|
|
6699
|
+
}
|
|
6700
|
+
},
|
|
6701
|
+
text: {
|
|
6702
|
+
type: "text",
|
|
6703
|
+
nullable: false
|
|
6704
|
+
},
|
|
6705
|
+
metadata: {
|
|
6706
|
+
type: "jsonb",
|
|
6707
|
+
nullable: true
|
|
6708
|
+
},
|
|
6709
|
+
version: {
|
|
6710
|
+
type: "integer",
|
|
6711
|
+
nullable: false
|
|
6712
|
+
},
|
|
6713
|
+
capturedAt: {
|
|
6714
|
+
type: "timestamp",
|
|
6715
|
+
nullable: false
|
|
6716
|
+
},
|
|
6717
|
+
updatedAt: {
|
|
6718
|
+
type: "timestamp",
|
|
6719
|
+
nullable: false
|
|
6720
|
+
},
|
|
6721
|
+
deletedAt: {
|
|
6722
|
+
type: "timestamp",
|
|
6723
|
+
nullable: true
|
|
6724
|
+
},
|
|
6725
|
+
deletedBy: {
|
|
6726
|
+
type: "text",
|
|
6727
|
+
nullable: true
|
|
6728
|
+
},
|
|
6729
|
+
scope: {
|
|
6730
|
+
type: "jsonb",
|
|
6731
|
+
nullable: true
|
|
6732
|
+
},
|
|
6733
|
+
scopeKey: {
|
|
6734
|
+
type: "text",
|
|
6735
|
+
nullable: true
|
|
6736
|
+
},
|
|
6737
|
+
sourceThreadId: {
|
|
6738
|
+
type: "text",
|
|
6739
|
+
nullable: true
|
|
6740
|
+
},
|
|
6741
|
+
when: {
|
|
6742
|
+
type: "timestamp",
|
|
6743
|
+
nullable: true
|
|
6744
|
+
}
|
|
6745
|
+
};
|
|
6746
|
+
const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
|
|
6747
|
+
recordId: {
|
|
6748
|
+
type: "text",
|
|
6749
|
+
nullable: false,
|
|
6750
|
+
references: {
|
|
6751
|
+
table: TABLE_KNOWLEDGE_RECORDS$1,
|
|
6752
|
+
column: "id"
|
|
6753
|
+
}
|
|
6754
|
+
},
|
|
6755
|
+
targetNodeId: {
|
|
6756
|
+
type: "text",
|
|
6757
|
+
nullable: true,
|
|
6758
|
+
references: {
|
|
6759
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6760
|
+
column: "id"
|
|
6761
|
+
}
|
|
6762
|
+
},
|
|
6763
|
+
sourceType: {
|
|
6764
|
+
type: "text",
|
|
6765
|
+
nullable: true
|
|
6766
|
+
},
|
|
6767
|
+
sourceId: {
|
|
6768
|
+
type: "text",
|
|
6769
|
+
nullable: true
|
|
6770
|
+
}
|
|
6771
|
+
};
|
|
6772
|
+
const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
|
|
6773
|
+
nodeId: {
|
|
6774
|
+
type: "text",
|
|
6775
|
+
nullable: false,
|
|
6776
|
+
references: {
|
|
6777
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6778
|
+
column: "id"
|
|
6779
|
+
}
|
|
6780
|
+
},
|
|
6781
|
+
scopeNodeId: {
|
|
6782
|
+
type: "text",
|
|
6783
|
+
nullable: false,
|
|
6784
|
+
references: {
|
|
6785
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6786
|
+
column: "id"
|
|
6787
|
+
}
|
|
6788
|
+
},
|
|
6789
|
+
addedAt: {
|
|
6790
|
+
type: "timestamp",
|
|
6791
|
+
nullable: false
|
|
6792
|
+
}
|
|
6793
|
+
};
|
|
6794
|
+
const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
|
|
6795
|
+
recordId: {
|
|
6796
|
+
type: "text",
|
|
6797
|
+
nullable: false,
|
|
6798
|
+
references: {
|
|
6799
|
+
table: TABLE_KNOWLEDGE_RECORDS$1,
|
|
6800
|
+
column: "id"
|
|
6801
|
+
}
|
|
6802
|
+
},
|
|
6803
|
+
scopeNodeId: {
|
|
6804
|
+
type: "text",
|
|
6805
|
+
nullable: false,
|
|
6806
|
+
references: {
|
|
6807
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6808
|
+
column: "id"
|
|
6809
|
+
}
|
|
6810
|
+
},
|
|
6811
|
+
addedAt: {
|
|
6812
|
+
type: "timestamp",
|
|
6813
|
+
nullable: false
|
|
6814
|
+
}
|
|
6815
|
+
};
|
|
6816
|
+
const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
|
|
6817
|
+
scopeNodeId: {
|
|
6818
|
+
type: "text",
|
|
6819
|
+
nullable: false,
|
|
6820
|
+
references: {
|
|
6821
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6822
|
+
column: "id"
|
|
6823
|
+
}
|
|
6824
|
+
},
|
|
6825
|
+
scopeRefId: {
|
|
6826
|
+
type: "text",
|
|
6827
|
+
nullable: false,
|
|
6828
|
+
references: {
|
|
6829
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6830
|
+
column: "id"
|
|
6831
|
+
}
|
|
6832
|
+
},
|
|
6833
|
+
role: {
|
|
6834
|
+
type: "text",
|
|
6835
|
+
nullable: false
|
|
6836
|
+
},
|
|
6837
|
+
canSuggest: {
|
|
6838
|
+
type: "boolean",
|
|
6839
|
+
nullable: true
|
|
6840
|
+
}
|
|
6841
|
+
};
|
|
6842
|
+
const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
|
|
6843
|
+
id: {
|
|
6844
|
+
type: "text",
|
|
6845
|
+
nullable: false,
|
|
6846
|
+
primaryKey: true
|
|
6847
|
+
},
|
|
6848
|
+
epoch: {
|
|
6849
|
+
type: "integer",
|
|
6850
|
+
nullable: false
|
|
6851
|
+
},
|
|
6852
|
+
schemaVersion: {
|
|
6853
|
+
type: "integer",
|
|
6854
|
+
nullable: false
|
|
6855
|
+
}
|
|
6856
|
+
};
|
|
6857
|
+
const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
|
|
6858
|
+
address: {
|
|
6859
|
+
type: "text",
|
|
6860
|
+
nullable: false,
|
|
6861
|
+
primaryKey: true
|
|
6862
|
+
},
|
|
6863
|
+
scopeNodeId: {
|
|
6864
|
+
type: "text",
|
|
6865
|
+
nullable: false,
|
|
6866
|
+
references: {
|
|
6867
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6868
|
+
column: "id"
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6871
|
+
};
|
|
6872
|
+
const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
|
|
6873
|
+
source: {
|
|
6874
|
+
type: "text",
|
|
6875
|
+
nullable: false
|
|
6876
|
+
},
|
|
6877
|
+
address: {
|
|
6878
|
+
type: "text",
|
|
6879
|
+
nullable: false
|
|
6880
|
+
},
|
|
6881
|
+
nodeId: {
|
|
6882
|
+
type: "text",
|
|
6883
|
+
nullable: false,
|
|
6884
|
+
references: {
|
|
6885
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6886
|
+
column: "id"
|
|
6887
|
+
}
|
|
6888
|
+
}
|
|
6889
|
+
};
|
|
6890
|
+
const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
|
|
6891
|
+
importerId: {
|
|
6892
|
+
type: "text",
|
|
6893
|
+
nullable: false
|
|
6894
|
+
},
|
|
6895
|
+
binding: {
|
|
6896
|
+
type: "text",
|
|
6897
|
+
nullable: false
|
|
6898
|
+
},
|
|
6899
|
+
key: {
|
|
6900
|
+
type: "text",
|
|
6901
|
+
nullable: false
|
|
6902
|
+
},
|
|
6903
|
+
value: {
|
|
6904
|
+
type: "text",
|
|
6905
|
+
nullable: false
|
|
6906
|
+
}
|
|
6907
|
+
};
|
|
6908
|
+
const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
|
|
6909
|
+
id: {
|
|
6910
|
+
type: "text",
|
|
6911
|
+
nullable: false,
|
|
6912
|
+
primaryKey: true
|
|
6913
|
+
},
|
|
6914
|
+
importerId: {
|
|
6915
|
+
type: "text",
|
|
6916
|
+
nullable: false
|
|
6917
|
+
},
|
|
6918
|
+
binding: {
|
|
6919
|
+
type: "text",
|
|
6920
|
+
nullable: false
|
|
6921
|
+
},
|
|
6922
|
+
importKind: {
|
|
6923
|
+
type: "text",
|
|
6924
|
+
nullable: false
|
|
6925
|
+
},
|
|
6926
|
+
triggerKind: {
|
|
6927
|
+
type: "text",
|
|
6928
|
+
nullable: false
|
|
6929
|
+
},
|
|
6930
|
+
status: {
|
|
6931
|
+
type: "text",
|
|
6932
|
+
nullable: false
|
|
6933
|
+
},
|
|
6934
|
+
error: {
|
|
6935
|
+
type: "text",
|
|
6936
|
+
nullable: true
|
|
6937
|
+
},
|
|
6938
|
+
transcriptThreadId: {
|
|
6939
|
+
type: "text",
|
|
6940
|
+
nullable: true
|
|
6941
|
+
},
|
|
6942
|
+
traceId: {
|
|
6943
|
+
type: "text",
|
|
6944
|
+
nullable: true
|
|
6945
|
+
},
|
|
6946
|
+
queuedAt: {
|
|
6947
|
+
type: "timestamp",
|
|
6948
|
+
nullable: false
|
|
6949
|
+
},
|
|
6950
|
+
startedAt: {
|
|
6951
|
+
type: "timestamp",
|
|
6952
|
+
nullable: true
|
|
6953
|
+
},
|
|
6954
|
+
completedAt: {
|
|
6955
|
+
type: "timestamp",
|
|
6956
|
+
nullable: true
|
|
6957
|
+
}
|
|
6958
|
+
};
|
|
6959
|
+
const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
|
|
6960
|
+
id: {
|
|
6961
|
+
type: "text",
|
|
6962
|
+
nullable: false,
|
|
6963
|
+
primaryKey: true
|
|
6964
|
+
},
|
|
6965
|
+
action: {
|
|
6966
|
+
type: "text",
|
|
6967
|
+
nullable: false
|
|
6968
|
+
},
|
|
6969
|
+
targetType: {
|
|
6970
|
+
type: "text",
|
|
6971
|
+
nullable: true
|
|
6972
|
+
},
|
|
6973
|
+
targetId: {
|
|
6974
|
+
type: "text",
|
|
6975
|
+
nullable: true
|
|
6976
|
+
},
|
|
6977
|
+
contextScopeId: {
|
|
6978
|
+
type: "text",
|
|
6979
|
+
nullable: true,
|
|
6980
|
+
references: {
|
|
6981
|
+
table: TABLE_KNOWLEDGE_NODES$1,
|
|
6982
|
+
column: "id"
|
|
6983
|
+
}
|
|
6984
|
+
},
|
|
6985
|
+
importRunId: {
|
|
6986
|
+
type: "text",
|
|
6987
|
+
nullable: true,
|
|
6988
|
+
references: {
|
|
6989
|
+
table: TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
6990
|
+
column: "id"
|
|
6991
|
+
}
|
|
6992
|
+
},
|
|
6993
|
+
details: {
|
|
6994
|
+
type: "jsonb",
|
|
6995
|
+
nullable: true
|
|
6996
|
+
},
|
|
6997
|
+
createdAt: {
|
|
6998
|
+
type: "timestamp",
|
|
6999
|
+
nullable: false
|
|
7000
|
+
},
|
|
7001
|
+
recordType: {
|
|
7002
|
+
type: "text",
|
|
7003
|
+
nullable: true
|
|
7004
|
+
},
|
|
7005
|
+
recordId: {
|
|
7006
|
+
type: "text",
|
|
7007
|
+
nullable: true
|
|
7008
|
+
},
|
|
7009
|
+
scope: {
|
|
7010
|
+
type: "jsonb",
|
|
7011
|
+
nullable: true
|
|
7012
|
+
},
|
|
7013
|
+
scopeKey: {
|
|
7014
|
+
type: "text",
|
|
7015
|
+
nullable: true
|
|
7016
|
+
},
|
|
7017
|
+
sourceThreadId: {
|
|
7018
|
+
type: "text",
|
|
7019
|
+
nullable: true
|
|
7020
|
+
}
|
|
7021
|
+
};
|
|
7022
|
+
const KNOWLEDGE_PROPOSALS_SCHEMA = {
|
|
7023
|
+
id: {
|
|
7024
|
+
type: "text",
|
|
7025
|
+
nullable: false,
|
|
7026
|
+
primaryKey: true
|
|
7027
|
+
},
|
|
7028
|
+
targetType: {
|
|
7029
|
+
type: "text",
|
|
7030
|
+
nullable: false
|
|
7031
|
+
},
|
|
7032
|
+
targetId: {
|
|
7033
|
+
type: "text",
|
|
7034
|
+
nullable: false
|
|
7035
|
+
},
|
|
7036
|
+
action: {
|
|
7037
|
+
type: "text",
|
|
7038
|
+
nullable: false
|
|
7039
|
+
},
|
|
7040
|
+
changes: {
|
|
7041
|
+
type: "jsonb",
|
|
7042
|
+
nullable: false
|
|
7043
|
+
},
|
|
7044
|
+
reason: {
|
|
7045
|
+
type: "text",
|
|
7046
|
+
nullable: true
|
|
7047
|
+
},
|
|
7048
|
+
proposerContextScopeId: {
|
|
7049
|
+
type: "text",
|
|
7050
|
+
nullable: false
|
|
7051
|
+
},
|
|
7052
|
+
expectedVersion: {
|
|
7053
|
+
type: "integer",
|
|
7054
|
+
nullable: false
|
|
7055
|
+
},
|
|
7056
|
+
status: {
|
|
7057
|
+
type: "text",
|
|
7058
|
+
nullable: false
|
|
7059
|
+
},
|
|
7060
|
+
reviewerContextScopeId: {
|
|
7061
|
+
type: "text",
|
|
7062
|
+
nullable: true
|
|
7063
|
+
},
|
|
7064
|
+
reviewedAt: {
|
|
7065
|
+
type: "timestamp",
|
|
7066
|
+
nullable: true
|
|
7067
|
+
},
|
|
7068
|
+
createdAt: {
|
|
7069
|
+
type: "timestamp",
|
|
7070
|
+
nullable: false
|
|
7071
|
+
}
|
|
7072
|
+
};
|
|
7073
|
+
function assertKnowledgeV2CoreSupport(features) {
|
|
7074
|
+
if (!features.has("knowledge-v2")) throw new Error(`Knowledge v2 requires a @mastra/core release with the "${KNOWLEDGE_V2_CORE_FEATURE}" feature. Upgrade @mastra/core to use Knowledge; other storage domains keep working on this version.`);
|
|
7075
|
+
}
|
|
7076
|
+
function resolveKnowledgeV2Core(storageModule) {
|
|
7077
|
+
if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
|
|
7078
|
+
const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
|
|
7079
|
+
if (typeof assertDescription !== "function" || typeof assertRecordText !== "function" || typeof assertCompatible !== "function" || typeof inspectSchema !== "function") throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
|
|
7080
|
+
return {
|
|
7081
|
+
assertKnowledgeDescriptionWithinBound: (description) => assertDescription(description),
|
|
7082
|
+
assertKnowledgeRecordTextWithinBound: (text) => assertRecordText(text),
|
|
7083
|
+
assertKnowledgeSchemaCompatible: (inspection) => assertCompatible(inspection),
|
|
7084
|
+
inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
|
|
7085
|
+
};
|
|
7086
|
+
}
|
|
7087
|
+
function createKnowledgeV2CoreLoader(features, loadStorageModule) {
|
|
7088
|
+
let knowledgeV2Core;
|
|
7089
|
+
return async () => {
|
|
7090
|
+
assertKnowledgeV2CoreSupport(features);
|
|
7091
|
+
knowledgeV2Core ??= loadStorageModule().then(resolveKnowledgeV2Core).catch((error) => {
|
|
7092
|
+
knowledgeV2Core = void 0;
|
|
7093
|
+
throw error;
|
|
7094
|
+
});
|
|
7095
|
+
return knowledgeV2Core;
|
|
7096
|
+
};
|
|
7097
|
+
}
|
|
7098
|
+
//#endregion
|
|
6050
7099
|
//#region src/storage/domains/knowledge/index.ts
|
|
6051
|
-
|
|
7100
|
+
const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(coreFeatures, () => import("@mastra/core/storage"));
|
|
6052
7101
|
async function assertKnowledgeDescriptionWithinBoundCompat(description) {
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
7102
|
+
const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
|
|
7103
|
+
assertKnowledgeDescriptionWithinBound(description);
|
|
7104
|
+
}
|
|
7105
|
+
async function assertKnowledgeRecordTextWithinBoundCompat(text) {
|
|
7106
|
+
const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
|
|
7107
|
+
assertKnowledgeRecordTextWithinBound(text);
|
|
7108
|
+
}
|
|
7109
|
+
const reconcileChains = /* @__PURE__ */ new Map();
|
|
7110
|
+
const unidentifiedClientReconcileKey = {};
|
|
7111
|
+
function withReconcileLock(key, operation) {
|
|
7112
|
+
const lockKey = typeof key === "string" ? key : unidentifiedClientReconcileKey;
|
|
7113
|
+
const result = (reconcileChains.get(lockKey) ?? Promise.resolve()).then(operation, operation);
|
|
7114
|
+
const tail = result.then(() => void 0, () => void 0);
|
|
7115
|
+
reconcileChains.set(lockKey, tail);
|
|
7116
|
+
tail.finally(() => {
|
|
7117
|
+
if (reconcileChains.get(lockKey) === tail) reconcileChains.delete(lockKey);
|
|
7118
|
+
});
|
|
7119
|
+
return result;
|
|
6062
7120
|
}
|
|
6063
|
-
|
|
7121
|
+
/**
|
|
7122
|
+
* Scope keys visible to a caller holding `scope`: a row is visible when its scope is a subset of the
|
|
7123
|
+
* caller's (companion entries such as `resource:x:uncurated` make this more than a prefix match).
|
|
7124
|
+
* Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
|
|
7125
|
+
* entries, so the list stays small.
|
|
7126
|
+
*/
|
|
7127
|
+
function visibleScopeKeys(scope) {
|
|
7128
|
+
const canonical = canonicalizeKnowledgeScope(scope);
|
|
7129
|
+
const keys = /* @__PURE__ */ new Set();
|
|
7130
|
+
for (let mask = 1; mask < 1 << canonical.length; mask++) try {
|
|
7131
|
+
keys.add(knowledgeScopeKey(canonical.filter((_, index) => mask & 1 << index)));
|
|
7132
|
+
} catch {}
|
|
7133
|
+
return [...keys];
|
|
7134
|
+
}
|
|
7135
|
+
const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
|
|
6064
7136
|
function parseJson$3(value) {
|
|
6065
7137
|
if (typeof value === "string") return JSON.parse(value);
|
|
6066
7138
|
if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
|
|
@@ -6090,6 +7162,7 @@ function parseNode(row) {
|
|
|
6090
7162
|
kind: row.kind == null ? "" : String(row.kind),
|
|
6091
7163
|
content: row.content == null ? void 0 : String(row.content),
|
|
6092
7164
|
description: row.description == null ? void 0 : String(row.description),
|
|
7165
|
+
isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
|
|
6093
7166
|
scope: parseJson$3(row.scopeJson ?? row.scope),
|
|
6094
7167
|
version: Number(row.version),
|
|
6095
7168
|
mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
|
|
@@ -6106,7 +7179,6 @@ function parseKnowledge(row) {
|
|
|
6106
7179
|
sourceThreadId: String(row.sourceThreadId),
|
|
6107
7180
|
capturedAt: toDate(row.capturedAt),
|
|
6108
7181
|
when: optionalDate(row.when),
|
|
6109
|
-
maxScope: row.maxScope == null ? void 0 : String(row.maxScope),
|
|
6110
7182
|
metadata: (row.metadataJson ?? row.metadata) == null ? void 0 : parseJson$3(row.metadataJson ?? row.metadata),
|
|
6111
7183
|
deletedAt: optionalDate(row.deletedAt),
|
|
6112
7184
|
deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
|
|
@@ -6129,100 +7201,458 @@ function parseOutbox(row) {
|
|
|
6129
7201
|
completedAt: optionalDate(row.completedAt)
|
|
6130
7202
|
};
|
|
6131
7203
|
}
|
|
7204
|
+
const KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE = "Knowledge curation cursors were removed: observation-time curate is the only Knowledge writer and needs no cursor.";
|
|
7205
|
+
const knowledgeV2TableSchemas = /* @__PURE__ */ new Map([
|
|
7206
|
+
[TABLE_KNOWLEDGE_NODES, KNOWLEDGE_V2_NODES_SCHEMA],
|
|
7207
|
+
[TABLE_KNOWLEDGE_RECORDS, KNOWLEDGE_V2_RECORDS_SCHEMA],
|
|
7208
|
+
[TABLE_KNOWLEDGE_MENTIONS, KNOWLEDGE_V2_MENTIONS_SCHEMA],
|
|
7209
|
+
[TABLE_KNOWLEDGE_ACTIVITY, KNOWLEDGE_V2_ACTIVITY_SCHEMA],
|
|
7210
|
+
[TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA],
|
|
7211
|
+
[TABLE_KNOWLEDGE_NODE_SCOPES, KNOWLEDGE_NODE_SCOPES_SCHEMA],
|
|
7212
|
+
[TABLE_KNOWLEDGE_RECORD_SCOPES, KNOWLEDGE_RECORD_SCOPES_SCHEMA],
|
|
7213
|
+
[TABLE_KNOWLEDGE_SCOPE_GRANTS, KNOWLEDGE_SCOPE_GRANTS_SCHEMA],
|
|
7214
|
+
[TABLE_KNOWLEDGE_ACCESS_STATE, KNOWLEDGE_ACCESS_STATE_SCHEMA],
|
|
7215
|
+
[TABLE_KNOWLEDGE_SCOPE_ADDRESSES, KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA],
|
|
7216
|
+
[TABLE_KNOWLEDGE_NODE_ADDRESSES, KNOWLEDGE_NODE_ADDRESSES_SCHEMA],
|
|
7217
|
+
[TABLE_KNOWLEDGE_IMPORT_STATE, KNOWLEDGE_IMPORT_STATE_SCHEMA],
|
|
7218
|
+
[TABLE_KNOWLEDGE_IMPORT_RUNS, KNOWLEDGE_IMPORT_RUNS_SCHEMA],
|
|
7219
|
+
[TABLE_KNOWLEDGE_PROPOSALS, KNOWLEDGE_PROPOSALS_SCHEMA]
|
|
7220
|
+
]);
|
|
7221
|
+
function canonicalizeLibSQLUrl(url) {
|
|
7222
|
+
if (url.includes(":memory:")) return /[?&]cache=shared(?:&|$)/.test(url) ? "file::memory:?cache=shared" : void 0;
|
|
7223
|
+
if (url.startsWith("file:")) {
|
|
7224
|
+
const [path] = url.slice(5).split("?");
|
|
7225
|
+
if (url.startsWith("file://")) {
|
|
7226
|
+
const parsed = new URL(url);
|
|
7227
|
+
const host = parsed.hostname.toLocaleLowerCase();
|
|
7228
|
+
const filePath = host && host !== "localhost" ? `//${host}${parsed.pathname}` : parsed.pathname;
|
|
7229
|
+
return `file:${resolve(decodeURIComponent(filePath))}`;
|
|
7230
|
+
}
|
|
7231
|
+
return `file:${resolve(decodeURIComponent(path))}`;
|
|
7232
|
+
}
|
|
7233
|
+
try {
|
|
7234
|
+
const parsed = new URL(url);
|
|
7235
|
+
const port = parsed.port || (parsed.protocol === "libsql:" || parsed.protocol === "https:" ? "443" : "80");
|
|
7236
|
+
return `${parsed.protocol}//${parsed.hostname.toLocaleLowerCase()}:${port}${parsed.pathname}`;
|
|
7237
|
+
} catch {
|
|
7238
|
+
return url;
|
|
7239
|
+
}
|
|
7240
|
+
}
|
|
7241
|
+
const unidentifiedClientIsolationKey = {};
|
|
7242
|
+
function getLibSQLKnowledgeIsolationKey(config, client) {
|
|
7243
|
+
if (config.storageIsolationKey !== void 0) return config.storageIsolationKey;
|
|
7244
|
+
if (!config.url) return unidentifiedClientIsolationKey;
|
|
7245
|
+
const urlKey = canonicalizeLibSQLUrl(config.url);
|
|
7246
|
+
return urlKey ? `libsql:${urlKey}` : client ?? config.client ?? config;
|
|
7247
|
+
}
|
|
6132
7248
|
var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
6133
7249
|
#client;
|
|
6134
7250
|
#db;
|
|
6135
7251
|
constructor(config) {
|
|
6136
|
-
|
|
6137
|
-
|
|
7252
|
+
const client = resolveClient(config);
|
|
7253
|
+
const storageIsolationKey = config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, client);
|
|
7254
|
+
super({ storageIsolationKey });
|
|
7255
|
+
this.#client = client;
|
|
6138
7256
|
this.#db = new LibSQLDB({
|
|
6139
7257
|
client: this.#client,
|
|
6140
7258
|
maxRetries: config.maxRetries,
|
|
6141
7259
|
initialBackoffMs: config.initialBackoffMs
|
|
6142
7260
|
});
|
|
6143
7261
|
}
|
|
7262
|
+
getCapabilities() {
|
|
7263
|
+
return {
|
|
7264
|
+
contractVersion: 2,
|
|
7265
|
+
schemaVersion: 2,
|
|
7266
|
+
supportsV2: true,
|
|
7267
|
+
supportsSchemaInspection: true,
|
|
7268
|
+
supportsExplicitReset: true
|
|
7269
|
+
};
|
|
7270
|
+
}
|
|
7271
|
+
async inspectSchema() {
|
|
7272
|
+
return this.#inspectSchema(this.#client);
|
|
7273
|
+
}
|
|
7274
|
+
async #inspectSchema(executor) {
|
|
7275
|
+
const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
|
|
7276
|
+
try {
|
|
7277
|
+
const placeholders = KNOWLEDGE_TABLE_NAMES.map(() => "?").join(",");
|
|
7278
|
+
const tableNames = (await executor.execute({
|
|
7279
|
+
sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders})`,
|
|
7280
|
+
args: [...KNOWLEDGE_TABLE_NAMES]
|
|
7281
|
+
})).rows.map((row) => String(row.name));
|
|
7282
|
+
if (tableNames.length === 0) return inspectKnowledgeSchema({
|
|
7283
|
+
available: true,
|
|
7284
|
+
tableNames
|
|
7285
|
+
});
|
|
7286
|
+
const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
|
|
7287
|
+
if (missingTables.length > 0) return inspectKnowledgeSchema({
|
|
7288
|
+
available: true,
|
|
7289
|
+
tableNames,
|
|
7290
|
+
reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
|
|
7291
|
+
});
|
|
7292
|
+
for (const [table, schema] of knowledgeV2TableSchemas) {
|
|
7293
|
+
const columns = await executor.execute(`PRAGMA table_info("${table}")`);
|
|
7294
|
+
const actual = new Set(columns.rows.map((row) => String(row.name)));
|
|
7295
|
+
const missing = Object.keys(schema).filter((column) => !actual.has(column));
|
|
7296
|
+
if (missing.length > 0) return inspectKnowledgeSchema({
|
|
7297
|
+
available: true,
|
|
7298
|
+
tableNames,
|
|
7299
|
+
reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
|
|
7300
|
+
});
|
|
7301
|
+
}
|
|
7302
|
+
const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
|
|
7303
|
+
if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
|
|
7304
|
+
available: true,
|
|
7305
|
+
tableNames,
|
|
7306
|
+
reason: "Knowledge schema version marker is missing or incompatible"
|
|
7307
|
+
});
|
|
7308
|
+
return inspectKnowledgeSchema({
|
|
7309
|
+
available: true,
|
|
7310
|
+
tableNames,
|
|
7311
|
+
schemaVersion: 2
|
|
7312
|
+
});
|
|
7313
|
+
} catch (error) {
|
|
7314
|
+
return inspectKnowledgeSchema({
|
|
7315
|
+
available: false,
|
|
7316
|
+
tableNames: [],
|
|
7317
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
7318
|
+
});
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
6144
7321
|
async init() {
|
|
6145
|
-
await this.#
|
|
6146
|
-
|
|
6147
|
-
|
|
7322
|
+
await this.#transaction((tx) => this.#initializeSchema(tx));
|
|
7323
|
+
}
|
|
7324
|
+
async #initializeSchema(tx) {
|
|
7325
|
+
const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
|
|
7326
|
+
let inspection = await this.#inspectSchema(tx);
|
|
7327
|
+
if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
|
|
7328
|
+
for (const table of [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()]) await tx.execute(`DROP TABLE IF EXISTS "${table}"`);
|
|
7329
|
+
inspection = await this.#inspectSchema(tx);
|
|
7330
|
+
}
|
|
7331
|
+
assertKnowledgeSchemaCompatible(inspection);
|
|
7332
|
+
const createTable = (input) => this.#db.createTable({
|
|
7333
|
+
...input,
|
|
7334
|
+
executor: tx
|
|
6148
7335
|
});
|
|
6149
|
-
await
|
|
7336
|
+
await createTable({
|
|
6150
7337
|
tableName: TABLE_KNOWLEDGE_NODES,
|
|
6151
|
-
schema:
|
|
6152
|
-
ifNotExists: ["description"]
|
|
7338
|
+
schema: KNOWLEDGE_V2_NODES_SCHEMA
|
|
6153
7339
|
});
|
|
6154
|
-
await
|
|
7340
|
+
await createTable({
|
|
6155
7341
|
tableName: TABLE_KNOWLEDGE_RECORDS,
|
|
6156
|
-
schema:
|
|
7342
|
+
schema: KNOWLEDGE_V2_RECORDS_SCHEMA
|
|
6157
7343
|
});
|
|
6158
|
-
await
|
|
7344
|
+
await createTable({
|
|
6159
7345
|
tableName: TABLE_KNOWLEDGE_MENTIONS,
|
|
6160
|
-
schema:
|
|
7346
|
+
schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
|
|
7347
|
+
compositePrimaryKey: ["recordId", "sourceId"]
|
|
7348
|
+
});
|
|
7349
|
+
await createTable({
|
|
7350
|
+
tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
|
|
7351
|
+
schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
|
|
7352
|
+
compositePrimaryKey: ["nodeId", "scopeNodeId"]
|
|
7353
|
+
});
|
|
7354
|
+
await createTable({
|
|
7355
|
+
tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
|
|
7356
|
+
schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
|
|
7357
|
+
compositePrimaryKey: ["recordId", "scopeNodeId"]
|
|
7358
|
+
});
|
|
7359
|
+
await createTable({
|
|
7360
|
+
tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
|
|
7361
|
+
schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
|
|
7362
|
+
compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
|
|
7363
|
+
});
|
|
7364
|
+
await createTable({
|
|
7365
|
+
tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
|
|
7366
|
+
schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
|
|
7367
|
+
});
|
|
7368
|
+
await createTable({
|
|
7369
|
+
tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
|
|
7370
|
+
schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
|
|
7371
|
+
});
|
|
7372
|
+
await createTable({
|
|
7373
|
+
tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
|
|
7374
|
+
schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
|
|
7375
|
+
compositePrimaryKey: ["source", "address"]
|
|
7376
|
+
});
|
|
7377
|
+
await createTable({
|
|
7378
|
+
tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
|
|
7379
|
+
schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
|
|
6161
7380
|
compositePrimaryKey: [
|
|
6162
|
-
"
|
|
6163
|
-
"
|
|
6164
|
-
"
|
|
7381
|
+
"importerId",
|
|
7382
|
+
"binding",
|
|
7383
|
+
"key"
|
|
6165
7384
|
]
|
|
6166
7385
|
});
|
|
6167
|
-
await
|
|
6168
|
-
tableName:
|
|
6169
|
-
schema:
|
|
6170
|
-
compositePrimaryKey: ["sourceThreadId", "agent"]
|
|
7386
|
+
await createTable({
|
|
7387
|
+
tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
7388
|
+
schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
|
|
6171
7389
|
});
|
|
6172
|
-
await
|
|
6173
|
-
tableName:
|
|
6174
|
-
schema:
|
|
7390
|
+
await createTable({
|
|
7391
|
+
tableName: TABLE_KNOWLEDGE_PROPOSALS,
|
|
7392
|
+
schema: KNOWLEDGE_PROPOSALS_SCHEMA
|
|
6175
7393
|
});
|
|
6176
|
-
await
|
|
6177
|
-
tableName:
|
|
6178
|
-
schema:
|
|
7394
|
+
await createTable({
|
|
7395
|
+
tableName: TABLE_KNOWLEDGE_ACTIVITY,
|
|
7396
|
+
schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
|
|
6179
7397
|
});
|
|
6180
|
-
await
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
{
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
},
|
|
6189
|
-
{
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
},
|
|
6193
|
-
{
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
},
|
|
6197
|
-
{
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
},
|
|
6201
|
-
{
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
},
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
7398
|
+
await createTable({
|
|
7399
|
+
tableName: TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
7400
|
+
schema: KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
7401
|
+
});
|
|
7402
|
+
for (const statement of [
|
|
7403
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_nodes_identity ON "${TABLE_KNOWLEDGE_NODES}" (type, scopeKey, canonicalName)`,
|
|
7404
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON "${TABLE_KNOWLEDGE_NODES}" (scopeKey, type)`,
|
|
7405
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_name ON "${TABLE_KNOWLEDGE_NODES}" (type, canonicalName)`,
|
|
7406
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (node, id DESC)`,
|
|
7407
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_records_thread_latest ON "${TABLE_KNOWLEDGE_RECORDS}" (sourceThreadId, id DESC)`,
|
|
7408
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_mentions_record ON "${TABLE_KNOWLEDGE_MENTIONS}" (recordId, sourceType, sourceId)`,
|
|
7409
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON "${TABLE_KNOWLEDGE_ACTIVITY}" (id DESC)`,
|
|
7410
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_records_scope ON "${TABLE_KNOWLEDGE_RECORDS}" (scopeKey, id)`,
|
|
7411
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_activity_scope ON "${TABLE_KNOWLEDGE_ACTIVITY}" (scopeKey, id DESC)`,
|
|
7412
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (idempotencyKey)`,
|
|
7413
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON "${TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (status, availableAt, createdAt)`,
|
|
7414
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_node_scopes_scope ON "${TABLE_KNOWLEDGE_NODE_SCOPES}" (scopeNodeId, nodeId)`,
|
|
7415
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_record_scopes_scope ON "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (scopeNodeId, recordId)`,
|
|
7416
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_scope_grants_ref ON "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeRefId, scopeNodeId)`,
|
|
7417
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_node_addresses_node ON "${TABLE_KNOWLEDGE_NODE_ADDRESSES}" (nodeId)`,
|
|
7418
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_import_runs_lookup ON "${TABLE_KNOWLEDGE_IMPORT_RUNS}" (importerId, binding, queuedAt DESC)`,
|
|
7419
|
+
`CREATE INDEX IF NOT EXISTS idx_knowledge_activity_import_run ON "${TABLE_KNOWLEDGE_ACTIVITY}" (importRunId, id DESC)`
|
|
7420
|
+
]) await tx.execute(statement);
|
|
7421
|
+
await tx.execute({
|
|
7422
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?)`,
|
|
7423
|
+
args: [2]
|
|
7424
|
+
});
|
|
7425
|
+
}
|
|
7426
|
+
/**
|
|
7427
|
+
* True when the only Knowledge objects are the tables and indexes published v1 adapters created,
|
|
7428
|
+
* with or without rows. Anything else (v2 tables, unknown tables, views, triggers) needs an
|
|
7429
|
+
* explicit reset.
|
|
7430
|
+
*/
|
|
7431
|
+
async #isPublishedV1Layout(executor) {
|
|
7432
|
+
const objects = await executor.execute("SELECT type, name FROM sqlite_master WHERE name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR tbl_name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR sql LIKE '%mastra\\_knowledge\\_%' ESCAPE '\\'");
|
|
7433
|
+
for (const row of objects.rows) {
|
|
7434
|
+
const type = String(row.type);
|
|
7435
|
+
const name = String(row.name);
|
|
7436
|
+
if (type === "table" && PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) continue;
|
|
7437
|
+
const knownIndex = PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) || name.startsWith("sqlite_autoindex_");
|
|
7438
|
+
if (type !== "index" || !knownIndex) return false;
|
|
7439
|
+
}
|
|
7440
|
+
return true;
|
|
6214
7441
|
}
|
|
6215
7442
|
async dangerouslyClearAll() {
|
|
6216
7443
|
await this.#transaction(async (tx) => {
|
|
6217
7444
|
for (const table of [
|
|
7445
|
+
TABLE_KNOWLEDGE_RECORD_SCOPES,
|
|
7446
|
+
TABLE_KNOWLEDGE_NODE_SCOPES,
|
|
7447
|
+
TABLE_KNOWLEDGE_SCOPE_GRANTS,
|
|
7448
|
+
TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
|
|
7449
|
+
TABLE_KNOWLEDGE_NODE_ADDRESSES,
|
|
6218
7450
|
TABLE_KNOWLEDGE_MENTIONS,
|
|
6219
|
-
|
|
6220
|
-
TABLE_KNOWLEDGE_NODES,
|
|
6221
|
-
TABLE_KNOWLEDGE_CURSORS,
|
|
7451
|
+
TABLE_KNOWLEDGE_PROPOSALS,
|
|
6222
7452
|
TABLE_KNOWLEDGE_ACTIVITY,
|
|
6223
|
-
|
|
7453
|
+
TABLE_KNOWLEDGE_IMPORT_STATE,
|
|
7454
|
+
TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
7455
|
+
TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
7456
|
+
TABLE_KNOWLEDGE_RECORDS,
|
|
7457
|
+
TABLE_KNOWLEDGE_NODES
|
|
6224
7458
|
]) await tx.execute(`DELETE FROM "${table}"`);
|
|
7459
|
+
await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
|
|
7460
|
+
});
|
|
7461
|
+
}
|
|
7462
|
+
async dangerouslyReset() {
|
|
7463
|
+
await withClientWriteLock(this.#client, async () => {
|
|
7464
|
+
await this.#client.batch([...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => ({
|
|
7465
|
+
sql: `DROP TABLE IF EXISTS "${table}"`,
|
|
7466
|
+
args: []
|
|
7467
|
+
})), "write");
|
|
7468
|
+
});
|
|
7469
|
+
await this.init();
|
|
7470
|
+
}
|
|
7471
|
+
async reconcileStructure(plan) {
|
|
7472
|
+
return withReconcileLock(this.getStorageIsolationKey(), () => this.#transaction(async (tx) => {
|
|
7473
|
+
await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch WHERE id='global'`);
|
|
7474
|
+
const scopes = {};
|
|
7475
|
+
const createdScopeIds = [];
|
|
7476
|
+
const createdAddresses = /* @__PURE__ */ new Set();
|
|
7477
|
+
const retrofit = plan.retrofit ?? true;
|
|
7478
|
+
let structureChanged = false;
|
|
7479
|
+
const deletedScopeAddresses = /* @__PURE__ */ new Set();
|
|
7480
|
+
const resolveAddress = async (address) => {
|
|
7481
|
+
if (scopes[address]) return scopes[address];
|
|
7482
|
+
const row = (await tx.execute({
|
|
7483
|
+
sql: `SELECT a.scopeNodeId,n.isScope,n.deletedAt FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=a.scopeNodeId WHERE a.address=?`,
|
|
7484
|
+
args: [address]
|
|
7485
|
+
})).rows[0];
|
|
7486
|
+
if (!row) return void 0;
|
|
7487
|
+
if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
|
|
7488
|
+
if (row.deletedAt) deletedScopeAddresses.add(address);
|
|
7489
|
+
scopes[address] = String(row.scopeNodeId);
|
|
7490
|
+
return scopes[address];
|
|
7491
|
+
};
|
|
7492
|
+
for (const scope of plan.scopes) {
|
|
7493
|
+
if (await resolveAddress(scope.address)) continue;
|
|
7494
|
+
const id = randomUUID();
|
|
7495
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7496
|
+
await tx.execute({
|
|
7497
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,description,isScope,metadata,version,createdAt,updatedAt) VALUES (?,'node',?,?,?,?,TRUE,jsonb(?),1,?,?)`,
|
|
7498
|
+
args: [
|
|
7499
|
+
id,
|
|
7500
|
+
scope.name,
|
|
7501
|
+
canonicalName(scope.name),
|
|
7502
|
+
scope.kind ?? null,
|
|
7503
|
+
scope.description ?? null,
|
|
7504
|
+
scope.metadata ? JSON.stringify(scope.metadata) : null,
|
|
7505
|
+
now,
|
|
7506
|
+
now
|
|
7507
|
+
]
|
|
7508
|
+
});
|
|
7509
|
+
await tx.execute({
|
|
7510
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,scopeNodeId) VALUES (?,?)`,
|
|
7511
|
+
args: [scope.address, id]
|
|
7512
|
+
});
|
|
7513
|
+
scopes[scope.address] = id;
|
|
7514
|
+
createdAddresses.add(scope.address);
|
|
7515
|
+
createdScopeIds.push(id);
|
|
7516
|
+
structureChanged = true;
|
|
7517
|
+
}
|
|
7518
|
+
for (const scope of plan.scopes) {
|
|
7519
|
+
if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
|
|
7520
|
+
const scopeNodeId = scopes[scope.address];
|
|
7521
|
+
for (const parentAddress of scope.parentAddresses ?? []) {
|
|
7522
|
+
const parentId = await resolveAddress(parentAddress);
|
|
7523
|
+
if (!parentId || deletedScopeAddresses.has(parentAddress)) {
|
|
7524
|
+
const deletedParentId = scopes[parentAddress];
|
|
7525
|
+
if ((deletedParentId ? await tx.execute({
|
|
7526
|
+
sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
|
|
7527
|
+
args: [scopeNodeId, deletedParentId]
|
|
7528
|
+
}) : void 0)?.rows.length) continue;
|
|
7529
|
+
throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
|
|
7530
|
+
}
|
|
7531
|
+
if ((await tx.execute({
|
|
7532
|
+
sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
|
|
7533
|
+
args: [scopeNodeId, parentId]
|
|
7534
|
+
})).rows.length) continue;
|
|
7535
|
+
if ((await tx.execute({
|
|
7536
|
+
sql: `SELECT n.id FROM "mastra_knowledge_node_scopes" ns JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=ns.nodeId WHERE ns.scopeNodeId=? AND n.isScope AND n.canonicalName=? AND n.deletedAt IS NULL AND n.id<>? LIMIT 1`,
|
|
7537
|
+
args: [
|
|
7538
|
+
parentId,
|
|
7539
|
+
canonicalName(scope.name),
|
|
7540
|
+
scopeNodeId
|
|
7541
|
+
]
|
|
7542
|
+
})).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
|
|
7543
|
+
const inserted = await tx.execute({
|
|
7544
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
|
|
7545
|
+
args: [
|
|
7546
|
+
scopeNodeId,
|
|
7547
|
+
parentId,
|
|
7548
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
7549
|
+
]
|
|
7550
|
+
});
|
|
7551
|
+
structureChanged ||= inserted.rowsAffected > 0;
|
|
7552
|
+
}
|
|
7553
|
+
for (const grant of scope.grants ?? []) {
|
|
7554
|
+
const scopeRefId = await resolveAddress(grant.scopeRefAddress);
|
|
7555
|
+
if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
|
|
7556
|
+
const deletedScopeRefId = scopes[grant.scopeRefAddress];
|
|
7557
|
+
if ((deletedScopeRefId ? await tx.execute({
|
|
7558
|
+
sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE scopeNodeId=? AND scopeRefId=?`,
|
|
7559
|
+
args: [scopeNodeId, deletedScopeRefId]
|
|
7560
|
+
}) : void 0)?.rows.length) continue;
|
|
7561
|
+
throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
|
|
7562
|
+
}
|
|
7563
|
+
const inserted = await tx.execute({
|
|
7564
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeNodeId,scopeRefId,role,canSuggest) VALUES (?,?,?,?)`,
|
|
7565
|
+
args: [
|
|
7566
|
+
scopeNodeId,
|
|
7567
|
+
scopeRefId,
|
|
7568
|
+
grant.role,
|
|
7569
|
+
grant.canSuggest ?? null
|
|
7570
|
+
]
|
|
7571
|
+
});
|
|
7572
|
+
structureChanged ||= inserted.rowsAffected > 0;
|
|
7573
|
+
}
|
|
7574
|
+
}
|
|
7575
|
+
if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
|
|
7576
|
+
const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
|
|
7577
|
+
return {
|
|
7578
|
+
scopes,
|
|
7579
|
+
createdScopeIds,
|
|
7580
|
+
deletedScopeAddresses: [...deletedScopeAddresses],
|
|
7581
|
+
changed: structureChanged,
|
|
7582
|
+
accessEpoch: Number(state.rows[0]?.epoch ?? 0)
|
|
7583
|
+
};
|
|
7584
|
+
}));
|
|
7585
|
+
}
|
|
7586
|
+
async listScopeNodes(input = {}) {
|
|
7587
|
+
const { limit, after } = parseListKnowledgeScopeNodesInput(input);
|
|
7588
|
+
if (input.addresses?.length === 0 || input.ids?.length === 0) return {
|
|
7589
|
+
scopes: [],
|
|
7590
|
+
nextCursor: null
|
|
7591
|
+
};
|
|
7592
|
+
const args = [];
|
|
7593
|
+
const where = [`n.isScope`, `n.deletedAt IS NULL`];
|
|
7594
|
+
let within = "";
|
|
7595
|
+
if (input.withinAddress !== void 0) {
|
|
7596
|
+
within = `WITH RECURSIVE within_scope(id) AS (SELECT r.id FROM "${TABLE_KNOWLEDGE_NODES}" r JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" ra ON ra.scopeNodeId=r.id WHERE ra.address=? AND r.isScope AND r.deletedAt IS NULL UNION SELECT ns.nodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns JOIN within_scope w ON ns.scopeNodeId=w.id JOIN "${TABLE_KNOWLEDGE_NODES}" c ON c.id=ns.nodeId WHERE c.isScope AND c.deletedAt IS NULL) `;
|
|
7597
|
+
args.push(input.withinAddress);
|
|
7598
|
+
where.push("n.id IN (SELECT id FROM within_scope)");
|
|
7599
|
+
}
|
|
7600
|
+
if (input.addresses) {
|
|
7601
|
+
where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
|
|
7602
|
+
args.push(...input.addresses);
|
|
7603
|
+
}
|
|
7604
|
+
if (input.ids) {
|
|
7605
|
+
where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
|
|
7606
|
+
args.push(...input.ids);
|
|
7607
|
+
}
|
|
7608
|
+
if (after) {
|
|
7609
|
+
where.push("(n.name > ? OR (n.name = ? AND n.id > ?))");
|
|
7610
|
+
args.push(after.name, after.name, after.id);
|
|
7611
|
+
}
|
|
7612
|
+
args.push(limit + 1);
|
|
7613
|
+
const rows = (await this.#client.execute({
|
|
7614
|
+
sql: `${within}SELECT n.id,n.name,n.kind,n.description,a.address FROM "${TABLE_KNOWLEDGE_NODES}" n LEFT JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a ON a.scopeNodeId=n.id WHERE ${where.join(" AND ")} ORDER BY n.name, n.id LIMIT ?`,
|
|
7615
|
+
args
|
|
7616
|
+
})).rows;
|
|
7617
|
+
if (rows.length === 0) return {
|
|
7618
|
+
scopes: [],
|
|
7619
|
+
nextCursor: null
|
|
7620
|
+
};
|
|
7621
|
+
const parents = await this.#client.execute({
|
|
7622
|
+
sql: `SELECT ns.nodeId,ns.scopeNodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId IN (${rows.map(() => "?").join(",")}) ORDER BY ns.scopeNodeId`,
|
|
7623
|
+
args: rows.map((row) => String(row.id))
|
|
6225
7624
|
});
|
|
7625
|
+
const parentsByScopeId = /* @__PURE__ */ new Map();
|
|
7626
|
+
for (const row of parents.rows) {
|
|
7627
|
+
const scopeId = String(row.nodeId);
|
|
7628
|
+
const parentId = String(row.scopeNodeId);
|
|
7629
|
+
const existing = parentsByScopeId.get(scopeId);
|
|
7630
|
+
if (existing) existing.push(parentId);
|
|
7631
|
+
else parentsByScopeId.set(scopeId, [parentId]);
|
|
7632
|
+
}
|
|
7633
|
+
return pageKnowledgeScopeNodes(rows.map((row) => ({
|
|
7634
|
+
id: String(row.id),
|
|
7635
|
+
address: String(row.address),
|
|
7636
|
+
name: String(row.name),
|
|
7637
|
+
...row.kind == null ? {} : { kind: String(row.kind) },
|
|
7638
|
+
...row.description == null ? {} : { description: String(row.description) },
|
|
7639
|
+
parentIds: parentsByScopeId.get(String(row.id)) ?? []
|
|
7640
|
+
})), limit, input);
|
|
7641
|
+
}
|
|
7642
|
+
async listScopeMembers(input) {
|
|
7643
|
+
const { limit, after } = parseListKnowledgeScopeMembersInput(input);
|
|
7644
|
+
const args = [input.scopeNodeId];
|
|
7645
|
+
let keyset = "";
|
|
7646
|
+
if (after) {
|
|
7647
|
+
const updatedAt = after.updatedAt.toISOString();
|
|
7648
|
+
keyset = ` AND (n.updatedAt < ? OR (n.updatedAt = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
|
|
7649
|
+
args.push(updatedAt, updatedAt, after.name, after.name, after.id);
|
|
7650
|
+
}
|
|
7651
|
+
args.push(limit + 1);
|
|
7652
|
+
return pageKnowledgeScopeMembers((await this.#client.execute({
|
|
7653
|
+
sql: `SELECT *, json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" n WHERE EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId=n.id AND ns.scopeNodeId=?) AND n.deletedAt IS NULL AND n.mergedInto IS NULL${keyset} ORDER BY n.updatedAt DESC, n.name ASC, n.id ASC LIMIT ?`,
|
|
7654
|
+
args
|
|
7655
|
+
})).rows.map(parseNode), limit, input);
|
|
6226
7656
|
}
|
|
6227
7657
|
async createNode(input) {
|
|
6228
7658
|
await assertKnowledgeDescriptionWithinBoundCompat(input.description);
|
|
@@ -6232,6 +7662,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6232
7662
|
if (existing) {
|
|
6233
7663
|
const terminal = await this.#resolveTerminalNode(tx, existing.id);
|
|
6234
7664
|
if (!isKnowledgeScopeVisible(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
|
|
7665
|
+
await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
|
|
6235
7666
|
return terminal;
|
|
6236
7667
|
}
|
|
6237
7668
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -6248,7 +7679,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6248
7679
|
updatedAt: now
|
|
6249
7680
|
};
|
|
6250
7681
|
await tx.execute({
|
|
6251
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
|
|
7682
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,FALSE,jsonb(?),?,?,NULL,?,?)`,
|
|
6252
7683
|
args: [
|
|
6253
7684
|
node.id,
|
|
6254
7685
|
"node",
|
|
@@ -6264,6 +7695,8 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6264
7695
|
now.toISOString()
|
|
6265
7696
|
]
|
|
6266
7697
|
});
|
|
7698
|
+
await this.#replaceNodeScopes(tx, node.id, scope, now);
|
|
7699
|
+
await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
|
|
6267
7700
|
await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
|
|
6268
7701
|
await this.#activity(tx, "node-created", "node", node.id, scope);
|
|
6269
7702
|
await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
|
|
@@ -6280,13 +7713,13 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6280
7713
|
return this.#resolveNode(this.#client, input.name, canonicalizeKnowledgeScope(input.scope));
|
|
6281
7714
|
}
|
|
6282
7715
|
async listNodes(input) {
|
|
6283
|
-
const
|
|
7716
|
+
const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
|
|
6284
7717
|
const clauses = [
|
|
6285
7718
|
`type = 'node'`,
|
|
6286
7719
|
"mergedInto IS NULL",
|
|
6287
|
-
visibleSql
|
|
7720
|
+
visibleSql(keys)
|
|
6288
7721
|
];
|
|
6289
|
-
const args = [
|
|
7722
|
+
const args = [...keys];
|
|
6290
7723
|
if (input.namePrefix) {
|
|
6291
7724
|
clauses.push("canonicalName LIKE ? ESCAPE '='");
|
|
6292
7725
|
args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
|
|
@@ -6338,6 +7771,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6338
7771
|
input.version
|
|
6339
7772
|
]
|
|
6340
7773
|
})).rowsAffected === 0) throw new KnowledgeConflictError(input.id);
|
|
7774
|
+
if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
|
|
6341
7775
|
if (input.content !== void 0 || input.name !== void 0 || input.scope !== void 0) await this.#replaceMentions(tx, "node", input.id, content ?? "", input.resolutionScope ?? scope, scope);
|
|
6342
7776
|
await this.#activity(tx, "node-updated", "node", input.id, scope);
|
|
6343
7777
|
if (knowledgeScopeKey(existing.scope) !== knowledgeScopeKey(scope)) {
|
|
@@ -6392,8 +7826,12 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6392
7826
|
]
|
|
6393
7827
|
})).rowsAffected === 0) throw new KnowledgeConflictError(source.id);
|
|
6394
7828
|
await tx.execute({
|
|
6395
|
-
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=? WHERE node=?`,
|
|
6396
|
-
args: [
|
|
7829
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
|
|
7830
|
+
args: [
|
|
7831
|
+
target.id,
|
|
7832
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
7833
|
+
source.id
|
|
7834
|
+
]
|
|
6397
7835
|
});
|
|
6398
7836
|
await tx.execute({
|
|
6399
7837
|
sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE recordId=? AND EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_MENTIONS}" target WHERE target.sourceType="${TABLE_KNOWLEDGE_MENTIONS}".sourceType AND target.sourceId="${TABLE_KNOWLEDGE_MENTIONS}".sourceId AND target.recordId=?)`,
|
|
@@ -6433,10 +7871,10 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6433
7871
|
});
|
|
6434
7872
|
}
|
|
6435
7873
|
async appendKnowledge(input) {
|
|
7874
|
+
await assertKnowledgeRecordTextWithinBoundCompat(input.text);
|
|
6436
7875
|
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
6437
7876
|
const resolutionScope = canonicalizeKnowledgeScope(input.resolutionScope);
|
|
6438
7877
|
const defaultScope = canonicalizeKnowledgeScope(input.defaultScope);
|
|
6439
|
-
assertKnowledgeScopeWithinCeiling(scope, input.maxScope);
|
|
6440
7878
|
return this.#transaction(async (tx) => {
|
|
6441
7879
|
const node = nodeReferenceId(input.node);
|
|
6442
7880
|
const parent = await this.#resolveTerminalNode(tx, node);
|
|
@@ -6449,11 +7887,10 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6449
7887
|
sourceThreadId: input.sourceThreadId,
|
|
6450
7888
|
capturedAt: /* @__PURE__ */ new Date(),
|
|
6451
7889
|
when: input.when ? new Date(input.when) : void 0,
|
|
6452
|
-
maxScope: input.maxScope,
|
|
6453
7890
|
metadata: input.metadata
|
|
6454
7891
|
};
|
|
6455
7892
|
await tx.execute({
|
|
6456
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",
|
|
7893
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",metadata,version,updatedAt,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,jsonb(?),?,?,NULL,NULL)`,
|
|
6457
7894
|
args: [
|
|
6458
7895
|
record.id,
|
|
6459
7896
|
record.node,
|
|
@@ -6463,10 +7900,12 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6463
7900
|
record.sourceThreadId,
|
|
6464
7901
|
record.capturedAt.toISOString(),
|
|
6465
7902
|
record.when?.toISOString() ?? null,
|
|
6466
|
-
record.
|
|
6467
|
-
|
|
7903
|
+
record.metadata ? JSON.stringify(record.metadata) : null,
|
|
7904
|
+
1,
|
|
7905
|
+
record.capturedAt.toISOString()
|
|
6468
7906
|
]
|
|
6469
7907
|
});
|
|
7908
|
+
await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
|
|
6470
7909
|
await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
|
|
6471
7910
|
await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
|
|
6472
7911
|
await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
|
|
@@ -6490,17 +7929,13 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6490
7929
|
return this.#queryKnowledge(input, "related");
|
|
6491
7930
|
}
|
|
6492
7931
|
async knowledgeBySource(input) {
|
|
6493
|
-
const
|
|
6494
|
-
const args = [
|
|
6495
|
-
input.sourceThreadId,
|
|
6496
|
-
key,
|
|
6497
|
-
key
|
|
6498
|
-
];
|
|
7932
|
+
const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
|
|
7933
|
+
const args = [input.sourceThreadId, ...keys];
|
|
6499
7934
|
if (input.after) args.push(input.after);
|
|
6500
7935
|
const limit = input.limit ?? 100;
|
|
6501
7936
|
args.push(limit + 1);
|
|
6502
7937
|
const records = (await this.#client.execute({
|
|
6503
|
-
sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
|
|
7938
|
+
sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql(keys)}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
|
|
6504
7939
|
args
|
|
6505
7940
|
})).rows.map(parseKnowledge);
|
|
6506
7941
|
return {
|
|
@@ -6515,10 +7950,11 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6515
7950
|
if (record.deletedAt) return record;
|
|
6516
7951
|
const deletedAt = /* @__PURE__ */ new Date();
|
|
6517
7952
|
await tx.execute({
|
|
6518
|
-
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=? WHERE id=? AND deletedAt IS NULL`,
|
|
7953
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
|
|
6519
7954
|
args: [
|
|
6520
7955
|
deletedAt.toISOString(),
|
|
6521
7956
|
input.deletedBy,
|
|
7957
|
+
deletedAt.toISOString(),
|
|
6522
7958
|
input.id
|
|
6523
7959
|
]
|
|
6524
7960
|
});
|
|
@@ -6537,8 +7973,8 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6537
7973
|
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
6538
7974
|
if (!record.deletedAt) return record;
|
|
6539
7975
|
await tx.execute({
|
|
6540
|
-
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL WHERE id=?`,
|
|
6541
|
-
args: [input.id]
|
|
7976
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
|
|
7977
|
+
args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
|
|
6542
7978
|
});
|
|
6543
7979
|
await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
|
|
6544
7980
|
await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), record.scope);
|
|
@@ -6554,15 +7990,17 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6554
7990
|
return this.#transaction(async (tx) => {
|
|
6555
7991
|
const record = await this.#getKnowledge(tx, input.id, true);
|
|
6556
7992
|
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
6557
|
-
|
|
7993
|
+
const updatedAt = /* @__PURE__ */ new Date();
|
|
6558
7994
|
await tx.execute({
|
|
6559
|
-
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=? WHERE id=?`,
|
|
7995
|
+
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
|
|
6560
7996
|
args: [
|
|
6561
7997
|
JSON.stringify(scope),
|
|
6562
7998
|
knowledgeScopeKey(scope),
|
|
7999
|
+
updatedAt.toISOString(),
|
|
6563
8000
|
input.id
|
|
6564
8001
|
]
|
|
6565
8002
|
});
|
|
8003
|
+
await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
|
|
6566
8004
|
await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
|
|
6567
8005
|
if (knowledgeScopeKey(record.scope) !== knowledgeScopeKey(scope)) await this.#outbox(tx, "record", input.id, "delete", createKnowledgeUlid(), record.scope);
|
|
6568
8006
|
if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", createKnowledgeUlid(), scope);
|
|
@@ -6572,32 +8010,16 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6572
8010
|
};
|
|
6573
8011
|
});
|
|
6574
8012
|
}
|
|
6575
|
-
async raiseKnowledgeCeiling(input) {
|
|
6576
|
-
return this.#transaction(async (tx) => {
|
|
6577
|
-
const record = await this.#getKnowledge(tx, input.id, true);
|
|
6578
|
-
if (!record) throw new KnowledgeNotFoundError("record", input.id);
|
|
6579
|
-
assertKnowledgeScopeWithinCeiling(record.scope, input.maxScope);
|
|
6580
|
-
assertKnowledgeCeilingRaised(record.maxScope, input.maxScope);
|
|
6581
|
-
await tx.execute({
|
|
6582
|
-
sql: `UPDATE "${TABLE_KNOWLEDGE_RECORDS}" SET maxScope=? WHERE id=?`,
|
|
6583
|
-
args: [input.maxScope ?? null, input.id]
|
|
6584
|
-
});
|
|
6585
|
-
return {
|
|
6586
|
-
...record,
|
|
6587
|
-
maxScope: input.maxScope
|
|
6588
|
-
};
|
|
6589
|
-
});
|
|
6590
|
-
}
|
|
6591
8013
|
async search(input) {
|
|
6592
|
-
const
|
|
8014
|
+
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
8015
|
+
const keys = visibleScopeKeys(scope);
|
|
6593
8016
|
const normalizedQuery = input.query.trim().toLocaleLowerCase();
|
|
6594
8017
|
if (!normalizedQuery) return [];
|
|
6595
8018
|
const query = `%${escapeLikePattern(normalizedQuery)}%`;
|
|
6596
8019
|
const results = (await this.#client.execute({
|
|
6597
|
-
sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
|
|
8020
|
+
sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql(keys)} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
|
|
6598
8021
|
args: [
|
|
6599
|
-
|
|
6600
|
-
key,
|
|
8022
|
+
...keys,
|
|
6601
8023
|
query,
|
|
6602
8024
|
query,
|
|
6603
8025
|
query,
|
|
@@ -6618,60 +8040,47 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6618
8040
|
}));
|
|
6619
8041
|
if (results.length < (input.limit ?? 20)) {
|
|
6620
8042
|
const knowledge = await this.#client.execute({
|
|
6621
|
-
sql: `SELECT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson,n.name FROM "${TABLE_KNOWLEDGE_RECORDS}" k JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=k.node AND n.type='node' AND n.mergedInto IS NULL WHERE k.deletedAt IS NULL AND ${visibleSql
|
|
8043
|
+
sql: `SELECT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson,n.name,json(n.scope) AS parentScopeJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k CROSS JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=k.node AND n.type='node' AND n.mergedInto IS NULL WHERE k.deletedAt IS NULL AND ${visibleSql(keys, "k.scopeKey")} AND lower(k.text) LIKE ? ESCAPE '=' ORDER BY k.id DESC LIMIT ?`,
|
|
6622
8044
|
args: [
|
|
6623
|
-
|
|
6624
|
-
key,
|
|
8045
|
+
...keys,
|
|
6625
8046
|
query,
|
|
6626
8047
|
(input.limit ?? 20) - results.length
|
|
6627
8048
|
]
|
|
6628
8049
|
});
|
|
6629
|
-
results.push(...knowledge.rows.map((row) =>
|
|
6630
|
-
|
|
6631
|
-
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
8050
|
+
results.push(...knowledge.rows.map((row) => {
|
|
8051
|
+
const parentVisible = isKnowledgeScopeVisible(parseJson$3(row.parentScopeJson), scope);
|
|
8052
|
+
return {
|
|
8053
|
+
type: "record",
|
|
8054
|
+
id: String(row.id),
|
|
8055
|
+
recordId: parentVisible ? String(row.node) : String(row.id),
|
|
8056
|
+
name: parentVisible ? String(row.name) : "(private node)",
|
|
8057
|
+
text: String(row.text),
|
|
8058
|
+
scope: parseJson$3(row.scopeJson)
|
|
8059
|
+
};
|
|
8060
|
+
}));
|
|
6637
8061
|
}
|
|
6638
8062
|
return results;
|
|
6639
8063
|
}
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
const updatedAt = /* @__PURE__ */ new Date();
|
|
6654
|
-
if ((await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
|
|
6655
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_CURSORS}" (sourceThreadId,agent,lastKnowledgeId,updatedAt) VALUES (?,?,?,?) ON CONFLICT(sourceThreadId,agent) DO UPDATE SET lastKnowledgeId=excluded.lastKnowledgeId,updatedAt=excluded.updatedAt WHERE excluded.lastKnowledgeId >= "${TABLE_KNOWLEDGE_CURSORS}".lastKnowledgeId`,
|
|
6656
|
-
args: [
|
|
6657
|
-
input.sourceThreadId,
|
|
6658
|
-
input.agent,
|
|
6659
|
-
input.lastKnowledgeId,
|
|
6660
|
-
updatedAt.toISOString()
|
|
6661
|
-
]
|
|
6662
|
-
})), "advance knowledge curation cursor")).rowsAffected === 0) throw new Error("Knowledge curation cursor cannot move backwards");
|
|
6663
|
-
return {
|
|
6664
|
-
...input,
|
|
6665
|
-
updatedAt
|
|
6666
|
-
};
|
|
8064
|
+
/**
|
|
8065
|
+
* @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
|
|
8066
|
+
* cursor. Always throws.
|
|
8067
|
+
*/
|
|
8068
|
+
async getCurationCursor(_input) {
|
|
8069
|
+
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
8070
|
+
}
|
|
8071
|
+
/**
|
|
8072
|
+
* @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
|
|
8073
|
+
* cursor. Always throws.
|
|
8074
|
+
*/
|
|
8075
|
+
async advanceCurationCursor(_input) {
|
|
8076
|
+
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
6667
8077
|
}
|
|
6668
8078
|
async listActivity(input) {
|
|
6669
|
-
const
|
|
8079
|
+
const keys = visibleScopeKeys(canonicalizeKnowledgeScope(input.scope));
|
|
6670
8080
|
return (await this.#client.execute({
|
|
6671
|
-
sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
|
|
8081
|
+
sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
|
|
6672
8082
|
args: [
|
|
6673
|
-
|
|
6674
|
-
key,
|
|
8083
|
+
...keys,
|
|
6675
8084
|
...input.after ? [input.after] : [],
|
|
6676
8085
|
input.limit ?? 100
|
|
6677
8086
|
]
|
|
@@ -6693,9 +8102,9 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6693
8102
|
args.push(input.status);
|
|
6694
8103
|
}
|
|
6695
8104
|
if (input.scope) {
|
|
6696
|
-
const
|
|
6697
|
-
clauses.push(visibleSql);
|
|
6698
|
-
args.push(
|
|
8105
|
+
const keys = visibleScopeKeys(input.scope);
|
|
8106
|
+
clauses.push(visibleSql(keys));
|
|
8107
|
+
args.push(...keys);
|
|
6699
8108
|
}
|
|
6700
8109
|
args.push(input.limit ?? 100);
|
|
6701
8110
|
return (await this.#client.execute({
|
|
@@ -6714,9 +8123,9 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6714
8123
|
];
|
|
6715
8124
|
const args = [now.toISOString(), stale.toISOString()];
|
|
6716
8125
|
if (input.scope) {
|
|
6717
|
-
const
|
|
6718
|
-
clauses.push(visibleSql);
|
|
6719
|
-
args.push(
|
|
8126
|
+
const keys = visibleScopeKeys(input.scope);
|
|
8127
|
+
clauses.push(visibleSql(keys));
|
|
8128
|
+
args.push(...keys);
|
|
6720
8129
|
}
|
|
6721
8130
|
args.push(input.limit ?? 100);
|
|
6722
8131
|
const ids = (await tx.execute({
|
|
@@ -6793,12 +8202,14 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6793
8202
|
return result.rows[0] ? parseNode(result.rows[0]) : null;
|
|
6794
8203
|
}
|
|
6795
8204
|
async #resolveNode(executor, name, scope) {
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
8205
|
+
const keys = visibleScopeKeys(scope);
|
|
8206
|
+
const candidates = (await executor.execute({
|
|
8207
|
+
sql: `SELECT *,json(scope) AS scopeJson FROM "${TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND (scopeKey IN (${keys.map(() => "?").join(",")}) OR mergedInto IS NOT NULL)`,
|
|
8208
|
+
args: [canonicalName(name), ...keys]
|
|
8209
|
+
})).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
|
|
8210
|
+
for (const candidate of candidates) {
|
|
8211
|
+
const terminal = await this.#resolveTerminalNode(executor, candidate.id);
|
|
8212
|
+
if (terminal && isKnowledgeScopeVisible(terminal.scope, scope)) return terminal;
|
|
6802
8213
|
}
|
|
6803
8214
|
return null;
|
|
6804
8215
|
}
|
|
@@ -6823,19 +8234,18 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6823
8234
|
const scope = canonicalizeKnowledgeScope(input.scope);
|
|
6824
8235
|
const node = await this.#resolveTerminalNode(this.#client, nodeReferenceId(input.node));
|
|
6825
8236
|
if (!node) return { records: [] };
|
|
6826
|
-
const
|
|
8237
|
+
const keys = visibleScopeKeys(scope);
|
|
6827
8238
|
const includesMentions = relationship !== "about";
|
|
6828
8239
|
const relationSql = relationship === "about" ? "k.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(k.node=? OR m.recordId=?)";
|
|
6829
8240
|
const args = [
|
|
6830
8241
|
node.id,
|
|
6831
8242
|
...relationship === "related" ? [node.id] : [],
|
|
6832
|
-
|
|
6833
|
-
key
|
|
8243
|
+
...keys
|
|
6834
8244
|
];
|
|
6835
8245
|
if (input.after) args.push(input.after);
|
|
6836
8246
|
args.push((input.limit ?? 100) + 1);
|
|
6837
8247
|
const result = await this.#client.execute({
|
|
6838
|
-
sql: `SELECT DISTINCT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k${includesMentions ? ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=k.id` : ""} WHERE ${relationSql} AND ${visibleSql
|
|
8248
|
+
sql: `SELECT DISTINCT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson FROM "${TABLE_KNOWLEDGE_RECORDS}" k${includesMentions ? ` LEFT JOIN "${TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=k.id` : ""} WHERE ${relationSql} AND ${visibleSql(keys, "k.scopeKey")}${input.includeDeleted ? "" : " AND k.deletedAt IS NULL"}${input.after ? " AND k.id < ?" : ""} ORDER BY k.id DESC LIMIT ?`,
|
|
6839
8249
|
args
|
|
6840
8250
|
});
|
|
6841
8251
|
const limit = input.limit ?? 100;
|
|
@@ -6845,6 +8255,90 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6845
8255
|
nextCursor: result.rows.length > limit ? String(rows.at(-1)?.id) : void 0
|
|
6846
8256
|
};
|
|
6847
8257
|
}
|
|
8258
|
+
async #resolveScopeNodeIds(executor, addresses) {
|
|
8259
|
+
const scopeNodeIds = /* @__PURE__ */ new Set();
|
|
8260
|
+
for (const address of addresses) {
|
|
8261
|
+
const result = await executor.execute({
|
|
8262
|
+
sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
|
|
8263
|
+
args: [address]
|
|
8264
|
+
});
|
|
8265
|
+
if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
|
|
8266
|
+
}
|
|
8267
|
+
return [...scopeNodeIds];
|
|
8268
|
+
}
|
|
8269
|
+
async #replaceNodeScopes(executor, nodeId, addresses, addedAt) {
|
|
8270
|
+
await executor.execute({
|
|
8271
|
+
sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
|
|
8272
|
+
args: [nodeId]
|
|
8273
|
+
});
|
|
8274
|
+
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
|
|
8275
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
|
|
8276
|
+
args: [
|
|
8277
|
+
nodeId,
|
|
8278
|
+
scopeNodeId,
|
|
8279
|
+
addedAt.toISOString()
|
|
8280
|
+
]
|
|
8281
|
+
});
|
|
8282
|
+
}
|
|
8283
|
+
/**
|
|
8284
|
+
* Move a node's identity memberships from `from` to `to` without touching structural
|
|
8285
|
+
* placements added by `#placeNodeInScopes`, which a scope change must not erase.
|
|
8286
|
+
*/
|
|
8287
|
+
async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
|
|
8288
|
+
const next = await this.#resolveScopeNodeIds(executor, to);
|
|
8289
|
+
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
|
|
8290
|
+
if (next.includes(scopeNodeId)) continue;
|
|
8291
|
+
await executor.execute({
|
|
8292
|
+
sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
|
|
8293
|
+
args: [nodeId, scopeNodeId]
|
|
8294
|
+
});
|
|
8295
|
+
}
|
|
8296
|
+
for (const scopeNodeId of next) await executor.execute({
|
|
8297
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
|
|
8298
|
+
args: [
|
|
8299
|
+
nodeId,
|
|
8300
|
+
scopeNodeId,
|
|
8301
|
+
addedAt.toISOString()
|
|
8302
|
+
]
|
|
8303
|
+
});
|
|
8304
|
+
}
|
|
8305
|
+
/**
|
|
8306
|
+
* Additive structural placement: every address must resolve to a live scope node
|
|
8307
|
+
* (unlike identity membership, an unknown address is a caller error, not a lazy
|
|
8308
|
+
* materialization miss). Runs inside the caller's transaction, so a throw aborts
|
|
8309
|
+
* the whole mutation.
|
|
8310
|
+
*/
|
|
8311
|
+
async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
|
|
8312
|
+
for (const address of addresses ?? []) {
|
|
8313
|
+
const scopeNodeId = (await executor.execute({
|
|
8314
|
+
sql: `SELECT sa.scopeNodeId AS scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" sa JOIN "${TABLE_KNOWLEDGE_NODES}" n ON n.id=sa.scopeNodeId WHERE sa.address=? AND n.isScope AND n.deletedAt IS NULL`,
|
|
8315
|
+
args: [address]
|
|
8316
|
+
})).rows[0]?.scopeNodeId;
|
|
8317
|
+
if (scopeNodeId == null) throw new KnowledgeNotFoundError("scope", address);
|
|
8318
|
+
await executor.execute({
|
|
8319
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
|
|
8320
|
+
args: [
|
|
8321
|
+
nodeId,
|
|
8322
|
+
String(scopeNodeId),
|
|
8323
|
+
addedAt.toISOString()
|
|
8324
|
+
]
|
|
8325
|
+
});
|
|
8326
|
+
}
|
|
8327
|
+
}
|
|
8328
|
+
async #replaceRecordScopes(executor, recordId, addresses, addedAt) {
|
|
8329
|
+
await executor.execute({
|
|
8330
|
+
sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
|
|
8331
|
+
args: [recordId]
|
|
8332
|
+
});
|
|
8333
|
+
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
|
|
8334
|
+
sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?)`,
|
|
8335
|
+
args: [
|
|
8336
|
+
recordId,
|
|
8337
|
+
scopeNodeId,
|
|
8338
|
+
addedAt.toISOString()
|
|
8339
|
+
]
|
|
8340
|
+
});
|
|
8341
|
+
}
|
|
6848
8342
|
async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
|
|
6849
8343
|
await tx.execute({
|
|
6850
8344
|
sql: `DELETE FROM "${TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
|
|
@@ -6868,7 +8362,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6868
8362
|
updatedAt: now
|
|
6869
8363
|
};
|
|
6870
8364
|
await tx.execute({
|
|
6871
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
|
|
8365
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,FALSE,jsonb(?),?,?,NULL,?,?)`,
|
|
6872
8366
|
args: [
|
|
6873
8367
|
node.id,
|
|
6874
8368
|
"node",
|
|
@@ -6882,6 +8376,7 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6882
8376
|
now.toISOString()
|
|
6883
8377
|
]
|
|
6884
8378
|
});
|
|
8379
|
+
await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
|
|
6885
8380
|
await this.#activity(tx, "node-created", "node", node.id, defaultScope);
|
|
6886
8381
|
await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
|
|
6887
8382
|
}
|
|
@@ -6898,13 +8393,17 @@ var KnowledgeLibSQL = class extends KnowledgeStorage {
|
|
|
6898
8393
|
}
|
|
6899
8394
|
async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
|
|
6900
8395
|
const now = /* @__PURE__ */ new Date();
|
|
8396
|
+
const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
|
|
6901
8397
|
await executor.execute({
|
|
6902
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (
|
|
8398
|
+
sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
|
|
6903
8399
|
args: [
|
|
6904
8400
|
createKnowledgeUlid(),
|
|
6905
8401
|
action,
|
|
6906
8402
|
recordType,
|
|
6907
8403
|
recordId,
|
|
8404
|
+
contextScopeId ?? null,
|
|
8405
|
+
recordType,
|
|
8406
|
+
recordId,
|
|
6908
8407
|
JSON.stringify(scope),
|
|
6909
8408
|
knowledgeScopeKey(scope),
|
|
6910
8409
|
sourceThreadId ?? null,
|
|
@@ -7831,6 +9330,10 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7831
9330
|
* Retention-eligible tables. `threads`, `messages`, and `resources` all anchor
|
|
7832
9331
|
* on `createdAt` and are indexed for fast batched deletes. Cascade order is
|
|
7833
9332
|
* enforced in `prune()` (children before threads), not here.
|
|
9333
|
+
*
|
|
9334
|
+
* Run fences anchor on `retiredAt` (epoch ms), which stays NULL until the
|
|
9335
|
+
* execution holding the fence settles, so a running execution's fence is
|
|
9336
|
+
* never pruned.
|
|
7834
9337
|
*/
|
|
7835
9338
|
static retentionTables = {
|
|
7836
9339
|
messages: {
|
|
@@ -7847,6 +9350,12 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7847
9350
|
table: TABLE_THREADS,
|
|
7848
9351
|
column: "createdAt",
|
|
7849
9352
|
indexed: true
|
|
9353
|
+
},
|
|
9354
|
+
runFences: {
|
|
9355
|
+
table: TABLE_MEMORY_RUN_FENCES,
|
|
9356
|
+
column: "retiredAt",
|
|
9357
|
+
indexed: true,
|
|
9358
|
+
anchorType: "epoch-ms"
|
|
7850
9359
|
}
|
|
7851
9360
|
};
|
|
7852
9361
|
#client;
|
|
@@ -7869,6 +9378,13 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7869
9378
|
#write(operation, fn) {
|
|
7870
9379
|
return this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, fn), operation);
|
|
7871
9380
|
}
|
|
9381
|
+
/**
|
|
9382
|
+
* `#write` for a write that may carry a run fence: with a check, it runs in a write
|
|
9383
|
+
* transaction behind the check, and a retry reruns the whole transaction.
|
|
9384
|
+
*/
|
|
9385
|
+
#fencedWrite(operation, check, write) {
|
|
9386
|
+
return this.#db.executeWriteOperationWithRetry(() => withRunFence(this.#client, check, write), operation);
|
|
9387
|
+
}
|
|
7872
9388
|
async init() {
|
|
7873
9389
|
await this.#db.createTable({
|
|
7874
9390
|
tableName: TABLE_THREADS,
|
|
@@ -7882,6 +9398,10 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7882
9398
|
tableName: TABLE_RESOURCES,
|
|
7883
9399
|
schema: TABLE_SCHEMAS[TABLE_RESOURCES]
|
|
7884
9400
|
});
|
|
9401
|
+
await this.#db.createTable({
|
|
9402
|
+
tableName: TABLE_MEMORY_RUN_FENCES,
|
|
9403
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_MEMORY_RUN_FENCES]
|
|
9404
|
+
});
|
|
7885
9405
|
const omSchema = OBSERVATIONAL_MEMORY_TABLE_SCHEMA?.[OM_TABLE];
|
|
7886
9406
|
if (omSchema) {
|
|
7887
9407
|
await this.#db.createTable({
|
|
@@ -7933,6 +9453,70 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7933
9453
|
await this.#db.deleteData({ tableName: TABLE_THREADS });
|
|
7934
9454
|
await this.#db.deleteData({ tableName: TABLE_RESOURCES });
|
|
7935
9455
|
await this.#db.deleteData({ tableName: OM_TABLE });
|
|
9456
|
+
await this.#db.deleteData({ tableName: TABLE_MEMORY_RUN_FENCES });
|
|
9457
|
+
}
|
|
9458
|
+
supportsRunFencing() {
|
|
9459
|
+
return true;
|
|
9460
|
+
}
|
|
9461
|
+
async raiseRunFence(fence) {
|
|
9462
|
+
try {
|
|
9463
|
+
return await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
9464
|
+
const row = (await tx.execute({
|
|
9465
|
+
sql: `SELECT generation, ownerId FROM ${TABLE_MEMORY_RUN_FENCES} WHERE runId = ?`,
|
|
9466
|
+
args: [fence.runId]
|
|
9467
|
+
})).rows[0];
|
|
9468
|
+
const generation = row ? Number(row.generation) : void 0;
|
|
9469
|
+
const raises = generation === void 0 || generation < fence.generation;
|
|
9470
|
+
const reaffirms = generation === fence.generation && String(row.ownerId) === fence.ownerId;
|
|
9471
|
+
if (!raises && !reaffirms) return false;
|
|
9472
|
+
await tx.execute({
|
|
9473
|
+
sql: `INSERT INTO ${TABLE_MEMORY_RUN_FENCES} (runId, generation, ownerId) VALUES (?, ?, ?)
|
|
9474
|
+
ON CONFLICT(runId) DO UPDATE SET generation = excluded.generation, ownerId = excluded.ownerId, retiredAt = NULL`,
|
|
9475
|
+
args: [
|
|
9476
|
+
fence.runId,
|
|
9477
|
+
fence.generation,
|
|
9478
|
+
fence.ownerId
|
|
9479
|
+
]
|
|
9480
|
+
});
|
|
9481
|
+
return true;
|
|
9482
|
+
}), "raiseRunFence");
|
|
9483
|
+
} catch (error) {
|
|
9484
|
+
throw new MastraError({
|
|
9485
|
+
id: createStorageErrorId("LIBSQL", "RAISE_RUN_FENCE", "FAILED"),
|
|
9486
|
+
domain: ErrorDomain.STORAGE,
|
|
9487
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
9488
|
+
details: { runId: fence.runId }
|
|
9489
|
+
}, error);
|
|
9490
|
+
}
|
|
9491
|
+
}
|
|
9492
|
+
async retireRunFence(fence) {
|
|
9493
|
+
try {
|
|
9494
|
+
return (await this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
|
|
9495
|
+
sql: `UPDATE ${TABLE_MEMORY_RUN_FENCES} SET retiredAt = ${DB_NOW_MS}
|
|
9496
|
+
WHERE runId = ? AND generation = ? AND ownerId = ?`,
|
|
9497
|
+
args: [
|
|
9498
|
+
fence.runId,
|
|
9499
|
+
fence.generation,
|
|
9500
|
+
fence.ownerId
|
|
9501
|
+
]
|
|
9502
|
+
})), "retireRunFence")).rowsAffected > 0;
|
|
9503
|
+
} catch (error) {
|
|
9504
|
+
throw new MastraError({
|
|
9505
|
+
id: createStorageErrorId("LIBSQL", "RETIRE_RUN_FENCE", "FAILED"),
|
|
9506
|
+
domain: ErrorDomain.STORAGE,
|
|
9507
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
9508
|
+
details: { runId: fence.runId }
|
|
9509
|
+
}, error);
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
/** The check a write must pass: its own fence, otherwise the one in scope. */
|
|
9513
|
+
#runFenceCheck(fence, operation) {
|
|
9514
|
+
const resolved = resolveRunFence(this, fence);
|
|
9515
|
+
return resolved && {
|
|
9516
|
+
claimsTable: TABLE_MEMORY_RUN_FENCES,
|
|
9517
|
+
fence: resolved,
|
|
9518
|
+
operation
|
|
9519
|
+
};
|
|
7936
9520
|
}
|
|
7937
9521
|
/**
|
|
7938
9522
|
* Delete rows older than each policy's `maxAge`, batched and cancellable.
|
|
@@ -7948,7 +9532,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
7948
9532
|
order: [
|
|
7949
9533
|
"messages",
|
|
7950
9534
|
"resources",
|
|
7951
|
-
"threads"
|
|
9535
|
+
"threads",
|
|
9536
|
+
"runFences"
|
|
7952
9537
|
]
|
|
7953
9538
|
});
|
|
7954
9539
|
return runPrune({
|
|
@@ -8330,7 +9915,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8330
9915
|
throw mastraError;
|
|
8331
9916
|
}
|
|
8332
9917
|
}
|
|
8333
|
-
async saveMessages({ messages }) {
|
|
9918
|
+
async saveMessages({ messages, fence }) {
|
|
8334
9919
|
if (messages.length === 0) return { messages };
|
|
8335
9920
|
try {
|
|
8336
9921
|
const threadId = messages[0]?.threadId;
|
|
@@ -8368,13 +9953,16 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8368
9953
|
const BATCH_SIZE = 50;
|
|
8369
9954
|
const messageStatements = batchStatements.slice(0, -1);
|
|
8370
9955
|
const threadUpdateStatement = batchStatements[batchStatements.length - 1];
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
|
|
8375
|
-
|
|
9956
|
+
await this.#fencedWrite("saveMessages", this.#runFenceCheck(fence, "saveMessages"), async (writer) => {
|
|
9957
|
+
for (let i = 0; i < messageStatements.length; i += BATCH_SIZE) {
|
|
9958
|
+
const batch = messageStatements.slice(i, i + BATCH_SIZE);
|
|
9959
|
+
if (batch.length > 0) await writer.batch(batch);
|
|
9960
|
+
}
|
|
9961
|
+
if (threadUpdateStatement) await writer.execute(threadUpdateStatement);
|
|
9962
|
+
});
|
|
8376
9963
|
return { messages: new MessageList().add(messages, "memory").get.all.db() };
|
|
8377
9964
|
} catch (error) {
|
|
9965
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8378
9966
|
throw new MastraError({
|
|
8379
9967
|
id: createStorageErrorId("LIBSQL", "SAVE_MESSAGES", "FAILED"),
|
|
8380
9968
|
domain: ErrorDomain.STORAGE,
|
|
@@ -8382,11 +9970,14 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8382
9970
|
}, error);
|
|
8383
9971
|
}
|
|
8384
9972
|
}
|
|
8385
|
-
async updateMessages({ messages }) {
|
|
9973
|
+
async updateMessages({ messages, fence }) {
|
|
8386
9974
|
if (messages.length === 0) return [];
|
|
9975
|
+
return this.#fencedWrite("updateMessages", this.#runFenceCheck(fence, "updateMessages"), (writer) => this.#updateMessages(writer, messages));
|
|
9976
|
+
}
|
|
9977
|
+
async #updateMessages(writer, messages) {
|
|
8387
9978
|
const messageIds = messages.map((m) => m.id);
|
|
8388
9979
|
const selectSql = `SELECT * FROM ${TABLE_MESSAGES} WHERE id IN (${messageIds.map(() => "?").join(",")})`;
|
|
8389
|
-
const existingMessages = (await
|
|
9980
|
+
const existingMessages = (await writer.execute({
|
|
8390
9981
|
sql: selectSql,
|
|
8391
9982
|
args: messageIds
|
|
8392
9983
|
})).rows.map((row) => this.parseRow(row));
|
|
@@ -8438,48 +10029,44 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8438
10029
|
sql: `UPDATE ${TABLE_THREADS} SET updatedAt = ? WHERE id = ?`,
|
|
8439
10030
|
args: [now, threadId]
|
|
8440
10031
|
});
|
|
8441
|
-
await
|
|
8442
|
-
return (await
|
|
10032
|
+
await writer.batch(batchStatements);
|
|
10033
|
+
return (await writer.execute({
|
|
8443
10034
|
sql: selectSql,
|
|
8444
10035
|
args: messageIds
|
|
8445
10036
|
})).rows.map((row) => this.parseRow(row));
|
|
8446
10037
|
}
|
|
8447
|
-
async deleteMessages(messageIds) {
|
|
10038
|
+
async deleteMessages(messageIds, options) {
|
|
8448
10039
|
if (!messageIds || messageIds.length === 0) return;
|
|
8449
10040
|
try {
|
|
8450
10041
|
const BATCH_SIZE = 100;
|
|
8451
|
-
|
|
10042
|
+
const check = this.#runFenceCheck(options?.fence, "deleteMessages");
|
|
10043
|
+
await this.#db.executeWriteOperationWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
10044
|
+
if (check) await assertRunFence(tx, check);
|
|
8452
10045
|
const threadIds = /* @__PURE__ */ new Set();
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8463
|
-
})
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
args: batch
|
|
8467
|
-
});
|
|
8468
|
-
}
|
|
8469
|
-
if (threadIds.size > 0) {
|
|
8470
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8471
|
-
for (const threadId of threadIds) await tx.execute({
|
|
8472
|
-
sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
|
|
8473
|
-
args: [now, threadId]
|
|
8474
|
-
});
|
|
8475
|
-
}
|
|
8476
|
-
await tx.commit();
|
|
8477
|
-
} catch (error) {
|
|
8478
|
-
await tx.rollback();
|
|
8479
|
-
throw error;
|
|
10046
|
+
for (let i = 0; i < messageIds.length; i += BATCH_SIZE) {
|
|
10047
|
+
const batch = messageIds.slice(i, i + BATCH_SIZE);
|
|
10048
|
+
const placeholders = batch.map(() => "?").join(",");
|
|
10049
|
+
(await tx.execute({
|
|
10050
|
+
sql: `SELECT DISTINCT thread_id FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
|
|
10051
|
+
args: batch
|
|
10052
|
+
})).rows?.forEach((row) => {
|
|
10053
|
+
if (row.thread_id) threadIds.add(row.thread_id);
|
|
10054
|
+
});
|
|
10055
|
+
await tx.execute({
|
|
10056
|
+
sql: `DELETE FROM "${TABLE_MESSAGES}" WHERE id IN (${placeholders})`,
|
|
10057
|
+
args: batch
|
|
10058
|
+
});
|
|
8480
10059
|
}
|
|
8481
|
-
|
|
10060
|
+
if (threadIds.size > 0) {
|
|
10061
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10062
|
+
for (const threadId of threadIds) await tx.execute({
|
|
10063
|
+
sql: `UPDATE "${TABLE_THREADS}" SET "updatedAt" = ? WHERE id = ?`,
|
|
10064
|
+
args: [now, threadId]
|
|
10065
|
+
});
|
|
10066
|
+
}
|
|
10067
|
+
}), "deleteMessages");
|
|
8482
10068
|
} catch (error) {
|
|
10069
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8483
10070
|
throw new MastraError({
|
|
8484
10071
|
id: createStorageErrorId("LIBSQL", "DELETE_MESSAGES", "FAILED"),
|
|
8485
10072
|
domain: ErrorDomain.STORAGE,
|
|
@@ -8503,13 +10090,18 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8503
10090
|
};
|
|
8504
10091
|
}
|
|
8505
10092
|
async saveResource({ resource }) {
|
|
10093
|
+
return this.#insertResource(resource, void 0);
|
|
10094
|
+
}
|
|
10095
|
+
async #insertResource(resource, check) {
|
|
8506
10096
|
await this.#db.insert({
|
|
8507
10097
|
tableName: TABLE_RESOURCES,
|
|
8508
|
-
record: { ...resource }
|
|
10098
|
+
record: { ...resource },
|
|
10099
|
+
check
|
|
8509
10100
|
});
|
|
8510
10101
|
return resource;
|
|
8511
10102
|
}
|
|
8512
|
-
async updateResource({ resourceId, workingMemory, metadata }) {
|
|
10103
|
+
async updateResource({ resourceId, workingMemory, metadata, fence }) {
|
|
10104
|
+
const check = this.#runFenceCheck(fence, "updateResource");
|
|
8513
10105
|
const existingResource = await this.getResourceById({ resourceId });
|
|
8514
10106
|
if (!existingResource) {
|
|
8515
10107
|
const newResource = {
|
|
@@ -8519,7 +10111,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8519
10111
|
createdAt: /* @__PURE__ */ new Date(),
|
|
8520
10112
|
updatedAt: /* @__PURE__ */ new Date()
|
|
8521
10113
|
};
|
|
8522
|
-
return this
|
|
10114
|
+
return this.#insertResource(newResource, check);
|
|
8523
10115
|
}
|
|
8524
10116
|
const updatedResource = {
|
|
8525
10117
|
...existingResource,
|
|
@@ -8543,7 +10135,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8543
10135
|
updates.push("updatedAt = ?");
|
|
8544
10136
|
values.push(updatedResource.updatedAt.toISOString());
|
|
8545
10137
|
values.push(resourceId);
|
|
8546
|
-
await this.#
|
|
10138
|
+
await this.#fencedWrite("updateResource", check, (writer) => writer.execute({
|
|
8547
10139
|
sql: `UPDATE ${TABLE_RESOURCES} SET ${updates.join(", ")} WHERE id = ?`,
|
|
8548
10140
|
args: values
|
|
8549
10141
|
}));
|
|
@@ -8747,14 +10339,16 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8747
10339
|
throw mastraError;
|
|
8748
10340
|
}
|
|
8749
10341
|
}
|
|
8750
|
-
async saveThread({ thread }) {
|
|
10342
|
+
async saveThread({ thread, fence }) {
|
|
8751
10343
|
try {
|
|
8752
10344
|
await this.#db.insert({
|
|
8753
10345
|
tableName: TABLE_THREADS,
|
|
8754
|
-
record: { ...thread }
|
|
10346
|
+
record: { ...thread },
|
|
10347
|
+
check: this.#runFenceCheck(fence, "saveThread")
|
|
8755
10348
|
});
|
|
8756
10349
|
return thread;
|
|
8757
10350
|
} catch (error) {
|
|
10351
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8758
10352
|
const mastraError = new MastraError({
|
|
8759
10353
|
id: createStorageErrorId("LIBSQL", "SAVE_THREAD", "FAILED"),
|
|
8760
10354
|
domain: ErrorDomain.STORAGE,
|
|
@@ -8766,7 +10360,8 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8766
10360
|
throw mastraError;
|
|
8767
10361
|
}
|
|
8768
10362
|
}
|
|
8769
|
-
async updateThread({ id, title, metadata }) {
|
|
10363
|
+
async updateThread({ id, title, metadata, fence }) {
|
|
10364
|
+
const check = this.#runFenceCheck(fence, "updateThread");
|
|
8770
10365
|
const thread = await this.getThreadById({ threadId: id });
|
|
8771
10366
|
if (!thread) throw new MastraError({
|
|
8772
10367
|
id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "NOT_FOUND"),
|
|
@@ -8789,7 +10384,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8789
10384
|
updatedAt: now
|
|
8790
10385
|
};
|
|
8791
10386
|
try {
|
|
8792
|
-
await this.#
|
|
10387
|
+
await this.#fencedWrite("updateThread", check, (writer) => writer.execute({
|
|
8793
10388
|
sql: `UPDATE ${TABLE_THREADS} SET title = COALESCE(?, title), metadata = jsonb(?), updatedAt = ? WHERE id = ?`,
|
|
8794
10389
|
args: [
|
|
8795
10390
|
title ?? null,
|
|
@@ -8800,6 +10395,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
8800
10395
|
}));
|
|
8801
10396
|
return updatedThread;
|
|
8802
10397
|
} catch (error) {
|
|
10398
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
8803
10399
|
throw new MastraError({
|
|
8804
10400
|
id: createStorageErrorId("LIBSQL", "UPDATE_THREAD", "FAILED"),
|
|
8805
10401
|
domain: ErrorDomain.STORAGE,
|
|
@@ -9210,7 +10806,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9210
10806
|
try {
|
|
9211
10807
|
const now = /* @__PURE__ */ new Date();
|
|
9212
10808
|
const observedMessageIdsJson = input.observedMessageIds ? JSON.stringify(input.observedMessageIds) : null;
|
|
9213
|
-
if ((await this.#
|
|
10809
|
+
if ((await this.#fencedWrite("updateActiveObservations", this.#runFenceCheck(void 0, "updateActiveObservations"), (writer) => writer.execute({
|
|
9214
10810
|
sql: `UPDATE "${OM_TABLE}" SET
|
|
9215
10811
|
"activeObservations" = ?,
|
|
9216
10812
|
"lastObservedAt" = ?,
|
|
@@ -9248,9 +10844,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9248
10844
|
}
|
|
9249
10845
|
async createReflectionGeneration(input) {
|
|
9250
10846
|
try {
|
|
10847
|
+
const check = this.#runFenceCheck(void 0, "createReflectionGeneration");
|
|
9251
10848
|
return await this.#write("createReflectionGeneration", async () => {
|
|
9252
10849
|
const tx = await this.#client.transaction("write");
|
|
9253
10850
|
try {
|
|
10851
|
+
if (check) await assertRunFence(tx, check);
|
|
9254
10852
|
const record = await this.#insertReflectionGeneration(tx, input);
|
|
9255
10853
|
await tx.commit();
|
|
9256
10854
|
return record;
|
|
@@ -9587,9 +11185,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9587
11185
|
async updateBufferedObservations(input) {
|
|
9588
11186
|
try {
|
|
9589
11187
|
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
11188
|
+
const check = this.#runFenceCheck(void 0, "updateBufferedObservations");
|
|
9590
11189
|
await this.#write("updateBufferedObservations", async () => {
|
|
9591
11190
|
const tx = await this.#client.transaction("write");
|
|
9592
11191
|
try {
|
|
11192
|
+
if (check) await assertRunFence(tx, check);
|
|
9593
11193
|
const current = await tx.execute({
|
|
9594
11194
|
sql: `SELECT "bufferedObservationChunks" FROM "${OM_TABLE}" WHERE id = ?`,
|
|
9595
11195
|
args: [input.id]
|
|
@@ -9668,9 +11268,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9668
11268
|
async swapBufferedToActive(input) {
|
|
9669
11269
|
try {
|
|
9670
11270
|
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
11271
|
+
const check = this.#runFenceCheck(void 0, "swapBufferedToActive");
|
|
9671
11272
|
return await this.#write("swapBufferedToActive", async () => {
|
|
9672
11273
|
const tx = await this.#client.transaction("write");
|
|
9673
11274
|
try {
|
|
11275
|
+
if (check) await assertRunFence(tx, check);
|
|
9674
11276
|
const current = await tx.execute({
|
|
9675
11277
|
sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
|
|
9676
11278
|
args: [input.id]
|
|
@@ -9817,7 +11419,7 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9817
11419
|
async updateBufferedReflection(input) {
|
|
9818
11420
|
try {
|
|
9819
11421
|
const nowStr = (/* @__PURE__ */ new Date()).toISOString();
|
|
9820
|
-
if ((await this.#
|
|
11422
|
+
if ((await this.#fencedWrite("updateBufferedReflection", this.#runFenceCheck(void 0, "updateBufferedReflection"), (writer) => writer.execute({
|
|
9821
11423
|
sql: `UPDATE "${OM_TABLE}" SET
|
|
9822
11424
|
"bufferedReflection" = CASE
|
|
9823
11425
|
WHEN "bufferedReflection" IS NOT NULL AND "bufferedReflection" != ''
|
|
@@ -9857,9 +11459,11 @@ var MemoryLibSQL = class MemoryLibSQL extends MemoryStorage {
|
|
|
9857
11459
|
}
|
|
9858
11460
|
async swapBufferedReflectionToActive(input) {
|
|
9859
11461
|
try {
|
|
11462
|
+
const check = this.#runFenceCheck(void 0, "swapBufferedReflectionToActive");
|
|
9860
11463
|
return await this.#write("swapBufferedReflectionToActive", async () => {
|
|
9861
11464
|
const tx = await this.#client.transaction("write");
|
|
9862
11465
|
try {
|
|
11466
|
+
if (check) await assertRunFence(tx, check);
|
|
9863
11467
|
const current = await tx.execute({
|
|
9864
11468
|
sql: `SELECT * FROM "${OM_TABLE}" WHERE id = ?`,
|
|
9865
11469
|
args: [input.currentRecord.id]
|
|
@@ -13359,16 +14963,39 @@ var WorkflowDefinitionsLibSQL = class extends WorkflowDefinitionsStorage {
|
|
|
13359
14963
|
};
|
|
13360
14964
|
//#endregion
|
|
13361
14965
|
//#region src/storage/domains/workflows/index.ts
|
|
14966
|
+
const RUN_OWNER_COLUMNS = `generation, ownerId, leaseExpiresAt, ${DB_NOW_MS} AS nowMs`;
|
|
14967
|
+
function toRunOwnershipRecord(runId, row) {
|
|
14968
|
+
const leaseExpiresAt = row.leaseExpiresAt === null ? null : Number(row.leaseExpiresAt);
|
|
14969
|
+
return {
|
|
14970
|
+
runId,
|
|
14971
|
+
generation: Number(row.generation),
|
|
14972
|
+
ownerId: String(row.ownerId),
|
|
14973
|
+
leaseExpiresAt: leaseExpiresAt === null ? null : new Date(leaseExpiresAt),
|
|
14974
|
+
live: leaseExpiresAt !== null && leaseExpiresAt > Number(row.nowMs)
|
|
14975
|
+
};
|
|
14976
|
+
}
|
|
13362
14977
|
var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
13363
14978
|
/**
|
|
13364
14979
|
* Workflow run snapshots accumulate as runs execute. Anchored on `updatedAt`
|
|
13365
14980
|
* (last activity) so suspended/long-running runs are not pruned by start age.
|
|
14981
|
+
*
|
|
14982
|
+
* Run ownership rows are anchored on `updatedAt` (epoch ms), which every
|
|
14983
|
+
* claim, renewal and release refreshes, so a live execution's row is never
|
|
14984
|
+
* older than its renew interval.
|
|
13366
14985
|
*/
|
|
13367
|
-
static retentionTables = {
|
|
13368
|
-
|
|
13369
|
-
|
|
13370
|
-
|
|
13371
|
-
|
|
14986
|
+
static retentionTables = {
|
|
14987
|
+
workflowSnapshot: {
|
|
14988
|
+
table: TABLE_WORKFLOW_SNAPSHOT,
|
|
14989
|
+
column: "updatedAt",
|
|
14990
|
+
indexed: true
|
|
14991
|
+
},
|
|
14992
|
+
runOwnership: {
|
|
14993
|
+
table: TABLE_WORKFLOW_RUN_OWNERS,
|
|
14994
|
+
column: "updatedAt",
|
|
14995
|
+
indexed: true,
|
|
14996
|
+
anchorType: "epoch-ms"
|
|
14997
|
+
}
|
|
14998
|
+
};
|
|
13372
14999
|
#db;
|
|
13373
15000
|
#client;
|
|
13374
15001
|
executeWithRetry;
|
|
@@ -13420,16 +15047,132 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13420
15047
|
schema,
|
|
13421
15048
|
ifNotExists: ["resourceId"]
|
|
13422
15049
|
});
|
|
15050
|
+
await this.#db.createTable({
|
|
15051
|
+
tableName: TABLE_WORKFLOW_RUN_OWNERS,
|
|
15052
|
+
schema: RUN_FENCING_TABLE_SCHEMAS[TABLE_WORKFLOW_RUN_OWNERS]
|
|
15053
|
+
});
|
|
13423
15054
|
}
|
|
13424
15055
|
async dangerouslyClearAll() {
|
|
13425
15056
|
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
15057
|
+
await this.#db.deleteData({ tableName: TABLE_WORKFLOW_RUN_OWNERS });
|
|
15058
|
+
}
|
|
15059
|
+
supportsRunFencing() {
|
|
15060
|
+
return true;
|
|
15061
|
+
}
|
|
15062
|
+
#runFenceCheck(fence, runId, operation) {
|
|
15063
|
+
const resolved = resolveRunFence(this, fence, runId);
|
|
15064
|
+
return resolved && {
|
|
15065
|
+
claimsTable: TABLE_WORKFLOW_RUN_OWNERS,
|
|
15066
|
+
fence: resolved,
|
|
15067
|
+
operation
|
|
15068
|
+
};
|
|
15069
|
+
}
|
|
15070
|
+
async #readRunOwner(q, runId) {
|
|
15071
|
+
const row = (await q.execute({
|
|
15072
|
+
sql: `SELECT ${RUN_OWNER_COLUMNS} FROM ${TABLE_WORKFLOW_RUN_OWNERS} WHERE runId = ?`,
|
|
15073
|
+
args: [runId]
|
|
15074
|
+
})).rows[0];
|
|
15075
|
+
return row ? toRunOwnershipRecord(runId, row) : null;
|
|
15076
|
+
}
|
|
15077
|
+
#ownershipError(operation, runId, error) {
|
|
15078
|
+
return new MastraError({
|
|
15079
|
+
id: createStorageErrorId("LIBSQL", operation, "FAILED"),
|
|
15080
|
+
domain: ErrorDomain.STORAGE,
|
|
15081
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
15082
|
+
details: { runId }
|
|
15083
|
+
}, error);
|
|
15084
|
+
}
|
|
15085
|
+
async claimRunOwnership({ runId, ownerId, leaseMs, force, expectedGeneration }) {
|
|
15086
|
+
try {
|
|
15087
|
+
return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
15088
|
+
const current = await this.#readRunOwner(tx, runId);
|
|
15089
|
+
const generation = current?.generation ?? 0;
|
|
15090
|
+
if (expectedGeneration !== void 0 && expectedGeneration !== generation) return {
|
|
15091
|
+
acquired: false,
|
|
15092
|
+
record: current
|
|
15093
|
+
};
|
|
15094
|
+
if (current?.live && !force) return {
|
|
15095
|
+
acquired: false,
|
|
15096
|
+
record: current
|
|
15097
|
+
};
|
|
15098
|
+
await tx.execute({
|
|
15099
|
+
sql: `INSERT INTO ${TABLE_WORKFLOW_RUN_OWNERS} (runId, generation, ownerId, leaseExpiresAt, updatedAt)
|
|
15100
|
+
VALUES (?, COALESCE(?, MAX(1, ${DB_NOW_MS})), ?, ${DB_NOW_MS} + ?, ${DB_NOW_MS})
|
|
15101
|
+
ON CONFLICT(runId) DO UPDATE SET
|
|
15102
|
+
generation = excluded.generation,
|
|
15103
|
+
ownerId = excluded.ownerId,
|
|
15104
|
+
leaseExpiresAt = excluded.leaseExpiresAt,
|
|
15105
|
+
updatedAt = excluded.updatedAt`,
|
|
15106
|
+
args: [
|
|
15107
|
+
runId,
|
|
15108
|
+
current ? generation + 1 : null,
|
|
15109
|
+
ownerId,
|
|
15110
|
+
leaseMs
|
|
15111
|
+
]
|
|
15112
|
+
});
|
|
15113
|
+
return {
|
|
15114
|
+
acquired: true,
|
|
15115
|
+
record: await this.#readRunOwner(tx, runId)
|
|
15116
|
+
};
|
|
15117
|
+
}), "claimRunOwnership");
|
|
15118
|
+
} catch (error) {
|
|
15119
|
+
throw this.#ownershipError("CLAIM_RUN_OWNERSHIP", runId, error);
|
|
15120
|
+
}
|
|
15121
|
+
}
|
|
15122
|
+
async renewRunOwnership({ leaseMs, ...fence }) {
|
|
15123
|
+
try {
|
|
15124
|
+
return await this.executeWithRetry(() => inWriteTransaction(this.#client, async (tx) => {
|
|
15125
|
+
const renewed = await tx.execute({
|
|
15126
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = ${DB_NOW_MS} + ?, updatedAt = ${DB_NOW_MS}
|
|
15127
|
+
WHERE runId = ? AND generation = ? AND ownerId = ? AND leaseExpiresAt IS NOT NULL`,
|
|
15128
|
+
args: [
|
|
15129
|
+
leaseMs,
|
|
15130
|
+
fence.runId,
|
|
15131
|
+
fence.generation,
|
|
15132
|
+
fence.ownerId
|
|
15133
|
+
]
|
|
15134
|
+
});
|
|
15135
|
+
const record = await this.#readRunOwner(tx, fence.runId);
|
|
15136
|
+
return renewed.rowsAffected > 0 ? {
|
|
15137
|
+
renewed: true,
|
|
15138
|
+
record
|
|
15139
|
+
} : {
|
|
15140
|
+
renewed: false,
|
|
15141
|
+
record
|
|
15142
|
+
};
|
|
15143
|
+
}), "renewRunOwnership");
|
|
15144
|
+
} catch (error) {
|
|
15145
|
+
throw this.#ownershipError("RENEW_RUN_OWNERSHIP", fence.runId, error);
|
|
15146
|
+
}
|
|
15147
|
+
}
|
|
15148
|
+
async releaseRunOwnership(fence) {
|
|
15149
|
+
try {
|
|
15150
|
+
return (await this.executeWithRetry(() => withClientWriteLock(this.#client, () => this.#client.execute({
|
|
15151
|
+
sql: `UPDATE ${TABLE_WORKFLOW_RUN_OWNERS} SET leaseExpiresAt = NULL, updatedAt = ${DB_NOW_MS}
|
|
15152
|
+
WHERE runId = ? AND generation = ? AND ownerId = ?`,
|
|
15153
|
+
args: [
|
|
15154
|
+
fence.runId,
|
|
15155
|
+
fence.generation,
|
|
15156
|
+
fence.ownerId
|
|
15157
|
+
]
|
|
15158
|
+
})), "releaseRunOwnership")).rowsAffected > 0;
|
|
15159
|
+
} catch (error) {
|
|
15160
|
+
throw this.#ownershipError("RELEASE_RUN_OWNERSHIP", fence.runId, error);
|
|
15161
|
+
}
|
|
13426
15162
|
}
|
|
13427
|
-
|
|
15163
|
+
async getRunOwnership({ runId }) {
|
|
15164
|
+
try {
|
|
15165
|
+
return await this.#readRunOwner(this.#client, runId);
|
|
15166
|
+
} catch (error) {
|
|
15167
|
+
throw this.#ownershipError("GET_RUN_OWNERSHIP", runId, error);
|
|
15168
|
+
}
|
|
15169
|
+
}
|
|
15170
|
+
/** Delete workflow run snapshots and run ownership rows older than their policy's `maxAge`, batched. */
|
|
13428
15171
|
async prune(policies, options) {
|
|
13429
15172
|
const targets = resolveTargets({
|
|
13430
15173
|
policies,
|
|
13431
15174
|
descriptor: WorkflowsLibSQL.retentionTables,
|
|
13432
|
-
order: ["workflowSnapshot"]
|
|
15175
|
+
order: ["workflowSnapshot", "runOwnership"]
|
|
13433
15176
|
});
|
|
13434
15177
|
return runPrune({
|
|
13435
15178
|
db: this.#db,
|
|
@@ -13459,10 +15202,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13459
15202
|
this.logger.warn("LibSQL Workflows: Failed to set PRAGMA settings.", err);
|
|
13460
15203
|
}
|
|
13461
15204
|
}
|
|
13462
|
-
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state }) {
|
|
15205
|
+
async updateWorkflowResults({ workflowName, runId, stepId, result, requestContext, state, fence: explicitFence }) {
|
|
15206
|
+
const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowResults");
|
|
13463
15207
|
return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
|
|
13464
15208
|
const tx = await this.#client.transaction("write");
|
|
13465
15209
|
try {
|
|
15210
|
+
if (check) await assertRunFence(tx, check);
|
|
13466
15211
|
const existingSnapshotResult = await tx.execute({
|
|
13467
15212
|
sql: `SELECT json(snapshot) as snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
13468
15213
|
args: [workflowName, runId]
|
|
@@ -13515,10 +15260,12 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13515
15260
|
}
|
|
13516
15261
|
}), "updateWorkflowResults");
|
|
13517
15262
|
}
|
|
13518
|
-
async updateWorkflowState({ workflowName, runId, opts }) {
|
|
15263
|
+
async updateWorkflowState({ workflowName, runId, opts, fence: explicitFence }) {
|
|
15264
|
+
const check = this.#runFenceCheck(explicitFence, runId, "updateWorkflowState");
|
|
13519
15265
|
return this.executeWithRetry(() => withClientWriteLock(this.#client, async () => {
|
|
13520
15266
|
const tx = await this.#client.transaction("write");
|
|
13521
15267
|
try {
|
|
15268
|
+
if (check) await assertRunFence(tx, check);
|
|
13522
15269
|
const existingSnapshotResult = await tx.execute({
|
|
13523
15270
|
sql: `SELECT json(snapshot) as snapshot FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
13524
15271
|
args: [workflowName, runId]
|
|
@@ -13559,7 +15306,7 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13559
15306
|
}
|
|
13560
15307
|
}), "updateWorkflowState");
|
|
13561
15308
|
}
|
|
13562
|
-
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt }) {
|
|
15309
|
+
async persistWorkflowSnapshot({ workflowName, runId, resourceId, snapshot, createdAt, updatedAt, fence }) {
|
|
13563
15310
|
const now = /* @__PURE__ */ new Date();
|
|
13564
15311
|
const createdAtValue = (createdAt ?? now).toISOString();
|
|
13565
15312
|
const updatedAtValue = (updatedAt ?? now).toISOString();
|
|
@@ -13567,7 +15314,8 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13567
15314
|
workflowName,
|
|
13568
15315
|
runId
|
|
13569
15316
|
});
|
|
13570
|
-
|
|
15317
|
+
const check = this.#runFenceCheck(fence, runId, "persistWorkflowSnapshot");
|
|
15318
|
+
await this.executeWithRetry(() => withRunFence(this.#client, check, (writer) => writer.execute({
|
|
13571
15319
|
sql: `INSERT INTO ${TABLE_WORKFLOW_SNAPSHOT} (workflow_name, run_id, resourceId, snapshot, createdAt, updatedAt)
|
|
13572
15320
|
VALUES (?, ?, ?, jsonb(?), ?, ?)
|
|
13573
15321
|
ON CONFLICT(workflow_name, run_id)
|
|
@@ -13623,14 +15371,16 @@ var WorkflowsLibSQL = class WorkflowsLibSQL extends WorkflowsStorage {
|
|
|
13623
15371
|
}, error);
|
|
13624
15372
|
}
|
|
13625
15373
|
}
|
|
13626
|
-
async deleteWorkflowRunById({ runId, workflowName }) {
|
|
15374
|
+
async deleteWorkflowRunById({ runId, workflowName, fence }) {
|
|
15375
|
+
const check = this.#runFenceCheck(fence, runId, "deleteWorkflowRunById");
|
|
13627
15376
|
return this.executeWithRetry(async () => {
|
|
13628
15377
|
try {
|
|
13629
|
-
await this.#client.execute({
|
|
15378
|
+
await withRunFence(this.#client, check, (writer) => writer.execute({
|
|
13630
15379
|
sql: `DELETE FROM ${TABLE_WORKFLOW_SNAPSHOT} WHERE workflow_name = ? AND run_id = ?`,
|
|
13631
15380
|
args: [workflowName, runId]
|
|
13632
|
-
});
|
|
15381
|
+
}));
|
|
13633
15382
|
} catch (error) {
|
|
15383
|
+
if (isRunFenceConflictError(error)) throw error;
|
|
13634
15384
|
throw new MastraError({
|
|
13635
15385
|
id: createStorageErrorId("LIBSQL", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
|
|
13636
15386
|
domain: ErrorDomain.STORAGE,
|
|
@@ -14500,7 +16250,7 @@ var LibSQLFactoryStorage = class extends FactoryStorage {
|
|
|
14500
16250
|
...config.authToken ? { authToken: config.authToken } : {},
|
|
14501
16251
|
...isLocalDb ? { timeout: DEFAULT_CONNECTION_TIMEOUT_MS } : {}
|
|
14502
16252
|
});
|
|
14503
|
-
this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client;
|
|
16253
|
+
this.#client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }), config.url, { maxWaitMs: DEFAULT_CONNECTION_TIMEOUT_MS }) : client;
|
|
14504
16254
|
this.ops = new LibSQLFactoryStorageOps(this.#client, this.#schemas, (fn) => withClientWriteLock(this.#client, fn));
|
|
14505
16255
|
}
|
|
14506
16256
|
getMastraStorage() {
|
|
@@ -14666,7 +16416,10 @@ var LibSQLStore = class extends MastraCompositeStore {
|
|
|
14666
16416
|
...config.syncInterval !== void 0 ? { syncInterval: config.syncInterval } : {},
|
|
14667
16417
|
...this.isLocalDb ? { timeout: this.connectionTimeoutMs } : {}
|
|
14668
16418
|
});
|
|
14669
|
-
this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : client
|
|
16419
|
+
this.client = isSingleConnectionDatabase(config) ? gateSingleConnectionClient(client) : this.isLocalDb ? gateIfLocalFile(resetConnectionsAfterBusy(client, {
|
|
16420
|
+
afterReset: () => this.applyLocalPragmas(client),
|
|
16421
|
+
maxWaitMs: this.connectionTimeoutMs
|
|
16422
|
+
}), config.url, { maxWaitMs: this.connectionTimeoutMs }) : client;
|
|
14670
16423
|
this.pragmasReady = this.isLocalDb ? this.applyLocalPragmas() : Promise.resolve();
|
|
14671
16424
|
} else {
|
|
14672
16425
|
this.client = config.client;
|
|
@@ -14682,7 +16435,10 @@ var LibSQLStore = class extends MastraCompositeStore {
|
|
|
14682
16435
|
const workflows = new WorkflowsLibSQL(domainConfig);
|
|
14683
16436
|
const workflowDefinitions = new WorkflowDefinitionsLibSQL(domainConfig);
|
|
14684
16437
|
const memory = new MemoryLibSQL(domainConfig);
|
|
14685
|
-
const knowledge = new KnowledgeLibSQL(
|
|
16438
|
+
const knowledge = new KnowledgeLibSQL({
|
|
16439
|
+
...domainConfig,
|
|
16440
|
+
storageIsolationKey: config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, this.client)
|
|
16441
|
+
});
|
|
14686
16442
|
const observability = new ObservabilityLibSQL(domainConfig);
|
|
14687
16443
|
const agents = new AgentsLibSQL(domainConfig);
|
|
14688
16444
|
const channels = new ChannelsLibSQL(domainConfig);
|
|
@@ -14729,7 +16485,7 @@ var LibSQLStore = class extends MastraCompositeStore {
|
|
|
14729
16485
|
threadState
|
|
14730
16486
|
};
|
|
14731
16487
|
}
|
|
14732
|
-
async applyLocalPragmas() {
|
|
16488
|
+
async applyLocalPragmas(client = this.client) {
|
|
14733
16489
|
const pragmas = [
|
|
14734
16490
|
["journal_mode=WAL", "PRAGMA journal_mode=WAL;"],
|
|
14735
16491
|
[`busy_timeout=${this.connectionTimeoutMs}`, `PRAGMA busy_timeout=${this.connectionTimeoutMs};`],
|
|
@@ -14739,14 +16495,14 @@ var LibSQLStore = class extends MastraCompositeStore {
|
|
|
14739
16495
|
[`mmap_size=${this.localPragmas.mmapSize}`, `PRAGMA mmap_size=${this.localPragmas.mmapSize};`]
|
|
14740
16496
|
];
|
|
14741
16497
|
for (const [label, sql] of pragmas) try {
|
|
14742
|
-
await
|
|
16498
|
+
await client.execute(sql);
|
|
14743
16499
|
this.logger.debug(`LibSQLStore: PRAGMA ${label} set.`);
|
|
14744
16500
|
} catch (err) {
|
|
14745
16501
|
this.logger.warn(`LibSQLStore: Failed to set PRAGMA ${label}.`, err);
|
|
14746
16502
|
}
|
|
14747
16503
|
}
|
|
14748
16504
|
getStoresToInit() {
|
|
14749
|
-
return Object.
|
|
16505
|
+
return Object.entries(this.stores).filter(([name]) => name !== "knowledge").map(([, store]) => store).filter(Boolean);
|
|
14750
16506
|
}
|
|
14751
16507
|
async initDomainsSequentially() {
|
|
14752
16508
|
for (const store of this.getStoresToInit()) await store.init();
|