@mastra/mysql 0.15.0-alpha.0 → 0.16.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core/dist/knowledge-compat/index.d.ts +338 -0
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +1521 -774
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1522 -775
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/knowledge/index.d.ts +39 -77
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/pages.d.ts +167 -0
- package/dist/storage/domains/knowledge/pages.d.ts.map +1 -0
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -3,6 +3,7 @@ let _mastra_core_error = require("@mastra/core/error");
|
|
|
3
3
|
let _mastra_core_storage = require("@mastra/core/storage");
|
|
4
4
|
let mysql2_promise = require("mysql2/promise");
|
|
5
5
|
let _mastra_core_utils = require("@mastra/core/utils");
|
|
6
|
+
let _mastra_core_features = require("@mastra/core/features");
|
|
6
7
|
let crypto$1 = require("crypto");
|
|
7
8
|
let _mastra_core_agent = require("@mastra/core/agent");
|
|
8
9
|
let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
|
|
@@ -1376,7 +1377,7 @@ function serializeJson(v) {
|
|
|
1376
1377
|
if (typeof v === "object" && v != null) return JSON.stringify(v);
|
|
1377
1378
|
return v ?? null;
|
|
1378
1379
|
}
|
|
1379
|
-
function parseJson$
|
|
1380
|
+
function parseJson$1(val) {
|
|
1380
1381
|
if (val == null) return void 0;
|
|
1381
1382
|
if (typeof val === "string") try {
|
|
1382
1383
|
return JSON.parse(val);
|
|
@@ -1391,14 +1392,14 @@ function rowToTask(row) {
|
|
|
1391
1392
|
status: String(row.status),
|
|
1392
1393
|
toolName: String(row.tool_name),
|
|
1393
1394
|
toolCallId: String(row.tool_call_id),
|
|
1394
|
-
args: parseJson$
|
|
1395
|
+
args: parseJson$1(row.args) ?? {},
|
|
1395
1396
|
agentId: String(row.agent_id),
|
|
1396
1397
|
threadId: row.thread_id != null ? String(row.thread_id) : void 0,
|
|
1397
1398
|
resourceId: row.resource_id != null ? String(row.resource_id) : void 0,
|
|
1398
1399
|
runId: String(row.run_id),
|
|
1399
|
-
result: parseJson$
|
|
1400
|
-
error: parseJson$
|
|
1401
|
-
suspendPayload: parseJson$
|
|
1400
|
+
result: parseJson$1(row.result),
|
|
1401
|
+
error: parseJson$1(row.error),
|
|
1402
|
+
suspendPayload: parseJson$1(row.suspend_payload),
|
|
1402
1403
|
retryCount: Number(row.retry_count),
|
|
1403
1404
|
maxRetries: Number(row.max_retries),
|
|
1404
1405
|
timeoutMs: Number(row.timeout_ms),
|
|
@@ -1409,7 +1410,7 @@ function rowToTask(row) {
|
|
|
1409
1410
|
ownerId: row.ownerId != null ? String(row.ownerId) : void 0,
|
|
1410
1411
|
leaseExpiresAt: parseDateTime(row.leaseExpiresAt),
|
|
1411
1412
|
expiresAt: parseDateTime(row.expiresAt),
|
|
1412
|
-
metadata: parseJson$
|
|
1413
|
+
metadata: parseJson$1(row.metadata)
|
|
1413
1414
|
};
|
|
1414
1415
|
}
|
|
1415
1416
|
var BackgroundTasksMySQL = class BackgroundTasksMySQL extends _mastra_core_storage.BackgroundTasksStorage {
|
|
@@ -4187,899 +4188,1645 @@ var FavoritesMySQL = class FavoritesMySQL extends _mastra_core_storage.Favorites
|
|
|
4187
4188
|
}
|
|
4188
4189
|
}
|
|
4189
4190
|
};
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4191
|
+
const KNOWLEDGE_TABLE_NAMES = [
|
|
4192
|
+
"mastra_knowledge_nodes",
|
|
4193
|
+
"mastra_knowledge_records",
|
|
4194
|
+
"mastra_knowledge_mentions",
|
|
4195
|
+
"mastra_knowledge_activity",
|
|
4196
|
+
"mastra_knowledge_semantic_outbox",
|
|
4197
|
+
"mastra_knowledge_node_scopes",
|
|
4198
|
+
"mastra_knowledge_record_scopes",
|
|
4199
|
+
"mastra_knowledge_scope_grants",
|
|
4200
|
+
"mastra_knowledge_access_state",
|
|
4201
|
+
"mastra_knowledge_scope_addresses",
|
|
4202
|
+
"mastra_knowledge_node_addresses",
|
|
4203
|
+
"mastra_knowledge_import_state",
|
|
4204
|
+
"mastra_knowledge_import_runs",
|
|
4205
|
+
"mastra_knowledge_proposals"
|
|
4206
|
+
];
|
|
4207
|
+
/** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
|
|
4208
|
+
const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
|
|
4209
|
+
const KNOWLEDGE_PAGES_CORE_FEATURE = "knowledge-pages";
|
|
4210
|
+
const TABLE_KNOWLEDGE_PAGES = "mastra_knowledge_pages";
|
|
4211
|
+
const TABLE_KNOWLEDGE_NOTES = "mastra_knowledge_notes";
|
|
4212
|
+
const TABLE_KNOWLEDGE_NOTE_STAMPS = "mastra_knowledge_note_stamps";
|
|
4213
|
+
/** Same name as the schema-2 table; schema 4 re-keys it to note → page. */
|
|
4214
|
+
const TABLE_KNOWLEDGE_PAGE_MENTIONS = "mastra_knowledge_mentions";
|
|
4215
|
+
const TABLE_KNOWLEDGE_PINS = "mastra_knowledge_pins";
|
|
4216
|
+
/** Same name as schema 2; schema 4 re-keys it to scope/page/note. */
|
|
4217
|
+
const TABLE_KNOWLEDGE_PAGE_ACTIVITY = "mastra_knowledge_activity";
|
|
4218
|
+
/** Holds `{ id: 'knowledge', schemaVersion: 4 }`, written when the schema-4 tables are created. */
|
|
4219
|
+
const TABLE_KNOWLEDGE_PAGES_STATE = "mastra_knowledge_access_state";
|
|
4220
|
+
const KNOWLEDGE_PAGES_TABLE_NAMES = [
|
|
4221
|
+
TABLE_KNOWLEDGE_PAGES,
|
|
4222
|
+
TABLE_KNOWLEDGE_NOTES,
|
|
4223
|
+
TABLE_KNOWLEDGE_NOTE_STAMPS,
|
|
4224
|
+
TABLE_KNOWLEDGE_PAGE_MENTIONS,
|
|
4225
|
+
TABLE_KNOWLEDGE_PINS,
|
|
4226
|
+
TABLE_KNOWLEDGE_PAGE_ACTIVITY,
|
|
4227
|
+
TABLE_KNOWLEDGE_PAGES_STATE
|
|
4228
|
+
];
|
|
4229
|
+
/** Every Knowledge table any schema version created. Reset drops these and nothing else. */
|
|
4230
|
+
const KNOWLEDGE_RESETTABLE_TABLE_NAMES = [.../* @__PURE__ */ new Set([
|
|
4231
|
+
...KNOWLEDGE_PAGES_TABLE_NAMES,
|
|
4232
|
+
...KNOWLEDGE_TABLE_NAMES,
|
|
4233
|
+
...RETIRED_KNOWLEDGE_TABLE_NAMES
|
|
4234
|
+
])];
|
|
4235
|
+
/** Schema-1/2 tables whose presence means the store needs a reset before schema 4 can run. */
|
|
4236
|
+
const LEGACY_KNOWLEDGE_TABLE_NAMES = new Set([...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES].filter((name) => !KNOWLEDGE_PAGES_TABLE_NAMES.includes(name)));
|
|
4237
|
+
/**
|
|
4238
|
+
* Classifies what an adapter found before creating schema-4 tables. `tableNames` is every
|
|
4239
|
+
* `mastra_knowledge_*` table present; `schemaVersion` is the state row's version, if any.
|
|
4240
|
+
*/
|
|
4241
|
+
function inspectKnowledgePagesSchema(snapshot) {
|
|
4242
|
+
const legacy = snapshot.tableNames.filter((name) => LEGACY_KNOWLEDGE_TABLE_NAMES.has(name));
|
|
4243
|
+
if (legacy.length > 0) return {
|
|
4244
|
+
status: "incompatible-reset-required",
|
|
4245
|
+
schemaVersion: snapshot.schemaVersion ?? null,
|
|
4246
|
+
reason: `found Knowledge tables from an earlier schema (${legacy.join(", ")})`
|
|
4247
|
+
};
|
|
4248
|
+
if (snapshot.schemaVersion === 4) return {
|
|
4249
|
+
status: "compatible",
|
|
4250
|
+
schemaVersion: 4
|
|
4251
|
+
};
|
|
4252
|
+
if (snapshot.schemaVersion === void 0) {
|
|
4253
|
+
const present = snapshot.tableNames.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
4254
|
+
if (present.length === 0) return {
|
|
4255
|
+
status: "uninitialized",
|
|
4256
|
+
schemaVersion: null
|
|
4221
4257
|
};
|
|
4222
4258
|
return {
|
|
4223
|
-
|
|
4224
|
-
|
|
4259
|
+
status: "incompatible-reset-required",
|
|
4260
|
+
schemaVersion: null,
|
|
4261
|
+
reason: `found Knowledge tables (${present.join(", ")}) without a schema version`
|
|
4225
4262
|
};
|
|
4226
|
-
}
|
|
4263
|
+
}
|
|
4264
|
+
return {
|
|
4265
|
+
status: "incompatible-reset-required",
|
|
4266
|
+
schemaVersion: snapshot.schemaVersion,
|
|
4267
|
+
reason: `Knowledge schema version ${snapshot.schemaVersion} is not 4`
|
|
4268
|
+
};
|
|
4227
4269
|
}
|
|
4270
|
+
/** Schema-2 table names schema 4 reuses with a different shape. */
|
|
4271
|
+
const REUSED_KNOWLEDGE_TABLE_NAMES = KNOWLEDGE_TABLE_NAMES.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
4228
4272
|
/**
|
|
4229
|
-
*
|
|
4230
|
-
*
|
|
4231
|
-
*
|
|
4232
|
-
*
|
|
4273
|
+
* The old Knowledge tables an adapter drops on init, rows included: Knowledge before schema 4 was an
|
|
4274
|
+
* experimental store with no migration. Every schema-1/2-only table present is dropped. The table
|
|
4275
|
+
* names schema 4 reuses (mentions, activity, the state table) are dropped too, but only when an old
|
|
4276
|
+
* table is present and no schema-4 state row exists, so they are old-shaped. Schema-4-only tables
|
|
4277
|
+
* are never listed. On a schema-4 store this lists only tables an older process recreated during a
|
|
4278
|
+
* rolling deploy. Drop order is safe for adapters that enforce references.
|
|
4233
4279
|
*/
|
|
4234
|
-
function
|
|
4235
|
-
const
|
|
4236
|
-
const
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
return [...
|
|
4280
|
+
function legacyKnowledgeTablesToDrop(snapshot) {
|
|
4281
|
+
const present = new Set(snapshot.tableNames);
|
|
4282
|
+
const legacy = [...LEGACY_KNOWLEDGE_TABLE_NAMES].filter((name) => present.has(name));
|
|
4283
|
+
if (legacy.length === 0) return [];
|
|
4284
|
+
const reused = snapshot.schemaVersion === 4 ? [] : REUSED_KNOWLEDGE_TABLE_NAMES.filter((name) => present.has(name));
|
|
4285
|
+
const drop = /* @__PURE__ */ new Set([...legacy, ...reused]);
|
|
4286
|
+
return [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].filter((name) => drop.has(name));
|
|
4241
4287
|
}
|
|
4242
|
-
const
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4288
|
+
const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
|
|
4289
|
+
"assertKnowledgePageDescriptionWithinBound",
|
|
4290
|
+
"assertKnowledgeNoteTextWithinBound",
|
|
4291
|
+
"canonicalKnowledgeName",
|
|
4292
|
+
"knowledgePositionBetween",
|
|
4293
|
+
"KnowledgeMoveTooLargeError",
|
|
4294
|
+
"KnowledgePageDepthError",
|
|
4295
|
+
"KnowledgePageHasChildrenError",
|
|
4296
|
+
"KnowledgePageKeyConflictError"
|
|
4297
|
+
];
|
|
4298
|
+
const KNOWLEDGE_PAGES_CORE_NUMBERS = [
|
|
4299
|
+
"MAX_KNOWLEDGE_MOVE_PAGES",
|
|
4300
|
+
"MAX_KNOWLEDGE_PAGE_DEPTH",
|
|
4301
|
+
"MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
|
|
4302
|
+
];
|
|
4303
|
+
function resolveKnowledgePagesCore(storageModule) {
|
|
4304
|
+
const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
|
|
4305
|
+
const missing = [...KNOWLEDGE_PAGES_CORE_FUNCTIONS.filter((name) => typeof module[name] !== "function"), ...KNOWLEDGE_PAGES_CORE_NUMBERS.filter((name) => typeof module[name] !== "number")];
|
|
4306
|
+
if (missing.length > 0) throw new Error(`@mastra/core advertises Knowledge pages without the required storage API (missing ${missing.join(", ")})`);
|
|
4307
|
+
return module;
|
|
4248
4308
|
}
|
|
4249
|
-
|
|
4250
|
-
|
|
4309
|
+
/** Loads core's page-store runtime once, after checking core advertises `knowledge-pages`. */
|
|
4310
|
+
function createKnowledgePagesCoreLoader(features, loadStorageModule) {
|
|
4311
|
+
let core;
|
|
4312
|
+
return async () => {
|
|
4313
|
+
if (!features.has("knowledge-pages")) throw new Error(`Knowledge requires a @mastra/core release with the "${KNOWLEDGE_PAGES_CORE_FEATURE}" feature. Upgrade @mastra/core to use Knowledge; other storage domains keep working on this version.`);
|
|
4314
|
+
core ??= loadStorageModule().then(resolveKnowledgePagesCore).catch((error) => {
|
|
4315
|
+
core = void 0;
|
|
4316
|
+
throw error;
|
|
4317
|
+
});
|
|
4318
|
+
return core;
|
|
4319
|
+
};
|
|
4251
4320
|
}
|
|
4252
|
-
|
|
4253
|
-
|
|
4321
|
+
/**
|
|
4322
|
+
* Keyword-search words: the query split on whitespace, lowercased, deduplicated. Every word must
|
|
4323
|
+
* appear (as a case-insensitive substring) in a hit; an empty list means the search returns nothing.
|
|
4324
|
+
*/
|
|
4325
|
+
function knowledgeSearchWords(query) {
|
|
4326
|
+
return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
|
|
4254
4327
|
}
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4328
|
+
/**
|
|
4329
|
+
* Why `key` can't be a Knowledge page key, or undefined when it can. Keys are one path segment:
|
|
4330
|
+
* non-empty, with no `/` (path separator), `#` (scope/path separator) or `%` (escape character).
|
|
4331
|
+
*/
|
|
4332
|
+
function knowledgePageKeyProblem(key) {
|
|
4333
|
+
if (!key) return "Knowledge page key must not be empty";
|
|
4334
|
+
if (/[/#%]/.test(key)) return `Knowledge page key "${key}" must not contain "/", "#" or "%"`;
|
|
4258
4335
|
}
|
|
4259
|
-
|
|
4260
|
-
|
|
4336
|
+
// src/storage/domains/knowledge/pages.ts
|
|
4337
|
+
const loadPagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
|
|
4338
|
+
const assertKnowledgePageDescriptionWithinBound = async (description) => (await loadPagesCore()).assertKnowledgePageDescriptionWithinBound(description);
|
|
4339
|
+
const assertKnowledgeNoteTextWithinBound = async (text) => (await loadPagesCore()).assertKnowledgeNoteTextWithinBound(text);
|
|
4340
|
+
const T = {
|
|
4341
|
+
pages: "`mastra_knowledge_pages`",
|
|
4342
|
+
notes: "`mastra_knowledge_notes`",
|
|
4343
|
+
stamps: "`mastra_knowledge_note_stamps`",
|
|
4344
|
+
mentions: "`mastra_knowledge_mentions`",
|
|
4345
|
+
pins: "`mastra_knowledge_pins`",
|
|
4346
|
+
pinAudiences: "`mastra_knowledge_pin_audiences`",
|
|
4347
|
+
activity: "`mastra_knowledge_activity`"
|
|
4348
|
+
};
|
|
4349
|
+
const KEY = "`key`";
|
|
4350
|
+
const WHEN = "`when`";
|
|
4351
|
+
const BIN = "CHARACTER SET utf8mb4 COLLATE utf8mb4_bin";
|
|
4352
|
+
const JV = `(SELECT jv.v FROM JSON_TABLE(?, '$[*]' COLUMNS (v VARCHAR(512) ${BIN} PATH '$')) jv)`;
|
|
4353
|
+
/** Live sibling keys are unique per parent; NULL for deleted pages and pages without a key. */
|
|
4354
|
+
const LIVE_KEY_COLUMN = `VARCHAR(768) ${BIN} AS (IF(deletedAt IS NULL AND \`key\` IS NOT NULL, CONCAT(parentId, '#', \`key\`), NULL)) STORED`;
|
|
4355
|
+
const MISSING_PAGE_INDEX = `SELECT 1 FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'mastra_knowledge_pages' AND index_name = ?)`;
|
|
4356
|
+
/** Upgrades for Knowledge databases created by an earlier alpha build; `needed` returns a row when `apply` must run. */
|
|
4357
|
+
const KNOWLEDGE_PAGE_UPGRADES = [
|
|
4358
|
+
{
|
|
4359
|
+
needed: `SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'mastra_knowledge_pages' AND column_name = 'liveKey' AND generation_expression LIKE '%scopeId%'`,
|
|
4360
|
+
args: [],
|
|
4361
|
+
apply: [`ALTER TABLE \`mastra_knowledge_pages\` DROP INDEX idx_kpages_live_key, DROP COLUMN liveKey, ADD COLUMN liveKey ${LIVE_KEY_COLUMN}, ADD UNIQUE KEY idx_kpages_live_key (liveKey)`]
|
|
4362
|
+
},
|
|
4363
|
+
{
|
|
4364
|
+
needed: `SELECT 1 FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'mastra_knowledge_pin_audiences' AND column_name = 'audience' AND character_maximum_length < 512`,
|
|
4365
|
+
args: [],
|
|
4366
|
+
apply: [`ALTER TABLE \`mastra_knowledge_pin_audiences\` MODIFY audience VARCHAR(512) ${BIN} NOT NULL`]
|
|
4367
|
+
},
|
|
4368
|
+
{
|
|
4369
|
+
needed: MISSING_PAGE_INDEX,
|
|
4370
|
+
args: ["idx_kpages_path"],
|
|
4371
|
+
apply: ["ALTER TABLE `mastra_knowledge_pages` ADD KEY idx_kpages_path (path(255))"]
|
|
4372
|
+
},
|
|
4373
|
+
{
|
|
4374
|
+
needed: MISSING_PAGE_INDEX,
|
|
4375
|
+
args: ["idx_kpages_declaration_parent"],
|
|
4376
|
+
apply: ["ALTER TABLE `mastra_knowledge_pages` ADD KEY idx_kpages_declaration_parent (declaration, parentId)"]
|
|
4377
|
+
},
|
|
4378
|
+
{
|
|
4379
|
+
needed: `SELECT 1 FROM DUAL WHERE EXISTS (SELECT 1 FROM mastra_knowledge_pins pin, JSON_TABLE(pin.audience, '$[*]' COLUMNS (v VARCHAR(512) ${BIN} PATH '$')) jv WHERE NOT EXISTS (SELECT 1 FROM mastra_knowledge_pin_audiences pa WHERE pa.pinId = pin.id AND pa.audience = jv.v))`,
|
|
4380
|
+
args: [],
|
|
4381
|
+
apply: [`INSERT IGNORE INTO mastra_knowledge_pin_audiences (audience, pinId) SELECT jv.v, pin.id FROM mastra_knowledge_pins pin, JSON_TABLE(pin.audience, '$[*]' COLUMNS (v VARCHAR(512) ${BIN} PATH '$')) jv`]
|
|
4382
|
+
}
|
|
4383
|
+
];
|
|
4384
|
+
/** DDL for the schema-4 page tables, in creation order. Every statement is idempotent. */
|
|
4385
|
+
const KNOWLEDGE_PAGE_DDL = [
|
|
4386
|
+
`CREATE TABLE IF NOT EXISTS \`mastra_knowledge_access_state\` (
|
|
4387
|
+
id VARCHAR(64) ${BIN} NOT NULL PRIMARY KEY,
|
|
4388
|
+
schemaVersion INT NOT NULL
|
|
4389
|
+
)`,
|
|
4390
|
+
`CREATE TABLE IF NOT EXISTS ${T.pages} (
|
|
4391
|
+
id VARCHAR(255) ${BIN} NOT NULL PRIMARY KEY,
|
|
4392
|
+
scopeId VARCHAR(255) ${BIN} NOT NULL,
|
|
4393
|
+
isScope TINYINT NOT NULL,
|
|
4394
|
+
address VARCHAR(512) ${BIN} NULL,
|
|
4395
|
+
declaration VARCHAR(255) ${BIN} NULL,
|
|
4396
|
+
\`key\` VARCHAR(255) ${BIN} NULL,
|
|
4397
|
+
parentId VARCHAR(255) ${BIN} NULL,
|
|
4398
|
+
position VARCHAR(255) ${BIN} NOT NULL,
|
|
4399
|
+
path VARCHAR(2048) ${BIN} NOT NULL,
|
|
4400
|
+
depth INT NOT NULL,
|
|
4401
|
+
name VARCHAR(512) ${BIN} NOT NULL,
|
|
4402
|
+
canonicalName VARCHAR(512) ${BIN} NOT NULL,
|
|
4403
|
+
description TEXT NULL,
|
|
4404
|
+
body LONGTEXT NULL,
|
|
4405
|
+
version INT NOT NULL,
|
|
4406
|
+
mergedInto VARCHAR(255) ${BIN} NULL,
|
|
4407
|
+
createdBy TEXT NOT NULL,
|
|
4408
|
+
updatedBy TEXT NOT NULL,
|
|
4409
|
+
createdAt VARCHAR(32) NOT NULL,
|
|
4410
|
+
updatedAt VARCHAR(32) NOT NULL,
|
|
4411
|
+
deletedAt VARCHAR(32) NULL,
|
|
4412
|
+
deletedBy VARCHAR(255) NULL,
|
|
4413
|
+
liveKey ${LIVE_KEY_COLUMN},
|
|
4414
|
+
UNIQUE KEY idx_kpages_address (address),
|
|
4415
|
+
UNIQUE KEY idx_kpages_live_key (liveKey),
|
|
4416
|
+
KEY idx_kpages_scope (scopeId),
|
|
4417
|
+
KEY idx_kpages_parent (parentId, position, id),
|
|
4418
|
+
KEY idx_kpages_path (path(255)),
|
|
4419
|
+
KEY idx_kpages_declaration_parent (declaration, parentId),
|
|
4420
|
+
KEY idx_kpages_canonical (canonicalName(255))
|
|
4421
|
+
)`,
|
|
4422
|
+
`CREATE TABLE IF NOT EXISTS ${T.notes} (
|
|
4423
|
+
id VARCHAR(255) ${BIN} NOT NULL PRIMARY KEY,
|
|
4424
|
+
pageId VARCHAR(255) ${BIN} NOT NULL,
|
|
4425
|
+
text LONGTEXT NOT NULL,
|
|
4426
|
+
\`when\` VARCHAR(32) NULL,
|
|
4427
|
+
provenance TEXT NOT NULL,
|
|
4428
|
+
version INT NOT NULL,
|
|
4429
|
+
createdAt VARCHAR(32) NOT NULL,
|
|
4430
|
+
updatedAt VARCHAR(32) NOT NULL,
|
|
4431
|
+
deletedAt VARCHAR(32) NULL,
|
|
4432
|
+
deletedBy VARCHAR(255) NULL,
|
|
4433
|
+
KEY idx_knotes_page (pageId, createdAt, id)
|
|
4434
|
+
)`,
|
|
4435
|
+
`CREATE TABLE IF NOT EXISTS ${T.stamps} (
|
|
4436
|
+
noteId VARCHAR(255) ${BIN} NOT NULL,
|
|
4437
|
+
scopeId VARCHAR(255) ${BIN} NOT NULL,
|
|
4438
|
+
PRIMARY KEY (noteId, scopeId),
|
|
4439
|
+
KEY idx_kstamps_scope (scopeId)
|
|
4440
|
+
)`,
|
|
4441
|
+
`CREATE TABLE IF NOT EXISTS ${T.mentions} (
|
|
4442
|
+
noteId VARCHAR(255) ${BIN} NOT NULL,
|
|
4443
|
+
pageId VARCHAR(255) ${BIN} NOT NULL,
|
|
4444
|
+
PRIMARY KEY (noteId, pageId),
|
|
4445
|
+
KEY idx_kmentions_page (pageId)
|
|
4446
|
+
)`,
|
|
4447
|
+
`CREATE TABLE IF NOT EXISTS ${T.pins} (
|
|
4448
|
+
id VARCHAR(255) ${BIN} NOT NULL PRIMARY KEY,
|
|
4449
|
+
pageId VARCHAR(255) ${BIN} NOT NULL,
|
|
4450
|
+
audience JSON NOT NULL,
|
|
4451
|
+
createdBy VARCHAR(255) NOT NULL,
|
|
4452
|
+
createdAt VARCHAR(32) NOT NULL,
|
|
4453
|
+
updatedAt VARCHAR(32) NOT NULL,
|
|
4454
|
+
UNIQUE KEY idx_kpins_page (pageId)
|
|
4455
|
+
)`,
|
|
4456
|
+
`CREATE TABLE IF NOT EXISTS ${T.pinAudiences} (
|
|
4457
|
+
audience VARCHAR(512) ${BIN} NOT NULL,
|
|
4458
|
+
pinId VARCHAR(255) ${BIN} NOT NULL,
|
|
4459
|
+
PRIMARY KEY (audience, pinId),
|
|
4460
|
+
KEY idx_kpin_audiences_pin (pinId)
|
|
4461
|
+
)`,
|
|
4462
|
+
`CREATE TABLE IF NOT EXISTS ${T.activity} (
|
|
4463
|
+
id VARCHAR(255) ${BIN} NOT NULL PRIMARY KEY,
|
|
4464
|
+
action VARCHAR(64) NOT NULL,
|
|
4465
|
+
scopeId VARCHAR(255) ${BIN} NOT NULL,
|
|
4466
|
+
pageId VARCHAR(255) ${BIN} NOT NULL,
|
|
4467
|
+
noteId VARCHAR(255) ${BIN} NULL,
|
|
4468
|
+
provenance TEXT NOT NULL,
|
|
4469
|
+
createdAt VARCHAR(32) NOT NULL,
|
|
4470
|
+
KEY idx_kactivity_page (pageId, id)
|
|
4471
|
+
)`
|
|
4472
|
+
];
|
|
4473
|
+
function str(value) {
|
|
4474
|
+
return value === null || value === void 0 ? void 0 : String(value);
|
|
4261
4475
|
}
|
|
4262
|
-
function
|
|
4263
|
-
return
|
|
4476
|
+
function date(value) {
|
|
4477
|
+
return value === null || value === void 0 ? void 0 : new Date(String(value));
|
|
4264
4478
|
}
|
|
4265
|
-
function
|
|
4266
|
-
return
|
|
4479
|
+
function provenance(raw) {
|
|
4480
|
+
return JSON.parse(String(raw));
|
|
4267
4481
|
}
|
|
4268
|
-
function
|
|
4482
|
+
function toPage(row) {
|
|
4269
4483
|
return {
|
|
4270
4484
|
id: String(row.id),
|
|
4271
|
-
|
|
4485
|
+
scopeId: String(row.scopeId),
|
|
4486
|
+
parentId: str(row.parentId) ?? null,
|
|
4487
|
+
isScope: Number(row.isScope) === 1,
|
|
4488
|
+
...row.address !== null && row.address !== void 0 ? { address: String(row.address) } : {},
|
|
4489
|
+
...row.declaration !== null && row.declaration !== void 0 ? { declaration: String(row.declaration) } : {},
|
|
4490
|
+
...row.key !== null && row.key !== void 0 ? { key: String(row.key) } : {},
|
|
4491
|
+
position: String(row.position),
|
|
4272
4492
|
name: String(row.name),
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
description: row.description == null ? void 0 : String(row.description),
|
|
4276
|
-
scope: parseJson$1(row.scopeJson ?? row.scope),
|
|
4493
|
+
...row.description !== null && row.description !== void 0 ? { description: String(row.description) } : {},
|
|
4494
|
+
...row.body !== null && row.body !== void 0 ? { body: String(row.body) } : {},
|
|
4277
4495
|
version: Number(row.version),
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4496
|
+
...row.mergedInto !== null && row.mergedInto !== void 0 ? { mergedInto: String(row.mergedInto) } : {},
|
|
4497
|
+
createdBy: provenance(row.createdBy),
|
|
4498
|
+
updatedBy: provenance(row.updatedBy),
|
|
4499
|
+
createdAt: new Date(String(row.createdAt)),
|
|
4500
|
+
updatedAt: new Date(String(row.updatedAt)),
|
|
4501
|
+
...row.deletedAt ? { deletedAt: new Date(String(row.deletedAt)) } : {},
|
|
4502
|
+
...row.deletedBy ? { deletedBy: String(row.deletedBy) } : {}
|
|
4281
4503
|
};
|
|
4282
4504
|
}
|
|
4283
|
-
function
|
|
4505
|
+
function toNote(row, stamps, mentions) {
|
|
4506
|
+
const when = date(row.when);
|
|
4284
4507
|
return {
|
|
4285
4508
|
id: String(row.id),
|
|
4286
|
-
|
|
4509
|
+
pageId: String(row.pageId),
|
|
4287
4510
|
text: String(row.text),
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4511
|
+
...when ? { when } : {},
|
|
4512
|
+
stampScopeIds: stamps,
|
|
4513
|
+
mentions,
|
|
4514
|
+
provenance: provenance(row.provenance),
|
|
4515
|
+
version: Number(row.version),
|
|
4516
|
+
createdAt: new Date(String(row.createdAt)),
|
|
4517
|
+
updatedAt: new Date(String(row.updatedAt)),
|
|
4518
|
+
...row.deletedAt ? { deletedAt: new Date(String(row.deletedAt)) } : {},
|
|
4519
|
+
...row.deletedBy ? { deletedBy: String(row.deletedBy) } : {}
|
|
4295
4520
|
};
|
|
4296
4521
|
}
|
|
4297
|
-
function
|
|
4522
|
+
function toPin(row) {
|
|
4298
4523
|
return {
|
|
4299
4524
|
id: String(row.id),
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
status: String(row.status),
|
|
4306
|
-
attempts: Number(row.attempts),
|
|
4307
|
-
availableAt: toDate(row.availableAt),
|
|
4308
|
-
claimedAt: optionalDate(row.claimedAt),
|
|
4309
|
-
claimedBy: row.claimedBy == null ? void 0 : String(row.claimedBy),
|
|
4310
|
-
createdAt: toDate(row.createdAt),
|
|
4311
|
-
completedAt: optionalDate(row.completedAt)
|
|
4525
|
+
pageId: String(row.pageId),
|
|
4526
|
+
audience: parseJsonList(row.audience),
|
|
4527
|
+
createdBy: String(row.createdBy),
|
|
4528
|
+
createdAt: new Date(String(row.createdAt)),
|
|
4529
|
+
updatedAt: new Date(String(row.updatedAt))
|
|
4312
4530
|
};
|
|
4313
4531
|
}
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
return
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
}),
|
|
4339
|
-
generateTableSQL({
|
|
4340
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS,
|
|
4341
|
-
schema: _mastra_core_storage.KNOWLEDGE_MENTIONS_SCHEMA,
|
|
4342
|
-
compositePrimaryKey: [
|
|
4343
|
-
"sourceType",
|
|
4344
|
-
"sourceId",
|
|
4345
|
-
"recordId"
|
|
4346
|
-
]
|
|
4347
|
-
}),
|
|
4348
|
-
generateTableSQL({
|
|
4349
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
|
|
4350
|
-
schema: _mastra_core_storage.KNOWLEDGE_ACTIVITY_SCHEMA
|
|
4351
|
-
}),
|
|
4352
|
-
generateTableSQL({
|
|
4353
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
4354
|
-
schema: _mastra_core_storage.KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
4355
|
-
}),
|
|
4356
|
-
...KNOWLEDGE_INDEX_DDL.map(mysqlSql)
|
|
4357
|
-
];
|
|
4358
|
-
}
|
|
4359
|
-
#pool;
|
|
4360
|
-
#client;
|
|
4361
|
-
#operations;
|
|
4362
|
-
constructor({ pool, operations }) {
|
|
4363
|
-
super();
|
|
4364
|
-
this.#pool = pool;
|
|
4365
|
-
this.#client = createExecutor(pool);
|
|
4366
|
-
this.#operations = operations;
|
|
4532
|
+
function toListedPin(row) {
|
|
4533
|
+
return {
|
|
4534
|
+
...toPin(row),
|
|
4535
|
+
pageName: String(row.pageName)
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
function limitOf(limit, max) {
|
|
4539
|
+
return Math.max(1, Math.min(limit ?? max, max));
|
|
4540
|
+
}
|
|
4541
|
+
function encodeCursor(value) {
|
|
4542
|
+
return Buffer.from(JSON.stringify(value)).toString("base64url");
|
|
4543
|
+
}
|
|
4544
|
+
function decodeCursor(cursor, length) {
|
|
4545
|
+
if (cursor === void 0) return void 0;
|
|
4546
|
+
try {
|
|
4547
|
+
const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
4548
|
+
if (Array.isArray(parsed) && parsed.length === length && parsed.every((item) => typeof item === "string")) return parsed;
|
|
4549
|
+
} catch {}
|
|
4550
|
+
throw new Error("Invalid Knowledge cursor");
|
|
4551
|
+
}
|
|
4552
|
+
function isRetryableRace(error) {
|
|
4553
|
+
for (let current = error; current && typeof current === "object"; current = current.cause) {
|
|
4554
|
+
const { code, errno } = current;
|
|
4555
|
+
if (code === "ER_DUP_ENTRY" || errno === 1062 || code === "ER_LOCK_DEADLOCK" || errno === 1213) return true;
|
|
4367
4556
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
|
|
4388
|
-
schema: _mastra_core_storage.KNOWLEDGE_ACTIVITY_SCHEMA
|
|
4389
|
-
});
|
|
4390
|
-
await this.#operations.createTable({
|
|
4391
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
4392
|
-
schema: _mastra_core_storage.KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
4393
|
-
});
|
|
4394
|
-
for (const sql of KNOWLEDGE_INDEX_DDL) try {
|
|
4395
|
-
await this.#client.execute(sql);
|
|
4396
|
-
} catch (error) {
|
|
4397
|
-
if (error.code !== "ER_DUP_KEYNAME") throw error;
|
|
4557
|
+
return false;
|
|
4558
|
+
}
|
|
4559
|
+
function escapeLike(value) {
|
|
4560
|
+
return value.replace(/[=%_]/g, (match) => `=${match}`);
|
|
4561
|
+
}
|
|
4562
|
+
/**
|
|
4563
|
+
* A string above every string that starts with `prefix`, comparing by code point (which is how
|
|
4564
|
+
* `utf8mb4_bin` orders). `column >= prefix AND column < bound` is an exact, case-sensitive prefix
|
|
4565
|
+
* match that an index range scan can serve, where `LEFT(column, CHAR_LENGTH(prefix)) = prefix`
|
|
4566
|
+
* cannot. When the last code point cannot be raised (an empty prefix, or one made only of
|
|
4567
|
+
* U+10FFFF), the bound is the prefix plus more U+10FFFF than an address column can hold (512).
|
|
4568
|
+
*/
|
|
4569
|
+
function prefixUpperBound(prefix) {
|
|
4570
|
+
const points = Array.from(prefix);
|
|
4571
|
+
while (points.length > 0) {
|
|
4572
|
+
const last = points.pop().codePointAt(0);
|
|
4573
|
+
if (last < 1114111) {
|
|
4574
|
+
const next = last === 55295 ? 57344 : last + 1;
|
|
4575
|
+
return points.join("") + String.fromCodePoint(next);
|
|
4398
4576
|
}
|
|
4399
4577
|
}
|
|
4400
|
-
|
|
4578
|
+
return prefix + "".repeat(600);
|
|
4579
|
+
}
|
|
4580
|
+
/**
|
|
4581
|
+
* `utf8mb4_bin` is PAD SPACE: a value compares as if padded with spaces, so `p:a\t` sorts below `p:a`.
|
|
4582
|
+
* Starting the range at the prefix plus U+0000 keeps every value that begins with the prefix, whatever
|
|
4583
|
+
* code point follows. (Values equal to the prefix plus trailing spaces still match, as they always have.)
|
|
4584
|
+
*/
|
|
4585
|
+
function prefixLowerBound(prefix) {
|
|
4586
|
+
return prefix + "\0";
|
|
4587
|
+
}
|
|
4588
|
+
/** `column` starts with `prefix`, as an index-friendly range. */
|
|
4589
|
+
function prefixRange(column, prefix) {
|
|
4590
|
+
return {
|
|
4591
|
+
sql: `(${column} >= ? AND ${column} < ?)`,
|
|
4592
|
+
args: [prefixLowerBound(prefix), prefixUpperBound(prefix)]
|
|
4593
|
+
};
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* The readable scope page ids as a subquery. The caller's addresses, prefixes and children go in as
|
|
4597
|
+
* JSON arrays so a large read scope never hits a bind-parameter limit. Each kind is its own index
|
|
4598
|
+
* lookup (address equality, address range for a prefix, `(declaration, parentId)` for children),
|
|
4599
|
+
* unioned, so the cost follows the caller's reach rather than the number of scopes in the store.
|
|
4600
|
+
*/
|
|
4601
|
+
function readableScopes(read) {
|
|
4602
|
+
if (read === "all") return {
|
|
4603
|
+
sql: `SELECT id FROM ${T.pages} WHERE isScope = 1`,
|
|
4604
|
+
args: []
|
|
4605
|
+
};
|
|
4606
|
+
const parts = [];
|
|
4607
|
+
const args = [];
|
|
4608
|
+
if (read.addresses.length > 0) {
|
|
4609
|
+
parts.push(`SELECT id FROM ${T.pages} WHERE isScope = 1 AND address IN ${JV}`);
|
|
4610
|
+
args.push(JSON.stringify(read.addresses));
|
|
4611
|
+
}
|
|
4612
|
+
if (read.prefixes.length > 0) {
|
|
4613
|
+
parts.push(`SELECT sp.id FROM JSON_TABLE(?, '$[*]' COLUMNS (d VARCHAR(255) ${BIN} PATH '$.declaration', lo VARCHAR(512) ${BIN} PATH '$.lo', hi VARCHAR(1200) ${BIN} PATH '$.hi')) j STRAIGHT_JOIN ${T.pages} sp USE INDEX (idx_kpages_address) ON sp.address >= j.lo AND sp.address < j.hi AND sp.isScope = 1 AND sp.declaration = j.d`);
|
|
4614
|
+
args.push(JSON.stringify(read.prefixes.map((entry) => ({
|
|
4615
|
+
declaration: entry.declaration,
|
|
4616
|
+
lo: prefixLowerBound(entry.prefix),
|
|
4617
|
+
hi: prefixUpperBound(entry.prefix)
|
|
4618
|
+
}))));
|
|
4619
|
+
}
|
|
4620
|
+
if (read.children.length > 0) {
|
|
4621
|
+
parts.push(`SELECT sp.id FROM JSON_TABLE(?, '$[*]' COLUMNS (d VARCHAR(255) ${BIN} PATH '$.declaration', pid VARCHAR(255) ${BIN} PATH '$.parentId' NULL ON EMPTY)) j STRAIGHT_JOIN ${T.pages} sp ON sp.isScope = 1 AND sp.declaration = j.d AND sp.parentId <=> j.pid`);
|
|
4622
|
+
args.push(JSON.stringify(read.children));
|
|
4623
|
+
}
|
|
4624
|
+
if (parts.length === 0) return {
|
|
4625
|
+
sql: `SELECT id FROM ${T.pages} WHERE 0`,
|
|
4626
|
+
args: []
|
|
4627
|
+
};
|
|
4628
|
+
return {
|
|
4629
|
+
sql: parts.join(" UNION "),
|
|
4630
|
+
args
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
4633
|
+
/**
|
|
4634
|
+
* `FROM` clause that reads the readable scopes first and then each one's pages through `idx_kpages_scope`.
|
|
4635
|
+
* MySQL otherwise tends to scan every page in the store and test its scope, which touches other
|
|
4636
|
+
* tenants' rows. The `alias` pages are in readable scopes; add the page filters in `WHERE`.
|
|
4637
|
+
*/
|
|
4638
|
+
function pagesInReadableScopes(alias, read) {
|
|
4639
|
+
const scopes = readableScopes(read);
|
|
4640
|
+
return {
|
|
4641
|
+
sql: `FROM (${scopes.sql}) rs STRAIGHT_JOIN ${T.pages} ${alias} FORCE INDEX (idx_kpages_scope) ON ${alias}.scopeId = rs.id`,
|
|
4642
|
+
args: scopes.args
|
|
4643
|
+
};
|
|
4644
|
+
}
|
|
4645
|
+
/** `alias.scopeId` is readable. */
|
|
4646
|
+
function pageReadable(alias, read) {
|
|
4647
|
+
const scopes = readableScopes(read);
|
|
4648
|
+
return {
|
|
4649
|
+
sql: `${alias}.scopeId IN (SELECT rs.id FROM (${scopes.sql}) rs)`,
|
|
4650
|
+
args: scopes.args
|
|
4651
|
+
};
|
|
4652
|
+
}
|
|
4653
|
+
/**
|
|
4654
|
+
* Note `alias` is visible: its page's scope is readable, a stamp is readable, and every resolved
|
|
4655
|
+
* mention's page sits in a readable scope.
|
|
4656
|
+
*/
|
|
4657
|
+
function noteReadable(alias, read) {
|
|
4658
|
+
const scopes = readableScopes(read);
|
|
4659
|
+
return {
|
|
4660
|
+
sql: `EXISTS (SELECT 1 FROM ${T.pages} vp WHERE vp.id = ${alias}.pageId AND vp.scopeId IN (${scopes.sql}))
|
|
4661
|
+
AND EXISTS (SELECT 1 FROM ${T.stamps} vs WHERE vs.noteId = ${alias}.id AND vs.scopeId IN (${scopes.sql}))
|
|
4662
|
+
AND NOT EXISTS (SELECT 1 FROM ${T.mentions} vm JOIN ${T.pages} vmp ON vmp.id = vm.pageId WHERE vm.noteId = ${alias}.id AND vmp.scopeId NOT IN (${scopes.sql}))`,
|
|
4663
|
+
args: [
|
|
4664
|
+
...scopes.args,
|
|
4665
|
+
...scopes.args,
|
|
4666
|
+
...scopes.args
|
|
4667
|
+
]
|
|
4668
|
+
};
|
|
4669
|
+
}
|
|
4670
|
+
function jsonList(values) {
|
|
4671
|
+
return JSON.stringify(values);
|
|
4672
|
+
}
|
|
4673
|
+
var MySQLKnowledgePages = class {
|
|
4674
|
+
#read;
|
|
4675
|
+
#transaction;
|
|
4676
|
+
constructor(config) {
|
|
4677
|
+
this.#read = config.read;
|
|
4678
|
+
this.#transaction = config.transaction;
|
|
4679
|
+
}
|
|
4680
|
+
/** Deletes every row from the page tables (scopes, pages, notes, pins, activity). */
|
|
4681
|
+
async clearAll() {
|
|
4401
4682
|
await this.#transaction(async (tx) => {
|
|
4402
4683
|
for (const table of [
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4684
|
+
T.activity,
|
|
4685
|
+
T.pinAudiences,
|
|
4686
|
+
T.pins,
|
|
4687
|
+
T.mentions,
|
|
4688
|
+
T.stamps,
|
|
4689
|
+
T.notes,
|
|
4690
|
+
T.pages
|
|
4691
|
+
]) await tx.execute({
|
|
4692
|
+
sql: `DELETE FROM ${table}`,
|
|
4693
|
+
args: []
|
|
4694
|
+
});
|
|
4409
4695
|
});
|
|
4410
4696
|
}
|
|
4411
|
-
async
|
|
4412
|
-
await
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4697
|
+
async #query(executor, sql, args = []) {
|
|
4698
|
+
return (await executor.execute({
|
|
4699
|
+
sql,
|
|
4700
|
+
args
|
|
4701
|
+
})).rows;
|
|
4702
|
+
}
|
|
4703
|
+
async #pageRow(executor, id) {
|
|
4704
|
+
return (await this.#query(executor, `SELECT * FROM ${T.pages} WHERE id = ?`, [id]))[0];
|
|
4705
|
+
}
|
|
4706
|
+
async #livePage(executor, id) {
|
|
4707
|
+
const row = await this.#pageRow(executor, id);
|
|
4708
|
+
if (!row || row.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("page", id);
|
|
4709
|
+
return row;
|
|
4710
|
+
}
|
|
4711
|
+
async #logActivity(tx, action, event, now) {
|
|
4712
|
+
await tx.execute({
|
|
4713
|
+
sql: `INSERT INTO ${T.activity} (id, action, scopeId, pageId, noteId, provenance, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
4714
|
+
args: [
|
|
4715
|
+
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
4716
|
+
action,
|
|
4717
|
+
event.scopeId,
|
|
4718
|
+
event.pageId,
|
|
4719
|
+
event.noteId ?? null,
|
|
4720
|
+
JSON.stringify(event.provenance),
|
|
4721
|
+
now
|
|
4722
|
+
]
|
|
4723
|
+
});
|
|
4724
|
+
}
|
|
4725
|
+
async #lastPosition(tx, parentId, scopesOnly) {
|
|
4726
|
+
const rows = await this.#query(tx, `SELECT position FROM ${T.pages} WHERE parentId <=> ? AND isScope = ? AND deletedAt IS NULL ORDER BY position DESC, id DESC LIMIT 1`, [parentId, scopesOnly ? 1 : 0]);
|
|
4727
|
+
return rows[0] ? String(rows[0].position) : void 0;
|
|
4728
|
+
}
|
|
4729
|
+
async materializeScope(input) {
|
|
4730
|
+
for (let attempt = 1;; attempt++) try {
|
|
4731
|
+
return await this.#materializeScopeOnce(input);
|
|
4732
|
+
} catch (error) {
|
|
4733
|
+
if (attempt >= 5 || !isRetryableRace(error)) throw error;
|
|
4417
4734
|
}
|
|
4418
|
-
|
|
4735
|
+
}
|
|
4736
|
+
async #materializeScopeOnce(input) {
|
|
4737
|
+
const core = await loadPagesCore();
|
|
4738
|
+
await assertKnowledgePageDescriptionWithinBound(input.description);
|
|
4419
4739
|
return this.#transaction(async (tx) => {
|
|
4420
|
-
const
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
return
|
|
4740
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4741
|
+
const ensureScope = async (spec, parentId) => {
|
|
4742
|
+
await assertKnowledgePageDescriptionWithinBound(spec.description);
|
|
4743
|
+
const existing = (await this.#query(tx, `SELECT * FROM ${T.pages} WHERE isScope = 1 AND address = ?`, [spec.address]))[0];
|
|
4744
|
+
if (existing) return {
|
|
4745
|
+
row: existing,
|
|
4746
|
+
created: false
|
|
4747
|
+
};
|
|
4748
|
+
if (parentId !== null) await this.#livePage(tx, parentId);
|
|
4749
|
+
const id = (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
4750
|
+
const position = core.knowledgePositionBetween(await this.#lastPosition(tx, parentId, true));
|
|
4751
|
+
const who = JSON.stringify(input.provenance);
|
|
4752
|
+
await tx.execute({
|
|
4753
|
+
sql: `INSERT INTO ${T.pages} (id, scopeId, isScope, address, declaration, parentId, position, path, depth, name, canonicalName, description, version, createdBy, updatedBy, createdAt, updatedAt) VALUES (?, ?, 1, ?, ?, ?, ?, ?, 0, ?, ?, ?, 1, ?, ?, ?, ?)`,
|
|
4754
|
+
args: [
|
|
4755
|
+
id,
|
|
4756
|
+
id,
|
|
4757
|
+
spec.address,
|
|
4758
|
+
spec.declaration,
|
|
4759
|
+
parentId,
|
|
4760
|
+
position,
|
|
4761
|
+
`/${id}/`,
|
|
4762
|
+
spec.name,
|
|
4763
|
+
core.canonicalKnowledgeName(spec.name),
|
|
4764
|
+
spec.description ?? null,
|
|
4765
|
+
who,
|
|
4766
|
+
who,
|
|
4767
|
+
now,
|
|
4768
|
+
now
|
|
4769
|
+
]
|
|
4770
|
+
});
|
|
4771
|
+
await this.#logActivity(tx, "scope-created", {
|
|
4772
|
+
scopeId: id,
|
|
4773
|
+
pageId: id,
|
|
4774
|
+
provenance: input.provenance
|
|
4775
|
+
}, now);
|
|
4776
|
+
return {
|
|
4777
|
+
row: await this.#pageRow(tx, id),
|
|
4778
|
+
created: true
|
|
4779
|
+
};
|
|
4780
|
+
};
|
|
4781
|
+
const ensured = await ensureScope(input, input.parentId ?? null);
|
|
4782
|
+
let row = ensured.row;
|
|
4783
|
+
const created = ensured.created;
|
|
4784
|
+
const scopeId = String(row.id);
|
|
4785
|
+
if (!created && input.parentId != null && str(row.parentId) === void 0) {
|
|
4786
|
+
await this.#livePage(tx, input.parentId);
|
|
4787
|
+
if (input.parentId === scopeId) throw new Error(`Scope ${scopeId} cannot be its own parent`);
|
|
4788
|
+
let ancestor = input.parentId;
|
|
4789
|
+
while (ancestor) {
|
|
4790
|
+
const up = await this.#pageRow(tx, ancestor);
|
|
4791
|
+
if (String(up.id) === scopeId) throw new Error(`Scope ${scopeId} cannot be linked under its own descendant`);
|
|
4792
|
+
ancestor = str(up.parentId);
|
|
4793
|
+
}
|
|
4794
|
+
await tx.execute({
|
|
4795
|
+
sql: `UPDATE ${T.pages} SET parentId = ?, position = ?, version = version + 1, updatedAt = ? WHERE id = ? AND parentId IS NULL`,
|
|
4796
|
+
args: [
|
|
4797
|
+
input.parentId,
|
|
4798
|
+
core.knowledgePositionBetween(await this.#lastPosition(tx, input.parentId, true)),
|
|
4799
|
+
now,
|
|
4800
|
+
scopeId
|
|
4801
|
+
]
|
|
4802
|
+
});
|
|
4803
|
+
row = await this.#pageRow(tx, scopeId);
|
|
4425
4804
|
}
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4805
|
+
let parentConflict;
|
|
4806
|
+
if (!created && input.parentId !== void 0 && (str(row.parentId) ?? null) !== input.parentId) parentConflict = {
|
|
4807
|
+
existingParentId: str(row.parentId) ?? null,
|
|
4808
|
+
requestedParentId: input.parentId
|
|
4809
|
+
};
|
|
4810
|
+
for (const child of input.children ?? []) await ensureScope(child, scopeId);
|
|
4811
|
+
const ensureTemplates = async (templates, parentId) => {
|
|
4812
|
+
for (const template of templates) {
|
|
4813
|
+
const problem = knowledgePageKeyProblem(template.key);
|
|
4814
|
+
if (problem) throw new Error(problem);
|
|
4815
|
+
const existing = (await this.#query(tx, `SELECT id FROM ${T.pages} WHERE parentId = ? AND isScope = 0 AND ${KEY} = ? AND deletedAt IS NULL`, [parentId, template.key]))[0];
|
|
4816
|
+
const id = existing ? String(existing.id) : (await this.#insertPage(tx, {
|
|
4817
|
+
parentId,
|
|
4818
|
+
key: template.key,
|
|
4819
|
+
name: template.name,
|
|
4820
|
+
description: template.description,
|
|
4821
|
+
provenance: input.provenance
|
|
4822
|
+
})).id;
|
|
4823
|
+
if (template.pages?.length) await ensureTemplates(template.pages, id);
|
|
4824
|
+
}
|
|
4825
|
+
};
|
|
4826
|
+
await ensureTemplates(input.pages ?? [], scopeId);
|
|
4827
|
+
return {
|
|
4828
|
+
scope: toPage(await this.#pageRow(tx, scopeId)),
|
|
4829
|
+
created,
|
|
4830
|
+
...parentConflict ? { parentConflict } : {}
|
|
4438
4831
|
};
|
|
4439
|
-
await tx.execute({
|
|
4440
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,NULL,?,?)`,
|
|
4441
|
-
args: [
|
|
4442
|
-
node.id,
|
|
4443
|
-
"node",
|
|
4444
|
-
node.name,
|
|
4445
|
-
canonicalName(node.name),
|
|
4446
|
-
node.kind,
|
|
4447
|
-
node.content ?? null,
|
|
4448
|
-
node.description ?? null,
|
|
4449
|
-
JSON.stringify(scope),
|
|
4450
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
4451
|
-
node.version,
|
|
4452
|
-
now.toISOString(),
|
|
4453
|
-
now.toISOString()
|
|
4454
|
-
]
|
|
4455
|
-
});
|
|
4456
|
-
await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
|
|
4457
|
-
await this.#activity(tx, "node-created", "node", node.id, scope);
|
|
4458
|
-
await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
|
|
4459
|
-
return node;
|
|
4460
4832
|
});
|
|
4461
4833
|
}
|
|
4462
|
-
async
|
|
4463
|
-
return this.#
|
|
4834
|
+
async listScopeDeclarations() {
|
|
4835
|
+
return (await this.#query(this.#read, `SELECT declaration, COUNT(*) AS scopes FROM ${T.pages} WHERE isScope = 1 AND declaration IS NOT NULL GROUP BY declaration ORDER BY declaration`)).map((row) => ({
|
|
4836
|
+
declaration: String(row.declaration),
|
|
4837
|
+
scopes: Number(row.scopes)
|
|
4838
|
+
}));
|
|
4464
4839
|
}
|
|
4465
|
-
async
|
|
4466
|
-
return this.#
|
|
4840
|
+
async renameScopeDeclaration(input) {
|
|
4841
|
+
return this.#transaction(async (tx) => {
|
|
4842
|
+
const within = prefixRange("declaration", `${input.from}/`);
|
|
4843
|
+
const affected = (await this.#query(tx, `SELECT id, address, declaration FROM ${T.pages} WHERE isScope = 1 AND (declaration = ? OR ${within.sql}) FOR UPDATE`, [input.from, ...within.args])).map((row) => ({
|
|
4844
|
+
id: String(row.id),
|
|
4845
|
+
address: String(row.address),
|
|
4846
|
+
from: String(row.declaration)
|
|
4847
|
+
})).filter((row) => row.from === input.from || row.from.startsWith(`${input.from}/`)).map((row) => ({
|
|
4848
|
+
...row,
|
|
4849
|
+
to: input.to + row.from.slice(input.from.length)
|
|
4850
|
+
}));
|
|
4851
|
+
input.check(affected.map(({ address, from, to }) => ({
|
|
4852
|
+
address,
|
|
4853
|
+
from,
|
|
4854
|
+
to
|
|
4855
|
+
})));
|
|
4856
|
+
for (const row of affected) await tx.execute({
|
|
4857
|
+
sql: `UPDATE ${T.pages} SET declaration = ? WHERE id = ?`,
|
|
4858
|
+
args: [row.to, row.id]
|
|
4859
|
+
});
|
|
4860
|
+
return affected.length;
|
|
4861
|
+
});
|
|
4467
4862
|
}
|
|
4468
|
-
async
|
|
4469
|
-
|
|
4863
|
+
async getScopesByAddress(addresses) {
|
|
4864
|
+
if (addresses.length === 0) return [];
|
|
4865
|
+
return (await this.#query(this.#read, `SELECT * FROM ${T.pages} WHERE isScope = 1 AND address IN ${JV}`, [jsonList(addresses)])).map(toPage);
|
|
4470
4866
|
}
|
|
4471
|
-
async
|
|
4472
|
-
const
|
|
4473
|
-
const
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
}
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
kind: input.kind,
|
|
4492
|
-
hasContent: input.hasContent
|
|
4493
|
-
});
|
|
4494
|
-
const updatedAt = databaseTimestamp(cursor.updatedAt);
|
|
4495
|
-
clauses.push("(updatedAt < ? OR (updatedAt = ? AND (name > ? OR (name = ? AND id > ?))))");
|
|
4496
|
-
args.push(updatedAt, updatedAt, cursor.name, cursor.name, cursor.id);
|
|
4497
|
-
}
|
|
4498
|
-
args.push(input.limit ?? 100);
|
|
4499
|
-
return (await this.#client.execute({
|
|
4500
|
-
sql: `SELECT *, scope AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE ${clauses.join(" AND ")} ORDER BY updatedAt DESC, name ASC, id ASC LIMIT ?`,
|
|
4501
|
-
args
|
|
4502
|
-
})).rows.map(parseNode);
|
|
4867
|
+
async listChildScopes(input) {
|
|
4868
|
+
const core = await loadPagesCore();
|
|
4869
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
4870
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
4871
|
+
const args = [input.parentId];
|
|
4872
|
+
let after = "";
|
|
4873
|
+
if (cursor) {
|
|
4874
|
+
after = ` AND (position > ? OR (position = ? AND id > ?))`;
|
|
4875
|
+
args.push(cursor[0], cursor[0], cursor[1]);
|
|
4876
|
+
}
|
|
4877
|
+
args.push(limit + 1);
|
|
4878
|
+
const rows = await this.#query(this.#read, `SELECT * FROM ${T.pages} WHERE isScope = 1 AND parentId <=> ?${after} ORDER BY position, id LIMIT ?`, args);
|
|
4879
|
+
return this.#pageOutput(rows, limit, (row) => [String(row.position), String(row.id)]);
|
|
4880
|
+
}
|
|
4881
|
+
#pageOutput(rows, limit, cursorOf) {
|
|
4882
|
+
const page = rows.slice(0, limit);
|
|
4883
|
+
return {
|
|
4884
|
+
pages: page.map(toPage),
|
|
4885
|
+
...rows.length > limit ? { nextCursor: encodeCursor(cursorOf(page[page.length - 1])) } : {}
|
|
4886
|
+
};
|
|
4503
4887
|
}
|
|
4504
|
-
async
|
|
4505
|
-
|
|
4888
|
+
async getPage(input) {
|
|
4889
|
+
const readable = pageReadable("p", input.read);
|
|
4890
|
+
const rows = await this.#query(this.#read, `SELECT p.* FROM ${T.pages} p WHERE p.id = ? AND ${readable.sql}`, [input.id, ...readable.args]);
|
|
4891
|
+
return rows[0] ? toPage(rows[0]) : null;
|
|
4892
|
+
}
|
|
4893
|
+
async getPages(input) {
|
|
4894
|
+
const ids = [...new Set(input.ids)];
|
|
4895
|
+
if (ids.length === 0) return [];
|
|
4896
|
+
const readable = pageReadable("p", input.read);
|
|
4897
|
+
return (await this.#query(this.#read, `SELECT p.* FROM ${T.pages} p WHERE p.deletedAt IS NULL AND p.id IN ${JV} AND ${readable.sql}`, [jsonList(ids), ...readable.args])).map(toPage);
|
|
4898
|
+
}
|
|
4899
|
+
async getPageByPath(input) {
|
|
4900
|
+
const readable = pageReadable("p", input.read);
|
|
4901
|
+
let parentId = input.scopeId;
|
|
4902
|
+
let row;
|
|
4903
|
+
for (const key of input.path) {
|
|
4904
|
+
row = (await this.#query(this.#read, `SELECT p.* FROM ${T.pages} p WHERE p.parentId = ? AND p.isScope = 0 AND p.${KEY} = ? AND p.deletedAt IS NULL AND ${readable.sql}`, [
|
|
4905
|
+
parentId,
|
|
4906
|
+
key,
|
|
4907
|
+
...readable.args
|
|
4908
|
+
]))[0];
|
|
4909
|
+
if (!row) return null;
|
|
4910
|
+
parentId = String(row.id);
|
|
4911
|
+
}
|
|
4912
|
+
return row ? toPage(row) : null;
|
|
4913
|
+
}
|
|
4914
|
+
async findPagesByName(input) {
|
|
4915
|
+
const core = await loadPagesCore();
|
|
4916
|
+
const readable = pageReadable("p", input.read);
|
|
4917
|
+
const args = [core.canonicalKnowledgeName(input.name), ...readable.args];
|
|
4918
|
+
let scope = "";
|
|
4919
|
+
if (input.scopeId !== void 0) {
|
|
4920
|
+
scope = ` AND p.scopeId = ?`;
|
|
4921
|
+
args.push(input.scopeId);
|
|
4922
|
+
}
|
|
4923
|
+
args.push(limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT));
|
|
4924
|
+
return (await this.#query(this.#read, `SELECT p.* FROM ${T.pages} p WHERE p.canonicalName = ? AND p.deletedAt IS NULL AND ${readable.sql}${scope} ORDER BY p.createdAt DESC, p.id DESC LIMIT ?`, args)).map(toPage);
|
|
4925
|
+
}
|
|
4926
|
+
async listChildPages(input) {
|
|
4927
|
+
const core = await loadPagesCore();
|
|
4928
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
4929
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
4930
|
+
const readable = pageReadable("p", input.read);
|
|
4931
|
+
const args = [input.parentId, ...readable.args];
|
|
4932
|
+
let after = "";
|
|
4933
|
+
if (cursor) {
|
|
4934
|
+
after = ` AND (p.position > ? OR (p.position = ? AND p.id > ?))`;
|
|
4935
|
+
args.push(cursor[0], cursor[0], cursor[1]);
|
|
4936
|
+
}
|
|
4937
|
+
args.push(limit + 1);
|
|
4938
|
+
const rows = await this.#query(this.#read, `SELECT p.* FROM ${T.pages} p WHERE p.parentId = ? AND p.isScope = 0 AND p.deletedAt IS NULL AND ${readable.sql}${after} ORDER BY p.position, p.id LIMIT ?`, args);
|
|
4939
|
+
return this.#pageOutput(rows, limit, (row) => [String(row.position), String(row.id)]);
|
|
4940
|
+
}
|
|
4941
|
+
async listPages(input) {
|
|
4942
|
+
const core = await loadPagesCore();
|
|
4943
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
4944
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
4945
|
+
const readable = pagesInReadableScopes("p", input.read);
|
|
4946
|
+
const args = [...readable.args];
|
|
4947
|
+
let filters = "";
|
|
4948
|
+
if (input.scopeId !== void 0) {
|
|
4949
|
+
filters += ` AND p.scopeId = ?`;
|
|
4950
|
+
args.push(input.scopeId);
|
|
4951
|
+
}
|
|
4952
|
+
if (input.namePrefix) {
|
|
4953
|
+
const prefix = core.canonicalKnowledgeName(input.namePrefix);
|
|
4954
|
+
filters += ` AND LEFT(p.canonicalName, CHAR_LENGTH(?)) = ?`;
|
|
4955
|
+
args.push(prefix, prefix);
|
|
4956
|
+
}
|
|
4957
|
+
if (cursor) {
|
|
4958
|
+
filters += ` AND (p.name > ? OR (p.name = ? AND p.id > ?))`;
|
|
4959
|
+
args.push(cursor[0], cursor[0], cursor[1]);
|
|
4960
|
+
}
|
|
4961
|
+
args.push(limit + 1);
|
|
4962
|
+
const rows = await this.#query(this.#read, `SELECT p.* ${readable.sql} WHERE p.isScope = 0 AND p.deletedAt IS NULL${filters} ORDER BY p.name, p.id LIMIT ?`, args);
|
|
4963
|
+
return this.#pageOutput(rows, limit, (row) => [String(row.name), String(row.id)]);
|
|
4964
|
+
}
|
|
4965
|
+
async #insertPage(tx, input) {
|
|
4966
|
+
const core = await loadPagesCore();
|
|
4967
|
+
await assertKnowledgePageDescriptionWithinBound(input.description);
|
|
4968
|
+
const parent = await this.#livePage(tx, input.parentId);
|
|
4969
|
+
const isScopeParent = Number(parent.isScope) === 1;
|
|
4970
|
+
const scopeId = isScopeParent ? String(parent.id) : String(parent.scopeId);
|
|
4971
|
+
const depth = isScopeParent ? 1 : Number(parent.depth) + 1;
|
|
4972
|
+
if (depth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(input.parentId);
|
|
4973
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
4974
|
+
if (await this.#pageRow(tx, id)) throw new _mastra_core_storage.KnowledgeConflictError(id);
|
|
4975
|
+
if (input.key !== void 0) {
|
|
4976
|
+
const problem = knowledgePageKeyProblem(input.key);
|
|
4977
|
+
if (problem) throw new Error(problem);
|
|
4978
|
+
if ((await this.#query(tx, `SELECT id FROM ${T.pages} WHERE parentId = ? AND isScope = 0 AND ${KEY} = ? AND deletedAt IS NULL`, [String(parent.id), input.key])).length > 0) throw new core.KnowledgePageKeyConflictError(input.key, String(parent.id));
|
|
4979
|
+
}
|
|
4980
|
+
const position = input.position ?? core.knowledgePositionBetween(await this.#lastPosition(tx, String(parent.id), false));
|
|
4981
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4982
|
+
const who = JSON.stringify(input.provenance);
|
|
4983
|
+
await tx.execute({
|
|
4984
|
+
sql: `INSERT INTO ${T.pages} (id, scopeId, isScope, ${KEY}, parentId, position, path, depth, name, canonicalName, description, body, version, createdBy, updatedBy, createdAt, updatedAt) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`,
|
|
4985
|
+
args: [
|
|
4986
|
+
id,
|
|
4987
|
+
scopeId,
|
|
4988
|
+
input.key ?? null,
|
|
4989
|
+
String(parent.id),
|
|
4990
|
+
position,
|
|
4991
|
+
`${String(parent.path)}${id}/`,
|
|
4992
|
+
depth,
|
|
4993
|
+
input.name,
|
|
4994
|
+
core.canonicalKnowledgeName(input.name),
|
|
4995
|
+
input.description || null,
|
|
4996
|
+
input.body ?? null,
|
|
4997
|
+
who,
|
|
4998
|
+
who,
|
|
4999
|
+
now,
|
|
5000
|
+
now
|
|
5001
|
+
]
|
|
5002
|
+
});
|
|
5003
|
+
await this.#logActivity(tx, "page-created", {
|
|
5004
|
+
scopeId,
|
|
5005
|
+
pageId: id,
|
|
5006
|
+
provenance: input.provenance
|
|
5007
|
+
}, now);
|
|
5008
|
+
return toPage(await this.#pageRow(tx, id));
|
|
5009
|
+
}
|
|
5010
|
+
async createPage(input) {
|
|
5011
|
+
await assertKnowledgePageDescriptionWithinBound(input.description);
|
|
5012
|
+
return this.#transaction((tx) => this.#insertPage(tx, input));
|
|
5013
|
+
}
|
|
5014
|
+
async updatePage(input) {
|
|
5015
|
+
const core = await loadPagesCore();
|
|
5016
|
+
await assertKnowledgePageDescriptionWithinBound(input.description);
|
|
4506
5017
|
return this.#transaction(async (tx) => {
|
|
4507
|
-
const
|
|
4508
|
-
if (
|
|
4509
|
-
|
|
4510
|
-
const
|
|
4511
|
-
const
|
|
4512
|
-
const
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
if ((await tx.execute({
|
|
4516
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" SET name=?,canonicalName=?,kind=?,content=?,description=?,scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=?`,
|
|
5018
|
+
const row = await this.#livePage(tx, input.id);
|
|
5019
|
+
if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5020
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5021
|
+
const name = input.name ?? String(row.name);
|
|
5022
|
+
const description = input.description === void 0 ? row.description ?? null : input.description || null;
|
|
5023
|
+
const body = input.body === void 0 ? row.body ?? null : input.body;
|
|
5024
|
+
await tx.execute({
|
|
5025
|
+
sql: `UPDATE ${T.pages} SET name = ?, canonicalName = ?, description = ?, body = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
|
|
4517
5026
|
args: [
|
|
4518
5027
|
name,
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
now.toISOString(),
|
|
4526
|
-
input.id,
|
|
4527
|
-
input.version
|
|
5028
|
+
core.canonicalKnowledgeName(name),
|
|
5029
|
+
description,
|
|
5030
|
+
body,
|
|
5031
|
+
JSON.stringify(input.provenance),
|
|
5032
|
+
now,
|
|
5033
|
+
input.id
|
|
4528
5034
|
]
|
|
4529
|
-
})
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
args: [input.id]
|
|
4537
|
-
});
|
|
4538
|
-
for (const row of records.rows) {
|
|
4539
|
-
const factScope = parseJson$1(row.scopeJson);
|
|
4540
|
-
await this.#outbox(tx, "record", String(row.id), "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), factScope);
|
|
4541
|
-
if (row.deletedAt == null) await this.#outbox(tx, "record", String(row.id), "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), factScope);
|
|
4542
|
-
}
|
|
4543
|
-
}
|
|
4544
|
-
await this.#outbox(tx, "node", input.id, "upsert", input.version + 1, scope);
|
|
4545
|
-
return {
|
|
4546
|
-
...existing,
|
|
4547
|
-
name,
|
|
4548
|
-
kind: input.kind ?? existing.kind,
|
|
4549
|
-
content,
|
|
4550
|
-
description,
|
|
4551
|
-
scope,
|
|
4552
|
-
version: input.version + 1,
|
|
4553
|
-
updatedAt: now
|
|
4554
|
-
};
|
|
5035
|
+
});
|
|
5036
|
+
await this.#logActivity(tx, "page-updated", {
|
|
5037
|
+
scopeId: String(row.scopeId),
|
|
5038
|
+
pageId: input.id,
|
|
5039
|
+
provenance: input.provenance
|
|
5040
|
+
}, now);
|
|
5041
|
+
return toPage(await this.#pageRow(tx, input.id));
|
|
4555
5042
|
});
|
|
4556
5043
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
5044
|
+
/**
|
|
5045
|
+
* Rehomes `root` (and its subtree) under `parent`: new path prefix, scope and depth in one go.
|
|
5046
|
+
* Throws when the subtree is too large, too deep, or the root's key clashes with a live child of `parent`.
|
|
5047
|
+
*/
|
|
5048
|
+
async #rehome(tx, root, parent, options) {
|
|
5049
|
+
const core = await loadPagesCore();
|
|
5050
|
+
const isScopeParent = Number(parent.isScope) === 1;
|
|
5051
|
+
const newScopeId = isScopeParent ? String(parent.id) : String(parent.scopeId);
|
|
5052
|
+
const newDepth = isScopeParent ? 1 : Number(parent.depth) + 1;
|
|
5053
|
+
const oldPath = String(root.path);
|
|
5054
|
+
const within = prefixRange("path", oldPath);
|
|
5055
|
+
const live = (await this.#query(tx, `SELECT id, ${KEY}, depth, deletedAt FROM ${T.pages} WHERE ${within.sql}`, within.args)).filter((row) => !row.deletedAt);
|
|
5056
|
+
if (live.length > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(options.countLimitId, live.length);
|
|
5057
|
+
const shift = newDepth - Number(root.depth);
|
|
5058
|
+
if (Math.max(...live.map((row) => Number(row.depth))) + shift > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(String(root.id));
|
|
5059
|
+
if (!root.deletedAt && root.key !== null) {
|
|
5060
|
+
if ((await this.#query(tx, `SELECT id FROM ${T.pages} WHERE parentId = ? AND isScope = 0 AND ${KEY} = ? AND deletedAt IS NULL AND id <> ?`, [
|
|
5061
|
+
String(parent.id),
|
|
5062
|
+
String(root.key),
|
|
5063
|
+
String(root.id)
|
|
5064
|
+
])).length > 0) throw new core.KnowledgePageKeyConflictError(String(root.key), String(parent.id));
|
|
5065
|
+
}
|
|
5066
|
+
const newPath = `${String(parent.path)}${String(root.id)}/`;
|
|
5067
|
+
const position = options.position ?? core.knowledgePositionBetween(await this.#lastPosition(tx, String(parent.id), false));
|
|
5068
|
+
await tx.execute({
|
|
5069
|
+
sql: `UPDATE ${T.pages} SET path = CONCAT(?, SUBSTRING(path, CHAR_LENGTH(?) + 1)), depth = depth + ?, scopeId = ? WHERE ${within.sql}`,
|
|
5070
|
+
args: [
|
|
5071
|
+
newPath,
|
|
5072
|
+
oldPath,
|
|
5073
|
+
shift,
|
|
5074
|
+
newScopeId,
|
|
5075
|
+
...within.args
|
|
5076
|
+
]
|
|
5077
|
+
});
|
|
5078
|
+
await tx.execute({
|
|
5079
|
+
sql: `UPDATE ${T.pages} SET parentId = ?, position = ? WHERE id = ?`,
|
|
5080
|
+
args: [
|
|
5081
|
+
String(parent.id),
|
|
5082
|
+
position,
|
|
5083
|
+
String(root.id)
|
|
5084
|
+
]
|
|
5085
|
+
});
|
|
5086
|
+
return {
|
|
5087
|
+
moved: live.length,
|
|
5088
|
+
scopeId: newScopeId
|
|
5089
|
+
};
|
|
5090
|
+
}
|
|
5091
|
+
movePage(input) {
|
|
4559
5092
|
return this.#transaction(async (tx) => {
|
|
4560
|
-
const
|
|
4561
|
-
if (
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
if (
|
|
4566
|
-
const
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
});
|
|
4574
|
-
if ((await tx.execute({
|
|
4575
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" SET mergedInto=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=? AND mergedInto IS NULL`,
|
|
5093
|
+
const row = await this.#livePage(tx, input.id);
|
|
5094
|
+
if (Number(row.isScope) === 1) throw new Error(`Cannot move ${input.id}: it is a scope page`);
|
|
5095
|
+
if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5096
|
+
const parent = await this.#livePage(tx, input.parentId);
|
|
5097
|
+
const path = String(row.path);
|
|
5098
|
+
if (String(parent.id) === input.id || String(parent.path).startsWith(path)) throw new Error(`Cannot move ${input.id} into its own subtree`);
|
|
5099
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5100
|
+
const { scopeId } = await this.#rehome(tx, row, parent, {
|
|
5101
|
+
position: input.position,
|
|
5102
|
+
countLimitId: input.id
|
|
5103
|
+
});
|
|
5104
|
+
await tx.execute({
|
|
5105
|
+
sql: `UPDATE ${T.pages} SET version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
|
|
4576
5106
|
args: [
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
input.sourceVersion
|
|
5107
|
+
JSON.stringify(input.provenance),
|
|
5108
|
+
now,
|
|
5109
|
+
input.id
|
|
4581
5110
|
]
|
|
4582
|
-
})
|
|
5111
|
+
});
|
|
5112
|
+
await this.#logActivity(tx, "page-moved", {
|
|
5113
|
+
scopeId,
|
|
5114
|
+
pageId: input.id,
|
|
5115
|
+
provenance: input.provenance
|
|
5116
|
+
}, now);
|
|
5117
|
+
return toPage(await this.#pageRow(tx, input.id));
|
|
5118
|
+
});
|
|
5119
|
+
}
|
|
5120
|
+
async mergePages(input) {
|
|
5121
|
+
const core = await loadPagesCore();
|
|
5122
|
+
return this.#transaction(async (tx) => {
|
|
5123
|
+
const source = await this.#livePage(tx, input.sourceId);
|
|
5124
|
+
const target = await this.#livePage(tx, input.targetId);
|
|
5125
|
+
if (Number(source.isScope) === 1 || Number(target.isScope) === 1) throw new Error("Cannot merge a scope page: merging applies to non-scope pages only");
|
|
5126
|
+
if (Number(source.version) !== input.sourceVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.sourceId);
|
|
5127
|
+
const sourcePath = String(source.path);
|
|
5128
|
+
if (input.sourceId === input.targetId || String(target.path).startsWith(sourcePath)) throw new Error(`Page ${input.targetId} is inside page ${input.sourceId}; it cannot be the merge target (subtree)`);
|
|
5129
|
+
const within = prefixRange("path", sourcePath);
|
|
5130
|
+
const carried = await this.#query(tx, `SELECT COUNT(*) AS n FROM ${T.pages} WHERE ${within.sql} AND id <> ? AND deletedAt IS NULL`, [...within.args, input.sourceId]);
|
|
5131
|
+
if (Number(carried[0].n) > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(input.sourceId, Number(carried[0].n));
|
|
5132
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4583
5133
|
await tx.execute({
|
|
4584
|
-
sql: `UPDATE
|
|
4585
|
-
args: [
|
|
5134
|
+
sql: `UPDATE ${T.pages} SET deletedAt = ?, deletedBy = ?, mergedInto = ?, scopeId = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
|
|
5135
|
+
args: [
|
|
5136
|
+
now,
|
|
5137
|
+
input.provenance.author,
|
|
5138
|
+
input.targetId,
|
|
5139
|
+
String(target.scopeId),
|
|
5140
|
+
JSON.stringify(input.provenance),
|
|
5141
|
+
now,
|
|
5142
|
+
input.sourceId
|
|
5143
|
+
]
|
|
5144
|
+
});
|
|
5145
|
+
const children = await this.#query(tx, `SELECT * FROM ${T.pages} WHERE parentId = ? AND isScope = 0`, [input.sourceId]);
|
|
5146
|
+
for (const child of children) await this.#rehome(tx, child, target, {
|
|
5147
|
+
position: String(child.position),
|
|
5148
|
+
countLimitId: input.sourceId
|
|
4586
5149
|
});
|
|
4587
5150
|
await tx.execute({
|
|
4588
|
-
sql: `
|
|
4589
|
-
args: [
|
|
5151
|
+
sql: `UPDATE ${T.notes} SET pageId = ? WHERE pageId = ?`,
|
|
5152
|
+
args: [input.targetId, input.sourceId]
|
|
4590
5153
|
});
|
|
4591
5154
|
await tx.execute({
|
|
4592
|
-
sql: `UPDATE
|
|
4593
|
-
args: [
|
|
4594
|
-
});
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
5155
|
+
sql: `UPDATE IGNORE ${T.mentions} SET pageId = ? WHERE pageId = ?`,
|
|
5156
|
+
args: [input.targetId, input.sourceId]
|
|
5157
|
+
});
|
|
5158
|
+
await tx.execute({
|
|
5159
|
+
sql: `DELETE FROM ${T.mentions} WHERE pageId = ?`,
|
|
5160
|
+
args: [input.sourceId]
|
|
5161
|
+
});
|
|
5162
|
+
const pins = await this.#query(tx, `SELECT * FROM ${T.pins} WHERE pageId IN (?, ?)`, [input.sourceId, input.targetId]);
|
|
5163
|
+
const sourcePin = pins.find((pin) => String(pin.pageId) === input.sourceId);
|
|
5164
|
+
if (sourcePin) {
|
|
5165
|
+
const targetPin = pins.find((pin) => String(pin.pageId) === input.targetId);
|
|
5166
|
+
const audience = parseJsonList(sourcePin.audience);
|
|
5167
|
+
if (targetPin) {
|
|
5168
|
+
const merged = [.../* @__PURE__ */ new Set([...parseJsonList(targetPin.audience), ...audience])];
|
|
5169
|
+
await tx.execute({
|
|
5170
|
+
sql: `UPDATE ${T.pins} SET audience = ?, updatedAt = ? WHERE id = ?`,
|
|
5171
|
+
args: [
|
|
5172
|
+
JSON.stringify(merged),
|
|
5173
|
+
now,
|
|
5174
|
+
String(targetPin.id)
|
|
5175
|
+
]
|
|
5176
|
+
});
|
|
5177
|
+
await this.#indexAudience(tx, String(targetPin.id), merged);
|
|
5178
|
+
await this.#indexAudience(tx, String(sourcePin.id), []);
|
|
5179
|
+
await tx.execute({
|
|
5180
|
+
sql: `DELETE FROM ${T.pins} WHERE id = ?`,
|
|
5181
|
+
args: [String(sourcePin.id)]
|
|
5182
|
+
});
|
|
5183
|
+
} else await tx.execute({
|
|
5184
|
+
sql: `UPDATE ${T.pins} SET pageId = ?, updatedAt = ? WHERE id = ?`,
|
|
4602
5185
|
args: [
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
target.version
|
|
5186
|
+
input.targetId,
|
|
5187
|
+
now,
|
|
5188
|
+
String(sourcePin.id)
|
|
4607
5189
|
]
|
|
4608
|
-
})
|
|
4609
|
-
mergedTarget = {
|
|
4610
|
-
...target,
|
|
4611
|
-
description: source.description,
|
|
4612
|
-
version: target.version + 1,
|
|
4613
|
-
updatedAt: adoptedAt
|
|
4614
|
-
};
|
|
4615
|
-
await this.#activity(tx, "node-updated", "node", target.id, target.scope);
|
|
4616
|
-
}
|
|
5190
|
+
});
|
|
4617
5191
|
}
|
|
4618
|
-
await this.#
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
5192
|
+
await this.#logActivity(tx, "page-merged", {
|
|
5193
|
+
scopeId: String(target.scopeId),
|
|
5194
|
+
pageId: input.sourceId,
|
|
5195
|
+
provenance: input.provenance
|
|
5196
|
+
}, now);
|
|
5197
|
+
return toPage(await this.#pageRow(tx, input.targetId));
|
|
4622
5198
|
});
|
|
4623
5199
|
}
|
|
4624
|
-
async
|
|
4625
|
-
await
|
|
4626
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
|
|
4627
|
-
const resolutionScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.resolutionScope);
|
|
4628
|
-
const defaultScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.defaultScope);
|
|
5200
|
+
async deletePage(input) {
|
|
5201
|
+
const core = await loadPagesCore();
|
|
4629
5202
|
return this.#transaction(async (tx) => {
|
|
4630
|
-
const
|
|
4631
|
-
if (
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
text: input.text,
|
|
4636
|
-
scope,
|
|
4637
|
-
sourceThreadId: input.sourceThreadId,
|
|
4638
|
-
capturedAt: /* @__PURE__ */ new Date(),
|
|
4639
|
-
when: input.when ? new Date(input.when) : void 0,
|
|
4640
|
-
metadata: input.metadata
|
|
4641
|
-
};
|
|
5203
|
+
const row = await this.#livePage(tx, input.id);
|
|
5204
|
+
if (Number(row.isScope) === 1) throw new Error(`Cannot delete ${input.id}: it is a scope page`);
|
|
5205
|
+
if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5206
|
+
if ((await this.#query(tx, `SELECT 1 FROM ${T.pages} WHERE parentId = ? AND deletedAt IS NULL LIMIT 1`, [input.id])).length > 0) throw new core.KnowledgePageHasChildrenError(input.id);
|
|
5207
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4642
5208
|
await tx.execute({
|
|
4643
|
-
sql: `
|
|
5209
|
+
sql: `UPDATE ${T.pages} SET deletedAt = ?, deletedBy = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
|
|
4644
5210
|
args: [
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
record.sourceThreadId,
|
|
4651
|
-
record.capturedAt.toISOString(),
|
|
4652
|
-
record.when?.toISOString() ?? null,
|
|
4653
|
-
record.metadata ? JSON.stringify(record.metadata) : null
|
|
5211
|
+
now,
|
|
5212
|
+
input.deletedBy,
|
|
5213
|
+
JSON.stringify({ author: input.deletedBy }),
|
|
5214
|
+
now,
|
|
5215
|
+
input.id
|
|
4654
5216
|
]
|
|
4655
5217
|
});
|
|
4656
|
-
await
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
5218
|
+
await tx.execute({
|
|
5219
|
+
sql: `DELETE FROM ${T.pinAudiences} WHERE pinId IN (SELECT id FROM ${T.pins} WHERE pageId = ?)`,
|
|
5220
|
+
args: [input.id]
|
|
5221
|
+
});
|
|
5222
|
+
await tx.execute({
|
|
5223
|
+
sql: `DELETE FROM ${T.pins} WHERE pageId = ?`,
|
|
5224
|
+
args: [input.id]
|
|
5225
|
+
});
|
|
5226
|
+
await this.#logActivity(tx, "page-deleted", {
|
|
5227
|
+
scopeId: String(row.scopeId),
|
|
5228
|
+
pageId: input.id,
|
|
5229
|
+
provenance: { author: input.deletedBy }
|
|
5230
|
+
}, now);
|
|
5231
|
+
return toPage(await this.#pageRow(tx, input.id));
|
|
4666
5232
|
});
|
|
4667
|
-
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
4668
|
-
}
|
|
4669
|
-
async listKnowledgeAbout(input) {
|
|
4670
|
-
return this.#queryKnowledge(input, "about");
|
|
4671
|
-
}
|
|
4672
|
-
async listKnowledgeMentioning(input) {
|
|
4673
|
-
return this.#queryKnowledge(input, "mentioning");
|
|
4674
5233
|
}
|
|
4675
|
-
async
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
const
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
5234
|
+
async #hydrateNotes(executor, rows) {
|
|
5235
|
+
if (rows.length === 0) return [];
|
|
5236
|
+
const ids = jsonList(rows.map((row) => String(row.id)));
|
|
5237
|
+
const stamps = await this.#query(executor, `SELECT noteId, scopeId FROM ${T.stamps} WHERE noteId IN ${JV} ORDER BY scopeId`, [ids]);
|
|
5238
|
+
const mentions = await this.#query(executor, `SELECT noteId, pageId FROM ${T.mentions} WHERE noteId IN ${JV} ORDER BY pageId`, [ids]);
|
|
5239
|
+
return rows.map((row) => toNote(row, stamps.filter((stamp) => String(stamp.noteId) === String(row.id)).map((stamp) => String(stamp.scopeId)), mentions.filter((mention) => String(mention.noteId) === String(row.id)).map((mention) => String(mention.pageId))));
|
|
5240
|
+
}
|
|
5241
|
+
async #noteById(executor, id) {
|
|
5242
|
+
const rows = await this.#query(executor, `SELECT * FROM ${T.notes} WHERE id = ?`, [id]);
|
|
5243
|
+
return (await this.#hydrateNotes(executor, rows))[0] ?? null;
|
|
5244
|
+
}
|
|
5245
|
+
async getNote(input) {
|
|
5246
|
+
const readable = noteReadable("n", input.read);
|
|
5247
|
+
const rows = await this.#query(this.#read, `SELECT n.* FROM ${T.notes} n WHERE n.id = ?${input.includeDeleted ? "" : " AND n.deletedAt IS NULL"} AND ${readable.sql}`, [input.id, ...readable.args]);
|
|
5248
|
+
return (await this.#hydrateNotes(this.#read, rows))[0] ?? null;
|
|
5249
|
+
}
|
|
5250
|
+
async listNotes(input) {
|
|
5251
|
+
const core = await loadPagesCore();
|
|
5252
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5253
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
5254
|
+
const readable = noteReadable("n", input.read);
|
|
5255
|
+
const args = [input.pageId, ...readable.args];
|
|
5256
|
+
let after = "";
|
|
5257
|
+
if (cursor) {
|
|
5258
|
+
after = ` AND (n.createdAt > ? OR (n.createdAt = ? AND n.id > ?))`;
|
|
5259
|
+
args.push(cursor[0], cursor[0], cursor[1]);
|
|
5260
|
+
}
|
|
4683
5261
|
args.push(limit + 1);
|
|
4684
|
-
const
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
5262
|
+
const rows = await this.#query(this.#read, `SELECT n.* FROM ${T.notes} n WHERE n.pageId = ?${input.includeDeleted ? "" : " AND n.deletedAt IS NULL"} AND ${readable.sql}${after} ORDER BY n.createdAt, n.id LIMIT ?`, args);
|
|
5263
|
+
return this.#noteOutput(rows, limit);
|
|
5264
|
+
}
|
|
5265
|
+
async countNotes(input) {
|
|
5266
|
+
const ids = [...new Set(input.pageIds)];
|
|
5267
|
+
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
5268
|
+
if (ids.length === 0) return counts;
|
|
5269
|
+
const readable = noteReadable("n", input.read);
|
|
5270
|
+
const rows = await this.#query(this.#read, `SELECT n.pageId AS pageId, COUNT(*) AS total FROM ${T.notes} n WHERE n.pageId IN ${JV} AND n.deletedAt IS NULL AND ${readable.sql} GROUP BY n.pageId`, [jsonList(ids), ...readable.args]);
|
|
5271
|
+
for (const row of rows) counts[String(row.pageId)] = Number(row.total);
|
|
5272
|
+
return counts;
|
|
5273
|
+
}
|
|
5274
|
+
async listNotesMentioning(input) {
|
|
5275
|
+
const core = await loadPagesCore();
|
|
5276
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5277
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
5278
|
+
const readable = noteReadable("n", input.read);
|
|
5279
|
+
const args = [
|
|
5280
|
+
input.pageId,
|
|
5281
|
+
input.pageId,
|
|
5282
|
+
...readable.args
|
|
5283
|
+
];
|
|
5284
|
+
let after = "";
|
|
5285
|
+
if (cursor) {
|
|
5286
|
+
after = ` AND (n.createdAt < ? OR (n.createdAt = ? AND n.id < ?))`;
|
|
5287
|
+
args.push(cursor[0], cursor[0], cursor[1]);
|
|
5288
|
+
}
|
|
5289
|
+
args.push(limit + 1);
|
|
5290
|
+
const rows = await this.#query(this.#read, `SELECT n.* FROM ${T.notes} n WHERE n.pageId <> ? AND n.deletedAt IS NULL AND EXISTS (SELECT 1 FROM ${T.mentions} m WHERE m.noteId = n.id AND m.pageId = ?) AND ${readable.sql}${after} ORDER BY n.createdAt DESC, n.id DESC LIMIT ?`, args);
|
|
5291
|
+
return this.#noteOutput(rows, limit);
|
|
5292
|
+
}
|
|
5293
|
+
async #noteOutput(rows, limit) {
|
|
5294
|
+
const page = rows.slice(0, limit);
|
|
4688
5295
|
return {
|
|
4689
|
-
|
|
4690
|
-
|
|
5296
|
+
notes: await this.#hydrateNotes(this.#read, page),
|
|
5297
|
+
...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].createdAt), String(page[page.length - 1].id)]) } : {}
|
|
4691
5298
|
};
|
|
4692
5299
|
}
|
|
4693
|
-
async
|
|
5300
|
+
async #assertScopes(tx, ids) {
|
|
5301
|
+
if (ids.length === 0) throw new Error("A note needs at least one stamp scope");
|
|
5302
|
+
const found = await this.#query(tx, `SELECT id FROM ${T.pages} WHERE isScope = 1 AND id IN ${JV}`, [jsonList(ids)]);
|
|
5303
|
+
const known = new Set(found.map((row) => String(row.id)));
|
|
5304
|
+
const missing = ids.find((id) => !known.has(id));
|
|
5305
|
+
if (missing) throw new Error(`Note stamp ${missing} is not a scope page`);
|
|
5306
|
+
}
|
|
5307
|
+
async addNote(input) {
|
|
5308
|
+
await assertKnowledgeNoteTextWithinBound(input.text);
|
|
4694
5309
|
return this.#transaction(async (tx) => {
|
|
4695
|
-
const
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
const
|
|
5310
|
+
const page = await this.#livePage(tx, input.pageId);
|
|
5311
|
+
const stamps = [...new Set(input.stampScopeIds)];
|
|
5312
|
+
await this.#assertScopes(tx, stamps);
|
|
5313
|
+
const mentions = [...new Set(input.mentions)];
|
|
5314
|
+
for (const mention of mentions) await this.#livePage(tx, mention);
|
|
5315
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
5316
|
+
if ((await this.#query(tx, `SELECT 1 FROM ${T.notes} WHERE id = ?`, [id])).length > 0) throw new _mastra_core_storage.KnowledgeConflictError(id);
|
|
5317
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4699
5318
|
await tx.execute({
|
|
4700
|
-
sql: `
|
|
5319
|
+
sql: `INSERT INTO ${T.notes} (id, pageId, text, ${WHEN}, provenance, version, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
|
|
4701
5320
|
args: [
|
|
4702
|
-
|
|
4703
|
-
input.
|
|
4704
|
-
input.
|
|
5321
|
+
id,
|
|
5322
|
+
input.pageId,
|
|
5323
|
+
input.text,
|
|
5324
|
+
input.when ? input.when.toISOString() : null,
|
|
5325
|
+
JSON.stringify(input.provenance),
|
|
5326
|
+
now,
|
|
5327
|
+
now
|
|
4705
5328
|
]
|
|
4706
5329
|
});
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
5330
|
+
for (const stamp of stamps) await tx.execute({
|
|
5331
|
+
sql: `INSERT INTO ${T.stamps} (noteId, scopeId) VALUES (?, ?)`,
|
|
5332
|
+
args: [id, stamp]
|
|
5333
|
+
});
|
|
5334
|
+
for (const mention of mentions) await tx.execute({
|
|
5335
|
+
sql: `INSERT INTO ${T.mentions} (noteId, pageId) VALUES (?, ?)`,
|
|
5336
|
+
args: [id, mention]
|
|
5337
|
+
});
|
|
5338
|
+
await this.#logActivity(tx, "note-added", {
|
|
5339
|
+
scopeId: String(page.scopeId),
|
|
5340
|
+
pageId: input.pageId,
|
|
5341
|
+
noteId: id,
|
|
5342
|
+
provenance: input.provenance
|
|
5343
|
+
}, now);
|
|
5344
|
+
return await this.#noteById(tx, id);
|
|
4714
5345
|
});
|
|
4715
5346
|
}
|
|
4716
|
-
async
|
|
5347
|
+
async #noteWithPage(tx, id) {
|
|
5348
|
+
const note = (await this.#query(tx, `SELECT * FROM ${T.notes} WHERE id = ?`, [id]))[0];
|
|
5349
|
+
if (!note) throw new _mastra_core_storage.KnowledgeNotFoundError("note", id);
|
|
5350
|
+
const page = await this.#pageRow(tx, String(note.pageId));
|
|
5351
|
+
if (!page) throw new _mastra_core_storage.KnowledgeNotFoundError("page", String(note.pageId));
|
|
5352
|
+
return {
|
|
5353
|
+
note,
|
|
5354
|
+
page
|
|
5355
|
+
};
|
|
5356
|
+
}
|
|
5357
|
+
restampNote(input) {
|
|
4717
5358
|
return this.#transaction(async (tx) => {
|
|
4718
|
-
const
|
|
4719
|
-
if (
|
|
4720
|
-
if (
|
|
5359
|
+
const { note, page } = await this.#noteWithPage(tx, input.id);
|
|
5360
|
+
if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
|
|
5361
|
+
if (Number(note.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5362
|
+
const stamps = [...new Set(input.stampScopeIds)];
|
|
5363
|
+
await this.#assertScopes(tx, stamps);
|
|
5364
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4721
5365
|
await tx.execute({
|
|
4722
|
-
sql: `
|
|
5366
|
+
sql: `DELETE FROM ${T.stamps} WHERE noteId = ?`,
|
|
4723
5367
|
args: [input.id]
|
|
4724
5368
|
});
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
5369
|
+
for (const stamp of stamps) await tx.execute({
|
|
5370
|
+
sql: `INSERT INTO ${T.stamps} (noteId, scopeId) VALUES (?, ?)`,
|
|
5371
|
+
args: [input.id, stamp]
|
|
5372
|
+
});
|
|
5373
|
+
await tx.execute({
|
|
5374
|
+
sql: `UPDATE ${T.notes} SET version = version + 1, updatedAt = ? WHERE id = ?`,
|
|
5375
|
+
args: [now, input.id]
|
|
5376
|
+
});
|
|
5377
|
+
await this.#logActivity(tx, "note-restamped", {
|
|
5378
|
+
scopeId: String(page.scopeId),
|
|
5379
|
+
pageId: String(page.id),
|
|
5380
|
+
noteId: input.id,
|
|
5381
|
+
provenance: input.provenance
|
|
5382
|
+
}, now);
|
|
5383
|
+
return await this.#noteById(tx, input.id);
|
|
4732
5384
|
});
|
|
4733
5385
|
}
|
|
4734
|
-
|
|
4735
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
|
|
5386
|
+
removeNote(input) {
|
|
4736
5387
|
return this.#transaction(async (tx) => {
|
|
4737
|
-
const
|
|
4738
|
-
if (
|
|
5388
|
+
const { note, page } = await this.#noteWithPage(tx, input.id);
|
|
5389
|
+
if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
|
|
5390
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4739
5391
|
await tx.execute({
|
|
4740
|
-
sql: `UPDATE
|
|
5392
|
+
sql: `UPDATE ${T.notes} SET deletedAt = ?, deletedBy = ?, version = version + 1, updatedAt = ? WHERE id = ?`,
|
|
4741
5393
|
args: [
|
|
4742
|
-
|
|
4743
|
-
|
|
5394
|
+
now,
|
|
5395
|
+
input.deletedBy,
|
|
5396
|
+
now,
|
|
4744
5397
|
input.id
|
|
4745
5398
|
]
|
|
4746
5399
|
});
|
|
4747
|
-
await this.#
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
5400
|
+
await this.#logActivity(tx, "note-removed", {
|
|
5401
|
+
scopeId: String(page.scopeId),
|
|
5402
|
+
pageId: String(page.id),
|
|
5403
|
+
noteId: input.id,
|
|
5404
|
+
provenance: input.provenance
|
|
5405
|
+
}, now);
|
|
5406
|
+
return await this.#noteById(tx, input.id);
|
|
4754
5407
|
});
|
|
4755
5408
|
}
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
})
|
|
4773
|
-
type: String(row.type),
|
|
4774
|
-
id: String(row.id),
|
|
4775
|
-
recordId: String(row.id),
|
|
4776
|
-
name: String(row.name),
|
|
4777
|
-
text: [
|
|
4778
|
-
String(row.name),
|
|
4779
|
-
...row.description ? [String(row.description)] : [],
|
|
4780
|
-
...row.content ? [String(row.content)] : []
|
|
4781
|
-
].join("\n"),
|
|
4782
|
-
scope: parseJson$1(row.scopeJson)
|
|
4783
|
-
}));
|
|
4784
|
-
if (results.length < (input.limit ?? 20)) {
|
|
4785
|
-
const records = await this.#client.execute({
|
|
4786
|
-
sql: `SELECT f.*,f.scope AS scopeJson,r.name,r.scope AS parentScopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" f JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql(keys, "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
|
|
4787
|
-
args: [
|
|
4788
|
-
...keys,
|
|
4789
|
-
query,
|
|
4790
|
-
(input.limit ?? 20) - results.length
|
|
4791
|
-
]
|
|
4792
|
-
});
|
|
4793
|
-
results.push(...records.rows.map((row) => {
|
|
4794
|
-
const parentVisible = (0, _mastra_core_storage.isKnowledgeScopeVisible)(parseJson$1(row.parentScopeJson), scope);
|
|
4795
|
-
return {
|
|
4796
|
-
type: "record",
|
|
4797
|
-
id: String(row.id),
|
|
4798
|
-
recordId: parentVisible ? String(row.node) : String(row.id),
|
|
4799
|
-
name: parentVisible ? String(row.name) : "(private node)",
|
|
4800
|
-
text: String(row.text),
|
|
4801
|
-
scope: parseJson$1(row.scopeJson)
|
|
4802
|
-
};
|
|
4803
|
-
}));
|
|
4804
|
-
}
|
|
4805
|
-
return results;
|
|
4806
|
-
}
|
|
4807
|
-
/**
|
|
4808
|
-
* @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
|
|
4809
|
-
* cursor. Always throws.
|
|
4810
|
-
*/
|
|
4811
|
-
async getCurationCursor(_input) {
|
|
4812
|
-
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
4813
|
-
}
|
|
4814
|
-
/**
|
|
4815
|
-
* @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
|
|
4816
|
-
* cursor. Always throws.
|
|
4817
|
-
*/
|
|
4818
|
-
async advanceCurationCursor(_input) {
|
|
4819
|
-
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
5409
|
+
restoreNote(input) {
|
|
5410
|
+
return this.#transaction(async (tx) => {
|
|
5411
|
+
const { note, page } = await this.#noteWithPage(tx, input.id);
|
|
5412
|
+
if (!note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
|
|
5413
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5414
|
+
await tx.execute({
|
|
5415
|
+
sql: `UPDATE ${T.notes} SET deletedAt = NULL, deletedBy = NULL, version = version + 1, updatedAt = ? WHERE id = ?`,
|
|
5416
|
+
args: [now, input.id]
|
|
5417
|
+
});
|
|
5418
|
+
await this.#logActivity(tx, "note-restored", {
|
|
5419
|
+
scopeId: String(page.scopeId),
|
|
5420
|
+
pageId: String(page.id),
|
|
5421
|
+
noteId: input.id,
|
|
5422
|
+
provenance: input.provenance
|
|
5423
|
+
}, now);
|
|
5424
|
+
return await this.#noteById(tx, input.id);
|
|
5425
|
+
});
|
|
4820
5426
|
}
|
|
4821
|
-
async
|
|
4822
|
-
const
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
5427
|
+
async searchPages(input) {
|
|
5428
|
+
const words = knowledgeSearchWords(input.query);
|
|
5429
|
+
if (!words.length) return [];
|
|
5430
|
+
const phrase = input.query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
5431
|
+
const limit = Math.max(1, input.limit ?? 20);
|
|
5432
|
+
const likes = words.map((word) => `%${escapeLike(word)}%`);
|
|
5433
|
+
const readable = pagesInReadableScopes("p", input.read);
|
|
5434
|
+
const scopeFilter = input.scopeIds ? ` AND p.scopeId IN ${JV}` : "";
|
|
5435
|
+
const scopeArgs = input.scopeIds ? [jsonList(input.scopeIds)] : [];
|
|
5436
|
+
const pageMatch = words.map(() => `(LOWER(p.name) LIKE ? ESCAPE '=' OR LOWER(COALESCE(p.description, '')) LIKE ? ESCAPE '=' OR LOWER(COALESCE(p.body, '')) LIKE ? ESCAPE '=')`).join(" AND ");
|
|
5437
|
+
const nameMatch = words.map(() => `LOWER(p.name) LIKE ? ESCAPE '='`).join(" AND ");
|
|
5438
|
+
const hits = (await this.#query(this.#read, `SELECT p.* ${readable.sql} WHERE p.deletedAt IS NULL AND p.isScope = 0${scopeFilter} AND ${pageMatch} ORDER BY CASE WHEN LOCATE(?, p.canonicalName) > 0 THEN 0 WHEN ${nameMatch} THEN 1 ELSE 2 END, p.updatedAt DESC, p.id ASC LIMIT ?`, [
|
|
5439
|
+
...readable.args,
|
|
5440
|
+
...scopeArgs,
|
|
5441
|
+
...likes.flatMap((like) => [
|
|
5442
|
+
like,
|
|
5443
|
+
like,
|
|
5444
|
+
like
|
|
5445
|
+
]),
|
|
5446
|
+
phrase,
|
|
5447
|
+
...likes,
|
|
5448
|
+
limit
|
|
5449
|
+
])).map((row) => ({
|
|
5450
|
+
type: "page",
|
|
5451
|
+
page: toPage(row)
|
|
4838
5452
|
}));
|
|
5453
|
+
if (hits.length >= limit) return hits;
|
|
5454
|
+
const noteVisible = noteReadable("n", input.read);
|
|
5455
|
+
const noteMatch = words.map(() => `LOWER(n.text) LIKE ? ESCAPE '='`).join(" AND ");
|
|
5456
|
+
const noteRows = await this.#query(this.#read, `SELECT n.* FROM ${T.notes} n JOIN ${T.pages} p ON p.id = n.pageId WHERE n.deletedAt IS NULL AND p.deletedAt IS NULL${scopeFilter} AND ${noteMatch} AND ${noteVisible.sql} ORDER BY n.updatedAt DESC, n.id DESC LIMIT ?`, [
|
|
5457
|
+
...scopeArgs,
|
|
5458
|
+
...likes,
|
|
5459
|
+
...noteVisible.args,
|
|
5460
|
+
limit - hits.length
|
|
5461
|
+
]);
|
|
5462
|
+
const notes = await this.#hydrateNotes(this.#read, noteRows);
|
|
5463
|
+
for (const note of notes) {
|
|
5464
|
+
const page = await this.#pageRow(this.#read, note.pageId);
|
|
5465
|
+
if (page) hits.push({
|
|
5466
|
+
type: "note",
|
|
5467
|
+
note,
|
|
5468
|
+
page: toPage(page)
|
|
5469
|
+
});
|
|
5470
|
+
}
|
|
5471
|
+
return hits;
|
|
5472
|
+
}
|
|
5473
|
+
async listPageActivity(input) {
|
|
5474
|
+
const core = await loadPagesCore();
|
|
5475
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5476
|
+
const cursor = decodeCursor(input.cursor, 1);
|
|
5477
|
+
const readable = pageReadable("p", input.read);
|
|
5478
|
+
const noteVisible = noteReadable("n", input.read);
|
|
5479
|
+
const args = [...readable.args];
|
|
5480
|
+
let filters = "";
|
|
5481
|
+
if (input.scopeId !== void 0) {
|
|
5482
|
+
filters += ` AND p.scopeId = ?`;
|
|
5483
|
+
args.push(input.scopeId);
|
|
5484
|
+
}
|
|
5485
|
+
if (input.pageId !== void 0) {
|
|
5486
|
+
filters += ` AND e.pageId = ?`;
|
|
5487
|
+
args.push(input.pageId);
|
|
5488
|
+
}
|
|
5489
|
+
if (cursor) {
|
|
5490
|
+
filters += ` AND e.id < ?`;
|
|
5491
|
+
args.push(cursor[0]);
|
|
5492
|
+
}
|
|
5493
|
+
args.push(...noteVisible.args, limit + 1);
|
|
5494
|
+
const rows = await this.#query(this.#read, `SELECT e.* FROM ${T.activity} e JOIN ${T.pages} p ON p.id = e.pageId WHERE ${readable.sql}${filters} AND (e.noteId IS NULL OR EXISTS (SELECT 1 FROM ${T.notes} n WHERE n.id = e.noteId AND ${noteVisible.sql})) ORDER BY e.id DESC LIMIT ?`, args);
|
|
5495
|
+
const page = rows.slice(0, limit);
|
|
5496
|
+
return {
|
|
5497
|
+
events: page.map((row) => ({
|
|
5498
|
+
id: String(row.id),
|
|
5499
|
+
action: String(row.action),
|
|
5500
|
+
scopeId: String(row.scopeId),
|
|
5501
|
+
pageId: String(row.pageId),
|
|
5502
|
+
...row.noteId ? { noteId: String(row.noteId) } : {},
|
|
5503
|
+
provenance: provenance(row.provenance),
|
|
5504
|
+
createdAt: new Date(String(row.createdAt))
|
|
5505
|
+
})),
|
|
5506
|
+
...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].id)]) } : {}
|
|
5507
|
+
};
|
|
4839
5508
|
}
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
args.push(...keys);
|
|
4851
|
-
}
|
|
4852
|
-
args.push(input.limit ?? 100);
|
|
4853
|
-
return (await this.#client.execute({
|
|
4854
|
-
sql: `SELECT *,scope AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}"${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY createdAt ASC,id ASC LIMIT ?`,
|
|
4855
|
-
args
|
|
4856
|
-
})).rows.map(parseOutbox);
|
|
5509
|
+
/** Rewrites the audience index rows of one pin to match `audience`. */
|
|
5510
|
+
async #indexAudience(tx, pinId, audience) {
|
|
5511
|
+
await tx.execute({
|
|
5512
|
+
sql: `DELETE FROM ${T.pinAudiences} WHERE pinId = ?`,
|
|
5513
|
+
args: [pinId]
|
|
5514
|
+
});
|
|
5515
|
+
for (const entry of audience) await tx.execute({
|
|
5516
|
+
sql: `INSERT IGNORE INTO ${T.pinAudiences} (audience, pinId) VALUES (?, ?)`,
|
|
5517
|
+
args: [entry, pinId]
|
|
5518
|
+
});
|
|
4857
5519
|
}
|
|
4858
|
-
|
|
4859
|
-
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
4860
|
-
const stale = new Date(now.getTime() - (input.claimTimeoutMs ?? 6e4));
|
|
5520
|
+
addPinAudience(input) {
|
|
4861
5521
|
return this.#transaction(async (tx) => {
|
|
4862
|
-
const
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
input.
|
|
4883
|
-
|
|
4884
|
-
]
|
|
4885
|
-
}
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
5522
|
+
const page = await this.#livePage(tx, input.pageId);
|
|
5523
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5524
|
+
const existing = (await this.#query(tx, `SELECT * FROM ${T.pins} WHERE pageId = ?`, [input.pageId]))[0];
|
|
5525
|
+
if (!existing) {
|
|
5526
|
+
const id = (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
5527
|
+
await tx.execute({
|
|
5528
|
+
sql: `INSERT INTO ${T.pins} (id, pageId, audience, createdBy, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`,
|
|
5529
|
+
args: [
|
|
5530
|
+
id,
|
|
5531
|
+
input.pageId,
|
|
5532
|
+
JSON.stringify([...new Set(input.audience)]),
|
|
5533
|
+
input.createdBy,
|
|
5534
|
+
now,
|
|
5535
|
+
now
|
|
5536
|
+
]
|
|
5537
|
+
});
|
|
5538
|
+
await this.#indexAudience(tx, id, [...new Set(input.audience)]);
|
|
5539
|
+
await this.#logActivity(tx, "pin-added", {
|
|
5540
|
+
scopeId: String(page.scopeId),
|
|
5541
|
+
pageId: input.pageId,
|
|
5542
|
+
provenance: { author: input.createdBy }
|
|
5543
|
+
}, now);
|
|
5544
|
+
return toPin((await this.#query(tx, `SELECT * FROM ${T.pins} WHERE id = ?`, [id]))[0]);
|
|
5545
|
+
}
|
|
5546
|
+
const current = parseJsonList(existing.audience);
|
|
5547
|
+
const merged = [.../* @__PURE__ */ new Set([...current, ...input.audience])];
|
|
5548
|
+
if (merged.length !== current.length) {
|
|
5549
|
+
await tx.execute({
|
|
5550
|
+
sql: `UPDATE ${T.pins} SET audience = ?, updatedAt = ? WHERE id = ?`,
|
|
5551
|
+
args: [
|
|
5552
|
+
JSON.stringify(merged),
|
|
5553
|
+
now,
|
|
5554
|
+
String(existing.id)
|
|
5555
|
+
]
|
|
5556
|
+
});
|
|
5557
|
+
await this.#indexAudience(tx, String(existing.id), merged);
|
|
5558
|
+
await this.#logActivity(tx, "pin-added", {
|
|
5559
|
+
scopeId: String(page.scopeId),
|
|
5560
|
+
pageId: input.pageId,
|
|
5561
|
+
provenance: { author: input.createdBy }
|
|
5562
|
+
}, now);
|
|
5563
|
+
}
|
|
5564
|
+
return toPin((await this.#query(tx, `SELECT * FROM ${T.pins} WHERE id = ?`, [String(existing.id)]))[0]);
|
|
4891
5565
|
});
|
|
4892
5566
|
}
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
5567
|
+
removePinAudience(input) {
|
|
5568
|
+
return this.#transaction(async (tx) => {
|
|
5569
|
+
const existing = (await this.#query(tx, `SELECT * FROM ${T.pins} WHERE pageId = ?`, [input.pageId]))[0];
|
|
5570
|
+
if (!existing) return null;
|
|
5571
|
+
const current = parseJsonList(existing.audience);
|
|
5572
|
+
const remove = new Set(input.audience);
|
|
5573
|
+
const remaining = current.filter((entry) => !remove.has(entry));
|
|
5574
|
+
if (remaining.length === current.length) return toPin(existing);
|
|
5575
|
+
const page = await this.#pageRow(tx, input.pageId);
|
|
5576
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5577
|
+
if (page) await this.#logActivity(tx, "pin-removed", {
|
|
5578
|
+
scopeId: String(page.scopeId),
|
|
5579
|
+
pageId: input.pageId,
|
|
5580
|
+
provenance: { author: input.removedBy }
|
|
5581
|
+
}, now);
|
|
5582
|
+
if (remaining.length === 0) {
|
|
5583
|
+
await this.#indexAudience(tx, String(existing.id), []);
|
|
5584
|
+
await tx.execute({
|
|
5585
|
+
sql: `DELETE FROM ${T.pins} WHERE id = ?`,
|
|
5586
|
+
args: [String(existing.id)]
|
|
5587
|
+
});
|
|
5588
|
+
return null;
|
|
5589
|
+
}
|
|
5590
|
+
await tx.execute({
|
|
5591
|
+
sql: `UPDATE ${T.pins} SET audience = ?, updatedAt = ? WHERE id = ?`,
|
|
4899
5592
|
args: [
|
|
5593
|
+
JSON.stringify(remaining),
|
|
4900
5594
|
now,
|
|
4901
|
-
id
|
|
4902
|
-
input.workerId
|
|
5595
|
+
String(existing.id)
|
|
4903
5596
|
]
|
|
4904
5597
|
});
|
|
5598
|
+
await this.#indexAudience(tx, String(existing.id), remaining);
|
|
5599
|
+
return toPin((await this.#query(tx, `SELECT * FROM ${T.pins} WHERE id = ?`, [String(existing.id)]))[0]);
|
|
4905
5600
|
});
|
|
4906
5601
|
}
|
|
4907
|
-
async
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
5602
|
+
async listPins(input) {
|
|
5603
|
+
const core = await loadPagesCore();
|
|
5604
|
+
const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5605
|
+
const cursor = decodeCursor(input.cursor, 2);
|
|
5606
|
+
const readable = pageReadable("p", input.read);
|
|
5607
|
+
const args = [...readable.args];
|
|
5608
|
+
let filters = "";
|
|
5609
|
+
if (input.audience !== "all") {
|
|
5610
|
+
filters += ` AND pin.id IN (SELECT pa.pinId FROM (SELECT jv.v FROM JSON_TABLE(?, '$[*]' COLUMNS (v VARCHAR(512) ${BIN} PATH '$')) jv) aud STRAIGHT_JOIN ${T.pinAudiences} pa ON pa.audience = aud.v)`;
|
|
5611
|
+
args.push(jsonList(input.audience));
|
|
5612
|
+
}
|
|
5613
|
+
if (input.pageIds) {
|
|
5614
|
+
filters += ` AND pin.pageId IN ${JV}`;
|
|
5615
|
+
args.push(jsonList(input.pageIds));
|
|
5616
|
+
}
|
|
5617
|
+
if (input.excludePageIds?.length) {
|
|
5618
|
+
filters += ` AND pin.pageId NOT IN ${JV}`;
|
|
5619
|
+
args.push(jsonList(input.excludePageIds));
|
|
5620
|
+
}
|
|
5621
|
+
if (input.withBody) filters += ` AND REGEXP_LIKE(p.body, '[^[:space:]]')`;
|
|
5622
|
+
const from = `FROM ${T.pins} pin JOIN ${T.pages} p ON p.id = pin.pageId WHERE p.deletedAt IS NULL AND ${readable.sql}${filters}`;
|
|
5623
|
+
let total;
|
|
5624
|
+
if (input.count) total = Number((await this.#query(this.#read, `SELECT COUNT(*) AS n ${from}`, args))[0].n);
|
|
5625
|
+
const pageArgs = [...args];
|
|
5626
|
+
let cursorFilter = "";
|
|
5627
|
+
if (cursor) {
|
|
5628
|
+
cursorFilter = ` AND (pin.createdAt < ? OR (pin.createdAt = ? AND pin.id < ?))`;
|
|
5629
|
+
pageArgs.push(cursor[0], cursor[0], cursor[1]);
|
|
5630
|
+
}
|
|
5631
|
+
pageArgs.push(limit + 1);
|
|
5632
|
+
const rows = await this.#query(this.#read, `SELECT pin.*, p.name AS pageName ${from}${cursorFilter} ORDER BY pin.createdAt DESC, pin.id DESC LIMIT ?`, pageArgs);
|
|
5633
|
+
const page = rows.slice(0, limit);
|
|
5634
|
+
return {
|
|
5635
|
+
pins: page.map(toListedPin),
|
|
5636
|
+
...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].createdAt), String(page[page.length - 1].id)]) } : {},
|
|
5637
|
+
...total === void 0 ? {} : { total }
|
|
5638
|
+
};
|
|
5639
|
+
}
|
|
5640
|
+
};
|
|
5641
|
+
/** mysql2 returns JSON columns already parsed; accept the raw string form too. */
|
|
5642
|
+
function parseJsonList(value) {
|
|
5643
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
5644
|
+
}
|
|
5645
|
+
// src/storage/domains/knowledge/index.ts
|
|
5646
|
+
var KnowledgeMySQL = class extends _mastra_core_storage.KnowledgeStorage {
|
|
5647
|
+
static getExportDDL() {
|
|
5648
|
+
return KNOWLEDGE_PAGE_DDL.map((statement) => `${statement};`);
|
|
5649
|
+
}
|
|
5650
|
+
#pool;
|
|
5651
|
+
#pages;
|
|
5652
|
+
constructor({ pool }) {
|
|
5653
|
+
super();
|
|
5654
|
+
this.#pool = pool;
|
|
5655
|
+
const rawExecutor = (client) => ({ async execute(statement) {
|
|
5656
|
+
const [result] = await client.query(statement.sql, statement.args ?? []);
|
|
5657
|
+
return { rows: Array.isArray(result) ? result : [] };
|
|
5658
|
+
} });
|
|
5659
|
+
this.#pages = new MySQLKnowledgePages({
|
|
5660
|
+
read: rawExecutor(pool),
|
|
5661
|
+
transaction: async (operation) => {
|
|
5662
|
+
const connection = await pool.getConnection();
|
|
5663
|
+
try {
|
|
5664
|
+
await connection.beginTransaction();
|
|
5665
|
+
const result = await operation(rawExecutor(connection));
|
|
5666
|
+
await connection.commit();
|
|
5667
|
+
return result;
|
|
5668
|
+
} catch (error) {
|
|
5669
|
+
await connection.rollback();
|
|
5670
|
+
throw error;
|
|
5671
|
+
} finally {
|
|
5672
|
+
connection.release();
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
4918
5675
|
});
|
|
4919
5676
|
}
|
|
4920
|
-
|
|
5677
|
+
getCapabilities() {
|
|
5678
|
+
return {
|
|
5679
|
+
contractVersion: 4,
|
|
5680
|
+
schemaVersion: 4,
|
|
5681
|
+
supportsSchemaInspection: true,
|
|
5682
|
+
supportsExplicitReset: true
|
|
5683
|
+
};
|
|
5684
|
+
}
|
|
5685
|
+
async #inspectPages(client = this.#pool) {
|
|
5686
|
+
return inspectKnowledgePagesSchema(await this.#pagesSnapshot(client));
|
|
5687
|
+
}
|
|
5688
|
+
async #pagesSnapshot(client) {
|
|
5689
|
+
const exec = async (sql) => {
|
|
5690
|
+
const [rows] = await client.query(sql);
|
|
5691
|
+
return rows;
|
|
5692
|
+
};
|
|
5693
|
+
const tableNames = (await exec("SELECT table_name AS name FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name LIKE 'mastra\\_knowledge\\_%'")).map((row) => String(row.name));
|
|
5694
|
+
let schemaVersion;
|
|
5695
|
+
if (tableNames.includes("mastra_knowledge_access_state")) {
|
|
5696
|
+
const state = await exec(`SELECT schemaVersion FROM \`${TABLE_KNOWLEDGE_PAGES_STATE}\` WHERE id = 'knowledge'`);
|
|
5697
|
+
if (state[0]) schemaVersion = Number(state[0].schemaVersion);
|
|
5698
|
+
}
|
|
5699
|
+
return {
|
|
5700
|
+
tableNames,
|
|
5701
|
+
schemaVersion
|
|
5702
|
+
};
|
|
5703
|
+
}
|
|
5704
|
+
async inspectSchema() {
|
|
5705
|
+
return this.#inspectPages();
|
|
5706
|
+
}
|
|
5707
|
+
/** Runs the earlier-alpha upgrades under a named lock so processes starting together don't race their ALTERs. */
|
|
5708
|
+
async #upgrade() {
|
|
4921
5709
|
const connection = await this.#pool.getConnection();
|
|
4922
5710
|
try {
|
|
4923
|
-
await connection.
|
|
4924
|
-
const
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
await connection.rollback();
|
|
4929
|
-
throw error;
|
|
5711
|
+
await connection.query("SELECT GET_LOCK('mastra_knowledge_upgrade', 60)");
|
|
5712
|
+
for (const upgrade of KNOWLEDGE_PAGE_UPGRADES) {
|
|
5713
|
+
const [rows] = await connection.query(upgrade.needed, upgrade.args);
|
|
5714
|
+
if (rows.length > 0) for (const statement of upgrade.apply) await connection.query(statement);
|
|
5715
|
+
}
|
|
4930
5716
|
} finally {
|
|
5717
|
+
await connection.query("SELECT RELEASE_LOCK('mastra_knowledge_upgrade')").catch(() => {});
|
|
4931
5718
|
connection.release();
|
|
4932
5719
|
}
|
|
4933
5720
|
}
|
|
4934
|
-
async
|
|
4935
|
-
const
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
}
|
|
4948
|
-
async #resolveNode(executor, name, scope) {
|
|
4949
|
-
const keys = visibleScopeKeys(scope);
|
|
4950
|
-
const candidates = (await executor.execute({
|
|
4951
|
-
sql: `SELECT *,scope AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND (scopeKey IN (${keys.map(() => "?").join(",")}) OR mergedInto IS NOT NULL)`,
|
|
4952
|
-
args: [canonicalName(name), ...keys]
|
|
4953
|
-
})).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
|
|
4954
|
-
for (const candidate of candidates) {
|
|
4955
|
-
const terminal = await this.#resolveTerminalNode(executor, candidate.id);
|
|
4956
|
-
if (terminal && (0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) return terminal;
|
|
5721
|
+
async init() {
|
|
5722
|
+
const connection = await this.#pool.getConnection();
|
|
5723
|
+
try {
|
|
5724
|
+
const [locked] = await connection.query("SELECT GET_LOCK('mastra_knowledge_init', 60) AS ok");
|
|
5725
|
+
if (Number(locked[0]?.ok) !== 1) throw new Error("Timed out waiting for the Knowledge schema lock");
|
|
5726
|
+
for (const table of legacyKnowledgeTablesToDrop(await this.#pagesSnapshot(connection))) await connection.query(`DROP TABLE IF EXISTS \`${table}\``);
|
|
5727
|
+
const inspection = await this.#inspectPages(connection);
|
|
5728
|
+
if (inspection.status === "incompatible-reset-required") throw new _mastra_core_storage.KnowledgeSchemaResetRequiredError(inspection);
|
|
5729
|
+
for (const statement of KNOWLEDGE_PAGE_DDL) await connection.query(statement);
|
|
5730
|
+
await connection.query(`INSERT IGNORE INTO \`${TABLE_KNOWLEDGE_PAGES_STATE}\` (id, schemaVersion) VALUES ('knowledge', ?)`, [4]);
|
|
5731
|
+
} finally {
|
|
5732
|
+
await connection.query("SELECT RELEASE_LOCK('mastra_knowledge_init')").catch(() => {});
|
|
5733
|
+
connection.release();
|
|
4957
5734
|
}
|
|
4958
|
-
|
|
5735
|
+
await this.#upgrade();
|
|
4959
5736
|
}
|
|
4960
|
-
async
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
while (node?.mergedInto) {
|
|
4964
|
-
if (seen.has(node.id)) throw new Error(`Knowledge merge cycle detected at ${node.id}`);
|
|
4965
|
-
seen.add(node.id);
|
|
4966
|
-
node = await this.#getNode(executor, node.mergedInto);
|
|
4967
|
-
}
|
|
4968
|
-
return node;
|
|
5737
|
+
async dangerouslyReset() {
|
|
5738
|
+
for (const table of [...KNOWLEDGE_RESETTABLE_TABLE_NAMES, "mastra_knowledge_pin_audiences"]) await this.#pool.query(`DROP TABLE IF EXISTS \`${table}\``);
|
|
5739
|
+
await this.init();
|
|
4969
5740
|
}
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
sql: `SELECT *,scope AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${includeDeleted ? "" : " AND deletedAt IS NULL"}`,
|
|
4973
|
-
args: [id]
|
|
4974
|
-
});
|
|
4975
|
-
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
5741
|
+
materializeScope(input) {
|
|
5742
|
+
return this.#pages.materializeScope(input);
|
|
4976
5743
|
}
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
const node = await this.#resolveTerminalNode(this.#client, nodeReferenceId(input.node));
|
|
4980
|
-
if (!node) return { records: [] };
|
|
4981
|
-
const keys = visibleScopeKeys(scope);
|
|
4982
|
-
const args = [
|
|
4983
|
-
node.id,
|
|
4984
|
-
...relationship === "related" ? [node.id] : [],
|
|
4985
|
-
...keys
|
|
4986
|
-
];
|
|
4987
|
-
if (input.after) args.push(input.after);
|
|
4988
|
-
args.push((input.limit ?? 100) + 1);
|
|
4989
|
-
const records = (await this.#client.execute({
|
|
4990
|
-
sql: `SELECT DISTINCT f.*,f.scope AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql(keys, "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
|
|
4991
|
-
args
|
|
4992
|
-
})).rows.map(parseKnowledge);
|
|
4993
|
-
const limit = input.limit ?? 100;
|
|
4994
|
-
return {
|
|
4995
|
-
records: records.slice(0, limit),
|
|
4996
|
-
nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
|
|
4997
|
-
};
|
|
5744
|
+
listScopeDeclarations() {
|
|
5745
|
+
return this.#pages.listScopeDeclarations();
|
|
4998
5746
|
}
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
sql: `DELETE FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
|
|
5002
|
-
args: [sourceType, sourceId]
|
|
5003
|
-
});
|
|
5004
|
-
for (const name of (0, _mastra_core_storage.parseKnowledgeWikilinks)(text)) {
|
|
5005
|
-
let node = await this.#resolveNode(tx, name, resolutionScope);
|
|
5006
|
-
if (!node) {
|
|
5007
|
-
node = await this.#getNodeByName(tx, name, defaultScope);
|
|
5008
|
-
if (node) node = await this.#resolveTerminalNode(tx, node.id);
|
|
5009
|
-
if (!node) {
|
|
5010
|
-
const now = /* @__PURE__ */ new Date();
|
|
5011
|
-
node = {
|
|
5012
|
-
id: crypto.randomUUID(),
|
|
5013
|
-
type: "node",
|
|
5014
|
-
name,
|
|
5015
|
-
kind: "node",
|
|
5016
|
-
scope: defaultScope,
|
|
5017
|
-
version: 1,
|
|
5018
|
-
createdAt: now,
|
|
5019
|
-
updatedAt: now
|
|
5020
|
-
};
|
|
5021
|
-
await tx.execute({
|
|
5022
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,jsonb(?),?,?,NULL,?,?)`,
|
|
5023
|
-
args: [
|
|
5024
|
-
node.id,
|
|
5025
|
-
"node",
|
|
5026
|
-
node.name,
|
|
5027
|
-
canonicalName(node.name),
|
|
5028
|
-
node.kind,
|
|
5029
|
-
JSON.stringify(defaultScope),
|
|
5030
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(defaultScope),
|
|
5031
|
-
1,
|
|
5032
|
-
now.toISOString()
|
|
5033
|
-
]
|
|
5034
|
-
});
|
|
5035
|
-
await this.#activity(tx, "node-created", "node", node.id, defaultScope);
|
|
5036
|
-
await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
5039
|
-
await tx.execute({
|
|
5040
|
-
sql: `INSERT OR IGNORE INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" (sourceType,sourceId,recordId) VALUES (?,?,?)`,
|
|
5041
|
-
args: [
|
|
5042
|
-
sourceType,
|
|
5043
|
-
sourceId,
|
|
5044
|
-
node.id
|
|
5045
|
-
]
|
|
5046
|
-
});
|
|
5047
|
-
}
|
|
5747
|
+
renameScopeDeclaration(input) {
|
|
5748
|
+
return this.#pages.renameScopeDeclaration(input);
|
|
5048
5749
|
}
|
|
5049
|
-
|
|
5050
|
-
|
|
5051
|
-
await executor.execute({
|
|
5052
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (id,action,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,jsonb(?),?,?,?)`,
|
|
5053
|
-
args: [
|
|
5054
|
-
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
5055
|
-
action,
|
|
5056
|
-
recordType,
|
|
5057
|
-
recordId,
|
|
5058
|
-
JSON.stringify(scope),
|
|
5059
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
5060
|
-
sourceThreadId ?? null,
|
|
5061
|
-
now.toISOString()
|
|
5062
|
-
]
|
|
5063
|
-
});
|
|
5750
|
+
getScopesByAddress(input) {
|
|
5751
|
+
return this.#pages.getScopesByAddress(input);
|
|
5064
5752
|
}
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5753
|
+
listChildScopes(input) {
|
|
5754
|
+
return this.#pages.listChildScopes(input);
|
|
5755
|
+
}
|
|
5756
|
+
getPage(input) {
|
|
5757
|
+
return this.#pages.getPage(input);
|
|
5758
|
+
}
|
|
5759
|
+
getPageByPath(input) {
|
|
5760
|
+
return this.#pages.getPageByPath(input);
|
|
5761
|
+
}
|
|
5762
|
+
findPagesByName(input) {
|
|
5763
|
+
return this.#pages.findPagesByName(input);
|
|
5764
|
+
}
|
|
5765
|
+
listChildPages(input) {
|
|
5766
|
+
return this.#pages.listChildPages(input);
|
|
5767
|
+
}
|
|
5768
|
+
listPages(input) {
|
|
5769
|
+
return this.#pages.listPages(input);
|
|
5770
|
+
}
|
|
5771
|
+
createPage(input) {
|
|
5772
|
+
return this.#pages.createPage(input);
|
|
5773
|
+
}
|
|
5774
|
+
updatePage(input) {
|
|
5775
|
+
return this.#pages.updatePage(input);
|
|
5776
|
+
}
|
|
5777
|
+
movePage(input) {
|
|
5778
|
+
return this.#pages.movePage(input);
|
|
5779
|
+
}
|
|
5780
|
+
mergePages(input) {
|
|
5781
|
+
return this.#pages.mergePages(input);
|
|
5782
|
+
}
|
|
5783
|
+
deletePage(input) {
|
|
5784
|
+
return this.#pages.deletePage(input);
|
|
5785
|
+
}
|
|
5786
|
+
getNote(input) {
|
|
5787
|
+
return this.#pages.getNote(input);
|
|
5788
|
+
}
|
|
5789
|
+
listNotes(input) {
|
|
5790
|
+
return this.#pages.listNotes(input);
|
|
5791
|
+
}
|
|
5792
|
+
countNotes(input) {
|
|
5793
|
+
return this.#pages.countNotes(input);
|
|
5794
|
+
}
|
|
5795
|
+
listNotesMentioning(input) {
|
|
5796
|
+
return this.#pages.listNotesMentioning(input);
|
|
5797
|
+
}
|
|
5798
|
+
addNote(input) {
|
|
5799
|
+
return this.#pages.addNote(input);
|
|
5800
|
+
}
|
|
5801
|
+
restampNote(input) {
|
|
5802
|
+
return this.#pages.restampNote(input);
|
|
5803
|
+
}
|
|
5804
|
+
removeNote(input) {
|
|
5805
|
+
return this.#pages.removeNote(input);
|
|
5806
|
+
}
|
|
5807
|
+
restoreNote(input) {
|
|
5808
|
+
return this.#pages.restoreNote(input);
|
|
5809
|
+
}
|
|
5810
|
+
searchPages(input) {
|
|
5811
|
+
return this.#pages.searchPages(input);
|
|
5812
|
+
}
|
|
5813
|
+
listPageActivity(input) {
|
|
5814
|
+
return this.#pages.listPageActivity(input);
|
|
5815
|
+
}
|
|
5816
|
+
addPinAudience(input) {
|
|
5817
|
+
return this.#pages.addPinAudience(input);
|
|
5818
|
+
}
|
|
5819
|
+
removePinAudience(input) {
|
|
5820
|
+
return this.#pages.removePinAudience(input);
|
|
5821
|
+
}
|
|
5822
|
+
getPages(input) {
|
|
5823
|
+
return this.#pages.getPages(input);
|
|
5824
|
+
}
|
|
5825
|
+
listPins(input) {
|
|
5826
|
+
return this.#pages.listPins(input);
|
|
5827
|
+
}
|
|
5828
|
+
async dangerouslyClearAll() {
|
|
5829
|
+
await this.#pages.clearAll();
|
|
5083
5830
|
}
|
|
5084
5831
|
};
|
|
5085
5832
|
// src/storage/domains/mcp-clients/index.ts
|