@mastra/mongodb 1.25.0-alpha.0 → 1.26.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/docs/references/docs-memory-semantic-recall.md +2 -0
- package/dist/docs/references/reference-rag-retrieval.md +5 -1
- package/dist/index.cjs +1837 -728
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1838 -729
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/knowledge/index.d.ts +40 -77
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/pages.d.ts +172 -0
- package/dist/storage/domains/knowledge/pages.d.ts.map +1 -0
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -5,12 +5,13 @@ let _mastra_core_storage = require("@mastra/core/storage");
|
|
|
5
5
|
let _mastra_core_vector = require("@mastra/core/vector");
|
|
6
6
|
let mongodb = require("mongodb");
|
|
7
7
|
let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
|
|
8
|
+
let _mastra_core_features = require("@mastra/core/features");
|
|
8
9
|
let crypto$1 = require("crypto");
|
|
9
10
|
let _mastra_core_agent = require("@mastra/core/agent");
|
|
10
11
|
let _mastra_core_evals = require("@mastra/core/evals");
|
|
11
12
|
let _mastra_core_storage_domains_skills = require("@mastra/core/storage/domains/skills");
|
|
12
13
|
// package.json
|
|
13
|
-
var version = "1.
|
|
14
|
+
var version = "1.26.0-alpha.0";
|
|
14
15
|
// src/vector/filter.ts
|
|
15
16
|
/**
|
|
16
17
|
* Translator for MongoDB filter queries.
|
|
@@ -4769,845 +4770,1953 @@ var MongoDBExperimentsStorage = class MongoDBExperimentsStorage extends _mastra_
|
|
|
4769
4770
|
} catch {}
|
|
4770
4771
|
}
|
|
4771
4772
|
};
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4773
|
+
const KNOWLEDGE_TABLE_NAMES = [
|
|
4774
|
+
"mastra_knowledge_nodes",
|
|
4775
|
+
"mastra_knowledge_records",
|
|
4776
|
+
"mastra_knowledge_mentions",
|
|
4777
|
+
"mastra_knowledge_activity",
|
|
4778
|
+
"mastra_knowledge_semantic_outbox",
|
|
4779
|
+
"mastra_knowledge_node_scopes",
|
|
4780
|
+
"mastra_knowledge_record_scopes",
|
|
4781
|
+
"mastra_knowledge_scope_grants",
|
|
4782
|
+
"mastra_knowledge_access_state",
|
|
4783
|
+
"mastra_knowledge_scope_addresses",
|
|
4784
|
+
"mastra_knowledge_node_addresses",
|
|
4785
|
+
"mastra_knowledge_import_state",
|
|
4786
|
+
"mastra_knowledge_import_runs",
|
|
4787
|
+
"mastra_knowledge_proposals"
|
|
4788
|
+
];
|
|
4789
|
+
/** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
|
|
4790
|
+
const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
|
|
4791
|
+
const KNOWLEDGE_PAGES_CORE_FEATURE = "knowledge-pages";
|
|
4792
|
+
const KNOWLEDGE_PAGES_TABLE_NAMES = [
|
|
4793
|
+
"mastra_knowledge_pages",
|
|
4794
|
+
"mastra_knowledge_notes",
|
|
4795
|
+
"mastra_knowledge_note_stamps",
|
|
4796
|
+
"mastra_knowledge_mentions",
|
|
4797
|
+
"mastra_knowledge_pins",
|
|
4798
|
+
"mastra_knowledge_activity",
|
|
4799
|
+
"mastra_knowledge_access_state"
|
|
4800
|
+
];
|
|
4801
|
+
/** Every Knowledge table any schema version created. Reset drops these and nothing else. */
|
|
4802
|
+
const KNOWLEDGE_RESETTABLE_TABLE_NAMES = [.../* @__PURE__ */ new Set([
|
|
4803
|
+
...KNOWLEDGE_PAGES_TABLE_NAMES,
|
|
4804
|
+
...KNOWLEDGE_TABLE_NAMES,
|
|
4805
|
+
...RETIRED_KNOWLEDGE_TABLE_NAMES
|
|
4806
|
+
])];
|
|
4807
|
+
/** Schema-1/2 tables whose presence means the store needs a reset before schema 4 can run. */
|
|
4808
|
+
const LEGACY_KNOWLEDGE_TABLE_NAMES = new Set([...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES].filter((name) => !KNOWLEDGE_PAGES_TABLE_NAMES.includes(name)));
|
|
4809
|
+
/**
|
|
4810
|
+
* Classifies what an adapter found before creating schema-4 tables. `tableNames` is every
|
|
4811
|
+
* `mastra_knowledge_*` table present; `schemaVersion` is the state row's version, if any.
|
|
4812
|
+
*/
|
|
4813
|
+
function inspectKnowledgePagesSchema(snapshot) {
|
|
4814
|
+
const legacy = snapshot.tableNames.filter((name) => LEGACY_KNOWLEDGE_TABLE_NAMES.has(name));
|
|
4815
|
+
if (legacy.length > 0) return {
|
|
4816
|
+
status: "incompatible-reset-required",
|
|
4817
|
+
schemaVersion: snapshot.schemaVersion ?? null,
|
|
4818
|
+
reason: `found Knowledge tables from an earlier schema (${legacy.join(", ")})`
|
|
4819
|
+
};
|
|
4820
|
+
if (snapshot.schemaVersion === 4) return {
|
|
4821
|
+
status: "compatible",
|
|
4822
|
+
schemaVersion: 4
|
|
4823
|
+
};
|
|
4824
|
+
if (snapshot.schemaVersion === void 0) {
|
|
4825
|
+
const present = snapshot.tableNames.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
4826
|
+
if (present.length === 0) return {
|
|
4827
|
+
status: "uninitialized",
|
|
4828
|
+
schemaVersion: null
|
|
4829
|
+
};
|
|
4830
|
+
return {
|
|
4831
|
+
status: "incompatible-reset-required",
|
|
4832
|
+
schemaVersion: null,
|
|
4833
|
+
reason: `found Knowledge tables (${present.join(", ")}) without a schema version`
|
|
4834
|
+
};
|
|
4835
|
+
}
|
|
4836
|
+
return {
|
|
4837
|
+
status: "incompatible-reset-required",
|
|
4838
|
+
schemaVersion: snapshot.schemaVersion,
|
|
4839
|
+
reason: `Knowledge schema version ${snapshot.schemaVersion} is not 4`
|
|
4840
|
+
};
|
|
4841
|
+
}
|
|
4842
|
+
/** Schema-2 table names schema 4 reuses with a different shape. */
|
|
4843
|
+
const REUSED_KNOWLEDGE_TABLE_NAMES = KNOWLEDGE_TABLE_NAMES.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
4844
|
+
/**
|
|
4845
|
+
* The old Knowledge tables an adapter drops on init, rows included: Knowledge before schema 4 was an
|
|
4846
|
+
* experimental store with no migration. Every schema-1/2-only table present is dropped. The table
|
|
4847
|
+
* names schema 4 reuses (mentions, activity, the state table) are dropped too, but only when an old
|
|
4848
|
+
* table is present and no schema-4 state row exists, so they are old-shaped. Schema-4-only tables
|
|
4849
|
+
* are never listed. On a schema-4 store this lists only tables an older process recreated during a
|
|
4850
|
+
* rolling deploy. Drop order is safe for adapters that enforce references.
|
|
4851
|
+
*/
|
|
4852
|
+
function legacyKnowledgeTablesToDrop(snapshot) {
|
|
4853
|
+
const present = new Set(snapshot.tableNames);
|
|
4854
|
+
const legacy = [...LEGACY_KNOWLEDGE_TABLE_NAMES].filter((name) => present.has(name));
|
|
4855
|
+
if (legacy.length === 0) return [];
|
|
4856
|
+
const reused = snapshot.schemaVersion === 4 ? [] : REUSED_KNOWLEDGE_TABLE_NAMES.filter((name) => present.has(name));
|
|
4857
|
+
const drop = /* @__PURE__ */ new Set([...legacy, ...reused]);
|
|
4858
|
+
return [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].filter((name) => drop.has(name));
|
|
4859
|
+
}
|
|
4860
|
+
/** Message for the error an adapter throws on `incompatible-reset-required`. */
|
|
4861
|
+
function knowledgePagesResetMessage(reason) {
|
|
4862
|
+
return `Knowledge storage needs a reset before this version can use it: ${reason}. Knowledge data has no migration; run \`await storage.stores?.knowledge?.dangerouslyReset()\` to drop the Knowledge tables (Memory tables are untouched).`;
|
|
4863
|
+
}
|
|
4864
|
+
const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
|
|
4865
|
+
"assertKnowledgePageDescriptionWithinBound",
|
|
4866
|
+
"assertKnowledgeNoteTextWithinBound",
|
|
4867
|
+
"canonicalKnowledgeName",
|
|
4868
|
+
"knowledgePositionBetween",
|
|
4869
|
+
"KnowledgeMoveTooLargeError",
|
|
4870
|
+
"KnowledgePageDepthError",
|
|
4871
|
+
"KnowledgePageHasChildrenError",
|
|
4872
|
+
"KnowledgePageKeyConflictError"
|
|
4873
|
+
];
|
|
4874
|
+
const KNOWLEDGE_PAGES_CORE_NUMBERS = [
|
|
4875
|
+
"MAX_KNOWLEDGE_MOVE_PAGES",
|
|
4876
|
+
"MAX_KNOWLEDGE_PAGE_DEPTH",
|
|
4877
|
+
"MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
|
|
4878
|
+
];
|
|
4879
|
+
function resolveKnowledgePagesCore(storageModule) {
|
|
4880
|
+
const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
|
|
4881
|
+
const missing = [...KNOWLEDGE_PAGES_CORE_FUNCTIONS.filter((name) => typeof module[name] !== "function"), ...KNOWLEDGE_PAGES_CORE_NUMBERS.filter((name) => typeof module[name] !== "number")];
|
|
4882
|
+
if (missing.length > 0) throw new Error(`@mastra/core advertises Knowledge pages without the required storage API (missing ${missing.join(", ")})`);
|
|
4883
|
+
return module;
|
|
4884
|
+
}
|
|
4885
|
+
/** Loads core's page-store runtime once, after checking core advertises `knowledge-pages`. */
|
|
4886
|
+
function createKnowledgePagesCoreLoader(features, loadStorageModule) {
|
|
4887
|
+
let core;
|
|
4888
|
+
return async () => {
|
|
4889
|
+
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.`);
|
|
4890
|
+
core ??= loadStorageModule().then(resolveKnowledgePagesCore).catch((error) => {
|
|
4891
|
+
core = void 0;
|
|
4892
|
+
throw error;
|
|
4779
4893
|
});
|
|
4780
|
-
|
|
4781
|
-
|
|
4894
|
+
return core;
|
|
4895
|
+
};
|
|
4896
|
+
}
|
|
4897
|
+
/**
|
|
4898
|
+
* Keyword-search words: the query split on whitespace, lowercased, deduplicated. Every word must
|
|
4899
|
+
* appear (as a case-insensitive substring) in a hit; an empty list means the search returns nothing.
|
|
4900
|
+
*/
|
|
4901
|
+
function knowledgeSearchWords(query) {
|
|
4902
|
+
return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
|
|
4903
|
+
}
|
|
4904
|
+
/**
|
|
4905
|
+
* Why `key` can't be a Knowledge page key, or undefined when it can. Keys are one path segment:
|
|
4906
|
+
* non-empty, with no `/` (path separator), `#` (scope/path separator) or `%` (escape character).
|
|
4907
|
+
*/
|
|
4908
|
+
function knowledgePageKeyProblem(key) {
|
|
4909
|
+
if (!key) return "Knowledge page key must not be empty";
|
|
4910
|
+
if (/[/#%]/.test(key)) return `Knowledge page key "${key}" must not contain "/", "#" or "%"`;
|
|
4911
|
+
}
|
|
4912
|
+
// src/storage/domains/knowledge/pages.ts
|
|
4913
|
+
const loadPagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
|
|
4914
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
4915
|
+
const MONGO_DUPLICATE_KEY = 11e3;
|
|
4916
|
+
const MONGO_KNOWLEDGE_PAGES_COLLECTIONS = {
|
|
4917
|
+
pages: "mastra_knowledge_pages",
|
|
4918
|
+
notes: "mastra_knowledge_notes",
|
|
4919
|
+
pins: "mastra_knowledge_pins",
|
|
4920
|
+
activity: "mastra_knowledge_activity",
|
|
4921
|
+
state: "mastra_knowledge_access_state"
|
|
4922
|
+
};
|
|
4923
|
+
/** Thrown when existing Knowledge collections can't be used as schema 4 without a reset. */
|
|
4924
|
+
var MongoKnowledgeResetRequiredError = class extends Error {
|
|
4925
|
+
inspection;
|
|
4926
|
+
constructor(inspection) {
|
|
4927
|
+
super(knowledgePagesResetMessage(inspection.reason));
|
|
4928
|
+
this.name = "KnowledgeSchemaResetRequiredError";
|
|
4929
|
+
this.inspection = inspection;
|
|
4782
4930
|
}
|
|
4783
|
-
|
|
4931
|
+
};
|
|
4932
|
+
function escapeRegex(value) {
|
|
4933
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4784
4934
|
}
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
assertRecordTextWithinBound(text);
|
|
4935
|
+
function isDuplicateKey(error) {
|
|
4936
|
+
return typeof error === "object" && error !== null && error.code === MONGO_DUPLICATE_KEY;
|
|
4937
|
+
}
|
|
4938
|
+
function encodeCursor(parts) {
|
|
4939
|
+
return Buffer.from(JSON.stringify(parts)).toString("base64url");
|
|
4791
4940
|
}
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
const
|
|
4795
|
-
|
|
4796
|
-
function visibleScopeKeys(scope) {
|
|
4797
|
-
const canonical = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(scope);
|
|
4798
|
-
const subsets = [[]];
|
|
4799
|
-
for (const entry of canonical) subsets.push(...subsets.map((subset) => [...subset, entry]));
|
|
4800
|
-
const keys = /* @__PURE__ */ new Set();
|
|
4801
|
-
for (const subset of subsets.slice(1)) try {
|
|
4802
|
-
keys.add((0, _mastra_core_storage.knowledgeScopeKey)(subset));
|
|
4941
|
+
function decodeCursor(cursor) {
|
|
4942
|
+
try {
|
|
4943
|
+
const parts = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
4944
|
+
if (Array.isArray(parts) && parts.length === 2 && parts.every((part) => typeof part === "string")) return parts;
|
|
4803
4945
|
} catch {}
|
|
4804
|
-
|
|
4946
|
+
throw new Error("Invalid Knowledge cursor");
|
|
4947
|
+
}
|
|
4948
|
+
function clampLimit(limit, max) {
|
|
4949
|
+
const value = limit ?? DEFAULT_LIST_LIMIT;
|
|
4950
|
+
if (!Number.isInteger(value) || value < 1) throw new Error("Knowledge list limit must be a positive integer");
|
|
4951
|
+
return Math.min(value, max);
|
|
4805
4952
|
}
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
name: parsed.name,
|
|
4812
|
-
id: { $gt: parsed.id }
|
|
4813
|
-
}]
|
|
4814
|
-
}] };
|
|
4953
|
+
const live = { deletedAt: { $exists: false } };
|
|
4954
|
+
const notScope = { isScope: { $ne: true } };
|
|
4955
|
+
/** Unique-while-live page key per parent; unset on delete so the key can be reused. */
|
|
4956
|
+
function activeKeyOf(parentId, key) {
|
|
4957
|
+
return `${parentId}\u0000${key}`;
|
|
4815
4958
|
}
|
|
4816
|
-
|
|
4959
|
+
/** Pipeline stage recomputing `activeKey` for pages that have a live key after their parent changed. */
|
|
4960
|
+
function recomputeActiveKey(parentId) {
|
|
4961
|
+
return { $set: { activeKey: { $cond: [
|
|
4962
|
+
{ $and: [{ $eq: [{ $type: "$key" }, "string"] }, { $eq: [{ $type: "$deletedAt" }, "missing"] }] },
|
|
4963
|
+
{ $concat: [
|
|
4964
|
+
parentId,
|
|
4965
|
+
"\0",
|
|
4966
|
+
"$key"
|
|
4967
|
+
] },
|
|
4968
|
+
"$$REMOVE"
|
|
4969
|
+
] } } };
|
|
4970
|
+
}
|
|
4971
|
+
function toPage(row) {
|
|
4817
4972
|
return {
|
|
4818
4973
|
id: String(row.id),
|
|
4819
|
-
|
|
4974
|
+
scopeId: String(row.scopeId),
|
|
4975
|
+
parentId: row.parentId == null ? null : String(row.parentId),
|
|
4976
|
+
isScope: row.isScope === true,
|
|
4977
|
+
...row.address == null ? {} : { address: String(row.address) },
|
|
4978
|
+
...row.declaration == null ? {} : { declaration: String(row.declaration) },
|
|
4979
|
+
...row.key == null ? {} : { key: String(row.key) },
|
|
4980
|
+
position: String(row.position),
|
|
4820
4981
|
name: String(row.name),
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
description: row.description == null ? void 0 : String(row.description),
|
|
4824
|
-
scope: cloneScope(row.scope),
|
|
4982
|
+
...row.description == null ? {} : { description: String(row.description) },
|
|
4983
|
+
...row.body == null ? {} : { body: String(row.body) },
|
|
4825
4984
|
version: Number(row.version),
|
|
4826
|
-
mergedInto: row.mergedInto
|
|
4985
|
+
...row.mergedInto == null ? {} : { mergedInto: String(row.mergedInto) },
|
|
4986
|
+
createdBy: row.createdBy,
|
|
4987
|
+
updatedBy: row.updatedBy,
|
|
4827
4988
|
createdAt: new Date(row.createdAt),
|
|
4828
|
-
updatedAt: new Date(row.updatedAt)
|
|
4989
|
+
updatedAt: new Date(row.updatedAt),
|
|
4990
|
+
...row.deletedAt == null ? {} : { deletedAt: new Date(row.deletedAt) },
|
|
4991
|
+
...row.deletedBy == null ? {} : { deletedBy: String(row.deletedBy) }
|
|
4829
4992
|
};
|
|
4830
4993
|
}
|
|
4831
|
-
function
|
|
4994
|
+
function toNote(row) {
|
|
4832
4995
|
return {
|
|
4833
4996
|
id: String(row.id),
|
|
4834
|
-
|
|
4997
|
+
pageId: String(row.pageId),
|
|
4835
4998
|
text: String(row.text),
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4999
|
+
...row.when == null ? {} : { when: new Date(row.when) },
|
|
5000
|
+
stampScopeIds: [...row.stampScopeIds ?? []].sort(),
|
|
5001
|
+
mentions: [...row.mentions ?? []].sort(),
|
|
5002
|
+
provenance: row.provenance,
|
|
5003
|
+
version: Number(row.version),
|
|
5004
|
+
createdAt: new Date(row.createdAt),
|
|
5005
|
+
updatedAt: new Date(row.updatedAt),
|
|
5006
|
+
...row.deletedAt == null ? {} : { deletedAt: new Date(row.deletedAt) },
|
|
5007
|
+
...row.deletedBy == null ? {} : { deletedBy: String(row.deletedBy) }
|
|
4843
5008
|
};
|
|
4844
5009
|
}
|
|
4845
|
-
function
|
|
5010
|
+
function toListedPin(row) {
|
|
5011
|
+
return {
|
|
5012
|
+
...toPin(row),
|
|
5013
|
+
pageName: String(row.pageName)
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
function toPin(row) {
|
|
4846
5017
|
return {
|
|
4847
5018
|
id: String(row.id),
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
operation: row.operation,
|
|
4852
|
-
scope: cloneScope(row.scope),
|
|
4853
|
-
status: row.status,
|
|
4854
|
-
attempts: Number(row.attempts),
|
|
4855
|
-
availableAt: new Date(row.availableAt),
|
|
4856
|
-
claimedAt: row.claimedAt ? new Date(row.claimedAt) : void 0,
|
|
4857
|
-
claimedBy: row.claimedBy ?? void 0,
|
|
5019
|
+
pageId: String(row.pageId),
|
|
5020
|
+
audience: [...row.audience ?? []],
|
|
5021
|
+
createdBy: String(row.createdBy),
|
|
4858
5022
|
createdAt: new Date(row.createdAt),
|
|
4859
|
-
|
|
5023
|
+
updatedAt: new Date(row.updatedAt)
|
|
4860
5024
|
};
|
|
4861
5025
|
}
|
|
4862
|
-
const
|
|
4863
|
-
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
5026
|
+
const noProjection = { projection: { _id: 0 } };
|
|
5027
|
+
/**
|
|
5028
|
+
* MongoDB implementation of the schema-4 {@link KnowledgePageStore}.
|
|
5029
|
+
*
|
|
5030
|
+
* Notes embed their stamps and mentions and carry two denormalized copies of page scope
|
|
5031
|
+
* (`pageScopeId`, `mentionScopeIds`); activity events carry `pageScopeId`. Moves and merges rewrite
|
|
5032
|
+
* those copies in the same transaction as the page rows, so a read is one indexed filter against the
|
|
5033
|
+
* set of readable scope ids resolved from the read scope (never a per-address query).
|
|
5034
|
+
*/
|
|
5035
|
+
var MongoKnowledgePages = class {
|
|
4871
5036
|
#connector;
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
this.#connector =
|
|
5037
|
+
#bounds;
|
|
5038
|
+
constructor(options) {
|
|
5039
|
+
this.#connector = options.connector;
|
|
5040
|
+
this.#bounds = options.bounds;
|
|
5041
|
+
}
|
|
5042
|
+
#c(name) {
|
|
5043
|
+
return this.#connector.getCollection(MONGO_KNOWLEDGE_PAGES_COLLECTIONS[name]);
|
|
5044
|
+
}
|
|
5045
|
+
#tx(operation) {
|
|
5046
|
+
return this.#connector.withTransaction(operation);
|
|
5047
|
+
}
|
|
5048
|
+
async inspectSchema() {
|
|
5049
|
+
return inspectKnowledgePagesSchema(await this.#snapshot());
|
|
5050
|
+
}
|
|
5051
|
+
async #snapshot() {
|
|
5052
|
+
const tableNames = [];
|
|
5053
|
+
for (const name of KNOWLEDGE_RESETTABLE_TABLE_NAMES) if (await this.#connector.collectionExists(name)) tableNames.push(name);
|
|
5054
|
+
let schemaVersion;
|
|
5055
|
+
if (tableNames.includes(MONGO_KNOWLEDGE_PAGES_COLLECTIONS.state)) {
|
|
5056
|
+
const state = await (await this.#c("state")).findOne({ id: "knowledge" });
|
|
5057
|
+
schemaVersion = typeof state?.schemaVersion === "number" ? state.schemaVersion : void 0;
|
|
5058
|
+
}
|
|
5059
|
+
return {
|
|
5060
|
+
tableNames,
|
|
5061
|
+
schemaVersion
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
5064
|
+
async #drop(name) {
|
|
5065
|
+
const collection = await this.#connector.getCollection(name);
|
|
5066
|
+
try {
|
|
5067
|
+
await collection.drop();
|
|
5068
|
+
} catch (error) {
|
|
5069
|
+
if (error.codeName !== "NamespaceNotFound") throw error;
|
|
5070
|
+
}
|
|
4875
5071
|
}
|
|
4876
5072
|
async init() {
|
|
4877
|
-
const
|
|
4878
|
-
const
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
5073
|
+
const stateName = MONGO_KNOWLEDGE_PAGES_COLLECTIONS.state;
|
|
5074
|
+
for (const name of legacyKnowledgeTablesToDrop(await this.#snapshot())) if (name === stateName) await (await this.#c("state")).deleteMany({ schemaVersion: { $ne: 4 } });
|
|
5075
|
+
else await this.#drop(name);
|
|
5076
|
+
await (await this.#c("state")).updateOne({ id: "knowledge" }, { $setOnInsert: {
|
|
5077
|
+
id: "knowledge",
|
|
5078
|
+
schemaVersion: 4
|
|
5079
|
+
} }, { upsert: true });
|
|
5080
|
+
const inspection = await this.inspectSchema();
|
|
5081
|
+
if (inspection.status === "incompatible-reset-required") throw new MongoKnowledgeResetRequiredError(inspection);
|
|
5082
|
+
const [pages, notes, pins, activity] = await Promise.all([
|
|
5083
|
+
this.#c("pages"),
|
|
5084
|
+
this.#c("notes"),
|
|
5085
|
+
this.#c("pins"),
|
|
5086
|
+
this.#c("activity")
|
|
5087
|
+
]);
|
|
4882
5088
|
await Promise.all([
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
}, { unique: true }),
|
|
4888
|
-
nodes.createIndex({
|
|
4889
|
-
scopeKey: 1,
|
|
4890
|
-
type: 1
|
|
5089
|
+
pages.createIndex({ id: 1 }, { unique: true }),
|
|
5090
|
+
pages.createIndex({ address: 1 }, {
|
|
5091
|
+
unique: true,
|
|
5092
|
+
partialFilterExpression: { isScope: true }
|
|
4891
5093
|
}),
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
5094
|
+
pages.createIndex({ activeKey: 1 }, {
|
|
5095
|
+
unique: true,
|
|
5096
|
+
partialFilterExpression: { activeKey: { $type: "string" } }
|
|
4895
5097
|
}),
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
5098
|
+
pages.createIndex({
|
|
5099
|
+
parentId: 1,
|
|
5100
|
+
position: 1,
|
|
5101
|
+
id: 1
|
|
4899
5102
|
}),
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
5103
|
+
pages.createIndex({
|
|
5104
|
+
scopeId: 1,
|
|
5105
|
+
name: 1,
|
|
5106
|
+
id: 1
|
|
4903
5107
|
}),
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
5108
|
+
pages.createIndex({
|
|
5109
|
+
canonicalName: 1,
|
|
5110
|
+
scopeId: 1
|
|
4907
5111
|
}),
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
}, { unique: true }),
|
|
4913
|
-
mentions.createIndex({
|
|
4914
|
-
recordId: 1,
|
|
4915
|
-
sourceType: 1,
|
|
4916
|
-
sourceId: 1
|
|
5112
|
+
pages.createIndex({
|
|
5113
|
+
isScope: 1,
|
|
5114
|
+
declaration: 1,
|
|
5115
|
+
parentId: 1
|
|
4917
5116
|
}),
|
|
4918
|
-
|
|
5117
|
+
notes.createIndex({ id: 1 }, { unique: true }),
|
|
5118
|
+
notes.createIndex({
|
|
5119
|
+
pageId: 1,
|
|
5120
|
+
createdAt: 1,
|
|
5121
|
+
id: 1
|
|
5122
|
+
}),
|
|
5123
|
+
notes.createIndex({ mentions: 1 }),
|
|
5124
|
+
notes.createIndex({ pageScopeId: 1 }),
|
|
5125
|
+
notes.createIndex({ stampScopeIds: 1 }),
|
|
5126
|
+
pins.createIndex({ id: 1 }, { unique: true }),
|
|
5127
|
+
pins.createIndex({ pageId: 1 }, { unique: true }),
|
|
5128
|
+
pins.createIndex({ audience: 1 }),
|
|
5129
|
+
activity.createIndex({ id: 1 }, { unique: true }),
|
|
4919
5130
|
activity.createIndex({
|
|
4920
|
-
|
|
5131
|
+
pageScopeId: 1,
|
|
5132
|
+
createdAt: -1,
|
|
4921
5133
|
id: -1
|
|
4922
5134
|
}),
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
createdAt: 1
|
|
5135
|
+
activity.createIndex({
|
|
5136
|
+
pageId: 1,
|
|
5137
|
+
createdAt: -1,
|
|
5138
|
+
id: -1
|
|
4928
5139
|
})
|
|
4929
5140
|
]);
|
|
4930
5141
|
}
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
5142
|
+
/** Drops exactly the Knowledge collections (never Memory's) and recreates schema 4. */
|
|
5143
|
+
async reset() {
|
|
5144
|
+
for (const name of KNOWLEDGE_RESETTABLE_TABLE_NAMES) await this.#drop(name);
|
|
5145
|
+
await this.init();
|
|
5146
|
+
}
|
|
5147
|
+
/** The ids of every scope page the read scope can see; `null` means everything. */
|
|
5148
|
+
async #readable(read, session) {
|
|
5149
|
+
if (read === "all") return null;
|
|
5150
|
+
const any = this.#readableClauses(read);
|
|
5151
|
+
if (!any.length) return [];
|
|
5152
|
+
return (await (await this.#c("pages")).find({
|
|
5153
|
+
isScope: true,
|
|
5154
|
+
$or: any
|
|
5155
|
+
}, {
|
|
5156
|
+
projection: {
|
|
5157
|
+
_id: 0,
|
|
5158
|
+
id: 1
|
|
5159
|
+
},
|
|
5160
|
+
session
|
|
5161
|
+
}).toArray()).map((row) => String(row.id));
|
|
5162
|
+
}
|
|
5163
|
+
#readableClauses(read) {
|
|
5164
|
+
const any = [];
|
|
5165
|
+
if (read.addresses.length) any.push({ address: { $in: read.addresses } });
|
|
5166
|
+
for (const entry of read.prefixes) any.push({
|
|
5167
|
+
declaration: entry.declaration,
|
|
5168
|
+
address: { $regex: `^${escapeRegex(entry.prefix)}` }
|
|
4934
5169
|
});
|
|
5170
|
+
for (const entry of read.children) any.push({
|
|
5171
|
+
declaration: entry.declaration,
|
|
5172
|
+
parentId: entry.parentId
|
|
5173
|
+
});
|
|
5174
|
+
return any;
|
|
4935
5175
|
}
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
5176
|
+
/**
|
|
5177
|
+
* Whether one scope page is readable, answered by a single indexed lookup instead of resolving the
|
|
5178
|
+
* whole frontier. Use it when a call targets one scope; the frontier can be thousands of scopes.
|
|
5179
|
+
*/
|
|
5180
|
+
async #scopeReadable(read, scopeId) {
|
|
5181
|
+
if (read === "all") return true;
|
|
5182
|
+
const any = this.#readableClauses(read);
|
|
5183
|
+
if (!any.length) return false;
|
|
5184
|
+
return await (await this.#c("pages")).countDocuments({
|
|
5185
|
+
id: scopeId,
|
|
5186
|
+
isScope: true,
|
|
5187
|
+
$or: any
|
|
5188
|
+
}, { limit: 1 }) > 0;
|
|
5189
|
+
}
|
|
5190
|
+
#inScopes(field, ids) {
|
|
5191
|
+
return ids === null ? {} : { [field]: { $in: ids } };
|
|
5192
|
+
}
|
|
5193
|
+
#visibleNotes(ids) {
|
|
5194
|
+
if (ids === null) return {};
|
|
5195
|
+
return {
|
|
5196
|
+
pageScopeId: { $in: ids },
|
|
5197
|
+
stampScopeIds: { $in: ids },
|
|
5198
|
+
mentionScopeIds: { $not: { $elemMatch: { $nin: ids } } }
|
|
5199
|
+
};
|
|
5200
|
+
}
|
|
5201
|
+
async #page(id, session) {
|
|
5202
|
+
const row = await (await this.#c("pages")).findOne({ id }, {
|
|
5203
|
+
...noProjection,
|
|
5204
|
+
session
|
|
5205
|
+
});
|
|
5206
|
+
return row ? toPage(row) : null;
|
|
5207
|
+
}
|
|
5208
|
+
async #requireLivePage(id, session) {
|
|
5209
|
+
const page = await this.#page(id, session);
|
|
5210
|
+
if (!page || page.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("page", id);
|
|
5211
|
+
return page;
|
|
5212
|
+
}
|
|
5213
|
+
async #log(session, event) {
|
|
5214
|
+
await (await this.#c("activity")).insertOne({
|
|
5215
|
+
id: (0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
5216
|
+
action: event.action,
|
|
5217
|
+
scopeId: event.scopeId,
|
|
5218
|
+
pageScopeId: event.pageScopeId ?? event.scopeId,
|
|
5219
|
+
pageId: event.pageId,
|
|
5220
|
+
...event.noteId ? { noteId: event.noteId } : {},
|
|
5221
|
+
provenance: event.provenance,
|
|
5222
|
+
createdAt: event.at
|
|
5223
|
+
}, { session });
|
|
5224
|
+
}
|
|
5225
|
+
async #lastPosition(parentId, session) {
|
|
5226
|
+
const row = await (await this.#c("pages")).find({
|
|
5227
|
+
parentId,
|
|
5228
|
+
...live
|
|
5229
|
+
}, {
|
|
5230
|
+
projection: {
|
|
5231
|
+
_id: 0,
|
|
5232
|
+
position: 1
|
|
5233
|
+
},
|
|
5234
|
+
session
|
|
5235
|
+
}).sort({ position: -1 }).limit(1).next();
|
|
5236
|
+
return row ? String(row.position) : void 0;
|
|
5237
|
+
}
|
|
5238
|
+
/**
|
|
5239
|
+
* Ordinary descendants of `rootId`, level by level. Stops counting once the live total passes the
|
|
5240
|
+
* move cap, so an oversize subtree is reported without being walked to the end.
|
|
5241
|
+
*/
|
|
5242
|
+
async #subtree(rootId, session, find) {
|
|
5243
|
+
const core = await loadPagesCore();
|
|
5244
|
+
const pages = await this.#c("pages");
|
|
5245
|
+
const ids = [];
|
|
5246
|
+
let liveCount = 0;
|
|
5247
|
+
let maxDepth = 0;
|
|
5248
|
+
let frontier = [rootId];
|
|
5249
|
+
while (frontier.length) {
|
|
5250
|
+
const rows = await pages.find({
|
|
5251
|
+
parentId: { $in: frontier },
|
|
5252
|
+
...notScope
|
|
5253
|
+
}, {
|
|
5254
|
+
projection: {
|
|
5255
|
+
_id: 0,
|
|
5256
|
+
id: 1,
|
|
5257
|
+
depth: 1,
|
|
5258
|
+
deletedAt: 1
|
|
5259
|
+
},
|
|
5260
|
+
session
|
|
5261
|
+
}).toArray();
|
|
5262
|
+
frontier = [];
|
|
5263
|
+
for (const row of rows) {
|
|
5264
|
+
const id = String(row.id);
|
|
5265
|
+
if (find !== void 0 && id === find) return {
|
|
5266
|
+
ids,
|
|
5267
|
+
live: liveCount,
|
|
5268
|
+
maxDepth,
|
|
5269
|
+
found: true,
|
|
5270
|
+
truncated: false
|
|
5271
|
+
};
|
|
5272
|
+
ids.push(id);
|
|
5273
|
+
frontier.push(id);
|
|
5274
|
+
if (row.deletedAt == null) {
|
|
5275
|
+
liveCount++;
|
|
5276
|
+
maxDepth = Math.max(maxDepth, Number(row.depth));
|
|
5277
|
+
}
|
|
5278
|
+
}
|
|
5279
|
+
if (liveCount > core.MAX_KNOWLEDGE_MOVE_PAGES) return {
|
|
5280
|
+
ids,
|
|
5281
|
+
live: liveCount,
|
|
5282
|
+
maxDepth,
|
|
5283
|
+
found: false,
|
|
5284
|
+
truncated: true
|
|
5285
|
+
};
|
|
4942
5286
|
}
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
5287
|
+
return {
|
|
5288
|
+
ids,
|
|
5289
|
+
live: liveCount,
|
|
5290
|
+
maxDepth,
|
|
5291
|
+
found: false,
|
|
5292
|
+
truncated: false
|
|
5293
|
+
};
|
|
5294
|
+
}
|
|
5295
|
+
/** Refuses before any write when a live arriving page's key is already live under `parentId`. */
|
|
5296
|
+
async #assertNoKeyConflicts(keys, parentId, session, exceptId) {
|
|
5297
|
+
const core = await loadPagesCore();
|
|
5298
|
+
if (!keys.length) return;
|
|
5299
|
+
const clash = await (await this.#c("pages")).findOne({
|
|
5300
|
+
...live,
|
|
5301
|
+
parentId,
|
|
5302
|
+
key: { $in: keys },
|
|
5303
|
+
...exceptId ? { id: { $ne: exceptId } } : {}
|
|
5304
|
+
}, {
|
|
5305
|
+
projection: {
|
|
5306
|
+
_id: 0,
|
|
5307
|
+
key: 1
|
|
5308
|
+
},
|
|
5309
|
+
session
|
|
5310
|
+
});
|
|
5311
|
+
if (clash) throw new core.KnowledgePageKeyConflictError(String(clash.key), parentId);
|
|
5312
|
+
}
|
|
5313
|
+
/** Recomputes the denormalized scope of every page a note mentions, for notes touching `pageIds`. */
|
|
5314
|
+
async #refreshMentionScopes(pageIds, session) {
|
|
5315
|
+
if (!pageIds.length) return;
|
|
5316
|
+
const notes = await this.#c("notes");
|
|
5317
|
+
const pages = await this.#c("pages");
|
|
5318
|
+
const affected = await notes.find({ mentions: { $in: pageIds } }, {
|
|
5319
|
+
projection: {
|
|
5320
|
+
_id: 0,
|
|
5321
|
+
id: 1,
|
|
5322
|
+
mentions: 1
|
|
5323
|
+
},
|
|
5324
|
+
session
|
|
5325
|
+
}).toArray();
|
|
5326
|
+
for (const note of affected) {
|
|
5327
|
+
const mentioned = await pages.find({ id: { $in: note.mentions } }, {
|
|
5328
|
+
projection: {
|
|
5329
|
+
_id: 0,
|
|
5330
|
+
scopeId: 1
|
|
5331
|
+
},
|
|
5332
|
+
session
|
|
5333
|
+
}).toArray();
|
|
5334
|
+
await notes.updateOne({ id: note.id }, { $set: { mentionScopeIds: [...new Set(mentioned.map((page) => String(page.scopeId)))] } }, { session });
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
async materializeScope(input) {
|
|
5338
|
+
for (let attempt = 0;; attempt++) try {
|
|
5339
|
+
return await this.#materializeScopeOnce(input);
|
|
5340
|
+
} catch (error) {
|
|
5341
|
+
if (!(error instanceof (await loadPagesCore()).KnowledgePageKeyConflictError || error instanceof _mastra_core_storage.KnowledgeConflictError || isDuplicateKey(error) || error?.hasErrorLabel?.("TransientTransactionError")) || attempt >= 4) throw error;
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
async #materializeScopeOnce(input) {
|
|
5345
|
+
const bounds = await this.#bounds();
|
|
5346
|
+
bounds.assertKnowledgePageDescriptionWithinBound(input.description);
|
|
5347
|
+
for (const child of input.children ?? []) bounds.assertKnowledgePageDescriptionWithinBound(child.description);
|
|
5348
|
+
const checkTemplates = (templates) => {
|
|
5349
|
+
for (const template of templates) {
|
|
5350
|
+
bounds.assertKnowledgePageDescriptionWithinBound(template.description);
|
|
5351
|
+
checkTemplates(template.pages ?? []);
|
|
4950
5352
|
}
|
|
5353
|
+
};
|
|
5354
|
+
checkTemplates(input.pages ?? []);
|
|
5355
|
+
return this.#tx(async (session) => {
|
|
4951
5356
|
const now = /* @__PURE__ */ new Date();
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
5357
|
+
let scope = await this.#scopeByAddress(input.address, session);
|
|
5358
|
+
let created = false;
|
|
5359
|
+
let parentConflict;
|
|
5360
|
+
if (!scope) {
|
|
5361
|
+
if (input.parentId) await this.#requireLivePage(input.parentId, session);
|
|
5362
|
+
scope = await this.#insertScope(session, {
|
|
5363
|
+
address: input.address,
|
|
5364
|
+
declaration: input.declaration,
|
|
5365
|
+
name: input.name,
|
|
5366
|
+
description: input.description,
|
|
5367
|
+
parentId: input.parentId ?? null,
|
|
5368
|
+
provenance: input.provenance,
|
|
5369
|
+
at: now
|
|
5370
|
+
});
|
|
5371
|
+
created = true;
|
|
5372
|
+
} else if (input.parentId && scope.parentId === null) {
|
|
5373
|
+
await this.#requireLivePage(input.parentId, session);
|
|
5374
|
+
const linked = await (await this.#c("pages")).findOneAndUpdate({
|
|
5375
|
+
id: scope.id,
|
|
5376
|
+
parentId: null
|
|
5377
|
+
}, { $set: { parentId: input.parentId } }, {
|
|
5378
|
+
returnDocument: "after",
|
|
5379
|
+
projection: { _id: 0 },
|
|
5380
|
+
session
|
|
5381
|
+
});
|
|
5382
|
+
scope = toPage(linked ?? await this.#scopeByAddress(input.address, session));
|
|
5383
|
+
if (!linked && input.parentId !== scope.parentId) parentConflict = {
|
|
5384
|
+
existingParentId: scope.parentId,
|
|
5385
|
+
requestedParentId: input.parentId
|
|
5386
|
+
};
|
|
5387
|
+
} else if (input.parentId !== void 0 && (input.parentId ?? null) !== scope.parentId) parentConflict = {
|
|
5388
|
+
existingParentId: scope.parentId,
|
|
5389
|
+
requestedParentId: input.parentId ?? null
|
|
5390
|
+
};
|
|
5391
|
+
for (const child of input.children ?? []) {
|
|
5392
|
+
if (await this.#scopeByAddress(child.address, session)) continue;
|
|
5393
|
+
await this.#insertScope(session, {
|
|
5394
|
+
address: child.address,
|
|
5395
|
+
declaration: child.declaration,
|
|
5396
|
+
name: child.name,
|
|
5397
|
+
description: child.description,
|
|
5398
|
+
parentId: scope.id,
|
|
5399
|
+
provenance: input.provenance,
|
|
5400
|
+
at: now
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
await this.#materializeTemplates(session, scope, input.pages ?? [], input.provenance, now);
|
|
5404
|
+
return {
|
|
4959
5405
|
scope,
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
updatedAt: now
|
|
5406
|
+
created,
|
|
5407
|
+
...parentConflict ? { parentConflict } : {}
|
|
4963
5408
|
};
|
|
4964
|
-
try {
|
|
4965
|
-
await (await this.#nodes()).insertOne({
|
|
4966
|
-
...node,
|
|
4967
|
-
canonicalName: canonicalName(node.name),
|
|
4968
|
-
scopeKey: (0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
4969
|
-
mergedInto: null
|
|
4970
|
-
}, sessionOptions(session));
|
|
4971
|
-
} catch (error) {
|
|
4972
|
-
if (error.code !== 11e3) throw error;
|
|
4973
|
-
const concurrent = await this.#getNodeByName(input.name, scope, session);
|
|
4974
|
-
if (!concurrent) throw error;
|
|
4975
|
-
return await this.#resolveTerminalNode(concurrent.id, session);
|
|
4976
|
-
}
|
|
4977
|
-
await this.#replaceMentions("node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope, session);
|
|
4978
|
-
await this.#activity("node-created", "node", node.id, scope, void 0, session);
|
|
4979
|
-
await this.#outbox("node", node.id, "upsert", 1, scope, session);
|
|
4980
|
-
return node;
|
|
4981
5409
|
});
|
|
4982
5410
|
}
|
|
4983
|
-
async
|
|
4984
|
-
|
|
5411
|
+
async #materializeTemplates(session, parent, templates, provenance, at) {
|
|
5412
|
+
const pages = await this.#c("pages");
|
|
5413
|
+
for (const template of templates) {
|
|
5414
|
+
const problem = knowledgePageKeyProblem(template.key);
|
|
5415
|
+
if (problem) throw new Error(problem);
|
|
5416
|
+
const existing = await pages.findOne({
|
|
5417
|
+
parentId: parent.id,
|
|
5418
|
+
key: template.key,
|
|
5419
|
+
...live
|
|
5420
|
+
}, {
|
|
5421
|
+
...noProjection,
|
|
5422
|
+
session
|
|
5423
|
+
});
|
|
5424
|
+
const page = existing ? toPage(existing) : await this.#insertPage(session, {
|
|
5425
|
+
parent,
|
|
5426
|
+
key: template.key,
|
|
5427
|
+
name: template.name,
|
|
5428
|
+
description: template.description,
|
|
5429
|
+
provenance,
|
|
5430
|
+
at
|
|
5431
|
+
});
|
|
5432
|
+
if (template.pages?.length) await this.#materializeTemplates(session, page, template.pages, provenance, at);
|
|
5433
|
+
}
|
|
5434
|
+
}
|
|
5435
|
+
async #scopeByAddress(address, session) {
|
|
5436
|
+
const row = await (await this.#c("pages")).findOne({
|
|
5437
|
+
isScope: true,
|
|
5438
|
+
address
|
|
5439
|
+
}, {
|
|
5440
|
+
...noProjection,
|
|
5441
|
+
session
|
|
5442
|
+
});
|
|
5443
|
+
return row ? toPage(row) : null;
|
|
5444
|
+
}
|
|
5445
|
+
async #insertScope(session, input) {
|
|
5446
|
+
const core = await loadPagesCore();
|
|
5447
|
+
const id = (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
5448
|
+
const doc = {
|
|
5449
|
+
id,
|
|
5450
|
+
scopeId: id,
|
|
5451
|
+
parentId: input.parentId,
|
|
5452
|
+
isScope: true,
|
|
5453
|
+
address: input.address,
|
|
5454
|
+
declaration: input.declaration,
|
|
5455
|
+
position: core.knowledgePositionBetween(await this.#lastPosition(input.parentId, session)),
|
|
5456
|
+
name: input.name,
|
|
5457
|
+
canonicalName: core.canonicalKnowledgeName(input.name),
|
|
5458
|
+
...input.description === void 0 ? {} : { description: input.description },
|
|
5459
|
+
depth: 0,
|
|
5460
|
+
version: 1,
|
|
5461
|
+
createdBy: input.provenance,
|
|
5462
|
+
updatedBy: input.provenance,
|
|
5463
|
+
createdAt: input.at,
|
|
5464
|
+
updatedAt: input.at
|
|
5465
|
+
};
|
|
5466
|
+
try {
|
|
5467
|
+
await (await this.#c("pages")).insertOne({ ...doc }, { session });
|
|
5468
|
+
} catch (error) {
|
|
5469
|
+
if (isDuplicateKey(error)) throw new _mastra_core_storage.KnowledgeConflictError(input.address);
|
|
5470
|
+
throw error;
|
|
5471
|
+
}
|
|
5472
|
+
await this.#log(session, {
|
|
5473
|
+
action: "scope-created",
|
|
5474
|
+
scopeId: id,
|
|
5475
|
+
pageId: id,
|
|
5476
|
+
provenance: input.provenance,
|
|
5477
|
+
at: input.at
|
|
5478
|
+
});
|
|
5479
|
+
return toPage(doc);
|
|
5480
|
+
}
|
|
5481
|
+
async getScopesByAddress(addresses) {
|
|
5482
|
+
if (addresses.length === 0) return [];
|
|
5483
|
+
const rows = await (await this.#c("pages")).find({
|
|
5484
|
+
isScope: true,
|
|
5485
|
+
address: { $in: addresses }
|
|
5486
|
+
}, noProjection).toArray();
|
|
5487
|
+
const byAddress = new Map(rows.map((row) => [String(row.address), toPage(row)]));
|
|
5488
|
+
return [...new Set(addresses)].flatMap((address) => byAddress.get(address) ?? []);
|
|
5489
|
+
}
|
|
5490
|
+
async listScopeDeclarations() {
|
|
5491
|
+
return (await (await this.#c("pages")).aggregate([{ $match: {
|
|
5492
|
+
isScope: true,
|
|
5493
|
+
declaration: { $type: "string" }
|
|
5494
|
+
} }, { $group: {
|
|
5495
|
+
_id: "$declaration",
|
|
5496
|
+
scopes: { $sum: 1 }
|
|
5497
|
+
} }]).toArray()).map((row) => ({
|
|
5498
|
+
declaration: String(row._id),
|
|
5499
|
+
scopes: Number(row.scopes)
|
|
5500
|
+
})).sort((a, b) => a.declaration < b.declaration ? -1 : a.declaration > b.declaration ? 1 : 0);
|
|
5501
|
+
}
|
|
5502
|
+
async renameScopeDeclaration(input) {
|
|
5503
|
+
return this.#tx(async (session) => {
|
|
5504
|
+
const pages = await this.#c("pages");
|
|
5505
|
+
const rows = await pages.find({
|
|
5506
|
+
isScope: true,
|
|
5507
|
+
$or: [{ declaration: input.from }, { declaration: { $regex: `^${escapeRegex(`${input.from}/`)}` } }]
|
|
5508
|
+
}, {
|
|
5509
|
+
projection: {
|
|
5510
|
+
_id: 0,
|
|
5511
|
+
id: 1,
|
|
5512
|
+
address: 1,
|
|
5513
|
+
declaration: 1
|
|
5514
|
+
},
|
|
5515
|
+
session
|
|
5516
|
+
}).toArray();
|
|
5517
|
+
if (rows.length === 0) return 0;
|
|
5518
|
+
const renames = rows.map((row) => ({
|
|
5519
|
+
address: String(row.address),
|
|
5520
|
+
from: String(row.declaration),
|
|
5521
|
+
to: input.to + String(row.declaration).slice(input.from.length)
|
|
5522
|
+
}));
|
|
5523
|
+
input.check(renames);
|
|
5524
|
+
if ((await pages.bulkWrite(rows.map((row, index) => ({ updateOne: {
|
|
5525
|
+
filter: {
|
|
5526
|
+
id: row.id,
|
|
5527
|
+
isScope: true
|
|
5528
|
+
},
|
|
5529
|
+
update: { $set: { declaration: renames[index].to } }
|
|
5530
|
+
} })), { session })).matchedCount < rows.length) throw new _mastra_core_storage.KnowledgeConflictError("Knowledge scope declarations changed while renaming; retry the rename");
|
|
5531
|
+
return rows.length;
|
|
5532
|
+
});
|
|
5533
|
+
}
|
|
5534
|
+
async listChildScopes(input) {
|
|
5535
|
+
const core = await loadPagesCore();
|
|
5536
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5537
|
+
const filter = {
|
|
5538
|
+
isScope: true,
|
|
5539
|
+
parentId: input.parentId,
|
|
5540
|
+
...live
|
|
5541
|
+
};
|
|
5542
|
+
if (input.cursor) {
|
|
5543
|
+
const [position, id] = decodeCursor(input.cursor);
|
|
5544
|
+
filter.$or = [{ position: { $gt: position } }, {
|
|
5545
|
+
position,
|
|
5546
|
+
id: { $gt: id }
|
|
5547
|
+
}];
|
|
5548
|
+
}
|
|
5549
|
+
const rows = await (await this.#c("pages")).find(filter, noProjection).sort({
|
|
5550
|
+
position: 1,
|
|
5551
|
+
id: 1
|
|
5552
|
+
}).limit(limit + 1).toArray();
|
|
5553
|
+
return this.#pageOutput(rows, limit, (page) => [page.position, page.id]);
|
|
5554
|
+
}
|
|
5555
|
+
#pageOutput(rows, limit, cursorOf) {
|
|
5556
|
+
const pages = rows.slice(0, limit).map(toPage);
|
|
5557
|
+
const last = pages[pages.length - 1];
|
|
5558
|
+
return {
|
|
5559
|
+
pages,
|
|
5560
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor(cursorOf(last)) } : {}
|
|
5561
|
+
};
|
|
5562
|
+
}
|
|
5563
|
+
async getPage(input) {
|
|
5564
|
+
const row = await (await this.#c("pages")).findOne({ id: input.id }, noProjection);
|
|
5565
|
+
return row && await this.#scopeReadable(input.read, String(row.scopeId)) ? toPage(row) : null;
|
|
5566
|
+
}
|
|
5567
|
+
async getPageByPath(input) {
|
|
5568
|
+
if (!input.path.length) return null;
|
|
5569
|
+
if (!await this.#scopeReadable(input.read, input.scopeId)) return null;
|
|
5570
|
+
const pages = await this.#c("pages");
|
|
5571
|
+
let page;
|
|
5572
|
+
let parentId = input.scopeId;
|
|
5573
|
+
for (const key of input.path) {
|
|
5574
|
+
const row = await pages.findOne({
|
|
5575
|
+
parentId,
|
|
5576
|
+
key,
|
|
5577
|
+
scopeId: input.scopeId,
|
|
5578
|
+
...live,
|
|
5579
|
+
...notScope
|
|
5580
|
+
}, noProjection);
|
|
5581
|
+
if (!row) return null;
|
|
5582
|
+
page = toPage(row);
|
|
5583
|
+
parentId = page.id;
|
|
5584
|
+
}
|
|
5585
|
+
return page ?? null;
|
|
5586
|
+
}
|
|
5587
|
+
async findPagesByName(input) {
|
|
5588
|
+
const core = await loadPagesCore();
|
|
5589
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5590
|
+
const filter = {
|
|
5591
|
+
canonicalName: core.canonicalKnowledgeName(input.name),
|
|
5592
|
+
...live
|
|
5593
|
+
};
|
|
5594
|
+
if (!await this.#scopeFilter(filter, input.read, input.scopeId)) return [];
|
|
5595
|
+
return (await (await this.#c("pages")).find(filter, noProjection).sort({
|
|
5596
|
+
createdAt: -1,
|
|
5597
|
+
id: -1
|
|
5598
|
+
}).limit(limit).toArray()).map(toPage);
|
|
4985
5599
|
}
|
|
4986
|
-
|
|
4987
|
-
|
|
5600
|
+
/**
|
|
5601
|
+
* Adds the home-scope constraint: the explicit scope when given (one indexed readability check),
|
|
5602
|
+
* otherwise the readable frontier. Returns false when nothing can match.
|
|
5603
|
+
*/
|
|
5604
|
+
async #scopeFilter(filter, read, scopeId) {
|
|
5605
|
+
if (scopeId !== void 0) {
|
|
5606
|
+
if (!await this.#scopeReadable(read, scopeId)) return false;
|
|
5607
|
+
filter.scopeId = scopeId;
|
|
5608
|
+
return true;
|
|
5609
|
+
}
|
|
5610
|
+
const ids = await this.#readable(read);
|
|
5611
|
+
if (ids !== null) filter.scopeId = { $in: ids };
|
|
5612
|
+
return true;
|
|
4988
5613
|
}
|
|
4989
|
-
async
|
|
4990
|
-
|
|
5614
|
+
async listChildPages(input) {
|
|
5615
|
+
const core = await loadPagesCore();
|
|
5616
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
5617
|
+
const ids = await this.#readable(input.read);
|
|
5618
|
+
const filter = {
|
|
5619
|
+
parentId: input.parentId,
|
|
5620
|
+
...notScope,
|
|
5621
|
+
...live,
|
|
5622
|
+
...this.#inScopes("scopeId", ids)
|
|
5623
|
+
};
|
|
5624
|
+
if (input.cursor) {
|
|
5625
|
+
const [position, id] = decodeCursor(input.cursor);
|
|
5626
|
+
filter.$or = [{ position: { $gt: position } }, {
|
|
5627
|
+
position,
|
|
5628
|
+
id: { $gt: id }
|
|
5629
|
+
}];
|
|
5630
|
+
}
|
|
5631
|
+
const rows = await (await this.#c("pages")).find(filter, noProjection).sort({
|
|
5632
|
+
position: 1,
|
|
5633
|
+
id: 1
|
|
5634
|
+
}).limit(limit + 1).toArray();
|
|
5635
|
+
return this.#pageOutput(rows, limit, (page) => [page.position, page.id]);
|
|
4991
5636
|
}
|
|
4992
|
-
async
|
|
5637
|
+
async listPages(input) {
|
|
5638
|
+
const core = await loadPagesCore();
|
|
5639
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
4993
5640
|
const filter = {
|
|
4994
|
-
|
|
4995
|
-
|
|
4996
|
-
scopeKey: { $in: visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope)) },
|
|
4997
|
-
...input.namePrefix ? { canonicalName: { $regex: `^${this.#escapeRegex(canonicalName(input.namePrefix))}` } } : {},
|
|
4998
|
-
...input.kind ? { kind: input.kind } : {},
|
|
4999
|
-
...input.hasContent === void 0 ? {} : input.hasContent ? { content: {
|
|
5000
|
-
$exists: true,
|
|
5001
|
-
$nin: [null, ""]
|
|
5002
|
-
} } : { $or: [
|
|
5003
|
-
{ content: { $exists: false } },
|
|
5004
|
-
{ content: null },
|
|
5005
|
-
{ content: "" }
|
|
5006
|
-
] },
|
|
5007
|
-
...input.cursor ? recordCursorFilter(input.cursor, {
|
|
5008
|
-
namePrefix: input.namePrefix,
|
|
5009
|
-
kind: input.kind,
|
|
5010
|
-
hasContent: input.hasContent
|
|
5011
|
-
}) : {}
|
|
5641
|
+
...notScope,
|
|
5642
|
+
...live
|
|
5012
5643
|
};
|
|
5013
|
-
|
|
5014
|
-
|
|
5644
|
+
if (!await this.#scopeFilter(filter, input.read, input.scopeId)) return { pages: [] };
|
|
5645
|
+
if (input.namePrefix) filter.canonicalName = { $regex: `^${escapeRegex(core.canonicalKnowledgeName(input.namePrefix))}` };
|
|
5646
|
+
if (input.cursor) {
|
|
5647
|
+
const [name, id] = decodeCursor(input.cursor);
|
|
5648
|
+
filter.$or = [{ name: { $gt: name } }, {
|
|
5649
|
+
name,
|
|
5650
|
+
id: { $gt: id }
|
|
5651
|
+
}];
|
|
5652
|
+
}
|
|
5653
|
+
const rows = await (await this.#c("pages")).find(filter, noProjection).sort({
|
|
5015
5654
|
name: 1,
|
|
5016
5655
|
id: 1
|
|
5017
|
-
}).limit(
|
|
5656
|
+
}).limit(limit + 1).toArray();
|
|
5657
|
+
return this.#pageOutput(rows, limit, (page) => [page.name, page.id]);
|
|
5658
|
+
}
|
|
5659
|
+
async createPage(input) {
|
|
5660
|
+
(await this.#bounds()).assertKnowledgePageDescriptionWithinBound(input.description);
|
|
5661
|
+
if (input.key !== void 0) {
|
|
5662
|
+
const problem = knowledgePageKeyProblem(input.key);
|
|
5663
|
+
if (problem) throw new Error(problem);
|
|
5664
|
+
}
|
|
5665
|
+
return this.#tx(async (session) => {
|
|
5666
|
+
const parent = await this.#requireLivePage(input.parentId, session);
|
|
5667
|
+
return this.#insertPage(session, {
|
|
5668
|
+
id: input.id,
|
|
5669
|
+
parent,
|
|
5670
|
+
key: input.key,
|
|
5671
|
+
name: input.name,
|
|
5672
|
+
description: input.description,
|
|
5673
|
+
body: input.body,
|
|
5674
|
+
position: input.position,
|
|
5675
|
+
provenance: input.provenance,
|
|
5676
|
+
at: /* @__PURE__ */ new Date()
|
|
5677
|
+
});
|
|
5678
|
+
});
|
|
5018
5679
|
}
|
|
5019
|
-
async
|
|
5020
|
-
await
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5680
|
+
async #insertPage(session, input) {
|
|
5681
|
+
const core = await loadPagesCore();
|
|
5682
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
5683
|
+
const scopeId = input.parent.isScope ? input.parent.id : input.parent.scopeId;
|
|
5684
|
+
const depth = input.parent.isScope ? 1 : await this.#depthOf(input.parent.id, session) + 1;
|
|
5685
|
+
if (depth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(id);
|
|
5686
|
+
const doc = {
|
|
5687
|
+
id,
|
|
5688
|
+
scopeId,
|
|
5689
|
+
parentId: input.parent.id,
|
|
5690
|
+
isScope: false,
|
|
5691
|
+
...input.key === void 0 ? {} : {
|
|
5692
|
+
key: input.key,
|
|
5693
|
+
activeKey: activeKeyOf(input.parent.id, input.key)
|
|
5694
|
+
},
|
|
5695
|
+
position: input.position ?? core.knowledgePositionBetween(await this.#lastPosition(input.parent.id, session)),
|
|
5696
|
+
name: input.name,
|
|
5697
|
+
canonicalName: core.canonicalKnowledgeName(input.name),
|
|
5698
|
+
...input.description === void 0 ? {} : { description: input.description },
|
|
5699
|
+
...input.body === void 0 ? {} : { body: input.body },
|
|
5700
|
+
depth,
|
|
5701
|
+
version: 1,
|
|
5702
|
+
createdBy: input.provenance,
|
|
5703
|
+
updatedBy: input.provenance,
|
|
5704
|
+
createdAt: input.at,
|
|
5705
|
+
updatedAt: input.at
|
|
5706
|
+
};
|
|
5707
|
+
try {
|
|
5708
|
+
await (await this.#c("pages")).insertOne({ ...doc }, { session });
|
|
5709
|
+
} catch (error) {
|
|
5710
|
+
if (isDuplicateKey(error)) {
|
|
5711
|
+
if (input.key) throw new core.KnowledgePageKeyConflictError(input.key, input.parent.id);
|
|
5712
|
+
throw new _mastra_core_storage.KnowledgeConflictError(id);
|
|
5713
|
+
}
|
|
5714
|
+
throw error;
|
|
5715
|
+
}
|
|
5716
|
+
await this.#log(session, {
|
|
5717
|
+
action: "page-created",
|
|
5718
|
+
scopeId,
|
|
5719
|
+
pageId: id,
|
|
5720
|
+
provenance: input.provenance,
|
|
5721
|
+
at: input.at
|
|
5722
|
+
});
|
|
5723
|
+
return toPage(doc);
|
|
5724
|
+
}
|
|
5725
|
+
async #depthOf(id, session) {
|
|
5726
|
+
const row = await (await this.#c("pages")).findOne({ id }, {
|
|
5727
|
+
projection: {
|
|
5728
|
+
_id: 0,
|
|
5729
|
+
depth: 1
|
|
5730
|
+
},
|
|
5731
|
+
session
|
|
5732
|
+
});
|
|
5733
|
+
return Number(row?.depth ?? 0);
|
|
5734
|
+
}
|
|
5735
|
+
async updatePage(input) {
|
|
5736
|
+
const core = await loadPagesCore();
|
|
5737
|
+
(await this.#bounds()).assertKnowledgePageDescriptionWithinBound(input.description);
|
|
5738
|
+
return this.#tx(async (session) => {
|
|
5739
|
+
const page = await this.#requireLivePage(input.id, session);
|
|
5740
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5029
5741
|
const now = /* @__PURE__ */ new Date();
|
|
5030
|
-
const
|
|
5742
|
+
const set = {
|
|
5743
|
+
updatedAt: now,
|
|
5744
|
+
updatedBy: input.provenance
|
|
5745
|
+
};
|
|
5746
|
+
const unset = {};
|
|
5747
|
+
if (input.name !== void 0) {
|
|
5748
|
+
set.name = input.name;
|
|
5749
|
+
set.canonicalName = core.canonicalKnowledgeName(input.name);
|
|
5750
|
+
}
|
|
5751
|
+
if (input.description !== void 0) {
|
|
5752
|
+
if (input.description === "") unset.description = "";
|
|
5753
|
+
else set.description = input.description;
|
|
5754
|
+
}
|
|
5755
|
+
if (input.body !== void 0) set.body = input.body;
|
|
5756
|
+
const updated = await (await this.#c("pages")).findOneAndUpdate({
|
|
5031
5757
|
id: input.id,
|
|
5032
|
-
|
|
5033
|
-
|
|
5758
|
+
version: input.expectedVersion,
|
|
5759
|
+
...live
|
|
5034
5760
|
}, {
|
|
5035
|
-
$set:
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
kind: input.kind ?? existing.kind,
|
|
5039
|
-
content: content ?? null,
|
|
5040
|
-
description: description ?? null,
|
|
5041
|
-
scope,
|
|
5042
|
-
scopeKey: (0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
5043
|
-
updatedAt: now
|
|
5044
|
-
},
|
|
5045
|
-
$inc: { version: 1 }
|
|
5761
|
+
$set: set,
|
|
5762
|
+
$inc: { version: 1 },
|
|
5763
|
+
...Object.keys(unset).length ? { $unset: unset } : {}
|
|
5046
5764
|
}, {
|
|
5047
|
-
|
|
5048
|
-
|
|
5765
|
+
returnDocument: "after",
|
|
5766
|
+
projection: { _id: 0 },
|
|
5767
|
+
session
|
|
5049
5768
|
});
|
|
5050
|
-
if (!
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5769
|
+
if (!updated) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5770
|
+
await this.#log(session, {
|
|
5771
|
+
action: "page-updated",
|
|
5772
|
+
scopeId: page.scopeId,
|
|
5773
|
+
pageId: page.id,
|
|
5774
|
+
provenance: input.provenance,
|
|
5775
|
+
at: now
|
|
5776
|
+
});
|
|
5777
|
+
return toPage(updated);
|
|
5778
|
+
});
|
|
5779
|
+
}
|
|
5780
|
+
/** Rewrites everything that follows a page's home scope to `scopeId`, for the pages in `moved`. */
|
|
5781
|
+
async #rehome(moved, scopeId, depthDelta, session) {
|
|
5782
|
+
const pages = await this.#c("pages");
|
|
5783
|
+
const descendants = moved.slice(1);
|
|
5784
|
+
if (descendants.length) await pages.updateMany({ id: { $in: descendants } }, [{ $set: {
|
|
5785
|
+
scopeId,
|
|
5786
|
+
depth: { $add: ["$depth", depthDelta] }
|
|
5787
|
+
} }], { session });
|
|
5788
|
+
await (await this.#c("notes")).updateMany({ pageId: { $in: moved } }, { $set: { pageScopeId: scopeId } }, { session });
|
|
5789
|
+
await (await this.#c("activity")).updateMany({ pageId: { $in: moved } }, { $set: { pageScopeId: scopeId } }, { session });
|
|
5790
|
+
}
|
|
5791
|
+
async movePage(input) {
|
|
5792
|
+
const core = await loadPagesCore();
|
|
5793
|
+
return this.#tx(async (session) => {
|
|
5794
|
+
const page = await this.#requireLivePage(input.id, session);
|
|
5795
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5796
|
+
if (page.isScope) throw new Error(`Knowledge scope page ${input.id} can't be moved`);
|
|
5797
|
+
if (input.parentId === input.id) throw new Error(`Knowledge page ${input.id} can't move under itself or its own subtree`);
|
|
5798
|
+
const tree = await this.#subtree(input.id, session, input.parentId);
|
|
5799
|
+
if (tree.found) throw new Error(`Knowledge page ${input.id} can't move under itself or its own subtree`);
|
|
5800
|
+
const parent = await this.#requireLivePage(input.parentId, session);
|
|
5801
|
+
if (tree.live + 1 > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(input.id, tree.live + 1);
|
|
5802
|
+
const newScopeId = parent.isScope ? parent.id : parent.scopeId;
|
|
5803
|
+
const newDepth = parent.isScope ? 1 : await this.#depthOf(parent.id, session) + 1;
|
|
5804
|
+
const delta = newDepth - await this.#depthOf(page.id, session);
|
|
5805
|
+
if (page.key) await this.#assertNoKeyConflicts([page.key], parent.id, session, page.id);
|
|
5806
|
+
if (tree.maxDepth + delta > core.MAX_KNOWLEDGE_PAGE_DEPTH || newDepth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(input.id);
|
|
5807
|
+
const position = input.position ?? core.knowledgePositionBetween(await this.#lastPosition(parent.id, session));
|
|
5808
|
+
const now = /* @__PURE__ */ new Date();
|
|
5809
|
+
let updated;
|
|
5810
|
+
try {
|
|
5811
|
+
await this.#rehome([page.id, ...tree.ids], newScopeId, delta, session);
|
|
5812
|
+
updated = await (await this.#c("pages")).findOneAndUpdate({
|
|
5813
|
+
id: input.id,
|
|
5814
|
+
version: input.expectedVersion
|
|
5815
|
+
}, {
|
|
5816
|
+
$set: {
|
|
5817
|
+
parentId: parent.id,
|
|
5818
|
+
scopeId: newScopeId,
|
|
5819
|
+
...page.key ? { activeKey: activeKeyOf(parent.id, page.key) } : {},
|
|
5820
|
+
depth: newDepth,
|
|
5821
|
+
position,
|
|
5822
|
+
updatedAt: now,
|
|
5823
|
+
updatedBy: input.provenance
|
|
5824
|
+
},
|
|
5825
|
+
$inc: { version: 1 }
|
|
5826
|
+
}, {
|
|
5827
|
+
returnDocument: "after",
|
|
5828
|
+
projection: { _id: 0 },
|
|
5829
|
+
session
|
|
5830
|
+
});
|
|
5831
|
+
} catch (error) {
|
|
5832
|
+
if (isDuplicateKey(error)) throw new _mastra_core_storage.KnowledgeConflictError(page.key ? `${parent.id}#${page.key}` : input.id);
|
|
5833
|
+
throw error;
|
|
5059
5834
|
}
|
|
5060
|
-
|
|
5061
|
-
await this.#
|
|
5062
|
-
|
|
5835
|
+
if (!updated) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5836
|
+
await this.#refreshMentionScopes([page.id, ...tree.ids], session);
|
|
5837
|
+
await this.#log(session, {
|
|
5838
|
+
action: "page-moved",
|
|
5839
|
+
scopeId: newScopeId,
|
|
5840
|
+
pageId: input.id,
|
|
5841
|
+
provenance: input.provenance,
|
|
5842
|
+
at: now
|
|
5843
|
+
});
|
|
5844
|
+
return toPage(updated);
|
|
5063
5845
|
});
|
|
5064
5846
|
}
|
|
5065
|
-
async
|
|
5066
|
-
|
|
5067
|
-
return this.#
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
const target = await this.#
|
|
5071
|
-
if (
|
|
5072
|
-
if (
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
const
|
|
5077
|
-
|
|
5847
|
+
async mergePages(input) {
|
|
5848
|
+
const core = await loadPagesCore();
|
|
5849
|
+
return this.#tx(async (session) => {
|
|
5850
|
+
if (input.sourceId === input.targetId) throw new Error(`Knowledge page ${input.sourceId} can't merge into itself or its own subtree`);
|
|
5851
|
+
const source = await this.#requireLivePage(input.sourceId, session);
|
|
5852
|
+
const target = await this.#requireLivePage(input.targetId, session);
|
|
5853
|
+
if (source.version !== input.sourceVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.sourceId);
|
|
5854
|
+
if (source.isScope || target.isScope) throw new Error("Knowledge scope pages can't be merged");
|
|
5855
|
+
const tree = await this.#subtree(source.id, session, target.id);
|
|
5856
|
+
if (tree.found) throw new Error(`Knowledge page ${source.id} can't merge into itself or its own subtree`);
|
|
5857
|
+
if (tree.live > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(source.id, tree.live);
|
|
5858
|
+
const delta = await this.#depthOf(target.id, session) - await this.#depthOf(source.id, session);
|
|
5859
|
+
const arriving = await (await this.#c("pages")).find({
|
|
5860
|
+
parentId: source.id,
|
|
5861
|
+
...live,
|
|
5862
|
+
...notScope,
|
|
5863
|
+
key: { $type: "string" }
|
|
5864
|
+
}, {
|
|
5865
|
+
projection: {
|
|
5866
|
+
_id: 0,
|
|
5867
|
+
key: 1
|
|
5868
|
+
},
|
|
5869
|
+
session
|
|
5870
|
+
}).toArray();
|
|
5871
|
+
await this.#assertNoKeyConflicts(arriving.map((row) => String(row.key)), target.id, session, source.id);
|
|
5872
|
+
if (tree.maxDepth > 0 && tree.maxDepth + delta > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(source.id);
|
|
5873
|
+
const now = /* @__PURE__ */ new Date();
|
|
5874
|
+
const pages = await this.#c("pages");
|
|
5875
|
+
const notes = await this.#c("notes");
|
|
5876
|
+
if ((await pages.updateOne({
|
|
5078
5877
|
id: source.id,
|
|
5079
|
-
|
|
5080
|
-
version: input.sourceVersion,
|
|
5081
|
-
mergedInto: null
|
|
5878
|
+
version: input.sourceVersion
|
|
5082
5879
|
}, {
|
|
5083
5880
|
$set: {
|
|
5881
|
+
deletedAt: now,
|
|
5882
|
+
deletedBy: input.provenance.author,
|
|
5084
5883
|
mergedInto: target.id,
|
|
5085
|
-
|
|
5884
|
+
scopeId: target.scopeId,
|
|
5885
|
+
updatedAt: now,
|
|
5886
|
+
updatedBy: input.provenance
|
|
5086
5887
|
},
|
|
5888
|
+
$unset: { activeKey: "" },
|
|
5087
5889
|
$inc: { version: 1 }
|
|
5088
|
-
},
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
},
|
|
5102
|
-
|
|
5890
|
+
}, { session })).matchedCount === 0) throw new _mastra_core_storage.KnowledgeConflictError(input.sourceId);
|
|
5891
|
+
try {
|
|
5892
|
+
if (tree.ids.length) {
|
|
5893
|
+
await pages.updateMany({ id: { $in: tree.ids } }, [{ $set: {
|
|
5894
|
+
scopeId: target.scopeId,
|
|
5895
|
+
depth: { $add: ["$depth", delta] }
|
|
5896
|
+
} }], { session });
|
|
5897
|
+
await notes.updateMany({ pageId: { $in: tree.ids } }, { $set: { pageScopeId: target.scopeId } }, { session });
|
|
5898
|
+
await (await this.#c("activity")).updateMany({ pageId: { $in: tree.ids } }, { $set: { pageScopeId: target.scopeId } }, { session });
|
|
5899
|
+
}
|
|
5900
|
+
await pages.updateMany({
|
|
5901
|
+
parentId: source.id,
|
|
5902
|
+
...notScope
|
|
5903
|
+
}, [{ $set: { parentId: target.id } }, recomputeActiveKey(target.id)], { session });
|
|
5904
|
+
} catch (error) {
|
|
5905
|
+
if (isDuplicateKey(error)) throw new _mastra_core_storage.KnowledgeConflictError(source.id);
|
|
5906
|
+
throw error;
|
|
5103
5907
|
}
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
await this.#activity("node-updated", "node", target.id, target.scope, void 0, session);
|
|
5126
|
-
}
|
|
5908
|
+
await notes.updateMany({ pageId: source.id }, { $set: {
|
|
5909
|
+
pageId: target.id,
|
|
5910
|
+
pageScopeId: target.scopeId
|
|
5911
|
+
} }, { session });
|
|
5912
|
+
await notes.updateMany({ mentions: source.id }, [{ $set: { mentions: { $setUnion: [{ $map: {
|
|
5913
|
+
input: "$mentions",
|
|
5914
|
+
in: { $cond: [
|
|
5915
|
+
{ $eq: ["$$this", source.id] },
|
|
5916
|
+
target.id,
|
|
5917
|
+
"$$this"
|
|
5918
|
+
] }
|
|
5919
|
+
} }] } } }], { session });
|
|
5920
|
+
await this.#refreshMentionScopes([target.id, ...tree.ids], session);
|
|
5921
|
+
const pins = await this.#c("pins");
|
|
5922
|
+
const sourcePin = await pins.findOne({ pageId: source.id }, {
|
|
5923
|
+
...noProjection,
|
|
5924
|
+
session
|
|
5925
|
+
});
|
|
5926
|
+
if (sourcePin) {
|
|
5927
|
+
await this.#upsertPinAudience(session, target.id, sourcePin.audience ?? [], String(sourcePin.createdBy), now);
|
|
5928
|
+
await pins.deleteOne({ id: sourcePin.id }, { session });
|
|
5127
5929
|
}
|
|
5128
|
-
await this.#
|
|
5129
|
-
await this.#
|
|
5130
|
-
|
|
5131
|
-
|
|
5930
|
+
await (await this.#c("activity")).updateMany({ pageId: source.id }, { $set: { pageScopeId: target.scopeId } }, { session });
|
|
5931
|
+
await this.#log(session, {
|
|
5932
|
+
action: "page-merged",
|
|
5933
|
+
scopeId: target.scopeId,
|
|
5934
|
+
pageScopeId: target.scopeId,
|
|
5935
|
+
pageId: source.id,
|
|
5936
|
+
provenance: input.provenance,
|
|
5937
|
+
at: now
|
|
5938
|
+
});
|
|
5939
|
+
return await this.#page(target.id, session);
|
|
5132
5940
|
});
|
|
5133
5941
|
}
|
|
5134
|
-
async
|
|
5135
|
-
await
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
id,
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
}
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5942
|
+
async deletePage(input) {
|
|
5943
|
+
const core = await loadPagesCore();
|
|
5944
|
+
return this.#tx(async (session) => {
|
|
5945
|
+
const page = await this.#requireLivePage(input.id, session);
|
|
5946
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5947
|
+
if (page.isScope) throw new Error(`Knowledge scope page ${input.id} can't be deleted`);
|
|
5948
|
+
const pages = await this.#c("pages");
|
|
5949
|
+
if (await pages.findOne({
|
|
5950
|
+
parentId: input.id,
|
|
5951
|
+
...live
|
|
5952
|
+
}, {
|
|
5953
|
+
projection: { id: 1 },
|
|
5954
|
+
session
|
|
5955
|
+
})) throw new core.KnowledgePageHasChildrenError(input.id);
|
|
5956
|
+
const now = /* @__PURE__ */ new Date();
|
|
5957
|
+
const updated = await pages.findOneAndUpdate({
|
|
5958
|
+
id: input.id,
|
|
5959
|
+
version: input.expectedVersion,
|
|
5960
|
+
...live
|
|
5961
|
+
}, {
|
|
5962
|
+
$set: {
|
|
5963
|
+
deletedAt: now,
|
|
5964
|
+
deletedBy: input.deletedBy,
|
|
5965
|
+
updatedAt: now,
|
|
5966
|
+
updatedBy: { author: input.deletedBy }
|
|
5967
|
+
},
|
|
5968
|
+
$unset: { activeKey: "" },
|
|
5969
|
+
$inc: { version: 1 }
|
|
5970
|
+
}, {
|
|
5971
|
+
returnDocument: "after",
|
|
5972
|
+
projection: { _id: 0 },
|
|
5973
|
+
session
|
|
5974
|
+
});
|
|
5975
|
+
if (!updated) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
5976
|
+
await (await this.#c("pins")).deleteOne({ pageId: input.id }, { session });
|
|
5977
|
+
await this.#log(session, {
|
|
5978
|
+
action: "page-deleted",
|
|
5979
|
+
scopeId: page.scopeId,
|
|
5980
|
+
pageId: page.id,
|
|
5981
|
+
provenance: { author: input.deletedBy },
|
|
5982
|
+
at: now
|
|
5983
|
+
});
|
|
5984
|
+
return toPage(updated);
|
|
5165
5985
|
});
|
|
5166
5986
|
}
|
|
5167
|
-
async
|
|
5168
|
-
const
|
|
5987
|
+
async getNote(input) {
|
|
5988
|
+
const ids = await this.#readable(input.read);
|
|
5989
|
+
const filter = {
|
|
5169
5990
|
id: input.id,
|
|
5170
|
-
...
|
|
5171
|
-
}
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5991
|
+
...this.#visibleNotes(ids)
|
|
5992
|
+
};
|
|
5993
|
+
if (!input.includeDeleted) Object.assign(filter, live);
|
|
5994
|
+
const row = await (await this.#c("notes")).findOne(filter, noProjection);
|
|
5995
|
+
return row ? toNote(row) : null;
|
|
5996
|
+
}
|
|
5997
|
+
async listNotes(input) {
|
|
5998
|
+
const core = await loadPagesCore();
|
|
5999
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
6000
|
+
const ids = await this.#readable(input.read);
|
|
6001
|
+
const filter = {
|
|
6002
|
+
pageId: input.pageId,
|
|
6003
|
+
...this.#visibleNotes(ids)
|
|
6004
|
+
};
|
|
6005
|
+
if (!input.includeDeleted) Object.assign(filter, live);
|
|
6006
|
+
if (input.cursor) {
|
|
6007
|
+
const [createdAt, id] = decodeCursor(input.cursor);
|
|
6008
|
+
const at = new Date(createdAt);
|
|
6009
|
+
filter.$or = [{ createdAt: { $gt: at } }, {
|
|
6010
|
+
createdAt: at,
|
|
6011
|
+
id: { $gt: id }
|
|
6012
|
+
}];
|
|
6013
|
+
}
|
|
6014
|
+
const rows = await (await this.#c("notes")).find(filter, noProjection).sort({
|
|
6015
|
+
createdAt: 1,
|
|
6016
|
+
id: 1
|
|
6017
|
+
}).limit(limit + 1).toArray();
|
|
6018
|
+
return this.#noteOutput(rows, limit);
|
|
6019
|
+
}
|
|
6020
|
+
async countNotes(input) {
|
|
6021
|
+
const ids = [...new Set(input.pageIds)];
|
|
6022
|
+
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
6023
|
+
if (!ids.length) return counts;
|
|
6024
|
+
const readable = await this.#readable(input.read);
|
|
6025
|
+
const rows = await (await this.#c("notes")).aggregate([{ $match: {
|
|
6026
|
+
pageId: { $in: ids },
|
|
6027
|
+
...live,
|
|
6028
|
+
...this.#visibleNotes(readable)
|
|
6029
|
+
} }, { $group: {
|
|
6030
|
+
_id: "$pageId",
|
|
6031
|
+
count: { $sum: 1 }
|
|
6032
|
+
} }]).toArray();
|
|
6033
|
+
for (const row of rows) counts[row._id] = row.count;
|
|
6034
|
+
return counts;
|
|
6035
|
+
}
|
|
6036
|
+
async listNotesMentioning(input) {
|
|
6037
|
+
const core = await loadPagesCore();
|
|
6038
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
6039
|
+
const ids = await this.#readable(input.read);
|
|
6040
|
+
const match = {
|
|
6041
|
+
mentions: input.pageId,
|
|
6042
|
+
pageId: { $ne: input.pageId },
|
|
6043
|
+
...live,
|
|
6044
|
+
...this.#visibleNotes(ids)
|
|
6045
|
+
};
|
|
6046
|
+
if (input.cursor) {
|
|
6047
|
+
const [createdAt, id] = decodeCursor(input.cursor);
|
|
6048
|
+
const at = new Date(createdAt);
|
|
6049
|
+
match.$or = [{ createdAt: { $lt: at } }, {
|
|
6050
|
+
createdAt: at,
|
|
6051
|
+
id: { $lt: id }
|
|
6052
|
+
}];
|
|
6053
|
+
}
|
|
6054
|
+
const rows = await (await this.#c("notes")).aggregate([
|
|
6055
|
+
{ $match: match },
|
|
6056
|
+
{ $lookup: {
|
|
6057
|
+
from: MONGO_KNOWLEDGE_PAGES_COLLECTIONS.pages,
|
|
6058
|
+
localField: "pageId",
|
|
6059
|
+
foreignField: "id",
|
|
6060
|
+
as: "page"
|
|
6061
|
+
} },
|
|
6062
|
+
{ $match: { "page.deletedAt": { $exists: false } } },
|
|
6063
|
+
{ $project: {
|
|
6064
|
+
_id: 0,
|
|
6065
|
+
page: 0
|
|
6066
|
+
} },
|
|
6067
|
+
{ $sort: {
|
|
6068
|
+
createdAt: -1,
|
|
6069
|
+
id: -1
|
|
6070
|
+
} },
|
|
6071
|
+
{ $limit: limit + 1 }
|
|
6072
|
+
]).toArray();
|
|
6073
|
+
return this.#noteOutput(rows, limit);
|
|
6074
|
+
}
|
|
6075
|
+
#noteOutput(rows, limit) {
|
|
6076
|
+
const notes = rows.slice(0, limit).map(toNote);
|
|
6077
|
+
const last = notes[notes.length - 1];
|
|
5192
6078
|
return {
|
|
5193
|
-
|
|
5194
|
-
|
|
6079
|
+
notes,
|
|
6080
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor([last.createdAt.toISOString(), last.id]) } : {}
|
|
5195
6081
|
};
|
|
5196
6082
|
}
|
|
5197
|
-
async
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
id:
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
6083
|
+
async #assertScopePages(ids, session) {
|
|
6084
|
+
const rows = await (await this.#c("pages")).find({
|
|
6085
|
+
id: { $in: ids },
|
|
6086
|
+
isScope: true
|
|
6087
|
+
}, {
|
|
6088
|
+
projection: {
|
|
6089
|
+
_id: 0,
|
|
6090
|
+
id: 1
|
|
6091
|
+
},
|
|
6092
|
+
session
|
|
6093
|
+
}).toArray();
|
|
6094
|
+
const found = new Set(rows.map((row) => String(row.id)));
|
|
6095
|
+
const missing = ids.find((id) => !found.has(id));
|
|
6096
|
+
if (missing) throw new Error(`Knowledge note stamp ${missing} is not a scope page`);
|
|
6097
|
+
}
|
|
6098
|
+
async addNote(input) {
|
|
6099
|
+
(await this.#bounds()).assertKnowledgeNoteTextWithinBound(input.text);
|
|
6100
|
+
if (input.stampScopeIds.length === 0) throw new Error("A Knowledge note needs at least one stamp scope");
|
|
6101
|
+
return this.#tx(async (session) => {
|
|
6102
|
+
const page = await this.#requireLivePage(input.pageId, session);
|
|
6103
|
+
const stampIds = [...new Set(input.stampScopeIds)];
|
|
6104
|
+
await this.#assertScopePages(stampIds, session);
|
|
6105
|
+
const mentionIds = [...new Set(input.mentions)];
|
|
6106
|
+
let mentionScopeIds = [];
|
|
6107
|
+
if (mentionIds.length) {
|
|
6108
|
+
const found = await (await this.#c("pages")).find({ id: { $in: mentionIds } }, {
|
|
6109
|
+
projection: {
|
|
6110
|
+
_id: 0,
|
|
6111
|
+
id: 1,
|
|
6112
|
+
scopeId: 1
|
|
6113
|
+
},
|
|
6114
|
+
session
|
|
6115
|
+
}).toArray();
|
|
6116
|
+
const known = new Set(found.map((row) => String(row.id)));
|
|
6117
|
+
const missing = mentionIds.find((id) => !known.has(id));
|
|
6118
|
+
if (missing) throw new _mastra_core_storage.KnowledgeNotFoundError("page", missing);
|
|
6119
|
+
mentionScopeIds = [...new Set(found.map((row) => String(row.scopeId)))];
|
|
6120
|
+
}
|
|
6121
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
6122
|
+
const now = /* @__PURE__ */ new Date();
|
|
6123
|
+
const doc = {
|
|
6124
|
+
id,
|
|
6125
|
+
pageId: page.id,
|
|
6126
|
+
pageScopeId: page.scopeId,
|
|
6127
|
+
text: input.text,
|
|
6128
|
+
...input.when ? { when: input.when } : {},
|
|
6129
|
+
stampScopeIds: stampIds,
|
|
6130
|
+
mentions: mentionIds,
|
|
6131
|
+
mentionScopeIds,
|
|
6132
|
+
provenance: input.provenance,
|
|
6133
|
+
version: 1,
|
|
6134
|
+
createdAt: now,
|
|
6135
|
+
updatedAt: now
|
|
5216
6136
|
};
|
|
6137
|
+
try {
|
|
6138
|
+
await (await this.#c("notes")).insertOne({ ...doc }, { session });
|
|
6139
|
+
} catch (error) {
|
|
6140
|
+
if (isDuplicateKey(error)) throw new _mastra_core_storage.KnowledgeConflictError(id);
|
|
6141
|
+
throw error;
|
|
6142
|
+
}
|
|
6143
|
+
await this.#log(session, {
|
|
6144
|
+
action: "note-added",
|
|
6145
|
+
scopeId: page.scopeId,
|
|
6146
|
+
pageId: page.id,
|
|
6147
|
+
noteId: id,
|
|
6148
|
+
provenance: input.provenance,
|
|
6149
|
+
at: now
|
|
6150
|
+
});
|
|
6151
|
+
return toNote(doc);
|
|
5217
6152
|
});
|
|
5218
6153
|
}
|
|
5219
|
-
async
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
6154
|
+
async #requireNote(id, session) {
|
|
6155
|
+
const row = await (await this.#c("notes")).findOne({ id }, {
|
|
6156
|
+
...noProjection,
|
|
6157
|
+
session
|
|
6158
|
+
});
|
|
6159
|
+
if (!row) throw new _mastra_core_storage.KnowledgeNotFoundError("note", id);
|
|
6160
|
+
return row;
|
|
6161
|
+
}
|
|
6162
|
+
async restampNote(input) {
|
|
6163
|
+
if (input.stampScopeIds.length === 0) throw new Error("A Knowledge note needs at least one stamp scope");
|
|
6164
|
+
return this.#tx(async (session) => {
|
|
6165
|
+
const note = await this.#requireNote(input.id, session);
|
|
6166
|
+
if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
|
|
6167
|
+
if (Number(note.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
6168
|
+
const stampIds = [...new Set(input.stampScopeIds)];
|
|
6169
|
+
await this.#assertScopePages(stampIds, session);
|
|
6170
|
+
const now = /* @__PURE__ */ new Date();
|
|
6171
|
+
const updated = await (await this.#c("notes")).findOneAndUpdate({
|
|
6172
|
+
id: input.id,
|
|
6173
|
+
version: input.expectedVersion
|
|
6174
|
+
}, {
|
|
6175
|
+
$set: {
|
|
6176
|
+
stampScopeIds: stampIds,
|
|
6177
|
+
updatedAt: now
|
|
6178
|
+
},
|
|
6179
|
+
$inc: { version: 1 }
|
|
6180
|
+
}, {
|
|
6181
|
+
returnDocument: "after",
|
|
6182
|
+
projection: { _id: 0 },
|
|
6183
|
+
session
|
|
6184
|
+
});
|
|
6185
|
+
if (!updated) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
6186
|
+
await this.#log(session, {
|
|
6187
|
+
action: "note-restamped",
|
|
6188
|
+
scopeId: String(note.pageScopeId),
|
|
6189
|
+
pageId: String(note.pageId),
|
|
6190
|
+
noteId: input.id,
|
|
6191
|
+
provenance: input.provenance,
|
|
6192
|
+
at: now
|
|
6193
|
+
});
|
|
6194
|
+
return toNote(updated);
|
|
5235
6195
|
});
|
|
5236
6196
|
}
|
|
5237
|
-
async
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
await (await this.#
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
6197
|
+
async removeNote(input) {
|
|
6198
|
+
return this.#tx(async (session) => {
|
|
6199
|
+
const note = await this.#requireNote(input.id, session);
|
|
6200
|
+
if (note.deletedAt) return toNote(note);
|
|
6201
|
+
const now = /* @__PURE__ */ new Date();
|
|
6202
|
+
const updated = await (await this.#c("notes")).findOneAndUpdate({ id: input.id }, {
|
|
6203
|
+
$set: {
|
|
6204
|
+
deletedAt: now,
|
|
6205
|
+
deletedBy: input.deletedBy,
|
|
6206
|
+
updatedAt: now
|
|
6207
|
+
},
|
|
6208
|
+
$inc: { version: 1 }
|
|
6209
|
+
}, {
|
|
6210
|
+
returnDocument: "after",
|
|
6211
|
+
projection: { _id: 0 },
|
|
6212
|
+
session
|
|
6213
|
+
});
|
|
6214
|
+
await this.#log(session, {
|
|
6215
|
+
action: "note-removed",
|
|
6216
|
+
scopeId: String(note.pageScopeId),
|
|
6217
|
+
pageId: String(note.pageId),
|
|
6218
|
+
noteId: input.id,
|
|
6219
|
+
provenance: input.provenance,
|
|
6220
|
+
at: now
|
|
6221
|
+
});
|
|
6222
|
+
return toNote(updated);
|
|
5253
6223
|
});
|
|
5254
6224
|
}
|
|
5255
|
-
async
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
{
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
6225
|
+
async restoreNote(input) {
|
|
6226
|
+
return this.#tx(async (session) => {
|
|
6227
|
+
const note = await this.#requireNote(input.id, session);
|
|
6228
|
+
if (!note.deletedAt) return toNote(note);
|
|
6229
|
+
const now = /* @__PURE__ */ new Date();
|
|
6230
|
+
const updated = await (await this.#c("notes")).findOneAndUpdate({ id: input.id }, {
|
|
6231
|
+
$set: { updatedAt: now },
|
|
6232
|
+
$unset: {
|
|
6233
|
+
deletedAt: "",
|
|
6234
|
+
deletedBy: ""
|
|
6235
|
+
},
|
|
6236
|
+
$inc: { version: 1 }
|
|
6237
|
+
}, {
|
|
6238
|
+
returnDocument: "after",
|
|
6239
|
+
projection: { _id: 0 },
|
|
6240
|
+
session
|
|
6241
|
+
});
|
|
6242
|
+
await this.#log(session, {
|
|
6243
|
+
action: "note-restored",
|
|
6244
|
+
scopeId: String(note.pageScopeId),
|
|
6245
|
+
pageId: String(note.pageId),
|
|
6246
|
+
noteId: input.id,
|
|
6247
|
+
provenance: input.provenance,
|
|
6248
|
+
at: now
|
|
6249
|
+
});
|
|
6250
|
+
return toNote(updated);
|
|
6251
|
+
});
|
|
6252
|
+
}
|
|
6253
|
+
async searchPages(input) {
|
|
6254
|
+
const core = await loadPagesCore();
|
|
6255
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
6256
|
+
const words = knowledgeSearchWords(input.query);
|
|
6257
|
+
if (!words.length) return [];
|
|
6258
|
+
const phrase = input.query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
6259
|
+
const ids = await this.#readable(input.read);
|
|
6260
|
+
const scopes = input.scopeIds?.length ? input.scopeIds : void 0;
|
|
6261
|
+
const inScope = (field) => {
|
|
6262
|
+
if (ids === null) return scopes ? { [field]: { $in: scopes } } : {};
|
|
6263
|
+
const allowed = scopes ? scopes.filter((id) => ids.includes(id)) : ids;
|
|
6264
|
+
return { [field]: { $in: allowed } };
|
|
6265
|
+
};
|
|
6266
|
+
const matcher = (word) => ({
|
|
6267
|
+
$regex: escapeRegex(word),
|
|
6268
|
+
$options: "i"
|
|
6269
|
+
});
|
|
6270
|
+
const pages = await this.#c("pages");
|
|
6271
|
+
const pageFilter = {
|
|
6272
|
+
...live,
|
|
6273
|
+
...notScope,
|
|
6274
|
+
...inScope("scopeId"),
|
|
6275
|
+
$and: words.map((word) => ({ $or: [
|
|
6276
|
+
{ name: matcher(word) },
|
|
6277
|
+
{ description: matcher(word) },
|
|
6278
|
+
{ body: matcher(word) }
|
|
6279
|
+
] }))
|
|
6280
|
+
};
|
|
6281
|
+
const hits = (await pages.aggregate([
|
|
6282
|
+
{ $match: pageFilter },
|
|
6283
|
+
{ $addFields: { searchTier: { $cond: [
|
|
6284
|
+
{ $regexMatch: {
|
|
6285
|
+
input: "$canonicalName",
|
|
6286
|
+
regex: escapeRegex(phrase)
|
|
6287
|
+
} },
|
|
6288
|
+
0,
|
|
6289
|
+
{ $cond: [
|
|
6290
|
+
{ $and: words.map((word) => ({ $regexMatch: {
|
|
6291
|
+
input: "$name",
|
|
6292
|
+
regex: escapeRegex(word),
|
|
6293
|
+
options: "i"
|
|
6294
|
+
} })) },
|
|
6295
|
+
1,
|
|
6296
|
+
2
|
|
6297
|
+
] }
|
|
6298
|
+
] } } },
|
|
6299
|
+
{ $sort: {
|
|
6300
|
+
searchTier: 1,
|
|
6301
|
+
updatedAt: -1,
|
|
6302
|
+
id: 1
|
|
6303
|
+
} },
|
|
6304
|
+
{ $limit: limit },
|
|
6305
|
+
{ $project: {
|
|
6306
|
+
searchTier: 0,
|
|
6307
|
+
_id: 0
|
|
6308
|
+
} }
|
|
6309
|
+
]).toArray()).map((row) => ({
|
|
6310
|
+
type: "page",
|
|
6311
|
+
page: toPage(row)
|
|
5281
6312
|
}));
|
|
5282
|
-
if (
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
6313
|
+
if (hits.length >= limit) return hits;
|
|
6314
|
+
const noteRows = await (await this.#c("notes")).find({
|
|
6315
|
+
...live,
|
|
6316
|
+
...this.#visibleNotes(ids),
|
|
6317
|
+
...scopes ? { pageScopeId: { $in: ids === null ? scopes : scopes.filter((id) => ids.includes(id)) } } : {},
|
|
6318
|
+
$and: words.map((word) => ({ text: matcher(word) }))
|
|
6319
|
+
}, noProjection).sort({
|
|
6320
|
+
updatedAt: -1,
|
|
6321
|
+
id: -1
|
|
6322
|
+
}).limit(limit - hits.length).toArray();
|
|
6323
|
+
const notePages = new Map((await pages.find({
|
|
6324
|
+
id: { $in: [...new Set(noteRows.map((row) => String(row.pageId)))] },
|
|
6325
|
+
...live
|
|
6326
|
+
}, noProjection).toArray()).map((row) => [String(row.id), toPage(row)]));
|
|
6327
|
+
for (const row of noteRows) {
|
|
6328
|
+
const page = notePages.get(String(row.pageId));
|
|
6329
|
+
if (page) hits.push({
|
|
6330
|
+
type: "note",
|
|
6331
|
+
note: toNote(row),
|
|
6332
|
+
page
|
|
6333
|
+
});
|
|
5300
6334
|
}
|
|
5301
|
-
return
|
|
5302
|
-
}
|
|
5303
|
-
/**
|
|
5304
|
-
* @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
|
|
5305
|
-
* cursor. Always throws.
|
|
5306
|
-
*/
|
|
5307
|
-
async getCurationCursor(_input) {
|
|
5308
|
-
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
6335
|
+
return hits;
|
|
5309
6336
|
}
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
}
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
6337
|
+
async listPageActivity(input) {
|
|
6338
|
+
const core = await loadPagesCore();
|
|
6339
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
6340
|
+
const ids = await this.#readable(input.read);
|
|
6341
|
+
const activity = await this.#c("activity");
|
|
6342
|
+
const notes = await this.#c("notes");
|
|
6343
|
+
const filter = {};
|
|
6344
|
+
if (ids !== null) filter.pageScopeId = { $in: ids };
|
|
6345
|
+
delete filter.scopeId;
|
|
6346
|
+
if (input.scopeId) {
|
|
6347
|
+
if (ids !== null && !ids.includes(input.scopeId)) filter.pageScopeId = { $in: [] };
|
|
6348
|
+
else filter.pageScopeId = input.scopeId;
|
|
6349
|
+
}
|
|
6350
|
+
if (input.pageId) filter.pageId = input.pageId;
|
|
6351
|
+
if (input.cursor) {
|
|
6352
|
+
const [createdAt, id] = decodeCursor(input.cursor);
|
|
6353
|
+
const at = new Date(createdAt);
|
|
6354
|
+
filter.$or = [{ createdAt: { $lt: at } }, {
|
|
6355
|
+
createdAt: at,
|
|
6356
|
+
id: { $lt: id }
|
|
6357
|
+
}];
|
|
6358
|
+
}
|
|
6359
|
+
const events = [];
|
|
6360
|
+
let cursorFilter = filter;
|
|
6361
|
+
for (;;) {
|
|
6362
|
+
const batch = await activity.find(cursorFilter, noProjection).sort({
|
|
6363
|
+
createdAt: -1,
|
|
6364
|
+
id: -1
|
|
6365
|
+
}).limit(limit * 4 + 1).toArray();
|
|
6366
|
+
const noteIds = [...new Set(batch.flatMap((row) => row.noteId ? [String(row.noteId)] : []))];
|
|
6367
|
+
const visible = new Set(noteIds.length ? (await notes.find({
|
|
6368
|
+
id: { $in: noteIds },
|
|
6369
|
+
...this.#visibleNotes(ids)
|
|
6370
|
+
}, { projection: {
|
|
6371
|
+
_id: 0,
|
|
6372
|
+
id: 1
|
|
6373
|
+
} }).toArray()).map((row) => String(row.id)) : []);
|
|
6374
|
+
for (const row of batch) {
|
|
6375
|
+
if (row.noteId && !visible.has(String(row.noteId))) continue;
|
|
6376
|
+
events.push(row);
|
|
6377
|
+
if (events.length > limit) break;
|
|
6378
|
+
}
|
|
6379
|
+
if (events.length > limit || batch.length <= limit * 4) break;
|
|
6380
|
+
const last = batch[batch.length - 1];
|
|
6381
|
+
cursorFilter = {
|
|
6382
|
+
...filter,
|
|
6383
|
+
$or: [{ createdAt: { $lt: last.createdAt } }, {
|
|
6384
|
+
createdAt: last.createdAt,
|
|
6385
|
+
id: { $lt: last.id }
|
|
6386
|
+
}]
|
|
6387
|
+
};
|
|
6388
|
+
}
|
|
6389
|
+
const page = events.slice(0, limit).map((row) => ({
|
|
6390
|
+
id: String(row.id),
|
|
6391
|
+
action: String(row.action),
|
|
6392
|
+
scopeId: String(row.scopeId),
|
|
6393
|
+
pageId: String(row.pageId),
|
|
6394
|
+
...row.noteId == null ? {} : { noteId: String(row.noteId) },
|
|
6395
|
+
provenance: row.provenance,
|
|
5329
6396
|
createdAt: new Date(row.createdAt)
|
|
5330
6397
|
}));
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
...
|
|
5335
|
-
...input.scope ? { scopeKey: { $in: visibleScopeKeys(input.scope) } } : {}
|
|
6398
|
+
const last = page[page.length - 1];
|
|
6399
|
+
return {
|
|
6400
|
+
events: page,
|
|
6401
|
+
...events.length > limit && last ? { nextCursor: encodeCursor([last.createdAt.toISOString(), last.id]) } : {}
|
|
5336
6402
|
};
|
|
5337
|
-
return (await (await this.#outboxCollection()).find(filter).sort({
|
|
5338
|
-
createdAt: 1,
|
|
5339
|
-
id: 1
|
|
5340
|
-
}).limit(input.limit ?? 100).toArray()).map(outboxFromDocument);
|
|
5341
6403
|
}
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
const
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
6404
|
+
/** Deletes every document but keeps the collections, indexes and schema-version marker. */
|
|
6405
|
+
async clearAll() {
|
|
6406
|
+
await this.#tx(async (session) => {
|
|
6407
|
+
for (const name of [
|
|
6408
|
+
"activity",
|
|
6409
|
+
"pins",
|
|
6410
|
+
"notes",
|
|
6411
|
+
"pages"
|
|
6412
|
+
]) await (await this.#c(name)).deleteMany({}, { session });
|
|
6413
|
+
});
|
|
6414
|
+
}
|
|
6415
|
+
async addPinAudience(input) {
|
|
6416
|
+
return this.#tx(async (session) => {
|
|
6417
|
+
const page = await this.#requireLivePage(input.pageId, session);
|
|
6418
|
+
const now = /* @__PURE__ */ new Date();
|
|
6419
|
+
const { pin, changed } = await this.#upsertPinAudience(session, page.id, input.audience, input.createdBy, now);
|
|
6420
|
+
if (changed) await this.#log(session, {
|
|
6421
|
+
action: "pin-added",
|
|
6422
|
+
scopeId: page.scopeId,
|
|
6423
|
+
pageId: page.id,
|
|
6424
|
+
provenance: { author: input.createdBy },
|
|
6425
|
+
at: now
|
|
6426
|
+
});
|
|
6427
|
+
return pin;
|
|
6428
|
+
});
|
|
6429
|
+
}
|
|
6430
|
+
async #upsertPinAudience(session, pageId, audience, createdBy, now) {
|
|
6431
|
+
const pins = await this.#c("pins");
|
|
6432
|
+
const existing = await pins.findOne({ pageId }, {
|
|
6433
|
+
...noProjection,
|
|
6434
|
+
session
|
|
6435
|
+
});
|
|
6436
|
+
if (!existing) {
|
|
6437
|
+
const doc = {
|
|
6438
|
+
id: (0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
6439
|
+
pageId,
|
|
6440
|
+
audience: [...new Set(audience)],
|
|
6441
|
+
createdBy,
|
|
6442
|
+
createdAt: now,
|
|
6443
|
+
updatedAt: now
|
|
5356
6444
|
};
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
6445
|
+
try {
|
|
6446
|
+
await pins.insertOne({ ...doc }, { session });
|
|
6447
|
+
} catch (error) {
|
|
6448
|
+
if (isDuplicateKey(error)) throw new _mastra_core_storage.KnowledgeConflictError(pageId);
|
|
6449
|
+
throw error;
|
|
6450
|
+
}
|
|
6451
|
+
return {
|
|
6452
|
+
pin: toPin(doc),
|
|
6453
|
+
changed: true
|
|
6454
|
+
};
|
|
6455
|
+
}
|
|
6456
|
+
const current = existing.audience ?? [];
|
|
6457
|
+
const merged = [.../* @__PURE__ */ new Set([...current, ...audience])];
|
|
6458
|
+
if (merged.length === current.length) return {
|
|
6459
|
+
pin: toPin(existing),
|
|
6460
|
+
changed: false
|
|
6461
|
+
};
|
|
6462
|
+
return {
|
|
6463
|
+
pin: toPin(await pins.findOneAndUpdate({ id: existing.id }, { $set: {
|
|
6464
|
+
audience: merged,
|
|
6465
|
+
updatedAt: now
|
|
6466
|
+
} }, {
|
|
6467
|
+
returnDocument: "after",
|
|
6468
|
+
projection: { _id: 0 },
|
|
6469
|
+
session
|
|
6470
|
+
})),
|
|
6471
|
+
changed: true
|
|
6472
|
+
};
|
|
6473
|
+
}
|
|
6474
|
+
async removePinAudience(input) {
|
|
6475
|
+
return this.#tx(async (session) => {
|
|
6476
|
+
const pins = await this.#c("pins");
|
|
6477
|
+
const existing = await pins.findOne({ pageId: input.pageId }, {
|
|
6478
|
+
...noProjection,
|
|
6479
|
+
session
|
|
6480
|
+
});
|
|
6481
|
+
if (!existing) return null;
|
|
6482
|
+
const current = existing.audience ?? [];
|
|
6483
|
+
const remaining = current.filter((entry) => !input.audience.includes(entry));
|
|
6484
|
+
if (remaining.length === current.length) return toPin(existing);
|
|
6485
|
+
const now = /* @__PURE__ */ new Date();
|
|
6486
|
+
let pin = null;
|
|
6487
|
+
if (remaining.length === 0) await pins.deleteOne({ id: existing.id }, { session });
|
|
6488
|
+
else pin = toPin(await pins.findOneAndUpdate({ id: existing.id }, { $set: {
|
|
6489
|
+
audience: remaining,
|
|
6490
|
+
updatedAt: now
|
|
6491
|
+
} }, {
|
|
6492
|
+
returnDocument: "after",
|
|
6493
|
+
projection: { _id: 0 },
|
|
6494
|
+
session
|
|
6495
|
+
}));
|
|
6496
|
+
const page = await this.#page(input.pageId, session);
|
|
6497
|
+
await this.#log(session, {
|
|
6498
|
+
action: "pin-removed",
|
|
6499
|
+
scopeId: page?.scopeId ?? input.pageId,
|
|
6500
|
+
pageId: input.pageId,
|
|
6501
|
+
provenance: { author: input.removedBy },
|
|
6502
|
+
at: now
|
|
6503
|
+
});
|
|
6504
|
+
return pin;
|
|
6505
|
+
});
|
|
6506
|
+
}
|
|
6507
|
+
async getPages(input) {
|
|
6508
|
+
if (!input.ids.length) return [];
|
|
6509
|
+
const rows = await (await this.#c("pages")).find({
|
|
6510
|
+
id: { $in: [...new Set(input.ids)] },
|
|
6511
|
+
...live
|
|
6512
|
+
}, noProjection).toArray();
|
|
6513
|
+
if (input.read === "all") return rows.map(toPage);
|
|
6514
|
+
const read = input.read;
|
|
6515
|
+
const scopeIds = [...new Set(rows.map((row) => String(row.scopeId)))];
|
|
6516
|
+
const readable = /* @__PURE__ */ new Set();
|
|
6517
|
+
const any = this.#readableClauses(read);
|
|
6518
|
+
if (scopeIds.length && any.length) {
|
|
6519
|
+
const scopes = await (await this.#c("pages")).find({
|
|
6520
|
+
id: { $in: scopeIds },
|
|
6521
|
+
isScope: true,
|
|
6522
|
+
$or: any
|
|
6523
|
+
}, { projection: {
|
|
6524
|
+
_id: 0,
|
|
5360
6525
|
id: 1
|
|
5361
|
-
}
|
|
5362
|
-
const
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
5392
|
-
|
|
5393
|
-
|
|
6526
|
+
} }).toArray();
|
|
6527
|
+
for (const scope of scopes) readable.add(String(scope.id));
|
|
6528
|
+
}
|
|
6529
|
+
return rows.filter((row) => readable.has(String(row.scopeId))).map(toPage);
|
|
6530
|
+
}
|
|
6531
|
+
async listPins(input) {
|
|
6532
|
+
const core = await loadPagesCore();
|
|
6533
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
6534
|
+
const ids = await this.#readable(input.read);
|
|
6535
|
+
const match = {};
|
|
6536
|
+
if (input.audience !== "all") match.audience = { $in: input.audience };
|
|
6537
|
+
const pageId = {};
|
|
6538
|
+
if (input.pageIds) pageId.$in = input.pageIds;
|
|
6539
|
+
if (input.excludePageIds?.length) pageId.$nin = input.excludePageIds;
|
|
6540
|
+
if (Object.keys(pageId).length) match.pageId = pageId;
|
|
6541
|
+
const pins = await this.#c("pins");
|
|
6542
|
+
const base = [
|
|
6543
|
+
{ $match: match },
|
|
6544
|
+
{ $lookup: {
|
|
6545
|
+
from: MONGO_KNOWLEDGE_PAGES_COLLECTIONS.pages,
|
|
6546
|
+
localField: "pageId",
|
|
6547
|
+
foreignField: "id",
|
|
6548
|
+
as: "page"
|
|
6549
|
+
} },
|
|
6550
|
+
{ $match: {
|
|
6551
|
+
"page.deletedAt": { $exists: false },
|
|
6552
|
+
...ids === null ? {} : { "page.scopeId": { $in: ids } },
|
|
6553
|
+
...input.withBody ? { "page.body": { $regex: "\\S" } } : {}
|
|
6554
|
+
} }
|
|
6555
|
+
];
|
|
6556
|
+
const pageMatch = [];
|
|
6557
|
+
if (input.cursor) {
|
|
6558
|
+
const [createdAt, id] = decodeCursor(input.cursor);
|
|
6559
|
+
const at = new Date(createdAt);
|
|
6560
|
+
pageMatch.push({ $match: { $or: [{ createdAt: { $lt: at } }, {
|
|
6561
|
+
createdAt: at,
|
|
6562
|
+
id: { $lt: id }
|
|
6563
|
+
}] } });
|
|
6564
|
+
}
|
|
6565
|
+
const rows = await pins.aggregate([
|
|
6566
|
+
...base,
|
|
6567
|
+
...pageMatch,
|
|
6568
|
+
{ $addFields: { pageName: { $arrayElemAt: ["$page.name", 0] } } },
|
|
6569
|
+
{ $project: {
|
|
6570
|
+
_id: 0,
|
|
6571
|
+
page: 0
|
|
6572
|
+
} },
|
|
6573
|
+
{ $sort: {
|
|
6574
|
+
createdAt: -1,
|
|
6575
|
+
id: -1
|
|
6576
|
+
} },
|
|
6577
|
+
{ $limit: limit + 1 }
|
|
6578
|
+
]).toArray();
|
|
6579
|
+
let total;
|
|
6580
|
+
if (input.count) {
|
|
6581
|
+
const counted = await pins.aggregate([...base, { $count: "total" }]).toArray();
|
|
6582
|
+
total = Number(counted[0]?.total ?? 0);
|
|
6583
|
+
}
|
|
6584
|
+
const listed = rows.slice(0, limit).map(toListedPin);
|
|
6585
|
+
const last = listed[listed.length - 1];
|
|
6586
|
+
return {
|
|
6587
|
+
pins: listed,
|
|
6588
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor([last.createdAt.toISOString(), last.id]) } : {},
|
|
6589
|
+
...total === void 0 ? {} : { total }
|
|
6590
|
+
};
|
|
6591
|
+
}
|
|
6592
|
+
};
|
|
6593
|
+
// src/storage/domains/knowledge/index.ts
|
|
6594
|
+
const loadKnowledgePagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
|
|
6595
|
+
/** @experimental Knowledge APIs are experimental and may change without notice. */
|
|
6596
|
+
var KnowledgeMongoDB = class extends _mastra_core_storage.KnowledgeStorage {
|
|
6597
|
+
static MANAGED_COLLECTIONS = Object.values(MONGO_KNOWLEDGE_PAGES_COLLECTIONS);
|
|
6598
|
+
#connector;
|
|
6599
|
+
#pages;
|
|
6600
|
+
constructor(config) {
|
|
6601
|
+
super();
|
|
6602
|
+
this.#connector = resolveMongoDBConfig(config);
|
|
6603
|
+
this.#pages = new MongoKnowledgePages({
|
|
6604
|
+
connector: this.#connector,
|
|
6605
|
+
bounds: async () => {
|
|
6606
|
+
const core = await loadKnowledgePagesCore();
|
|
6607
|
+
return {
|
|
6608
|
+
assertKnowledgePageDescriptionWithinBound: core.assertKnowledgePageDescriptionWithinBound,
|
|
6609
|
+
assertKnowledgeNoteTextWithinBound: core.assertKnowledgeNoteTextWithinBound
|
|
6610
|
+
};
|
|
5394
6611
|
}
|
|
5395
|
-
return claimed.map(outboxFromDocument);
|
|
5396
6612
|
});
|
|
5397
6613
|
}
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
}
|
|
5405
|
-
status: "completed",
|
|
5406
|
-
completedAt: /* @__PURE__ */ new Date(),
|
|
5407
|
-
claimedAt: null,
|
|
5408
|
-
claimedBy: null
|
|
5409
|
-
} });
|
|
5410
|
-
}
|
|
5411
|
-
async releaseSemanticOutbox(input) {
|
|
5412
|
-
if (!input.ids.length) return;
|
|
5413
|
-
await (await this.#outboxCollection()).updateMany({
|
|
5414
|
-
id: { $in: input.ids },
|
|
5415
|
-
status: "processing",
|
|
5416
|
-
claimedBy: input.workerId
|
|
5417
|
-
}, { $set: {
|
|
5418
|
-
status: "pending",
|
|
5419
|
-
availableAt: input.retryAt ?? /* @__PURE__ */ new Date(),
|
|
5420
|
-
claimedAt: null,
|
|
5421
|
-
claimedBy: null
|
|
5422
|
-
} });
|
|
6614
|
+
getCapabilities() {
|
|
6615
|
+
return {
|
|
6616
|
+
...super.getCapabilities(),
|
|
6617
|
+
schemaVersion: 4,
|
|
6618
|
+
supportsSchemaInspection: true,
|
|
6619
|
+
supportsExplicitReset: true
|
|
6620
|
+
};
|
|
5423
6621
|
}
|
|
5424
|
-
async
|
|
5425
|
-
|
|
6622
|
+
async init() {
|
|
6623
|
+
await this.#pages.init();
|
|
5426
6624
|
}
|
|
5427
|
-
|
|
5428
|
-
return this.#
|
|
6625
|
+
async inspectSchema() {
|
|
6626
|
+
return this.#pages.inspectSchema();
|
|
5429
6627
|
}
|
|
5430
|
-
|
|
5431
|
-
|
|
6628
|
+
async dangerouslyReset() {
|
|
6629
|
+
await this.#pages.reset();
|
|
5432
6630
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
6631
|
+
async dangerouslyClearAll() {
|
|
6632
|
+
await this.#pages.clearAll();
|
|
5435
6633
|
}
|
|
5436
|
-
|
|
5437
|
-
return this.#
|
|
6634
|
+
materializeScope(input) {
|
|
6635
|
+
return this.#pages.materializeScope(input);
|
|
5438
6636
|
}
|
|
5439
|
-
|
|
5440
|
-
return this.#
|
|
6637
|
+
getScopesByAddress(input) {
|
|
6638
|
+
return this.#pages.getScopesByAddress(input);
|
|
5441
6639
|
}
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
id,
|
|
5445
|
-
type: "node"
|
|
5446
|
-
}, sessionOptions(session));
|
|
5447
|
-
return row ? nodeFromDocument(row) : null;
|
|
5448
|
-
}
|
|
5449
|
-
async #getNodeByName(name, scope, session) {
|
|
5450
|
-
const row = await (await this.#nodes()).findOne({
|
|
5451
|
-
type: "node",
|
|
5452
|
-
scopeKey: (0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
5453
|
-
canonicalName: canonicalName(name)
|
|
5454
|
-
}, sessionOptions(session));
|
|
5455
|
-
return row ? nodeFromDocument(row) : null;
|
|
5456
|
-
}
|
|
5457
|
-
async #resolveNode(name, scope, session) {
|
|
5458
|
-
const candidates = (await (await this.#nodes()).find({
|
|
5459
|
-
type: "node",
|
|
5460
|
-
canonicalName: canonicalName(name),
|
|
5461
|
-
$or: [{ scopeKey: { $in: visibleScopeKeys(scope) } }, { mergedInto: { $ne: null } }]
|
|
5462
|
-
}, sessionOptions(session)).toArray()).map(nodeFromDocument).sort((left, right) => right.scope.length - left.scope.length);
|
|
5463
|
-
for (const candidate of candidates) {
|
|
5464
|
-
const terminal = await this.#resolveTerminalNode(candidate.id, session);
|
|
5465
|
-
if (terminal && (0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) return terminal;
|
|
5466
|
-
}
|
|
5467
|
-
return null;
|
|
5468
|
-
}
|
|
5469
|
-
async #resolveTerminalNode(id, session) {
|
|
5470
|
-
let node = await this.#getNode(id, session);
|
|
5471
|
-
const seen = /* @__PURE__ */ new Set();
|
|
5472
|
-
while (node?.mergedInto) {
|
|
5473
|
-
if (seen.has(node.id)) throw new Error(`Knowledge merge cycle detected at ${node.id}`);
|
|
5474
|
-
seen.add(node.id);
|
|
5475
|
-
node = await this.#getNode(node.mergedInto, session);
|
|
5476
|
-
}
|
|
5477
|
-
return node;
|
|
6640
|
+
listScopeDeclarations() {
|
|
6641
|
+
return this.#pages.listScopeDeclarations();
|
|
5478
6642
|
}
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
type: "page",
|
|
5482
|
-
scopeKey: (0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
5483
|
-
canonicalName: canonicalName(name)
|
|
5484
|
-
}, sessionOptions(session));
|
|
5485
|
-
return row ? nodeFromDocument(row) : null;
|
|
6643
|
+
renameScopeDeclaration(input) {
|
|
6644
|
+
return this.#pages.renameScopeDeclaration(input);
|
|
5486
6645
|
}
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
id,
|
|
5490
|
-
...includeDeleted ? {} : { deletedAt: null }
|
|
5491
|
-
}, sessionOptions(session));
|
|
5492
|
-
return row ? recordFromDocument(row) : null;
|
|
5493
|
-
}
|
|
5494
|
-
async #queryKnowledge(input, relationship) {
|
|
5495
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
|
|
5496
|
-
const node = await this.#resolveTerminalNode(nodeReferenceId(input.node));
|
|
5497
|
-
if (!node) return { records: [] };
|
|
5498
|
-
const nodeIds = [node.id];
|
|
5499
|
-
if (relationship !== "about") {
|
|
5500
|
-
const mentions = await (await this.#mentions()).find({
|
|
5501
|
-
recordId: node.id,
|
|
5502
|
-
sourceType: "record"
|
|
5503
|
-
}).toArray();
|
|
5504
|
-
nodeIds.push(...mentions.map((row) => row.sourceId));
|
|
5505
|
-
}
|
|
5506
|
-
const limit = input.limit ?? 100;
|
|
5507
|
-
const filter = relationship === "about" ? { node: node.id } : relationship === "mentioning" ? { id: { $in: nodeIds.slice(1) } } : { $or: [{ node: node.id }, { id: { $in: nodeIds.slice(1) } }] };
|
|
5508
|
-
Object.assign(filter, {
|
|
5509
|
-
scopeKey: { $in: visibleScopeKeys(scope) },
|
|
5510
|
-
...input.includeDeleted ? {} : { deletedAt: null },
|
|
5511
|
-
...input.after ? { id: { $lt: input.after } } : {}
|
|
5512
|
-
});
|
|
5513
|
-
const rows = await (await this.#knowledge()).find(filter).sort({ id: -1 }).limit(limit + 1).toArray();
|
|
5514
|
-
return {
|
|
5515
|
-
records: rows.slice(0, limit).map(recordFromDocument),
|
|
5516
|
-
nextCursor: rows.length > limit ? rows[limit - 1]?.id : void 0
|
|
5517
|
-
};
|
|
6646
|
+
listChildScopes(input) {
|
|
6647
|
+
return this.#pages.listChildScopes(input);
|
|
5518
6648
|
}
|
|
5519
|
-
|
|
5520
|
-
|
|
5521
|
-
await mentions.deleteMany({
|
|
5522
|
-
sourceType,
|
|
5523
|
-
sourceId
|
|
5524
|
-
}, sessionOptions(session));
|
|
5525
|
-
for (const name of (0, _mastra_core_storage.parseKnowledgeWikilinks)(text)) {
|
|
5526
|
-
let node = await this.#resolveNode(name, resolutionScope, session);
|
|
5527
|
-
if (!node) {
|
|
5528
|
-
const existing = await this.#getNodeByName(name, defaultScope, session);
|
|
5529
|
-
node = existing ? await this.#resolveTerminalNode(existing.id, session) : null;
|
|
5530
|
-
}
|
|
5531
|
-
if (!node) {
|
|
5532
|
-
const now = /* @__PURE__ */ new Date();
|
|
5533
|
-
node = {
|
|
5534
|
-
id: crypto.randomUUID(),
|
|
5535
|
-
type: "node",
|
|
5536
|
-
name,
|
|
5537
|
-
kind: "node",
|
|
5538
|
-
scope: defaultScope,
|
|
5539
|
-
version: 1,
|
|
5540
|
-
createdAt: now,
|
|
5541
|
-
updatedAt: now
|
|
5542
|
-
};
|
|
5543
|
-
try {
|
|
5544
|
-
await (await this.#nodes()).insertOne({
|
|
5545
|
-
...node,
|
|
5546
|
-
canonicalName: canonicalName(name),
|
|
5547
|
-
scopeKey: (0, _mastra_core_storage.knowledgeScopeKey)(defaultScope),
|
|
5548
|
-
mergedInto: null
|
|
5549
|
-
}, sessionOptions(session));
|
|
5550
|
-
await this.#activity("node-created", "node", node.id, defaultScope, void 0, session);
|
|
5551
|
-
await this.#outbox("node", node.id, "upsert", 1, defaultScope, session);
|
|
5552
|
-
} catch (error) {
|
|
5553
|
-
if (error.code !== 11e3) throw error;
|
|
5554
|
-
node = await this.#getNodeByName(name, defaultScope, session);
|
|
5555
|
-
if (!node) throw error;
|
|
5556
|
-
}
|
|
5557
|
-
}
|
|
5558
|
-
await mentions.updateOne({
|
|
5559
|
-
sourceType,
|
|
5560
|
-
sourceId,
|
|
5561
|
-
recordId: node.id
|
|
5562
|
-
}, { $setOnInsert: {
|
|
5563
|
-
sourceType,
|
|
5564
|
-
sourceId,
|
|
5565
|
-
recordId: node.id
|
|
5566
|
-
} }, {
|
|
5567
|
-
...sessionOptions(session),
|
|
5568
|
-
upsert: true
|
|
5569
|
-
});
|
|
5570
|
-
}
|
|
6649
|
+
getPages(input) {
|
|
6650
|
+
return this.#pages.getPages(input);
|
|
5571
6651
|
}
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
}
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
6652
|
+
getPage(input) {
|
|
6653
|
+
return this.#pages.getPage(input);
|
|
6654
|
+
}
|
|
6655
|
+
getPageByPath(input) {
|
|
6656
|
+
return this.#pages.getPageByPath(input);
|
|
6657
|
+
}
|
|
6658
|
+
findPagesByName(input) {
|
|
6659
|
+
return this.#pages.findPagesByName(input);
|
|
6660
|
+
}
|
|
6661
|
+
listChildPages(input) {
|
|
6662
|
+
return this.#pages.listChildPages(input);
|
|
6663
|
+
}
|
|
6664
|
+
listPages(input) {
|
|
6665
|
+
return this.#pages.listPages(input);
|
|
6666
|
+
}
|
|
6667
|
+
createPage(input) {
|
|
6668
|
+
return this.#pages.createPage(input);
|
|
6669
|
+
}
|
|
6670
|
+
updatePage(input) {
|
|
6671
|
+
return this.#pages.updatePage(input);
|
|
6672
|
+
}
|
|
6673
|
+
movePage(input) {
|
|
6674
|
+
return this.#pages.movePage(input);
|
|
6675
|
+
}
|
|
6676
|
+
mergePages(input) {
|
|
6677
|
+
return this.#pages.mergePages(input);
|
|
6678
|
+
}
|
|
6679
|
+
deletePage(input) {
|
|
6680
|
+
return this.#pages.deletePage(input);
|
|
6681
|
+
}
|
|
6682
|
+
getNote(input) {
|
|
6683
|
+
return this.#pages.getNote(input);
|
|
6684
|
+
}
|
|
6685
|
+
listNotes(input) {
|
|
6686
|
+
return this.#pages.listNotes(input);
|
|
6687
|
+
}
|
|
6688
|
+
countNotes(input) {
|
|
6689
|
+
return this.#pages.countNotes(input);
|
|
6690
|
+
}
|
|
6691
|
+
listNotesMentioning(input) {
|
|
6692
|
+
return this.#pages.listNotesMentioning(input);
|
|
6693
|
+
}
|
|
6694
|
+
addNote(input) {
|
|
6695
|
+
return this.#pages.addNote(input);
|
|
6696
|
+
}
|
|
6697
|
+
restampNote(input) {
|
|
6698
|
+
return this.#pages.restampNote(input);
|
|
6699
|
+
}
|
|
6700
|
+
removeNote(input) {
|
|
6701
|
+
return this.#pages.removeNote(input);
|
|
6702
|
+
}
|
|
6703
|
+
restoreNote(input) {
|
|
6704
|
+
return this.#pages.restoreNote(input);
|
|
6705
|
+
}
|
|
6706
|
+
searchPages(input) {
|
|
6707
|
+
return this.#pages.searchPages(input);
|
|
6708
|
+
}
|
|
6709
|
+
listPageActivity(input) {
|
|
6710
|
+
return this.#pages.listPageActivity(input);
|
|
6711
|
+
}
|
|
6712
|
+
addPinAudience(input) {
|
|
6713
|
+
return this.#pages.addPinAudience(input);
|
|
6714
|
+
}
|
|
6715
|
+
removePinAudience(input) {
|
|
6716
|
+
return this.#pages.removePinAudience(input);
|
|
5608
6717
|
}
|
|
5609
|
-
|
|
5610
|
-
return
|
|
6718
|
+
listPins(input) {
|
|
6719
|
+
return this.#pages.listPins(input);
|
|
5611
6720
|
}
|
|
5612
6721
|
};
|
|
5613
6722
|
// src/storage/domains/mcp-clients/index.ts
|