@mastra/libsql 1.28.0-alpha.0 → 1.29.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/index.cjs CHANGED
@@ -9,7 +9,6 @@ let async_hooks = require("async_hooks");
9
9
  let _mastra_core_vector_filter = require("@mastra/core/vector/filter");
10
10
  let fs_promises = require("fs/promises");
11
11
  let _mastra_core_base = require("@mastra/core/base");
12
- let crypto$1 = require("crypto");
13
12
  let _mastra_core_features = require("@mastra/core/features");
14
13
  let _mastra_core_agent = require("@mastra/core/agent");
15
14
  let _mastra_core_evals = require("@mastra/core/evals");
@@ -693,7 +692,7 @@ const FILTER_OPERATORS = {
693
692
  transformValue: () => {
694
693
  if (Array.isArray(value)) return [JSON.stringify(value)];
695
694
  if (typeof value === "object" && value !== null) return [JSON.stringify(value)];
696
- if (typeof value === "string") return [escapeLikePattern$1(value)];
695
+ if (typeof value === "string") return [escapeLikePattern(value)];
697
696
  return [value];
698
697
  }
699
698
  };
@@ -710,7 +709,7 @@ const parseJsonPathKey = (key) => {
710
709
  const getJsonPath = (key) => {
711
710
  return `'$.${parseJsonPathKey(key)}'`;
712
711
  };
713
- function escapeLikePattern$1(str) {
712
+ function escapeLikePattern(str) {
714
713
  return str.replace(/([%_\\])/g, "\\$1");
715
714
  }
716
715
  function buildFilterQuery(filter) {
@@ -3395,7 +3394,7 @@ function serializeJson(v) {
3395
3394
  if (serialized === void 0) throw new Error("Failed to serialize background task value as JSON");
3396
3395
  return serialized;
3397
3396
  }
3398
- function parseJson$4(val) {
3397
+ function parseJson$3(val) {
3399
3398
  if (val == null) return void 0;
3400
3399
  if (typeof val === "string") try {
3401
3400
  return JSON.parse(val);
@@ -3410,14 +3409,14 @@ function rowToTask(row) {
3410
3409
  status: String(row.status),
3411
3410
  toolName: String(row.tool_name),
3412
3411
  toolCallId: String(row.tool_call_id),
3413
- args: parseJson$4(row.args) ?? {},
3412
+ args: parseJson$3(row.args) ?? {},
3414
3413
  agentId: String(row.agent_id),
3415
3414
  threadId: row.thread_id != null ? String(row.thread_id) : void 0,
3416
3415
  resourceId: row.resource_id != null ? String(row.resource_id) : void 0,
3417
3416
  runId: String(row.run_id),
3418
- result: parseJson$4(row.result),
3419
- error: parseJson$4(row.error),
3420
- suspendPayload: parseJson$4(row.suspend_payload),
3417
+ result: parseJson$3(row.result),
3418
+ error: parseJson$3(row.error),
3419
+ suspendPayload: parseJson$3(row.suspend_payload),
3421
3420
  retryCount: Number(row.retry_count),
3422
3421
  maxRetries: Number(row.max_retries),
3423
3422
  timeoutMs: Number(row.timeout_ms),
@@ -3428,7 +3427,7 @@ function rowToTask(row) {
3428
3427
  ownerId: row.ownerId != null ? String(row.ownerId) : void 0,
3429
3428
  leaseExpiresAt: row.leaseExpiresAt ? new Date(String(row.leaseExpiresAt)) : void 0,
3430
3429
  expiresAt: row.expiresAt ? new Date(String(row.expiresAt)) : void 0,
3431
- metadata: parseJson$4(row.metadata)
3430
+ metadata: parseJson$3(row.metadata)
3432
3431
  };
3433
3432
  }
3434
3433
  var BackgroundTasksLibSQL = class BackgroundTasksLibSQL extends _mastra_core_storage.BackgroundTasksStorage {
@@ -6395,10 +6394,10 @@ var FavoritesLibSQL = class extends _mastra_core_storage.FavoritesStorage {
6395
6394
  }
6396
6395
  };
6397
6396
  // src/storage/domains/harness/index.ts
6398
- const toDate$1 = (value) => new Date(value);
6397
+ const toDate = (value) => new Date(value);
6399
6398
  const toOptionalDate = (value) => {
6400
6399
  if (value == null) return void 0;
6401
- return toDate$1(value);
6400
+ return toDate(value);
6402
6401
  };
6403
6402
  function cloneJson(value) {
6404
6403
  return value === void 0 ? void 0 : structuredClone(value);
@@ -6409,8 +6408,8 @@ function hydratePendingItem(item) {
6409
6408
  kind: item.kind,
6410
6409
  status: item.status,
6411
6410
  sessionId: item.sessionId,
6412
- createdAt: toDate$1(item.createdAt),
6413
- updatedAt: toDate$1(item.updatedAt)
6411
+ createdAt: toDate(item.createdAt),
6412
+ updatedAt: toDate(item.updatedAt)
6414
6413
  };
6415
6414
  if (item.runId !== void 0) record.runId = item.runId;
6416
6415
  if (item.traceId !== void 0) record.traceId = item.traceId;
@@ -6428,8 +6427,8 @@ function rowToSession(row) {
6428
6427
  origin: row.origin,
6429
6428
  modeId: row.modeId,
6430
6429
  modelId: row.modelId,
6431
- createdAt: toDate$1(row.createdAt),
6432
- lastActivityAt: toDate$1(row.lastActivityAt)
6430
+ createdAt: toDate(row.createdAt),
6431
+ lastActivityAt: toDate(row.lastActivityAt)
6433
6432
  };
6434
6433
  if (row.parentSessionId != null) record.parentSessionId = row.parentSessionId;
6435
6434
  if (row.subagentDepth != null) record.subagentDepth = row.subagentDepth;
@@ -6538,1973 +6537,1585 @@ var HarnessLibSQL = class HarnessLibSQL extends _mastra_core_storage.HarnessStor
6538
6537
  })).map(rowToSession);
6539
6538
  }
6540
6539
  };
6541
- // ../../packages/_internal-core/dist/knowledge-compat/index.js
6542
- const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
6543
- /** Hard cap on scope nodes returned by one `listScopeNodes` read. */
6544
- const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
6545
- function knowledgeScopeNodeFilterKey(input) {
6546
- return JSON.stringify([
6547
- input.withinAddress ?? null,
6548
- input.addresses ? [...input.addresses].sort() : null,
6549
- input.ids ? [...input.ids].sort() : null
6550
- ]);
6551
- }
6552
- function createKnowledgeScopeNodeCursor(scope, input) {
6553
- return encodeURIComponent(JSON.stringify({
6554
- version: 1,
6555
- type: "scope",
6556
- name: scope.name,
6557
- id: scope.id,
6558
- filter: knowledgeScopeNodeFilterKey(input)
6559
- }));
6560
- }
6561
- /** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
6562
- function parseListKnowledgeScopeNodesInput(input = {}) {
6563
- const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
6564
- const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
6565
- if (!input.cursor) return {
6566
- limit,
6567
- after: null
6568
- };
6569
- let value;
6570
- try {
6571
- value = JSON.parse(decodeURIComponent(input.cursor));
6572
- } catch {
6573
- throw new Error("Invalid Knowledge scope node cursor.");
6574
- }
6575
- const parsed = value && typeof value === "object" ? value : {};
6576
- if (parsed.version !== 1 || parsed.type !== "scope" || typeof parsed.name !== "string" || typeof parsed.id !== "string" || parsed.filter !== knowledgeScopeNodeFilterKey(input)) throw new Error("Knowledge scope node cursor does not match this query.");
6577
- return {
6578
- limit,
6579
- after: {
6580
- name: parsed.name,
6581
- id: parsed.id
6582
- }
6583
- };
6584
- }
6585
- /** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
6586
- function pageKnowledgeScopeNodes(rows, limit, input) {
6587
- const scopes = rows.slice(0, limit);
6588
- const last = scopes.at(-1);
6589
- return {
6590
- scopes,
6591
- nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
6592
- };
6593
- }
6594
- /** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
6595
- function knowledgeScopeMembersLimit(limit) {
6596
- if (limit === void 0 || !Number.isFinite(limit)) return 500;
6597
- return Math.min(Math.max(Math.trunc(limit), 1), 500);
6598
- }
6599
- /** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
6600
- function parseListKnowledgeScopeMembersInput(input) {
6601
- const limit = knowledgeScopeMembersLimit(input.limit);
6602
- if (!input.cursor) return {
6603
- limit,
6604
- after: null
6605
- };
6606
- let value;
6607
- try {
6608
- value = JSON.parse(decodeURIComponent(input.cursor));
6609
- } catch {
6610
- throw new Error("Invalid Knowledge scope member cursor.");
6611
- }
6612
- const parsed = value && typeof value === "object" ? value : {};
6613
- const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
6614
- if (parsed.version !== 1 || parsed.type !== "scope-member" || parsed.scopeNodeId !== input.scopeNodeId || typeof parsed.name !== "string" || typeof parsed.id !== "string" || Number.isNaN(updatedAt.getTime())) throw new Error("Knowledge scope member cursor does not match this query.");
6615
- return {
6616
- limit,
6617
- after: {
6618
- updatedAt,
6619
- name: parsed.name,
6620
- id: parsed.id
6621
- }
6622
- };
6623
- }
6624
- /** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
6625
- function pageKnowledgeScopeMembers(rows, limit, input) {
6626
- const members = rows.slice(0, limit);
6627
- const last = members.at(-1);
6628
- const hasMore = rows.length > limit;
6629
- return {
6630
- members,
6631
- hasMore,
6632
- nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
6633
- version: 1,
6634
- type: "scope-member",
6635
- scopeNodeId: input.scopeNodeId,
6636
- updatedAt: last.updatedAt.toISOString(),
6637
- name: last.name,
6638
- id: last.id
6639
- })) : null
6640
- };
6641
- }
6642
- const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
6643
- const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
6644
- const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
6645
- const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
6646
- const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
6647
- const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
6648
- const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
6649
- const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
6650
- const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
6651
- const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
6652
- const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
6653
- const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
6654
- const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
6655
- const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
6656
6540
  const KNOWLEDGE_TABLE_NAMES = [
6657
- TABLE_KNOWLEDGE_NODES$1,
6658
- TABLE_KNOWLEDGE_RECORDS$1,
6659
- TABLE_KNOWLEDGE_MENTIONS$1,
6660
- TABLE_KNOWLEDGE_ACTIVITY$1,
6661
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
6662
- TABLE_KNOWLEDGE_NODE_SCOPES,
6663
- TABLE_KNOWLEDGE_RECORD_SCOPES,
6664
- TABLE_KNOWLEDGE_SCOPE_GRANTS,
6665
- TABLE_KNOWLEDGE_ACCESS_STATE,
6666
- TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
6667
- TABLE_KNOWLEDGE_NODE_ADDRESSES,
6668
- TABLE_KNOWLEDGE_IMPORT_STATE,
6669
- TABLE_KNOWLEDGE_IMPORT_RUNS,
6670
- TABLE_KNOWLEDGE_PROPOSALS
6541
+ "mastra_knowledge_nodes",
6542
+ "mastra_knowledge_records",
6543
+ "mastra_knowledge_mentions",
6544
+ "mastra_knowledge_activity",
6545
+ "mastra_knowledge_semantic_outbox",
6546
+ "mastra_knowledge_node_scopes",
6547
+ "mastra_knowledge_record_scopes",
6548
+ "mastra_knowledge_scope_grants",
6549
+ "mastra_knowledge_access_state",
6550
+ "mastra_knowledge_scope_addresses",
6551
+ "mastra_knowledge_node_addresses",
6552
+ "mastra_knowledge_import_state",
6553
+ "mastra_knowledge_import_runs",
6554
+ "mastra_knowledge_proposals"
6671
6555
  ];
6672
6556
  /** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
6673
6557
  const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
6674
- /** Tables and indexes published v1 adapters created for every app, whether or not it used Knowledge. */
6675
- const PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES = /* @__PURE__ */ new Set([
6676
- TABLE_KNOWLEDGE_NODES$1,
6677
- TABLE_KNOWLEDGE_RECORDS$1,
6678
- TABLE_KNOWLEDGE_MENTIONS$1,
6679
- TABLE_KNOWLEDGE_ACTIVITY$1,
6680
- TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1,
6558
+ const KNOWLEDGE_PAGES_CORE_FEATURE = "knowledge-pages";
6559
+ const TABLE_KNOWLEDGE_PAGES = "mastra_knowledge_pages";
6560
+ const TABLE_KNOWLEDGE_NOTES = "mastra_knowledge_notes";
6561
+ const TABLE_KNOWLEDGE_NOTE_STAMPS = "mastra_knowledge_note_stamps";
6562
+ /** Same name as the schema-2 table; schema 4 re-keys it to note → page. */
6563
+ const TABLE_KNOWLEDGE_PAGE_MENTIONS = "mastra_knowledge_mentions";
6564
+ const TABLE_KNOWLEDGE_PINS = "mastra_knowledge_pins";
6565
+ /** Same name as schema 2; schema 4 re-keys it to scope/page/note. */
6566
+ const TABLE_KNOWLEDGE_PAGE_ACTIVITY = "mastra_knowledge_activity";
6567
+ /** Holds `{ id: 'knowledge', schemaVersion: 4 }`, written when the schema-4 tables are created. */
6568
+ const TABLE_KNOWLEDGE_PAGES_STATE = "mastra_knowledge_access_state";
6569
+ const KNOWLEDGE_PAGES_TABLE_NAMES = [
6570
+ TABLE_KNOWLEDGE_PAGES,
6571
+ TABLE_KNOWLEDGE_NOTES,
6572
+ TABLE_KNOWLEDGE_NOTE_STAMPS,
6573
+ TABLE_KNOWLEDGE_PAGE_MENTIONS,
6574
+ TABLE_KNOWLEDGE_PINS,
6575
+ TABLE_KNOWLEDGE_PAGE_ACTIVITY,
6576
+ TABLE_KNOWLEDGE_PAGES_STATE
6577
+ ];
6578
+ /** Every Knowledge table any schema version created. Reset drops these and nothing else. */
6579
+ const KNOWLEDGE_RESETTABLE_TABLE_NAMES = [.../* @__PURE__ */ new Set([
6580
+ ...KNOWLEDGE_PAGES_TABLE_NAMES,
6581
+ ...KNOWLEDGE_TABLE_NAMES,
6681
6582
  ...RETIRED_KNOWLEDGE_TABLE_NAMES
6682
- ]);
6683
- const PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES = /* @__PURE__ */ new Set([
6684
- "idx_knowledge_nodes_identity",
6685
- "idx_knowledge_nodes_scope",
6686
- "idx_knowledge_records_node_latest",
6687
- "idx_knowledge_records_thread_latest",
6688
- "idx_knowledge_mentions_record",
6689
- "idx_knowledge_activity_latest",
6690
- "idx_knowledge_outbox_idempotency",
6691
- "idx_knowledge_outbox_claim"
6692
- ]);
6693
- const KNOWLEDGE_V2_NODES_SCHEMA = {
6694
- id: {
6695
- type: "text",
6696
- nullable: false,
6697
- primaryKey: true
6698
- },
6699
- name: {
6700
- type: "text",
6701
- nullable: false
6702
- },
6703
- kind: {
6704
- type: "text",
6705
- nullable: true
6706
- },
6707
- isScope: {
6708
- type: "boolean",
6709
- nullable: false
6710
- },
6711
- metadata: {
6712
- type: "jsonb",
6713
- nullable: true
6714
- },
6715
- version: {
6716
- type: "integer",
6717
- nullable: false
6718
- },
6719
- createdAt: {
6720
- type: "timestamp",
6721
- nullable: false
6722
- },
6723
- updatedAt: {
6724
- type: "timestamp",
6725
- nullable: false
6726
- },
6727
- deletedAt: {
6728
- type: "timestamp",
6729
- nullable: true
6730
- },
6731
- deletedBy: {
6732
- type: "text",
6733
- nullable: true
6734
- },
6735
- type: {
6736
- type: "text",
6737
- nullable: true
6738
- },
6739
- canonicalName: {
6740
- type: "text",
6741
- nullable: true
6742
- },
6743
- content: {
6744
- type: "text",
6745
- nullable: true
6746
- },
6747
- description: {
6748
- type: "text",
6749
- nullable: true
6750
- },
6751
- scope: {
6752
- type: "jsonb",
6753
- nullable: true
6754
- },
6755
- scopeKey: {
6756
- type: "text",
6757
- nullable: true
6758
- },
6759
- mergedInto: {
6760
- type: "text",
6761
- nullable: true
6762
- }
6763
- };
6764
- const KNOWLEDGE_V2_RECORDS_SCHEMA = {
6765
- id: {
6766
- type: "text",
6767
- nullable: false,
6768
- primaryKey: true
6769
- },
6770
- node: {
6771
- type: "text",
6772
- nullable: false,
6773
- references: {
6774
- table: TABLE_KNOWLEDGE_NODES$1,
6775
- column: "id"
6776
- }
6777
- },
6778
- text: {
6779
- type: "text",
6780
- nullable: false
6781
- },
6782
- metadata: {
6783
- type: "jsonb",
6784
- nullable: true
6785
- },
6786
- version: {
6787
- type: "integer",
6788
- nullable: false
6789
- },
6790
- capturedAt: {
6791
- type: "timestamp",
6792
- nullable: false
6793
- },
6794
- updatedAt: {
6795
- type: "timestamp",
6796
- nullable: false
6797
- },
6798
- deletedAt: {
6799
- type: "timestamp",
6800
- nullable: true
6801
- },
6802
- deletedBy: {
6803
- type: "text",
6804
- nullable: true
6805
- },
6806
- scope: {
6807
- type: "jsonb",
6808
- nullable: true
6809
- },
6810
- scopeKey: {
6811
- type: "text",
6812
- nullable: true
6813
- },
6814
- sourceThreadId: {
6815
- type: "text",
6816
- nullable: true
6817
- },
6818
- when: {
6819
- type: "timestamp",
6820
- nullable: true
6821
- }
6822
- };
6823
- const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
6824
- recordId: {
6825
- type: "text",
6826
- nullable: false,
6827
- references: {
6828
- table: TABLE_KNOWLEDGE_RECORDS$1,
6829
- column: "id"
6830
- }
6831
- },
6832
- targetNodeId: {
6833
- type: "text",
6834
- nullable: true,
6835
- references: {
6836
- table: TABLE_KNOWLEDGE_NODES$1,
6837
- column: "id"
6838
- }
6839
- },
6840
- sourceType: {
6841
- type: "text",
6842
- nullable: true
6843
- },
6844
- sourceId: {
6845
- type: "text",
6846
- nullable: true
6847
- }
6848
- };
6849
- const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
6850
- nodeId: {
6851
- type: "text",
6852
- nullable: false,
6853
- references: {
6854
- table: TABLE_KNOWLEDGE_NODES$1,
6855
- column: "id"
6856
- }
6857
- },
6858
- scopeNodeId: {
6859
- type: "text",
6860
- nullable: false,
6861
- references: {
6862
- table: TABLE_KNOWLEDGE_NODES$1,
6863
- column: "id"
6864
- }
6865
- },
6866
- addedAt: {
6867
- type: "timestamp",
6868
- nullable: false
6869
- }
6870
- };
6871
- const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
6872
- recordId: {
6873
- type: "text",
6874
- nullable: false,
6875
- references: {
6876
- table: TABLE_KNOWLEDGE_RECORDS$1,
6877
- column: "id"
6878
- }
6879
- },
6880
- scopeNodeId: {
6881
- type: "text",
6882
- nullable: false,
6883
- references: {
6884
- table: TABLE_KNOWLEDGE_NODES$1,
6885
- column: "id"
6886
- }
6887
- },
6888
- addedAt: {
6889
- type: "timestamp",
6890
- nullable: false
6891
- }
6892
- };
6893
- const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
6894
- scopeNodeId: {
6895
- type: "text",
6896
- nullable: false,
6897
- references: {
6898
- table: TABLE_KNOWLEDGE_NODES$1,
6899
- column: "id"
6900
- }
6901
- },
6902
- scopeRefId: {
6903
- type: "text",
6904
- nullable: false,
6905
- references: {
6906
- table: TABLE_KNOWLEDGE_NODES$1,
6907
- column: "id"
6908
- }
6909
- },
6910
- role: {
6911
- type: "text",
6912
- nullable: false
6913
- },
6914
- canSuggest: {
6915
- type: "boolean",
6916
- nullable: true
6917
- }
6918
- };
6919
- const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
6920
- id: {
6921
- type: "text",
6922
- nullable: false,
6923
- primaryKey: true
6924
- },
6925
- epoch: {
6926
- type: "integer",
6927
- nullable: false
6928
- },
6929
- schemaVersion: {
6930
- type: "integer",
6931
- nullable: false
6932
- }
6933
- };
6934
- const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
6935
- address: {
6936
- type: "text",
6937
- nullable: false,
6938
- primaryKey: true
6939
- },
6940
- scopeNodeId: {
6941
- type: "text",
6942
- nullable: false,
6943
- references: {
6944
- table: TABLE_KNOWLEDGE_NODES$1,
6945
- column: "id"
6946
- }
6947
- }
6948
- };
6949
- const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
6950
- source: {
6951
- type: "text",
6952
- nullable: false
6953
- },
6954
- address: {
6955
- type: "text",
6956
- nullable: false
6957
- },
6958
- nodeId: {
6959
- type: "text",
6960
- nullable: false,
6961
- references: {
6962
- table: TABLE_KNOWLEDGE_NODES$1,
6963
- column: "id"
6964
- }
6965
- }
6966
- };
6967
- const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
6968
- importerId: {
6969
- type: "text",
6970
- nullable: false
6971
- },
6972
- binding: {
6973
- type: "text",
6974
- nullable: false
6975
- },
6976
- key: {
6977
- type: "text",
6978
- nullable: false
6979
- },
6980
- value: {
6981
- type: "text",
6982
- nullable: false
6983
- }
6984
- };
6985
- const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
6986
- id: {
6987
- type: "text",
6988
- nullable: false,
6989
- primaryKey: true
6990
- },
6991
- importerId: {
6992
- type: "text",
6993
- nullable: false
6994
- },
6995
- binding: {
6996
- type: "text",
6997
- nullable: false
6998
- },
6999
- importKind: {
7000
- type: "text",
7001
- nullable: false
7002
- },
7003
- triggerKind: {
7004
- type: "text",
7005
- nullable: false
7006
- },
7007
- status: {
7008
- type: "text",
7009
- nullable: false
7010
- },
7011
- error: {
7012
- type: "text",
7013
- nullable: true
7014
- },
7015
- transcriptThreadId: {
7016
- type: "text",
7017
- nullable: true
7018
- },
7019
- traceId: {
7020
- type: "text",
7021
- nullable: true
7022
- },
7023
- queuedAt: {
7024
- type: "timestamp",
7025
- nullable: false
7026
- },
7027
- startedAt: {
7028
- type: "timestamp",
7029
- nullable: true
7030
- },
7031
- completedAt: {
7032
- type: "timestamp",
7033
- nullable: true
7034
- }
7035
- };
7036
- const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
7037
- id: {
7038
- type: "text",
7039
- nullable: false,
7040
- primaryKey: true
7041
- },
7042
- action: {
7043
- type: "text",
7044
- nullable: false
7045
- },
7046
- targetType: {
7047
- type: "text",
7048
- nullable: true
7049
- },
7050
- targetId: {
7051
- type: "text",
7052
- nullable: true
7053
- },
7054
- contextScopeId: {
7055
- type: "text",
7056
- nullable: true,
7057
- references: {
7058
- table: TABLE_KNOWLEDGE_NODES$1,
7059
- column: "id"
7060
- }
7061
- },
7062
- importRunId: {
7063
- type: "text",
7064
- nullable: true,
7065
- references: {
7066
- table: TABLE_KNOWLEDGE_IMPORT_RUNS,
7067
- column: "id"
7068
- }
7069
- },
7070
- details: {
7071
- type: "jsonb",
7072
- nullable: true
7073
- },
7074
- createdAt: {
7075
- type: "timestamp",
7076
- nullable: false
7077
- },
7078
- recordType: {
7079
- type: "text",
7080
- nullable: true
7081
- },
7082
- recordId: {
7083
- type: "text",
7084
- nullable: true
7085
- },
7086
- scope: {
7087
- type: "jsonb",
7088
- nullable: true
7089
- },
7090
- scopeKey: {
7091
- type: "text",
7092
- nullable: true
7093
- },
7094
- sourceThreadId: {
7095
- type: "text",
7096
- nullable: true
7097
- }
7098
- };
7099
- const KNOWLEDGE_PROPOSALS_SCHEMA = {
7100
- id: {
7101
- type: "text",
7102
- nullable: false,
7103
- primaryKey: true
7104
- },
7105
- targetType: {
7106
- type: "text",
7107
- nullable: false
7108
- },
7109
- targetId: {
7110
- type: "text",
7111
- nullable: false
7112
- },
7113
- action: {
7114
- type: "text",
7115
- nullable: false
7116
- },
7117
- changes: {
7118
- type: "jsonb",
7119
- nullable: false
7120
- },
7121
- reason: {
7122
- type: "text",
7123
- nullable: true
7124
- },
7125
- proposerContextScopeId: {
7126
- type: "text",
7127
- nullable: false
7128
- },
7129
- expectedVersion: {
7130
- type: "integer",
7131
- nullable: false
7132
- },
7133
- status: {
7134
- type: "text",
7135
- nullable: false
7136
- },
7137
- reviewerContextScopeId: {
7138
- type: "text",
7139
- nullable: true
7140
- },
7141
- reviewedAt: {
7142
- type: "timestamp",
7143
- nullable: true
7144
- },
7145
- createdAt: {
7146
- type: "timestamp",
7147
- nullable: false
6583
+ ])];
6584
+ /** Schema-1/2 tables whose presence means the store needs a reset before schema 4 can run. */
6585
+ const LEGACY_KNOWLEDGE_TABLE_NAMES = new Set([...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES].filter((name) => !KNOWLEDGE_PAGES_TABLE_NAMES.includes(name)));
6586
+ /**
6587
+ * Classifies what an adapter found before creating schema-4 tables. `tableNames` is every
6588
+ * `mastra_knowledge_*` table present; `schemaVersion` is the state row's version, if any.
6589
+ */
6590
+ function inspectKnowledgePagesSchema(snapshot) {
6591
+ const legacy = snapshot.tableNames.filter((name) => LEGACY_KNOWLEDGE_TABLE_NAMES.has(name));
6592
+ if (legacy.length > 0) return {
6593
+ status: "incompatible-reset-required",
6594
+ schemaVersion: snapshot.schemaVersion ?? null,
6595
+ reason: `found Knowledge tables from an earlier schema (${legacy.join(", ")})`
6596
+ };
6597
+ if (snapshot.schemaVersion === 4) return {
6598
+ status: "compatible",
6599
+ schemaVersion: 4
6600
+ };
6601
+ if (snapshot.schemaVersion === void 0) {
6602
+ const present = snapshot.tableNames.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
6603
+ if (present.length === 0) return {
6604
+ status: "uninitialized",
6605
+ schemaVersion: null
6606
+ };
6607
+ return {
6608
+ status: "incompatible-reset-required",
6609
+ schemaVersion: null,
6610
+ reason: `found Knowledge tables (${present.join(", ")}) without a schema version`
6611
+ };
7148
6612
  }
7149
- };
7150
- function assertKnowledgeV2CoreSupport(features) {
7151
- if (!features.has("knowledge-v2")) throw new Error(`Knowledge v2 requires a @mastra/core release with the "${KNOWLEDGE_V2_CORE_FEATURE}" feature. Upgrade @mastra/core to use Knowledge; other storage domains keep working on this version.`);
7152
- }
7153
- function resolveKnowledgeV2Core(storageModule) {
7154
- if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
7155
- const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
7156
- if (typeof assertDescription !== "function" || typeof assertRecordText !== "function" || typeof assertCompatible !== "function" || typeof inspectSchema !== "function") throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
7157
6613
  return {
7158
- assertKnowledgeDescriptionWithinBound: (description) => assertDescription(description),
7159
- assertKnowledgeRecordTextWithinBound: (text) => assertRecordText(text),
7160
- assertKnowledgeSchemaCompatible: (inspection) => assertCompatible(inspection),
7161
- inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
6614
+ status: "incompatible-reset-required",
6615
+ schemaVersion: snapshot.schemaVersion,
6616
+ reason: `Knowledge schema version ${snapshot.schemaVersion} is not 4`
7162
6617
  };
7163
6618
  }
7164
- function createKnowledgeV2CoreLoader(features, loadStorageModule) {
7165
- let knowledgeV2Core;
6619
+ /** Schema-2 table names schema 4 reuses with a different shape. */
6620
+ const REUSED_KNOWLEDGE_TABLE_NAMES = KNOWLEDGE_TABLE_NAMES.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
6621
+ /**
6622
+ * The old Knowledge tables an adapter drops on init, rows included: Knowledge before schema 4 was an
6623
+ * experimental store with no migration. Every schema-1/2-only table present is dropped. The table
6624
+ * names schema 4 reuses (mentions, activity, the state table) are dropped too, but only when an old
6625
+ * table is present and no schema-4 state row exists, so they are old-shaped. Schema-4-only tables
6626
+ * are never listed. On a schema-4 store this lists only tables an older process recreated during a
6627
+ * rolling deploy. Drop order is safe for adapters that enforce references.
6628
+ */
6629
+ function legacyKnowledgeTablesToDrop(snapshot) {
6630
+ const present = new Set(snapshot.tableNames);
6631
+ const legacy = [...LEGACY_KNOWLEDGE_TABLE_NAMES].filter((name) => present.has(name));
6632
+ if (legacy.length === 0) return [];
6633
+ const reused = snapshot.schemaVersion === 4 ? [] : REUSED_KNOWLEDGE_TABLE_NAMES.filter((name) => present.has(name));
6634
+ const drop = /* @__PURE__ */ new Set([...legacy, ...reused]);
6635
+ return [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].filter((name) => drop.has(name));
6636
+ }
6637
+ const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
6638
+ "assertKnowledgePageDescriptionWithinBound",
6639
+ "assertKnowledgeNoteTextWithinBound",
6640
+ "canonicalKnowledgeName",
6641
+ "knowledgePositionBetween",
6642
+ "KnowledgeMoveTooLargeError",
6643
+ "KnowledgePageDepthError",
6644
+ "KnowledgePageHasChildrenError",
6645
+ "KnowledgePageKeyConflictError"
6646
+ ];
6647
+ const KNOWLEDGE_PAGES_CORE_NUMBERS = [
6648
+ "MAX_KNOWLEDGE_MOVE_PAGES",
6649
+ "MAX_KNOWLEDGE_PAGE_DEPTH",
6650
+ "MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
6651
+ ];
6652
+ function resolveKnowledgePagesCore(storageModule) {
6653
+ const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
6654
+ const missing = [...KNOWLEDGE_PAGES_CORE_FUNCTIONS.filter((name) => typeof module[name] !== "function"), ...KNOWLEDGE_PAGES_CORE_NUMBERS.filter((name) => typeof module[name] !== "number")];
6655
+ if (missing.length > 0) throw new Error(`@mastra/core advertises Knowledge pages without the required storage API (missing ${missing.join(", ")})`);
6656
+ return module;
6657
+ }
6658
+ /** Loads core's page-store runtime once, after checking core advertises `knowledge-pages`. */
6659
+ function createKnowledgePagesCoreLoader(features, loadStorageModule) {
6660
+ let core;
7166
6661
  return async () => {
7167
- assertKnowledgeV2CoreSupport(features);
7168
- knowledgeV2Core ??= loadStorageModule().then(resolveKnowledgeV2Core).catch((error) => {
7169
- knowledgeV2Core = void 0;
6662
+ 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.`);
6663
+ core ??= loadStorageModule().then(resolveKnowledgePagesCore).catch((error) => {
6664
+ core = void 0;
7170
6665
  throw error;
7171
6666
  });
7172
- return knowledgeV2Core;
6667
+ return core;
7173
6668
  };
7174
6669
  }
7175
- // src/storage/domains/knowledge/index.ts
7176
- const loadKnowledgeV2Core = createKnowledgeV2CoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
7177
- async function assertKnowledgeDescriptionWithinBoundCompat(description) {
7178
- const { assertKnowledgeDescriptionWithinBound } = await loadKnowledgeV2Core();
7179
- assertKnowledgeDescriptionWithinBound(description);
7180
- }
7181
- async function assertKnowledgeRecordTextWithinBoundCompat(text) {
7182
- const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
7183
- assertKnowledgeRecordTextWithinBound(text);
7184
- }
7185
- const reconcileChains = /* @__PURE__ */ new Map();
7186
- const unidentifiedClientReconcileKey = {};
7187
- function withReconcileLock(key, operation) {
7188
- const lockKey = typeof key === "string" ? key : unidentifiedClientReconcileKey;
7189
- const result = (reconcileChains.get(lockKey) ?? Promise.resolve()).then(operation, operation);
7190
- const tail = result.then(() => void 0, () => void 0);
7191
- reconcileChains.set(lockKey, tail);
7192
- tail.finally(() => {
7193
- if (reconcileChains.get(lockKey) === tail) reconcileChains.delete(lockKey);
7194
- });
7195
- return result;
7196
- }
7197
6670
  /**
7198
- * Scope keys visible to a caller holding `scope`: a row is visible when its scope is a subset of the
7199
- * caller's (companion entries such as `resource:x:uncurated` make this more than a prefix match).
7200
- * Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
7201
- * entries, so the list stays small.
6671
+ * Keyword-search words: the query split on whitespace, lowercased, deduplicated. Every word must
6672
+ * appear (as a case-insensitive substring) in a hit; an empty list means the search returns nothing.
7202
6673
  */
7203
- function visibleScopeKeys(scope) {
7204
- const canonical = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(scope);
7205
- const keys = /* @__PURE__ */ new Set();
7206
- for (let mask = 1; mask < 1 << canonical.length; mask++) try {
7207
- keys.add((0, _mastra_core_storage.knowledgeScopeKey)(canonical.filter((_, index) => mask & 1 << index)));
7208
- } catch {}
7209
- return [...keys];
6674
+ function knowledgeSearchWords(query) {
6675
+ return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
7210
6676
  }
7211
- const visibleSql = (keys, scopeKeyColumn = "scopeKey") => `${scopeKeyColumn} IN (${keys.map(() => "?").join(",")})`;
7212
- function parseJson$3(value) {
7213
- if (typeof value === "string") return JSON.parse(value);
7214
- if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
7215
- if (value instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(value));
7216
- return value;
7217
- }
7218
- function toDate(value) {
7219
- return new Date(String(value));
7220
- }
7221
- function optionalDate(value) {
7222
- return value == null ? void 0 : toDate(value);
6677
+ /**
6678
+ * Why `key` can't be a Knowledge page key, or undefined when it can. Keys are one path segment:
6679
+ * non-empty, with no `/` (path separator), `#` (scope/path separator) or `%` (escape character).
6680
+ */
6681
+ function knowledgePageKeyProblem(key) {
6682
+ if (!key) return "Knowledge page key must not be empty";
6683
+ if (/[/#%]/.test(key)) return `Knowledge page key "${key}" must not contain "/", "#" or "%"`;
7223
6684
  }
7224
- function canonicalName(name) {
7225
- return name.trim().toLocaleLowerCase();
6685
+ // src/storage/domains/knowledge/pages.ts
6686
+ const loadPagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
6687
+ const assertKnowledgePageDescriptionWithinBound = async (description) => (await loadPagesCore()).assertKnowledgePageDescriptionWithinBound(description);
6688
+ const assertKnowledgeNoteTextWithinBound = async (text) => (await loadPagesCore()).assertKnowledgeNoteTextWithinBound(text);
6689
+ const KNOWLEDGE_PAGE_TABLES = {
6690
+ pages: "mastra_knowledge_pages",
6691
+ notes: "mastra_knowledge_notes",
6692
+ stamps: "mastra_knowledge_note_stamps",
6693
+ mentions: "mastra_knowledge_mentions",
6694
+ pins: "mastra_knowledge_pins",
6695
+ pinAudiences: "mastra_knowledge_pin_audiences",
6696
+ activity: "mastra_knowledge_activity"
6697
+ };
6698
+ const T = KNOWLEDGE_PAGE_TABLES;
6699
+ /** DDL for the schema-4 page tables, in creation order. Every statement is idempotent. */
6700
+ const KNOWLEDGE_PAGE_DDL = [
6701
+ `CREATE TABLE IF NOT EXISTS "mastra_knowledge_access_state" (
6702
+ id TEXT PRIMARY KEY,
6703
+ schemaVersion INTEGER NOT NULL
6704
+ )`,
6705
+ `CREATE TABLE IF NOT EXISTS "${T.pages}" (
6706
+ id TEXT PRIMARY KEY,
6707
+ scopeId TEXT NOT NULL,
6708
+ isScope INTEGER NOT NULL,
6709
+ address TEXT,
6710
+ declaration TEXT,
6711
+ key TEXT,
6712
+ parentId TEXT,
6713
+ position TEXT NOT NULL,
6714
+ path TEXT NOT NULL,
6715
+ depth INTEGER NOT NULL,
6716
+ name TEXT NOT NULL,
6717
+ canonicalName TEXT NOT NULL,
6718
+ description TEXT,
6719
+ body TEXT,
6720
+ version INTEGER NOT NULL,
6721
+ mergedInto TEXT,
6722
+ createdBy TEXT NOT NULL,
6723
+ updatedBy TEXT NOT NULL,
6724
+ createdAt TEXT NOT NULL,
6725
+ updatedAt TEXT NOT NULL,
6726
+ deletedAt TEXT,
6727
+ deletedBy TEXT
6728
+ )`,
6729
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_kpages_address ON "${T.pages}" (address) WHERE isScope = 1`,
6730
+ `DROP INDEX IF EXISTS idx_kpages_key`,
6731
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_kpages_parent_key ON "${T.pages}" (parentId, key) WHERE key IS NOT NULL AND deletedAt IS NULL`,
6732
+ `CREATE INDEX IF NOT EXISTS idx_kpages_scope ON "${T.pages}" (scopeId)`,
6733
+ `CREATE INDEX IF NOT EXISTS idx_kpages_parent ON "${T.pages}" (parentId, position, id)`,
6734
+ `CREATE INDEX IF NOT EXISTS idx_kpages_path ON "${T.pages}" (path)`,
6735
+ `CREATE INDEX IF NOT EXISTS idx_kpages_declaration_parent ON "${T.pages}" (declaration, parentId) WHERE isScope = 1`,
6736
+ `CREATE INDEX IF NOT EXISTS idx_kpages_canonical ON "${T.pages}" (canonicalName)`,
6737
+ `CREATE TABLE IF NOT EXISTS "${T.notes}" (
6738
+ id TEXT PRIMARY KEY,
6739
+ pageId TEXT NOT NULL,
6740
+ text TEXT NOT NULL,
6741
+ "when" TEXT,
6742
+ provenance TEXT NOT NULL,
6743
+ version INTEGER NOT NULL,
6744
+ createdAt TEXT NOT NULL,
6745
+ updatedAt TEXT NOT NULL,
6746
+ deletedAt TEXT,
6747
+ deletedBy TEXT
6748
+ )`,
6749
+ `CREATE INDEX IF NOT EXISTS idx_knotes_page ON "${T.notes}" (pageId, createdAt, id)`,
6750
+ `CREATE TABLE IF NOT EXISTS "${T.stamps}" (
6751
+ noteId TEXT NOT NULL,
6752
+ scopeId TEXT NOT NULL,
6753
+ PRIMARY KEY (noteId, scopeId)
6754
+ )`,
6755
+ `CREATE INDEX IF NOT EXISTS idx_kstamps_scope ON "${T.stamps}" (scopeId)`,
6756
+ `CREATE TABLE IF NOT EXISTS "${T.mentions}" (
6757
+ noteId TEXT NOT NULL,
6758
+ pageId TEXT NOT NULL,
6759
+ PRIMARY KEY (noteId, pageId)
6760
+ )`,
6761
+ `CREATE INDEX IF NOT EXISTS idx_kmentions_page ON "${T.mentions}" (pageId)`,
6762
+ `CREATE TABLE IF NOT EXISTS "${T.pins}" (
6763
+ id TEXT PRIMARY KEY,
6764
+ pageId TEXT NOT NULL UNIQUE,
6765
+ audience TEXT NOT NULL,
6766
+ createdBy TEXT NOT NULL,
6767
+ createdAt TEXT NOT NULL,
6768
+ updatedAt TEXT NOT NULL
6769
+ )`,
6770
+ `CREATE TABLE IF NOT EXISTS "${T.pinAudiences}" (
6771
+ pinId TEXT NOT NULL,
6772
+ audience TEXT NOT NULL,
6773
+ PRIMARY KEY (audience, pinId)
6774
+ ) WITHOUT ROWID`,
6775
+ `CREATE INDEX IF NOT EXISTS idx_kpin_audiences_pin ON "${T.pinAudiences}" (pinId)`,
6776
+ `INSERT OR IGNORE INTO "${T.pinAudiences}" (audience, pinId) SELECT j.value, pin.id FROM "${T.pins}" pin, json_each(pin.audience) j`,
6777
+ `CREATE TABLE IF NOT EXISTS "${T.activity}" (
6778
+ id TEXT PRIMARY KEY,
6779
+ action TEXT NOT NULL,
6780
+ scopeId TEXT NOT NULL,
6781
+ pageId TEXT NOT NULL,
6782
+ noteId TEXT,
6783
+ provenance TEXT NOT NULL,
6784
+ createdAt TEXT NOT NULL
6785
+ )`,
6786
+ `CREATE INDEX IF NOT EXISTS idx_kactivity_page ON "${T.activity}" (pageId, id DESC)`
6787
+ ];
6788
+ function str(value) {
6789
+ return value === null || value === void 0 ? void 0 : String(value);
7226
6790
  }
7227
- function escapeLikePattern(value) {
7228
- return value.replaceAll("=", "==").replaceAll("%", "=%").replaceAll("_", "=_");
6791
+ function date(value) {
6792
+ return value === null || value === void 0 ? void 0 : new Date(String(value));
7229
6793
  }
7230
- function nodeReferenceId(node) {
7231
- return typeof node === "string" ? node : node.id;
6794
+ function provenance(raw) {
6795
+ return JSON.parse(String(raw));
7232
6796
  }
7233
- function parseNode(row) {
6797
+ function toPage(row) {
7234
6798
  return {
7235
6799
  id: String(row.id),
7236
- type: "node",
6800
+ scopeId: String(row.scopeId),
6801
+ parentId: str(row.parentId) ?? null,
6802
+ isScope: Number(row.isScope) === 1,
6803
+ ...row.address !== null && row.address !== void 0 ? { address: String(row.address) } : {},
6804
+ ...row.declaration !== null && row.declaration !== void 0 ? { declaration: String(row.declaration) } : {},
6805
+ ...row.key !== null && row.key !== void 0 ? { key: String(row.key) } : {},
6806
+ position: String(row.position),
7237
6807
  name: String(row.name),
7238
- kind: row.kind == null ? "" : String(row.kind),
7239
- content: row.content == null ? void 0 : String(row.content),
7240
- description: row.description == null ? void 0 : String(row.description),
7241
- isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
7242
- scope: parseJson$3(row.scopeJson ?? row.scope),
6808
+ ...row.description !== null && row.description !== void 0 ? { description: String(row.description) } : {},
6809
+ ...row.body !== null && row.body !== void 0 ? { body: String(row.body) } : {},
7243
6810
  version: Number(row.version),
7244
- mergedInto: row.mergedInto == null ? void 0 : String(row.mergedInto),
7245
- createdAt: toDate(row.createdAt),
7246
- updatedAt: toDate(row.updatedAt)
6811
+ ...row.mergedInto !== null && row.mergedInto !== void 0 ? { mergedInto: String(row.mergedInto) } : {},
6812
+ createdBy: provenance(row.createdBy),
6813
+ updatedBy: provenance(row.updatedBy),
6814
+ createdAt: new Date(String(row.createdAt)),
6815
+ updatedAt: new Date(String(row.updatedAt)),
6816
+ ...row.deletedAt ? { deletedAt: new Date(String(row.deletedAt)) } : {},
6817
+ ...row.deletedBy ? { deletedBy: String(row.deletedBy) } : {}
7247
6818
  };
7248
6819
  }
7249
- function parseKnowledge(row) {
6820
+ function toNote(row, stamps, mentions) {
6821
+ const when = date(row.when);
7250
6822
  return {
7251
6823
  id: String(row.id),
7252
- node: String(row.node),
6824
+ pageId: String(row.pageId),
7253
6825
  text: String(row.text),
7254
- scope: parseJson$3(row.scopeJson ?? row.scope),
7255
- sourceThreadId: String(row.sourceThreadId),
7256
- capturedAt: toDate(row.capturedAt),
7257
- when: optionalDate(row.when),
7258
- metadata: (row.metadataJson ?? row.metadata) == null ? void 0 : parseJson$3(row.metadataJson ?? row.metadata),
7259
- deletedAt: optionalDate(row.deletedAt),
7260
- deletedBy: row.deletedBy == null ? void 0 : String(row.deletedBy)
6826
+ ...when ? { when } : {},
6827
+ stampScopeIds: stamps,
6828
+ mentions,
6829
+ provenance: provenance(row.provenance),
6830
+ version: Number(row.version),
6831
+ createdAt: new Date(String(row.createdAt)),
6832
+ updatedAt: new Date(String(row.updatedAt)),
6833
+ ...row.deletedAt ? { deletedAt: new Date(String(row.deletedAt)) } : {},
6834
+ ...row.deletedBy ? { deletedBy: String(row.deletedBy) } : {}
7261
6835
  };
7262
6836
  }
7263
- function parseOutbox(row) {
6837
+ function toPin(row) {
7264
6838
  return {
7265
6839
  id: String(row.id),
7266
- idempotencyKey: String(row.idempotencyKey),
7267
- documentId: String(row.documentId),
7268
- documentType: String(row.documentType),
7269
- operation: String(row.operation),
7270
- scope: parseJson$3(row.scopeJson ?? row.scope),
7271
- status: String(row.status),
7272
- attempts: Number(row.attempts),
7273
- availableAt: toDate(row.availableAt),
7274
- claimedAt: optionalDate(row.claimedAt),
7275
- claimedBy: row.claimedBy == null ? void 0 : String(row.claimedBy),
7276
- createdAt: toDate(row.createdAt),
7277
- completedAt: optionalDate(row.completedAt)
6840
+ pageId: String(row.pageId),
6841
+ audience: JSON.parse(String(row.audience)),
6842
+ createdBy: String(row.createdBy),
6843
+ createdAt: new Date(String(row.createdAt)),
6844
+ updatedAt: new Date(String(row.updatedAt))
7278
6845
  };
7279
6846
  }
7280
- const KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE = "Knowledge curation cursors were removed: observation-time curate is the only Knowledge writer and needs no cursor.";
7281
- const knowledgeV2TableSchemas = /* @__PURE__ */ new Map([
7282
- [_mastra_core_storage.TABLE_KNOWLEDGE_NODES, KNOWLEDGE_V2_NODES_SCHEMA],
7283
- [_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS, KNOWLEDGE_V2_RECORDS_SCHEMA],
7284
- [_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS, KNOWLEDGE_V2_MENTIONS_SCHEMA],
7285
- [_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY, KNOWLEDGE_V2_ACTIVITY_SCHEMA],
7286
- [_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX, _mastra_core_storage.KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA],
7287
- [TABLE_KNOWLEDGE_NODE_SCOPES, KNOWLEDGE_NODE_SCOPES_SCHEMA],
7288
- [TABLE_KNOWLEDGE_RECORD_SCOPES, KNOWLEDGE_RECORD_SCOPES_SCHEMA],
7289
- [TABLE_KNOWLEDGE_SCOPE_GRANTS, KNOWLEDGE_SCOPE_GRANTS_SCHEMA],
7290
- [TABLE_KNOWLEDGE_ACCESS_STATE, KNOWLEDGE_ACCESS_STATE_SCHEMA],
7291
- [TABLE_KNOWLEDGE_SCOPE_ADDRESSES, KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA],
7292
- [TABLE_KNOWLEDGE_NODE_ADDRESSES, KNOWLEDGE_NODE_ADDRESSES_SCHEMA],
7293
- [TABLE_KNOWLEDGE_IMPORT_STATE, KNOWLEDGE_IMPORT_STATE_SCHEMA],
7294
- [TABLE_KNOWLEDGE_IMPORT_RUNS, KNOWLEDGE_IMPORT_RUNS_SCHEMA],
7295
- [TABLE_KNOWLEDGE_PROPOSALS, KNOWLEDGE_PROPOSALS_SCHEMA]
7296
- ]);
7297
- function canonicalizeLibSQLUrl(url) {
7298
- if (url.includes(":memory:")) return /[?&]cache=shared(?:&|$)/.test(url) ? "file::memory:?cache=shared" : void 0;
7299
- if (url.startsWith("file:")) {
7300
- const [path$1] = url.slice(5).split("?");
7301
- if (url.startsWith("file://")) {
7302
- const parsed = new URL(url);
7303
- const host = parsed.hostname.toLocaleLowerCase();
7304
- const filePath = host && host !== "localhost" ? `//${host}${parsed.pathname}` : parsed.pathname;
7305
- return `file:${(0, path.resolve)(decodeURIComponent(filePath))}`;
7306
- }
7307
- return `file:${(0, path.resolve)(decodeURIComponent(path$1))}`;
7308
- }
6847
+ function toListedPin(row) {
6848
+ return {
6849
+ ...toPin(row),
6850
+ pageName: String(row.pageName)
6851
+ };
6852
+ }
6853
+ function limitOf(limit, max) {
6854
+ return Math.max(1, Math.min(limit ?? max, max));
6855
+ }
6856
+ function encodeCursor(value) {
6857
+ return Buffer.from(JSON.stringify(value)).toString("base64url");
6858
+ }
6859
+ function decodeCursor(cursor, length) {
6860
+ if (cursor === void 0) return void 0;
7309
6861
  try {
7310
- const parsed = new URL(url);
7311
- const port = parsed.port || (parsed.protocol === "libsql:" || parsed.protocol === "https:" ? "443" : "80");
7312
- return `${parsed.protocol}//${parsed.hostname.toLocaleLowerCase()}:${port}${parsed.pathname}`;
7313
- } catch {
7314
- return url;
7315
- }
6862
+ const parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
6863
+ if (Array.isArray(parsed) && parsed.length === length && parsed.every((item) => typeof item === "string")) return parsed;
6864
+ } catch {}
6865
+ throw new Error("Invalid Knowledge cursor");
7316
6866
  }
7317
- const unidentifiedClientIsolationKey = {};
7318
- function getLibSQLKnowledgeIsolationKey(config, client) {
7319
- if (config.storageIsolationKey !== void 0) return config.storageIsolationKey;
7320
- if (!config.url) return unidentifiedClientIsolationKey;
7321
- const urlKey = canonicalizeLibSQLUrl(config.url);
7322
- return urlKey ? `libsql:${urlKey}` : client ?? config.client ?? config;
6867
+ function escapeLike(value) {
6868
+ return value.replace(/[=%_]/g, (match) => `=${match}`);
7323
6869
  }
7324
- var KnowledgeLibSQL = class extends _mastra_core_storage.KnowledgeStorage {
7325
- #client;
7326
- #db;
7327
- constructor(config) {
7328
- const client = resolveClient(config);
7329
- const storageIsolationKey = config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, client);
7330
- super({ storageIsolationKey });
7331
- this.#client = client;
7332
- this.#db = new LibSQLDB({
7333
- client: this.#client,
7334
- maxRetries: config.maxRetries,
7335
- initialBackoffMs: config.initialBackoffMs
7336
- });
7337
- }
7338
- getCapabilities() {
7339
- return {
7340
- contractVersion: 2,
7341
- schemaVersion: 2,
7342
- supportsV2: true,
7343
- supportsSchemaInspection: true,
7344
- supportsExplicitReset: true
7345
- };
7346
- }
7347
- async inspectSchema() {
7348
- return this.#inspectSchema(this.#client);
7349
- }
7350
- async #inspectSchema(executor) {
7351
- const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
7352
- try {
7353
- const placeholders = KNOWLEDGE_TABLE_NAMES.map(() => "?").join(",");
7354
- const tableNames = (await executor.execute({
7355
- sql: `SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${placeholders})`,
7356
- args: [...KNOWLEDGE_TABLE_NAMES]
7357
- })).rows.map((row) => String(row.name));
7358
- if (tableNames.length === 0) return inspectKnowledgeSchema({
7359
- available: true,
7360
- tableNames
7361
- });
7362
- const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
7363
- if (missingTables.length > 0) return inspectKnowledgeSchema({
7364
- available: true,
7365
- tableNames,
7366
- reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
7367
- });
7368
- for (const [table, schema] of knowledgeV2TableSchemas) {
7369
- const columns = await executor.execute(`PRAGMA table_info("${table}")`);
7370
- const actual = new Set(columns.rows.map((row) => String(row.name)));
7371
- const missing = Object.keys(schema).filter((column) => !actual.has(column));
7372
- if (missing.length > 0) return inspectKnowledgeSchema({
7373
- available: true,
7374
- tableNames,
7375
- reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
7376
- });
7377
- }
7378
- const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
7379
- if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
7380
- available: true,
7381
- tableNames,
7382
- reason: "Knowledge schema version marker is missing or incompatible"
7383
- });
7384
- return inspectKnowledgeSchema({
7385
- available: true,
7386
- tableNames,
7387
- schemaVersion: 2
7388
- });
7389
- } catch (error) {
7390
- return inspectKnowledgeSchema({
7391
- available: false,
7392
- tableNames: [],
7393
- reason: error instanceof Error ? error.message : String(error)
7394
- });
6870
+ /**
6871
+ * A string above every string that starts with `prefix`, comparing by code point (which is also
6872
+ * UTF-8 byte order, so it matches SQLite's BINARY collation). `column >= prefix AND column < bound`
6873
+ * is an exact, case-sensitive prefix match that an index range scan can serve, where
6874
+ * `substr(column, 1, length(prefix)) = prefix` cannot. When the last code point cannot be raised
6875
+ * (an empty prefix, or one made only of U+10FFFF), the bound is the prefix plus more U+10FFFF than
6876
+ * any stored column can hold.
6877
+ */
6878
+ function prefixUpperBound(prefix) {
6879
+ const points = Array.from(prefix);
6880
+ while (points.length > 0) {
6881
+ const last = points.pop().codePointAt(0);
6882
+ if (last < 1114111) {
6883
+ const next = last === 55295 ? 57344 : last + 1;
6884
+ return points.join("") + String.fromCodePoint(next);
7395
6885
  }
7396
6886
  }
7397
- async init() {
7398
- await this.#transaction((tx) => this.#initializeSchema(tx));
7399
- }
7400
- async #initializeSchema(tx) {
7401
- const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
7402
- let inspection = await this.#inspectSchema(tx);
7403
- if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
7404
- for (const table of [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()]) await tx.execute(`DROP TABLE IF EXISTS "${table}"`);
7405
- inspection = await this.#inspectSchema(tx);
7406
- }
7407
- assertKnowledgeSchemaCompatible(inspection);
7408
- const createTable = (input) => this.#db.createTable({
7409
- ...input,
7410
- executor: tx
7411
- });
7412
- await createTable({
7413
- tableName: _mastra_core_storage.TABLE_KNOWLEDGE_NODES,
7414
- schema: KNOWLEDGE_V2_NODES_SCHEMA
7415
- });
7416
- await createTable({
7417
- tableName: _mastra_core_storage.TABLE_KNOWLEDGE_RECORDS,
7418
- schema: KNOWLEDGE_V2_RECORDS_SCHEMA
7419
- });
7420
- await createTable({
7421
- tableName: _mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS,
7422
- schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
7423
- compositePrimaryKey: ["recordId", "sourceId"]
7424
- });
7425
- await createTable({
7426
- tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
7427
- schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
7428
- compositePrimaryKey: ["nodeId", "scopeNodeId"]
7429
- });
7430
- await createTable({
7431
- tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
7432
- schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
7433
- compositePrimaryKey: ["recordId", "scopeNodeId"]
7434
- });
7435
- await createTable({
7436
- tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
7437
- schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
7438
- compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
7439
- });
7440
- await createTable({
7441
- tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
7442
- schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
7443
- });
7444
- await createTable({
7445
- tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
7446
- schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
7447
- });
7448
- await createTable({
7449
- tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
7450
- schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
7451
- compositePrimaryKey: ["source", "address"]
7452
- });
7453
- await createTable({
7454
- tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
7455
- schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
7456
- compositePrimaryKey: [
7457
- "importerId",
7458
- "binding",
7459
- "key"
7460
- ]
7461
- });
7462
- await createTable({
7463
- tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
7464
- schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
7465
- });
7466
- await createTable({
7467
- tableName: TABLE_KNOWLEDGE_PROPOSALS,
7468
- schema: KNOWLEDGE_PROPOSALS_SCHEMA
7469
- });
7470
- await createTable({
7471
- tableName: _mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
7472
- schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
7473
- });
7474
- await createTable({
7475
- tableName: _mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
7476
- schema: _mastra_core_storage.KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
7477
- });
7478
- for (const statement of [
7479
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_nodes_identity ON "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (type, scopeKey, canonicalName)`,
7480
- `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_scope ON "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (scopeKey, type)`,
7481
- `CREATE INDEX IF NOT EXISTS idx_knowledge_nodes_name ON "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (type, canonicalName)`,
7482
- `CREATE INDEX IF NOT EXISTS idx_knowledge_records_node_latest ON "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" (node, id DESC)`,
7483
- `CREATE INDEX IF NOT EXISTS idx_knowledge_records_thread_latest ON "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" (sourceThreadId, id DESC)`,
7484
- `CREATE INDEX IF NOT EXISTS idx_knowledge_mentions_record ON "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" (recordId, sourceType, sourceId)`,
7485
- `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_latest ON "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (id DESC)`,
7486
- `CREATE INDEX IF NOT EXISTS idx_knowledge_records_scope ON "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" (scopeKey, id)`,
7487
- `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_scope ON "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (scopeKey, id DESC)`,
7488
- `CREATE UNIQUE INDEX IF NOT EXISTS idx_knowledge_outbox_idempotency ON "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (idempotencyKey)`,
7489
- `CREATE INDEX IF NOT EXISTS idx_knowledge_outbox_claim ON "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (status, availableAt, createdAt)`,
7490
- `CREATE INDEX IF NOT EXISTS idx_knowledge_node_scopes_scope ON "${TABLE_KNOWLEDGE_NODE_SCOPES}" (scopeNodeId, nodeId)`,
7491
- `CREATE INDEX IF NOT EXISTS idx_knowledge_record_scopes_scope ON "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (scopeNodeId, recordId)`,
7492
- `CREATE INDEX IF NOT EXISTS idx_knowledge_scope_grants_ref ON "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeRefId, scopeNodeId)`,
7493
- `CREATE INDEX IF NOT EXISTS idx_knowledge_node_addresses_node ON "${TABLE_KNOWLEDGE_NODE_ADDRESSES}" (nodeId)`,
7494
- `CREATE INDEX IF NOT EXISTS idx_knowledge_import_runs_lookup ON "${TABLE_KNOWLEDGE_IMPORT_RUNS}" (importerId, binding, queuedAt DESC)`,
7495
- `CREATE INDEX IF NOT EXISTS idx_knowledge_activity_import_run ON "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (importRunId, id DESC)`
7496
- ]) await tx.execute(statement);
7497
- await tx.execute({
7498
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?)`,
7499
- args: [2]
7500
- });
7501
- }
7502
- /**
7503
- * True when the only Knowledge objects are the tables and indexes published v1 adapters created,
7504
- * with or without rows. Anything else (v2 tables, unknown tables, views, triggers) needs an
7505
- * explicit reset.
7506
- */
7507
- async #isPublishedV1Layout(executor) {
7508
- const objects = await executor.execute("SELECT type, name FROM sqlite_master WHERE name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR tbl_name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\' OR sql LIKE '%mastra\\_knowledge\\_%' ESCAPE '\\'");
7509
- for (const row of objects.rows) {
7510
- const type = String(row.type);
7511
- const name = String(row.name);
7512
- if (type === "table" && PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) continue;
7513
- const knownIndex = PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) || name.startsWith("sqlite_autoindex_");
7514
- if (type !== "index" || !knownIndex) return false;
7515
- }
7516
- return true;
6887
+ return prefix + "􏿿".repeat(4096);
6888
+ }
6889
+ /** `column` starts with `prefix`, as an index-friendly range. */
6890
+ function prefixRange(column, prefix) {
6891
+ return {
6892
+ sql: `(${column} >= ? AND ${column} < ?)`,
6893
+ args: [prefix, prefixUpperBound(prefix)]
6894
+ };
6895
+ }
6896
+ /**
6897
+ * The readable scope page ids as a subquery. The caller's addresses, prefixes and children go in as
6898
+ * JSON arrays so a large read scope never hits a bind-parameter limit. Each kind is its own index
6899
+ * lookup (address equality, address range for a prefix, `(declaration, parentId)` for children),
6900
+ * unioned, so the cost follows the caller's reach rather than the number of scopes in the store.
6901
+ */
6902
+ function readableScopes(read) {
6903
+ if (read === "all") return {
6904
+ sql: `SELECT id FROM "${T.pages}" WHERE isScope = 1`,
6905
+ args: []
6906
+ };
6907
+ const parts = [];
6908
+ const args = [];
6909
+ if (read.addresses.length > 0) {
6910
+ parts.push(`SELECT id FROM "${T.pages}" WHERE isScope = 1 AND address IN (SELECT value FROM json_each(?))`);
6911
+ args.push(JSON.stringify(read.addresses));
6912
+ }
6913
+ if (read.prefixes.length > 0) {
6914
+ parts.push(`SELECT sp.id FROM json_each(?) j CROSS JOIN "${T.pages}" sp ON sp.isScope = 1 AND sp.address >= json_extract(j.value, '$.lo') AND sp.address < json_extract(j.value, '$.hi') AND +sp.declaration = json_extract(j.value, '$.declaration')`);
6915
+ args.push(JSON.stringify(read.prefixes.map((entry) => ({
6916
+ declaration: entry.declaration,
6917
+ lo: entry.prefix,
6918
+ hi: prefixUpperBound(entry.prefix)
6919
+ }))));
6920
+ }
6921
+ if (read.children.length > 0) {
6922
+ parts.push(`SELECT sp.id FROM json_each(?) j CROSS JOIN "${T.pages}" sp ON sp.isScope = 1 AND sp.declaration = json_extract(j.value, '$.declaration') AND sp.parentId IS json_extract(j.value, '$.parentId')`);
6923
+ args.push(JSON.stringify(read.children));
6924
+ }
6925
+ if (parts.length === 0) return {
6926
+ sql: `SELECT id FROM "${T.pages}" WHERE 0`,
6927
+ args: []
6928
+ };
6929
+ return {
6930
+ sql: parts.join(" UNION "),
6931
+ args
6932
+ };
6933
+ }
6934
+ /** `alias.scopeId` is readable. */
6935
+ function pageReadable(alias, read) {
6936
+ const scopes = readableScopes(read);
6937
+ return {
6938
+ sql: `${alias}.scopeId IN (${scopes.sql})`,
6939
+ args: scopes.args
6940
+ };
6941
+ }
6942
+ /**
6943
+ * Note `alias` is visible: its page's scope is readable, a stamp is readable, and every resolved
6944
+ * mention's page sits in a readable scope.
6945
+ */
6946
+ function noteReadable(alias, read) {
6947
+ const scopes = readableScopes(read);
6948
+ return {
6949
+ sql: `EXISTS (SELECT 1 FROM "${T.pages}" vp WHERE vp.id = ${alias}.pageId AND vp.scopeId IN (${scopes.sql}))
6950
+ AND EXISTS (SELECT 1 FROM "${T.stamps}" vs WHERE vs.noteId = ${alias}.id AND vs.scopeId IN (${scopes.sql}))
6951
+ AND NOT EXISTS (SELECT 1 FROM "${T.mentions}" vm JOIN "${T.pages}" vmp ON vmp.id = vm.pageId WHERE vm.noteId = ${alias}.id AND vmp.scopeId NOT IN (${scopes.sql}))`,
6952
+ args: [
6953
+ ...scopes.args,
6954
+ ...scopes.args,
6955
+ ...scopes.args
6956
+ ]
6957
+ };
6958
+ }
6959
+ function jsonList(values) {
6960
+ return JSON.stringify(values);
6961
+ }
6962
+ var LibSQLKnowledgePages = class {
6963
+ #read;
6964
+ #transaction;
6965
+ constructor(config) {
6966
+ this.#read = config.read;
6967
+ this.#transaction = config.transaction;
7517
6968
  }
7518
- async dangerouslyClearAll() {
6969
+ /** Deletes every row from the page tables (scopes, pages, notes, pins, activity). */
6970
+ async clearAll() {
7519
6971
  await this.#transaction(async (tx) => {
7520
6972
  for (const table of [
7521
- TABLE_KNOWLEDGE_RECORD_SCOPES,
7522
- TABLE_KNOWLEDGE_NODE_SCOPES,
7523
- TABLE_KNOWLEDGE_SCOPE_GRANTS,
7524
- TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
7525
- TABLE_KNOWLEDGE_NODE_ADDRESSES,
7526
- _mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS,
7527
- TABLE_KNOWLEDGE_PROPOSALS,
7528
- _mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
7529
- TABLE_KNOWLEDGE_IMPORT_STATE,
7530
- TABLE_KNOWLEDGE_IMPORT_RUNS,
7531
- _mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
7532
- _mastra_core_storage.TABLE_KNOWLEDGE_RECORDS,
7533
- _mastra_core_storage.TABLE_KNOWLEDGE_NODES
7534
- ]) await tx.execute(`DELETE FROM "${table}"`);
7535
- await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
6973
+ T.activity,
6974
+ T.pinAudiences,
6975
+ T.pins,
6976
+ T.mentions,
6977
+ T.stamps,
6978
+ T.notes,
6979
+ T.pages
6980
+ ]) await tx.execute({
6981
+ sql: `DELETE FROM ${table}`,
6982
+ args: []
6983
+ });
7536
6984
  });
7537
6985
  }
7538
- async dangerouslyReset() {
7539
- await withClientWriteLock(this.#client, async () => {
7540
- await this.#client.batch([...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => ({
7541
- sql: `DROP TABLE IF EXISTS "${table}"`,
7542
- args: []
7543
- })), "write");
6986
+ async #query(executor, sql, args = []) {
6987
+ return (await executor.execute({
6988
+ sql,
6989
+ args
6990
+ })).rows;
6991
+ }
6992
+ async #pageRow(executor, id) {
6993
+ return (await this.#query(executor, `SELECT * FROM "${T.pages}" WHERE id = ?`, [id]))[0];
6994
+ }
6995
+ async #livePage(executor, id) {
6996
+ const row = await this.#pageRow(executor, id);
6997
+ if (!row || row.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("page", id);
6998
+ return row;
6999
+ }
7000
+ async #logActivity(tx, action, event, now) {
7001
+ await tx.execute({
7002
+ sql: `INSERT INTO "${T.activity}" (id, action, scopeId, pageId, noteId, provenance, createdAt) VALUES (?, ?, ?, ?, ?, ?, ?)`,
7003
+ args: [
7004
+ (0, _mastra_core_storage.createKnowledgeUlid)(),
7005
+ action,
7006
+ event.scopeId,
7007
+ event.pageId,
7008
+ event.noteId ?? null,
7009
+ JSON.stringify(event.provenance),
7010
+ now
7011
+ ]
7544
7012
  });
7545
- await this.init();
7546
7013
  }
7547
- async reconcileStructure(plan) {
7548
- return withReconcileLock(this.getStorageIsolationKey(), () => this.#transaction(async (tx) => {
7549
- await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch WHERE id='global'`);
7550
- const scopes = {};
7551
- const createdScopeIds = [];
7552
- const createdAddresses = /* @__PURE__ */ new Set();
7553
- const retrofit = plan.retrofit ?? true;
7554
- let structureChanged = false;
7555
- const deletedScopeAddresses = /* @__PURE__ */ new Set();
7556
- const resolveAddress = async (address) => {
7557
- if (scopes[address]) return scopes[address];
7558
- const row = (await tx.execute({
7559
- sql: `SELECT a.scopeNodeId,n.isScope,n.deletedAt FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n ON n.id=a.scopeNodeId WHERE a.address=?`,
7560
- args: [address]
7561
- })).rows[0];
7562
- if (!row) return void 0;
7563
- if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
7564
- if (row.deletedAt) deletedScopeAddresses.add(address);
7565
- scopes[address] = String(row.scopeNodeId);
7566
- return scopes[address];
7567
- };
7568
- for (const scope of plan.scopes) {
7569
- if (await resolveAddress(scope.address)) continue;
7570
- const id = (0, crypto$1.randomUUID)();
7571
- const now = (/* @__PURE__ */ new Date()).toISOString();
7014
+ async #lastPosition(tx, parentId, scopesOnly) {
7015
+ const rows = await this.#query(tx, `SELECT position FROM "${T.pages}" WHERE parentId IS ? AND isScope = ? AND deletedAt IS NULL ORDER BY position DESC, id DESC LIMIT 1`, [parentId, scopesOnly ? 1 : 0]);
7016
+ return rows[0] ? String(rows[0].position) : void 0;
7017
+ }
7018
+ async materializeScope(input) {
7019
+ const core = await loadPagesCore();
7020
+ await assertKnowledgePageDescriptionWithinBound(input.description);
7021
+ return this.#transaction(async (tx) => {
7022
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7023
+ const ensureScope = async (spec, parentId) => {
7024
+ await assertKnowledgePageDescriptionWithinBound(spec.description);
7025
+ const existing = (await this.#query(tx, `SELECT * FROM "${T.pages}" WHERE isScope = 1 AND address = ?`, [spec.address]))[0];
7026
+ if (existing) return {
7027
+ row: existing,
7028
+ created: false
7029
+ };
7030
+ if (parentId !== null) await this.#livePage(tx, parentId);
7031
+ const id = (0, _mastra_core_storage.createKnowledgeUlid)();
7032
+ const position = core.knowledgePositionBetween(await this.#lastPosition(tx, parentId, true));
7033
+ const who = JSON.stringify(input.provenance);
7572
7034
  await tx.execute({
7573
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,description,isScope,metadata,version,createdAt,updatedAt) VALUES (?,'node',?,?,?,?,TRUE,jsonb(?),1,?,?)`,
7035
+ sql: `INSERT INTO "${T.pages}" (id, scopeId, isScope, address, declaration, parentId, position, path, depth, name, canonicalName, description, version, createdBy, updatedBy, createdAt, updatedAt) VALUES (?, ?, 1, ?, ?, ?, ?, ?, 0, ?, ?, ?, 1, ?, ?, ?, ?)`,
7574
7036
  args: [
7575
7037
  id,
7576
- scope.name,
7577
- canonicalName(scope.name),
7578
- scope.kind ?? null,
7579
- scope.description ?? null,
7580
- scope.metadata ? JSON.stringify(scope.metadata) : null,
7038
+ id,
7039
+ spec.address,
7040
+ spec.declaration,
7041
+ parentId,
7042
+ position,
7043
+ `/${id}/`,
7044
+ spec.name,
7045
+ core.canonicalKnowledgeName(spec.name),
7046
+ spec.description ?? null,
7047
+ who,
7048
+ who,
7581
7049
  now,
7582
7050
  now
7583
7051
  ]
7584
7052
  });
7053
+ await this.#logActivity(tx, "scope-created", {
7054
+ scopeId: id,
7055
+ pageId: id,
7056
+ provenance: input.provenance
7057
+ }, now);
7058
+ return {
7059
+ row: await this.#pageRow(tx, id),
7060
+ created: true
7061
+ };
7062
+ };
7063
+ const ensured = await ensureScope(input, input.parentId ?? null);
7064
+ let row = ensured.row;
7065
+ const created = ensured.created;
7066
+ const scopeId = String(row.id);
7067
+ if (!created && input.parentId != null && str(row.parentId) === void 0) {
7068
+ await this.#livePage(tx, input.parentId);
7069
+ if (input.parentId === scopeId) throw new Error(`Scope ${scopeId} cannot be its own parent`);
7070
+ let ancestor = input.parentId;
7071
+ while (ancestor) {
7072
+ const up = await this.#pageRow(tx, ancestor);
7073
+ if (String(up.id) === scopeId) throw new Error(`Scope ${scopeId} cannot be linked under its own descendant`);
7074
+ ancestor = str(up.parentId);
7075
+ }
7585
7076
  await tx.execute({
7586
- sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,scopeNodeId) VALUES (?,?)`,
7587
- args: [scope.address, id]
7077
+ sql: `UPDATE "${T.pages}" SET parentId = ?, position = ?, version = version + 1, updatedAt = ? WHERE id = ? AND parentId IS NULL`,
7078
+ args: [
7079
+ input.parentId,
7080
+ core.knowledgePositionBetween(await this.#lastPosition(tx, input.parentId, true)),
7081
+ now,
7082
+ scopeId
7083
+ ]
7588
7084
  });
7589
- scopes[scope.address] = id;
7590
- createdAddresses.add(scope.address);
7591
- createdScopeIds.push(id);
7592
- structureChanged = true;
7085
+ row = await this.#pageRow(tx, scopeId);
7593
7086
  }
7594
- for (const scope of plan.scopes) {
7595
- if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
7596
- const scopeNodeId = scopes[scope.address];
7597
- for (const parentAddress of scope.parentAddresses ?? []) {
7598
- const parentId = await resolveAddress(parentAddress);
7599
- if (!parentId || deletedScopeAddresses.has(parentAddress)) {
7600
- const deletedParentId = scopes[parentAddress];
7601
- if ((deletedParentId ? await tx.execute({
7602
- sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
7603
- args: [scopeNodeId, deletedParentId]
7604
- }) : void 0)?.rows.length) continue;
7605
- throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
7606
- }
7607
- if ((await tx.execute({
7608
- sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE nodeId=? AND scopeNodeId=?`,
7609
- args: [scopeNodeId, parentId]
7610
- })).rows.length) continue;
7611
- if ((await tx.execute({
7612
- sql: `SELECT n.id FROM "mastra_knowledge_node_scopes" ns JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n ON n.id=ns.nodeId WHERE ns.scopeNodeId=? AND n.isScope AND n.canonicalName=? AND n.deletedAt IS NULL AND n.id<>? LIMIT 1`,
7613
- args: [
7614
- parentId,
7615
- canonicalName(scope.name),
7616
- scopeNodeId
7617
- ]
7618
- })).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
7619
- const inserted = await tx.execute({
7620
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
7621
- args: [
7622
- scopeNodeId,
7623
- parentId,
7624
- (/* @__PURE__ */ new Date()).toISOString()
7625
- ]
7626
- });
7627
- structureChanged ||= inserted.rowsAffected > 0;
7628
- }
7629
- for (const grant of scope.grants ?? []) {
7630
- const scopeRefId = await resolveAddress(grant.scopeRefAddress);
7631
- if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
7632
- const deletedScopeRefId = scopes[grant.scopeRefAddress];
7633
- if ((deletedScopeRefId ? await tx.execute({
7634
- sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE scopeNodeId=? AND scopeRefId=?`,
7635
- args: [scopeNodeId, deletedScopeRefId]
7636
- }) : void 0)?.rows.length) continue;
7637
- throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
7638
- }
7639
- const inserted = await tx.execute({
7640
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" (scopeNodeId,scopeRefId,role,canSuggest) VALUES (?,?,?,?)`,
7641
- args: [
7642
- scopeNodeId,
7643
- scopeRefId,
7644
- grant.role,
7645
- grant.canSuggest ?? null
7646
- ]
7647
- });
7648
- structureChanged ||= inserted.rowsAffected > 0;
7087
+ let parentConflict;
7088
+ if (!created && input.parentId !== void 0 && (str(row.parentId) ?? null) !== input.parentId) parentConflict = {
7089
+ existingParentId: str(row.parentId) ?? null,
7090
+ requestedParentId: input.parentId
7091
+ };
7092
+ for (const child of input.children ?? []) await ensureScope(child, scopeId);
7093
+ const ensureTemplates = async (templates, parentId) => {
7094
+ for (const template of templates) {
7095
+ const problem = knowledgePageKeyProblem(template.key);
7096
+ if (problem) throw new Error(problem);
7097
+ const existing = (await this.#query(tx, `SELECT id FROM "${T.pages}" WHERE parentId = ? AND isScope = 0 AND key = ? AND deletedAt IS NULL`, [parentId, template.key]))[0];
7098
+ const id = existing ? String(existing.id) : (await this.#insertPage(tx, {
7099
+ parentId,
7100
+ key: template.key,
7101
+ name: template.name,
7102
+ description: template.description,
7103
+ provenance: input.provenance
7104
+ })).id;
7105
+ if (template.pages?.length) await ensureTemplates(template.pages, id);
7649
7106
  }
7650
- }
7651
- if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
7652
- const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
7107
+ };
7108
+ await ensureTemplates(input.pages ?? [], scopeId);
7653
7109
  return {
7654
- scopes,
7655
- createdScopeIds,
7656
- deletedScopeAddresses: [...deletedScopeAddresses],
7657
- changed: structureChanged,
7658
- accessEpoch: Number(state.rows[0]?.epoch ?? 0)
7110
+ scope: toPage(await this.#pageRow(tx, scopeId)),
7111
+ created,
7112
+ ...parentConflict ? { parentConflict } : {}
7659
7113
  };
7114
+ });
7115
+ }
7116
+ async listScopeDeclarations() {
7117
+ return (await this.#query(this.#read, `SELECT declaration, COUNT(*) AS scopes FROM "${T.pages}" WHERE isScope = 1 AND declaration IS NOT NULL GROUP BY declaration ORDER BY declaration`)).map((row) => ({
7118
+ declaration: String(row.declaration),
7119
+ scopes: Number(row.scopes)
7660
7120
  }));
7661
7121
  }
7662
- async listScopeNodes(input = {}) {
7663
- const { limit, after } = parseListKnowledgeScopeNodesInput(input);
7664
- if (input.addresses?.length === 0 || input.ids?.length === 0) return {
7665
- scopes: [],
7666
- nextCursor: null
7122
+ async renameScopeDeclaration(input) {
7123
+ return this.#transaction(async (tx) => {
7124
+ const within = prefixRange("declaration", `${input.from}/`);
7125
+ const affected = (await this.#query(tx, `SELECT id, address, declaration FROM "${T.pages}" WHERE isScope = 1 AND (declaration = ? OR ${within.sql})`, [input.from, ...within.args])).map((row) => ({
7126
+ id: String(row.id),
7127
+ address: String(row.address),
7128
+ from: String(row.declaration)
7129
+ })).filter((row) => row.from === input.from || row.from.startsWith(`${input.from}/`)).map((row) => ({
7130
+ ...row,
7131
+ to: input.to + row.from.slice(input.from.length)
7132
+ }));
7133
+ input.check(affected.map(({ address, from, to }) => ({
7134
+ address,
7135
+ from,
7136
+ to
7137
+ })));
7138
+ for (const row of affected) await tx.execute({
7139
+ sql: `UPDATE "${T.pages}" SET declaration = ? WHERE id = ?`,
7140
+ args: [row.to, row.id]
7141
+ });
7142
+ return affected.length;
7143
+ });
7144
+ }
7145
+ async getScopesByAddress(addresses) {
7146
+ if (addresses.length === 0) return [];
7147
+ return (await this.#query(this.#read, `SELECT * FROM "${T.pages}" WHERE isScope = 1 AND address IN (SELECT value FROM json_each(?))`, [jsonList(addresses)])).map(toPage);
7148
+ }
7149
+ async listChildScopes(input) {
7150
+ const core = await loadPagesCore();
7151
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7152
+ const cursor = decodeCursor(input.cursor, 2);
7153
+ const args = [input.parentId];
7154
+ let after = "";
7155
+ if (cursor) {
7156
+ after = ` AND (position > ? OR (position = ? AND id > ?))`;
7157
+ args.push(cursor[0], cursor[0], cursor[1]);
7158
+ }
7159
+ args.push(limit + 1);
7160
+ const rows = await this.#query(this.#read, `SELECT * FROM "${T.pages}" WHERE isScope = 1 AND parentId IS ?${after} ORDER BY position, id LIMIT ?`, args);
7161
+ return this.#pageOutput(rows, limit, (row) => [String(row.position), String(row.id)]);
7162
+ }
7163
+ #pageOutput(rows, limit, cursorOf) {
7164
+ const page = rows.slice(0, limit);
7165
+ return {
7166
+ pages: page.map(toPage),
7167
+ ...rows.length > limit ? { nextCursor: encodeCursor(cursorOf(page[page.length - 1])) } : {}
7667
7168
  };
7668
- const args = [];
7669
- const where = [`n.isScope`, `n.deletedAt IS NULL`];
7670
- let within = "";
7671
- if (input.withinAddress !== void 0) {
7672
- within = `WITH RECURSIVE within_scope(id) AS (SELECT r.id FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" r JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" ra ON ra.scopeNodeId=r.id WHERE ra.address=? AND r.isScope AND r.deletedAt IS NULL UNION SELECT ns.nodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns JOIN within_scope w ON ns.scopeNodeId=w.id JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" c ON c.id=ns.nodeId WHERE c.isScope AND c.deletedAt IS NULL) `;
7673
- args.push(input.withinAddress);
7674
- where.push("n.id IN (SELECT id FROM within_scope)");
7169
+ }
7170
+ async getPage(input) {
7171
+ const readable = pageReadable("p", input.read);
7172
+ const rows = await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.id = ? AND ${readable.sql}`, [input.id, ...readable.args]);
7173
+ return rows[0] ? toPage(rows[0]) : null;
7174
+ }
7175
+ async getPages(input) {
7176
+ const ids = [...new Set(input.ids)];
7177
+ if (ids.length === 0) return [];
7178
+ const readable = pageReadable("p", input.read);
7179
+ return (await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.deletedAt IS NULL AND p.id IN (SELECT value FROM json_each(?)) AND ${readable.sql}`, [jsonList(ids), ...readable.args])).map(toPage);
7180
+ }
7181
+ async getPageByPath(input) {
7182
+ const readable = pageReadable("p", input.read);
7183
+ let parentId = input.scopeId;
7184
+ let row;
7185
+ for (const key of input.path) {
7186
+ row = (await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.parentId = ? AND p.isScope = 0 AND p.key = ? AND p.deletedAt IS NULL AND ${readable.sql}`, [
7187
+ parentId,
7188
+ key,
7189
+ ...readable.args
7190
+ ]))[0];
7191
+ if (!row) return null;
7192
+ parentId = String(row.id);
7193
+ }
7194
+ return row ? toPage(row) : null;
7195
+ }
7196
+ async findPagesByName(input) {
7197
+ const core = await loadPagesCore();
7198
+ const readable = pageReadable("p", input.read);
7199
+ const args = [core.canonicalKnowledgeName(input.name), ...readable.args];
7200
+ let scope = "";
7201
+ if (input.scopeId !== void 0) {
7202
+ scope = ` AND p.scopeId = ?`;
7203
+ args.push(input.scopeId);
7204
+ }
7205
+ args.push(limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT));
7206
+ return (await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.canonicalName = ? AND p.deletedAt IS NULL AND ${readable.sql}${scope} ORDER BY p.createdAt DESC, p.id DESC LIMIT ?`, args)).map(toPage);
7207
+ }
7208
+ async listChildPages(input) {
7209
+ const core = await loadPagesCore();
7210
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7211
+ const cursor = decodeCursor(input.cursor, 2);
7212
+ const readable = pageReadable("p", input.read);
7213
+ const args = [input.parentId, ...readable.args];
7214
+ let after = "";
7215
+ if (cursor) {
7216
+ after = ` AND (p.position > ? OR (p.position = ? AND p.id > ?))`;
7217
+ args.push(cursor[0], cursor[0], cursor[1]);
7675
7218
  }
7676
- if (input.addresses) {
7677
- where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
7678
- args.push(...input.addresses);
7219
+ args.push(limit + 1);
7220
+ const rows = await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.parentId = ? AND p.isScope = 0 AND p.deletedAt IS NULL AND ${readable.sql}${after} ORDER BY p.position, p.id LIMIT ?`, args);
7221
+ return this.#pageOutput(rows, limit, (row) => [String(row.position), String(row.id)]);
7222
+ }
7223
+ async listPages(input) {
7224
+ const core = await loadPagesCore();
7225
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7226
+ const cursor = decodeCursor(input.cursor, 2);
7227
+ const readable = pageReadable("p", input.read);
7228
+ const args = [...readable.args];
7229
+ let filters = "";
7230
+ if (input.scopeId !== void 0) {
7231
+ filters += ` AND p.scopeId = ?`;
7232
+ args.push(input.scopeId);
7679
7233
  }
7680
- if (input.ids) {
7681
- where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
7682
- args.push(...input.ids);
7234
+ if (input.namePrefix) {
7235
+ const prefix = core.canonicalKnowledgeName(input.namePrefix);
7236
+ filters += ` AND substr(p.canonicalName, 1, length(?)) = ?`;
7237
+ args.push(prefix, prefix);
7683
7238
  }
7684
- if (after) {
7685
- where.push("(n.name > ? OR (n.name = ? AND n.id > ?))");
7686
- args.push(after.name, after.name, after.id);
7239
+ if (cursor) {
7240
+ filters += ` AND (p.name > ? OR (p.name = ? AND p.id > ?))`;
7241
+ args.push(cursor[0], cursor[0], cursor[1]);
7687
7242
  }
7688
7243
  args.push(limit + 1);
7689
- const rows = (await this.#client.execute({
7690
- sql: `${within}SELECT n.id,n.name,n.kind,n.description,a.address FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n LEFT JOIN "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" a ON a.scopeNodeId=n.id WHERE ${where.join(" AND ")} ORDER BY n.name, n.id LIMIT ?`,
7691
- args
7692
- })).rows;
7693
- if (rows.length === 0) return {
7694
- scopes: [],
7695
- nextCursor: null
7696
- };
7697
- const parents = await this.#client.execute({
7698
- sql: `SELECT ns.nodeId,ns.scopeNodeId FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId IN (${rows.map(() => "?").join(",")}) ORDER BY ns.scopeNodeId`,
7699
- args: rows.map((row) => String(row.id))
7244
+ const rows = await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.isScope = 0 AND p.deletedAt IS NULL AND ${readable.sql}${filters} ORDER BY p.name, p.id LIMIT ?`, args);
7245
+ return this.#pageOutput(rows, limit, (row) => [String(row.name), String(row.id)]);
7246
+ }
7247
+ async #insertPage(tx, input) {
7248
+ const core = await loadPagesCore();
7249
+ await assertKnowledgePageDescriptionWithinBound(input.description);
7250
+ const parent = await this.#livePage(tx, input.parentId);
7251
+ const isScopeParent = Number(parent.isScope) === 1;
7252
+ const scopeId = isScopeParent ? String(parent.id) : String(parent.scopeId);
7253
+ const depth = isScopeParent ? 1 : Number(parent.depth) + 1;
7254
+ if (depth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(input.parentId);
7255
+ const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
7256
+ if (await this.#pageRow(tx, id)) throw new _mastra_core_storage.KnowledgeConflictError(id);
7257
+ if (input.key !== void 0) {
7258
+ const problem = knowledgePageKeyProblem(input.key);
7259
+ if (problem) throw new Error(problem);
7260
+ if ((await this.#query(tx, `SELECT id FROM "${T.pages}" WHERE parentId = ? AND isScope = 0 AND key = ? AND deletedAt IS NULL`, [String(parent.id), input.key])).length > 0) throw new core.KnowledgePageKeyConflictError(input.key, String(parent.id));
7261
+ }
7262
+ const position = input.position ?? core.knowledgePositionBetween(await this.#lastPosition(tx, String(parent.id), false));
7263
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7264
+ const who = JSON.stringify(input.provenance);
7265
+ await tx.execute({
7266
+ sql: `INSERT INTO "${T.pages}" (id, scopeId, isScope, key, parentId, position, path, depth, name, canonicalName, description, body, version, createdBy, updatedBy, createdAt, updatedAt) VALUES (?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)`,
7267
+ args: [
7268
+ id,
7269
+ scopeId,
7270
+ input.key ?? null,
7271
+ String(parent.id),
7272
+ position,
7273
+ `${String(parent.path)}${id}/`,
7274
+ depth,
7275
+ input.name,
7276
+ core.canonicalKnowledgeName(input.name),
7277
+ input.description || null,
7278
+ input.body ?? null,
7279
+ who,
7280
+ who,
7281
+ now,
7282
+ now
7283
+ ]
7700
7284
  });
7701
- const parentsByScopeId = /* @__PURE__ */ new Map();
7702
- for (const row of parents.rows) {
7703
- const scopeId = String(row.nodeId);
7704
- const parentId = String(row.scopeNodeId);
7705
- const existing = parentsByScopeId.get(scopeId);
7706
- if (existing) existing.push(parentId);
7707
- else parentsByScopeId.set(scopeId, [parentId]);
7708
- }
7709
- return pageKnowledgeScopeNodes(rows.map((row) => ({
7710
- id: String(row.id),
7711
- address: String(row.address),
7712
- name: String(row.name),
7713
- ...row.kind == null ? {} : { kind: String(row.kind) },
7714
- ...row.description == null ? {} : { description: String(row.description) },
7715
- parentIds: parentsByScopeId.get(String(row.id)) ?? []
7716
- })), limit, input);
7717
- }
7718
- async listScopeMembers(input) {
7719
- const { limit, after } = parseListKnowledgeScopeMembersInput(input);
7720
- const args = [input.scopeNodeId];
7721
- let keyset = "";
7722
- if (after) {
7723
- const updatedAt = after.updatedAt.toISOString();
7724
- keyset = ` AND (n.updatedAt < ? OR (n.updatedAt = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
7725
- args.push(updatedAt, updatedAt, after.name, after.name, after.id);
7726
- }
7727
- args.push(limit + 1);
7728
- return pageKnowledgeScopeMembers((await this.#client.execute({
7729
- sql: `SELECT *, json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n WHERE EXISTS (SELECT 1 FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns.nodeId=n.id AND ns.scopeNodeId=?) AND n.deletedAt IS NULL AND n.mergedInto IS NULL${keyset} ORDER BY n.updatedAt DESC, n.name ASC, n.id ASC LIMIT ?`,
7730
- args
7731
- })).rows.map(parseNode), limit, input);
7732
- }
7733
- async createNode(input) {
7734
- await assertKnowledgeDescriptionWithinBoundCompat(input.description);
7735
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
7285
+ await this.#logActivity(tx, "page-created", {
7286
+ scopeId,
7287
+ pageId: id,
7288
+ provenance: input.provenance
7289
+ }, now);
7290
+ return toPage(await this.#pageRow(tx, id));
7291
+ }
7292
+ async createPage(input) {
7293
+ await assertKnowledgePageDescriptionWithinBound(input.description);
7294
+ return this.#transaction((tx) => this.#insertPage(tx, input));
7295
+ }
7296
+ async updatePage(input) {
7297
+ const core = await loadPagesCore();
7298
+ await assertKnowledgePageDescriptionWithinBound(input.description);
7736
7299
  return this.#transaction(async (tx) => {
7737
- const existing = await this.#getNodeByName(tx, input.name, scope);
7738
- if (existing) {
7739
- const terminal = await this.#resolveTerminalNode(tx, existing.id);
7740
- if (!(0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
7741
- await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
7742
- return terminal;
7743
- }
7744
- const now = /* @__PURE__ */ new Date();
7745
- const node = {
7746
- id: input.id ?? crypto.randomUUID(),
7747
- type: "node",
7748
- name: input.name.trim(),
7749
- kind: input.kind,
7750
- content: input.content,
7751
- description: input.description,
7752
- scope,
7753
- version: 1,
7754
- createdAt: now,
7755
- updatedAt: now
7756
- };
7300
+ const row = await this.#livePage(tx, input.id);
7301
+ if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
7302
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7303
+ const name = input.name ?? String(row.name);
7304
+ const description = input.description === void 0 ? row.description ?? null : input.description || null;
7305
+ const body = input.body === void 0 ? row.body ?? null : input.body;
7757
7306
  await tx.execute({
7758
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,description,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,?,?,FALSE,jsonb(?),?,?,NULL,?,?)`,
7307
+ sql: `UPDATE "${T.pages}" SET name = ?, canonicalName = ?, description = ?, body = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
7759
7308
  args: [
7760
- node.id,
7761
- "node",
7762
- node.name,
7763
- canonicalName(node.name),
7764
- node.kind,
7765
- node.content ?? null,
7766
- node.description ?? null,
7767
- JSON.stringify(scope),
7768
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
7769
- node.version,
7770
- now.toISOString(),
7771
- now.toISOString()
7309
+ name,
7310
+ core.canonicalKnowledgeName(name),
7311
+ description,
7312
+ body,
7313
+ JSON.stringify(input.provenance),
7314
+ now,
7315
+ input.id
7772
7316
  ]
7773
7317
  });
7774
- await this.#replaceNodeScopes(tx, node.id, scope, now);
7775
- await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
7776
- await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
7777
- await this.#activity(tx, "node-created", "node", node.id, scope);
7778
- await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
7779
- return node;
7318
+ await this.#logActivity(tx, "page-updated", {
7319
+ scopeId: String(row.scopeId),
7320
+ pageId: input.id,
7321
+ provenance: input.provenance
7322
+ }, now);
7323
+ return toPage(await this.#pageRow(tx, input.id));
7780
7324
  });
7781
7325
  }
7782
- async getNode(id) {
7783
- return this.#getNode(this.#client, id);
7784
- }
7785
- async getNodeByName(input) {
7786
- return this.#getNodeByName(this.#client, input.name, (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
7787
- }
7788
- async resolveNode(input) {
7789
- return this.#resolveNode(this.#client, input.name, (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
7790
- }
7791
- async listNodes(input) {
7792
- const keys = visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
7793
- const clauses = [
7794
- `type = 'node'`,
7795
- "mergedInto IS NULL",
7796
- visibleSql(keys)
7797
- ];
7798
- const args = [...keys];
7799
- if (input.namePrefix) {
7800
- clauses.push("canonicalName LIKE ? ESCAPE '='");
7801
- args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
7802
- }
7803
- if (input.kind) {
7804
- clauses.push("kind = ?");
7805
- args.push(input.kind);
7806
- }
7807
- if (input.hasContent !== void 0) clauses.push(input.hasContent ? "content IS NOT NULL AND content <> ''" : "(content IS NULL OR content = '')");
7808
- if (input.cursor) {
7809
- const cursor = (0, _mastra_core_storage.parseKnowledgeNodeCursor)(input.cursor, {
7810
- namePrefix: input.namePrefix,
7811
- kind: input.kind,
7812
- hasContent: input.hasContent
7813
- });
7814
- clauses.push("(updatedAt < ? OR (updatedAt = ? AND (name > ? OR (name = ? AND id > ?))))");
7815
- const updatedAt = cursor.updatedAt.toISOString();
7816
- args.push(updatedAt, updatedAt, cursor.name, cursor.name, cursor.id);
7817
- }
7818
- args.push(input.limit ?? 100);
7819
- return (await this.#client.execute({
7820
- sql: `SELECT *, json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE ${clauses.join(" AND ")} ORDER BY updatedAt DESC, name ASC, id ASC LIMIT ?`,
7821
- args
7822
- })).rows.map(parseNode);
7326
+ /**
7327
+ * Rehomes `root` (and its subtree) under `parent`: new path prefix, scope and depth in one go.
7328
+ * Throws when the subtree is too large, too deep, or the root's key clashes with a live child of `parent`.
7329
+ */
7330
+ async #rehome(tx, root, parent, options) {
7331
+ const core = await loadPagesCore();
7332
+ const isScopeParent = Number(parent.isScope) === 1;
7333
+ const newScopeId = isScopeParent ? String(parent.id) : String(parent.scopeId);
7334
+ const newDepth = isScopeParent ? 1 : Number(parent.depth) + 1;
7335
+ const oldPath = String(root.path);
7336
+ const within = prefixRange("path", oldPath);
7337
+ const live = (await this.#query(tx, `SELECT id, key, depth, deletedAt FROM "${T.pages}" WHERE ${within.sql}`, within.args)).filter((row) => !row.deletedAt);
7338
+ if (live.length > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(options.countLimitId, live.length);
7339
+ const shift = newDepth - Number(root.depth);
7340
+ if (Math.max(...live.map((row) => Number(row.depth))) + shift > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(String(root.id));
7341
+ if (!root.deletedAt && root.key !== null) {
7342
+ if ((await this.#query(tx, `SELECT id FROM "${T.pages}" WHERE parentId = ? AND isScope = 0 AND key = ? AND deletedAt IS NULL AND id <> ?`, [
7343
+ String(parent.id),
7344
+ String(root.key),
7345
+ String(root.id)
7346
+ ])).length > 0) throw new core.KnowledgePageKeyConflictError(String(root.key), String(parent.id));
7347
+ }
7348
+ const newPath = `${String(parent.path)}${String(root.id)}/`;
7349
+ const position = options.position ?? core.knowledgePositionBetween(await this.#lastPosition(tx, String(parent.id), false));
7350
+ await tx.execute({
7351
+ sql: `UPDATE "${T.pages}" SET path = ? || substr(path, length(?) + 1), depth = depth + ?, scopeId = ? WHERE ${within.sql}`,
7352
+ args: [
7353
+ newPath,
7354
+ oldPath,
7355
+ shift,
7356
+ newScopeId,
7357
+ ...within.args
7358
+ ]
7359
+ });
7360
+ await tx.execute({
7361
+ sql: `UPDATE "${T.pages}" SET parentId = ?, position = ? WHERE id = ?`,
7362
+ args: [
7363
+ String(parent.id),
7364
+ position,
7365
+ String(root.id)
7366
+ ]
7367
+ });
7368
+ return {
7369
+ moved: live.length,
7370
+ scopeId: newScopeId
7371
+ };
7823
7372
  }
7824
- async updateNode(input) {
7825
- await assertKnowledgeDescriptionWithinBoundCompat(input.description);
7373
+ movePage(input) {
7826
7374
  return this.#transaction(async (tx) => {
7827
- const existing = await this.#getNode(tx, input.id);
7828
- if (!existing) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.id);
7829
- if (existing.mergedInto) throw new Error(`Cannot update merged knowledge node: ${input.id}`);
7830
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope ?? existing.scope);
7831
- const name = (input.name ?? existing.name).trim();
7832
- const content = input.content ?? existing.content;
7833
- const description = input.description ?? existing.description;
7834
- const now = /* @__PURE__ */ new Date();
7835
- if ((await tx.execute({
7836
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" SET name=?,canonicalName=?,kind=?,content=?,description=?,scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=?`,
7375
+ const row = await this.#livePage(tx, input.id);
7376
+ if (Number(row.isScope) === 1) throw new Error(`Cannot move ${input.id}: it is a scope page`);
7377
+ if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
7378
+ const parent = await this.#livePage(tx, input.parentId);
7379
+ const path = String(row.path);
7380
+ if (String(parent.id) === input.id || String(parent.path).startsWith(path)) throw new Error(`Cannot move ${input.id} into its own subtree`);
7381
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7382
+ const { scopeId } = await this.#rehome(tx, row, parent, {
7383
+ position: input.position,
7384
+ countLimitId: input.id
7385
+ });
7386
+ await tx.execute({
7387
+ sql: `UPDATE "${T.pages}" SET version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
7837
7388
  args: [
7838
- name,
7839
- canonicalName(name),
7840
- input.kind ?? existing.kind,
7841
- content ?? null,
7842
- description ?? null,
7843
- JSON.stringify(scope),
7844
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
7845
- now.toISOString(),
7846
- input.id,
7847
- input.version
7389
+ JSON.stringify(input.provenance),
7390
+ now,
7391
+ input.id
7848
7392
  ]
7849
- })).rowsAffected === 0) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
7850
- if ((0, _mastra_core_storage.knowledgeScopeKey)(existing.scope) !== (0, _mastra_core_storage.knowledgeScopeKey)(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
7851
- if (input.content !== void 0 || input.name !== void 0 || input.scope !== void 0) await this.#replaceMentions(tx, "node", input.id, content ?? "", input.resolutionScope ?? scope, scope);
7852
- await this.#activity(tx, "node-updated", "node", input.id, scope);
7853
- if ((0, _mastra_core_storage.knowledgeScopeKey)(existing.scope) !== (0, _mastra_core_storage.knowledgeScopeKey)(scope)) {
7854
- await this.#outbox(tx, "node", input.id, "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), existing.scope);
7855
- const knowledge = await tx.execute({
7856
- sql: `SELECT id,json(scope) AS scopeJson,deletedAt FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
7857
- args: [input.id]
7858
- });
7859
- for (const row of knowledge.rows) {
7860
- const recordScope = parseJson$3(row.scopeJson);
7861
- await this.#outbox(tx, "record", String(row.id), "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), recordScope);
7862
- if (row.deletedAt == null) await this.#outbox(tx, "record", String(row.id), "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), recordScope);
7863
- }
7864
- }
7865
- await this.#outbox(tx, "node", input.id, "upsert", input.version + 1, scope);
7866
- return {
7867
- ...existing,
7868
- name,
7869
- kind: input.kind ?? existing.kind,
7870
- content,
7871
- description,
7872
- scope,
7873
- version: input.version + 1,
7874
- updatedAt: now
7875
- };
7393
+ });
7394
+ await this.#logActivity(tx, "page-moved", {
7395
+ scopeId,
7396
+ pageId: input.id,
7397
+ provenance: input.provenance
7398
+ }, now);
7399
+ return toPage(await this.#pageRow(tx, input.id));
7876
7400
  });
7877
7401
  }
7878
- async mergeNodes(input) {
7879
- if (input.sourceId === input.targetId) throw new Error("Cannot merge a knowledge node into itself");
7402
+ async mergePages(input) {
7403
+ const core = await loadPagesCore();
7880
7404
  return this.#transaction(async (tx) => {
7881
- const source = await this.#getNode(tx, input.sourceId);
7882
- if (!source) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.sourceId);
7883
- const target = await this.#resolveTerminalNode(tx, input.targetId);
7884
- if (!target) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.targetId);
7885
- if (target.id === source.id) throw new Error("Cannot create a knowledge merge cycle");
7886
- if (!(0, _mastra_core_storage.isKnowledgeScopeVisible)(target.scope, source.scope)) throw new Error("Cannot merge a knowledge node into a target that is narrower than its source scope");
7887
- const affected = await tx.execute({
7888
- sql: `SELECT DISTINCT m.sourceType,m.sourceId,json(COALESCE(f.scope,r.scope)) AS scopeJson,CASE WHEN f.deletedAt IS NULL THEN 0 ELSE 1 END AS deleted FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" m LEFT JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" f ON m.sourceType='record' AND f.id=m.sourceId LEFT JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" r ON m.sourceType='node' AND r.id=m.sourceId WHERE m.recordId=?`,
7889
- args: [source.id]
7890
- });
7891
- const movedKnowledge = await tx.execute({
7892
- sql: `SELECT id,json(scope) AS scopeJson,deletedAt FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
7893
- args: [source.id]
7894
- });
7895
- if ((await tx.execute({
7896
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" SET mergedInto=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=? AND mergedInto IS NULL`,
7897
- args: [
7898
- target.id,
7899
- (/* @__PURE__ */ new Date()).toISOString(),
7900
- source.id,
7901
- input.sourceVersion
7902
- ]
7903
- })).rowsAffected === 0) throw new _mastra_core_storage.KnowledgeConflictError(source.id);
7405
+ const source = await this.#livePage(tx, input.sourceId);
7406
+ const target = await this.#livePage(tx, input.targetId);
7407
+ if (Number(source.isScope) === 1 || Number(target.isScope) === 1) throw new Error("Cannot merge a scope page: merging applies to non-scope pages only");
7408
+ if (Number(source.version) !== input.sourceVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.sourceId);
7409
+ const sourcePath = String(source.path);
7410
+ if (input.sourceId === input.targetId || String(target.path).startsWith(sourcePath)) throw new Error(`Page ${input.targetId} is inside page ${input.sourceId}; it cannot be the merge target (subtree)`);
7411
+ const within = prefixRange("path", sourcePath);
7412
+ const carried = await this.#query(tx, `SELECT COUNT(*) AS n FROM "${T.pages}" WHERE ${within.sql} AND id <> ? AND deletedAt IS NULL`, [...within.args, input.sourceId]);
7413
+ if (Number(carried[0].n) > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(input.sourceId, Number(carried[0].n));
7414
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7904
7415
  await tx.execute({
7905
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
7416
+ sql: `UPDATE "${T.pages}" SET deletedAt = ?, deletedBy = ?, mergedInto = ?, scopeId = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
7906
7417
  args: [
7907
- target.id,
7908
- (/* @__PURE__ */ new Date()).toISOString(),
7909
- source.id
7418
+ now,
7419
+ input.provenance.author,
7420
+ input.targetId,
7421
+ String(target.scopeId),
7422
+ JSON.stringify(input.provenance),
7423
+ now,
7424
+ input.sourceId
7910
7425
  ]
7911
7426
  });
7427
+ const children = await this.#query(tx, `SELECT * FROM "${T.pages}" WHERE parentId = ? AND isScope = 0`, [input.sourceId]);
7428
+ for (const child of children) await this.#rehome(tx, child, target, {
7429
+ position: String(child.position),
7430
+ countLimitId: input.sourceId
7431
+ });
7912
7432
  await tx.execute({
7913
- sql: `DELETE FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" WHERE recordId=? AND EXISTS (SELECT 1 FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" target WHERE target.sourceType="${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}".sourceType AND target.sourceId="${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}".sourceId AND target.recordId=?)`,
7914
- args: [source.id, target.id]
7433
+ sql: `UPDATE "${T.notes}" SET pageId = ? WHERE pageId = ?`,
7434
+ args: [input.targetId, input.sourceId]
7915
7435
  });
7916
7436
  await tx.execute({
7917
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" SET recordId=? WHERE recordId=?`,
7918
- args: [target.id, source.id]
7437
+ sql: `UPDATE OR IGNORE "${T.mentions}" SET pageId = ? WHERE pageId = ?`,
7438
+ args: [input.targetId, input.sourceId]
7919
7439
  });
7920
- for (const row of movedKnowledge.rows) await this.#outbox(tx, "record", String(row.id), row.deletedAt == null ? "upsert" : "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), parseJson$3(row.scopeJson));
7921
- for (const row of affected.rows) await this.#outbox(tx, String(row.sourceType), String(row.sourceId), Number(row.deleted) ? "delete" : "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), parseJson$3(row.scopeJson));
7922
- let mergedTarget = target;
7923
- if (target.description === void 0 && source.description) {
7924
- const adoptedAt = /* @__PURE__ */ new Date();
7925
- if ((await tx.execute({
7926
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" SET description=?,version=version+1,updatedAt=? WHERE id=? AND type='node' AND version=? AND description IS NULL`,
7440
+ await tx.execute({
7441
+ sql: `DELETE FROM "${T.mentions}" WHERE pageId = ?`,
7442
+ args: [input.sourceId]
7443
+ });
7444
+ const pins = await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE pageId IN (?, ?)`, [input.sourceId, input.targetId]);
7445
+ const sourcePin = pins.find((pin) => String(pin.pageId) === input.sourceId);
7446
+ if (sourcePin) {
7447
+ const targetPin = pins.find((pin) => String(pin.pageId) === input.targetId);
7448
+ const audience = JSON.parse(String(sourcePin.audience));
7449
+ if (targetPin) {
7450
+ const merged = [.../* @__PURE__ */ new Set([...JSON.parse(String(targetPin.audience)), ...audience])];
7451
+ await tx.execute({
7452
+ sql: `UPDATE "${T.pins}" SET audience = ?, updatedAt = ? WHERE id = ?`,
7453
+ args: [
7454
+ JSON.stringify(merged),
7455
+ now,
7456
+ String(targetPin.id)
7457
+ ]
7458
+ });
7459
+ await this.#indexAudience(tx, String(targetPin.id), merged);
7460
+ await this.#indexAudience(tx, String(sourcePin.id), []);
7461
+ await tx.execute({
7462
+ sql: `DELETE FROM "${T.pins}" WHERE id = ?`,
7463
+ args: [String(sourcePin.id)]
7464
+ });
7465
+ } else await tx.execute({
7466
+ sql: `UPDATE "${T.pins}" SET pageId = ?, updatedAt = ? WHERE id = ?`,
7927
7467
  args: [
7928
- source.description,
7929
- adoptedAt.toISOString(),
7930
- target.id,
7931
- target.version
7468
+ input.targetId,
7469
+ now,
7470
+ String(sourcePin.id)
7932
7471
  ]
7933
- })).rowsAffected > 0) {
7934
- mergedTarget = {
7935
- ...target,
7936
- description: source.description,
7937
- version: target.version + 1,
7938
- updatedAt: adoptedAt
7939
- };
7940
- await this.#activity(tx, "node-updated", "node", target.id, target.scope);
7941
- }
7472
+ });
7942
7473
  }
7943
- await this.#activity(tx, "node-merged", "node", source.id, source.scope);
7944
- await this.#outbox(tx, "node", source.id, "delete", input.sourceVersion + 1, source.scope);
7945
- await this.#outbox(tx, "node", target.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), mergedTarget.scope);
7946
- return mergedTarget;
7474
+ await this.#logActivity(tx, "page-merged", {
7475
+ scopeId: String(target.scopeId),
7476
+ pageId: input.sourceId,
7477
+ provenance: input.provenance
7478
+ }, now);
7479
+ return toPage(await this.#pageRow(tx, input.targetId));
7947
7480
  });
7948
7481
  }
7949
- async appendKnowledge(input) {
7950
- await assertKnowledgeRecordTextWithinBoundCompat(input.text);
7951
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
7952
- const resolutionScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.resolutionScope);
7953
- const defaultScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.defaultScope);
7482
+ async deletePage(input) {
7483
+ const core = await loadPagesCore();
7954
7484
  return this.#transaction(async (tx) => {
7955
- const node = nodeReferenceId(input.node);
7956
- const parent = await this.#resolveTerminalNode(tx, node);
7957
- if (!parent) throw new _mastra_core_storage.KnowledgeNotFoundError("node", node);
7958
- const record = {
7959
- id: input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)(),
7960
- node: parent.id,
7961
- text: input.text,
7962
- scope,
7963
- sourceThreadId: input.sourceThreadId,
7964
- capturedAt: /* @__PURE__ */ new Date(),
7965
- when: input.when ? new Date(input.when) : void 0,
7966
- metadata: input.metadata
7967
- };
7485
+ const row = await this.#livePage(tx, input.id);
7486
+ if (Number(row.isScope) === 1) throw new Error(`Cannot delete ${input.id}: it is a scope page`);
7487
+ if (Number(row.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
7488
+ if ((await this.#query(tx, `SELECT 1 FROM "${T.pages}" WHERE parentId = ? AND deletedAt IS NULL LIMIT 1`, [input.id])).length > 0) throw new core.KnowledgePageHasChildrenError(input.id);
7489
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7968
7490
  await tx.execute({
7969
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" (id,node,text,scope,scopeKey,sourceThreadId,capturedAt,"when",metadata,version,updatedAt,deletedAt,deletedBy) VALUES (?,?,?,jsonb(?),?,?,?,?,jsonb(?),?,?,NULL,NULL)`,
7491
+ sql: `UPDATE "${T.pages}" SET deletedAt = ?, deletedBy = ?, version = version + 1, updatedBy = ?, updatedAt = ? WHERE id = ?`,
7970
7492
  args: [
7971
- record.id,
7972
- record.node,
7973
- record.text,
7974
- JSON.stringify(scope),
7975
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
7976
- record.sourceThreadId,
7977
- record.capturedAt.toISOString(),
7978
- record.when?.toISOString() ?? null,
7979
- record.metadata ? JSON.stringify(record.metadata) : null,
7980
- 1,
7981
- record.capturedAt.toISOString()
7493
+ now,
7494
+ input.deletedBy,
7495
+ JSON.stringify({ author: input.deletedBy }),
7496
+ now,
7497
+ input.id
7982
7498
  ]
7983
7499
  });
7984
- await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
7985
- await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
7986
- await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
7987
- await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
7988
- return record;
7989
- });
7990
- }
7991
- async getKnowledge(input) {
7992
- const result = await this.#client.execute({
7993
- sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${input.includeDeleted ? "" : " AND deletedAt IS NULL"}`,
7994
- args: [input.id]
7500
+ await tx.execute({
7501
+ sql: `DELETE FROM "${T.pinAudiences}" WHERE pinId IN (SELECT id FROM "${T.pins}" WHERE pageId = ?)`,
7502
+ args: [input.id]
7503
+ });
7504
+ await tx.execute({
7505
+ sql: `DELETE FROM "${T.pins}" WHERE pageId = ?`,
7506
+ args: [input.id]
7507
+ });
7508
+ await this.#logActivity(tx, "page-deleted", {
7509
+ scopeId: String(row.scopeId),
7510
+ pageId: input.id,
7511
+ provenance: { author: input.deletedBy }
7512
+ }, now);
7513
+ return toPage(await this.#pageRow(tx, input.id));
7995
7514
  });
7996
- return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
7997
- }
7998
- async listKnowledgeAbout(input) {
7999
- return this.#queryKnowledge(input, "about");
8000
- }
8001
- async listKnowledgeMentioning(input) {
8002
- return this.#queryKnowledge(input, "mentioning");
8003
7515
  }
8004
- async listKnowledgeRelatedTo(input) {
8005
- return this.#queryKnowledge(input, "related");
8006
- }
8007
- async knowledgeBySource(input) {
8008
- const keys = visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
8009
- const args = [input.sourceThreadId, ...keys];
8010
- if (input.after) args.push(input.after);
8011
- const limit = input.limit ?? 100;
7516
+ async #hydrateNotes(executor, rows) {
7517
+ if (rows.length === 0) return [];
7518
+ const ids = jsonList(rows.map((row) => String(row.id)));
7519
+ const stamps = await this.#query(executor, `SELECT noteId, scopeId FROM "${T.stamps}" WHERE noteId IN (SELECT value FROM json_each(?)) ORDER BY scopeId`, [ids]);
7520
+ const mentions = await this.#query(executor, `SELECT noteId, pageId FROM "${T.mentions}" WHERE noteId IN (SELECT value FROM json_each(?)) ORDER BY pageId`, [ids]);
7521
+ return rows.map((row) => toNote(row, stamps.filter((stamp) => String(stamp.noteId) === String(row.id)).map((stamp) => String(stamp.scopeId)), mentions.filter((mention) => String(mention.noteId) === String(row.id)).map((mention) => String(mention.pageId))));
7522
+ }
7523
+ async #noteById(executor, id) {
7524
+ const rows = await this.#query(executor, `SELECT * FROM "${T.notes}" WHERE id = ?`, [id]);
7525
+ return (await this.#hydrateNotes(executor, rows))[0] ?? null;
7526
+ }
7527
+ async getNote(input) {
7528
+ const readable = noteReadable("n", input.read);
7529
+ const rows = await this.#query(this.#read, `SELECT n.* FROM "${T.notes}" n WHERE n.id = ?${input.includeDeleted ? "" : " AND n.deletedAt IS NULL"} AND ${readable.sql}`, [input.id, ...readable.args]);
7530
+ return (await this.#hydrateNotes(this.#read, rows))[0] ?? null;
7531
+ }
7532
+ async listNotes(input) {
7533
+ const core = await loadPagesCore();
7534
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7535
+ const cursor = decodeCursor(input.cursor, 2);
7536
+ const readable = noteReadable("n", input.read);
7537
+ const args = [input.pageId, ...readable.args];
7538
+ let after = "";
7539
+ if (cursor) {
7540
+ after = ` AND (n.createdAt > ? OR (n.createdAt = ? AND n.id > ?))`;
7541
+ args.push(cursor[0], cursor[0], cursor[1]);
7542
+ }
8012
7543
  args.push(limit + 1);
8013
- const records = (await this.#client.execute({
8014
- sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE sourceThreadId=? AND ${visibleSql(keys)}${input.includeDeleted ? "" : " AND deletedAt IS NULL"}${input.after ? " AND id > ?" : ""} ORDER BY id ASC LIMIT ?`,
8015
- args
8016
- })).rows.map(parseKnowledge);
7544
+ const rows = await this.#query(this.#read, `SELECT n.* FROM "${T.notes}" n WHERE n.pageId = ?${input.includeDeleted ? "" : " AND n.deletedAt IS NULL"} AND ${readable.sql}${after} ORDER BY n.createdAt, n.id LIMIT ?`, args);
7545
+ return this.#noteOutput(rows, limit);
7546
+ }
7547
+ async countNotes(input) {
7548
+ const ids = [...new Set(input.pageIds)];
7549
+ const counts = Object.fromEntries(ids.map((id) => [id, 0]));
7550
+ if (ids.length === 0) return counts;
7551
+ const readable = noteReadable("n", input.read);
7552
+ const rows = await this.#query(this.#read, `SELECT n.pageId AS pageId, COUNT(*) AS total FROM "${T.notes}" n WHERE n.pageId IN (SELECT value FROM json_each(?)) AND n.deletedAt IS NULL AND ${readable.sql} GROUP BY n.pageId`, [jsonList(ids), ...readable.args]);
7553
+ for (const row of rows) counts[String(row.pageId)] = Number(row.total);
7554
+ return counts;
7555
+ }
7556
+ async listNotesMentioning(input) {
7557
+ const core = await loadPagesCore();
7558
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7559
+ const cursor = decodeCursor(input.cursor, 2);
7560
+ const readable = noteReadable("n", input.read);
7561
+ const args = [
7562
+ input.pageId,
7563
+ input.pageId,
7564
+ ...readable.args
7565
+ ];
7566
+ let after = "";
7567
+ if (cursor) {
7568
+ after = ` AND (n.createdAt < ? OR (n.createdAt = ? AND n.id < ?))`;
7569
+ args.push(cursor[0], cursor[0], cursor[1]);
7570
+ }
7571
+ args.push(limit + 1);
7572
+ const rows = await this.#query(this.#read, `SELECT n.* FROM "${T.notes}" n WHERE n.pageId <> ? AND n.deletedAt IS NULL AND EXISTS (SELECT 1 FROM "${T.mentions}" m WHERE m.noteId = n.id AND m.pageId = ?) AND ${readable.sql}${after} ORDER BY n.createdAt DESC, n.id DESC LIMIT ?`, args);
7573
+ return this.#noteOutput(rows, limit);
7574
+ }
7575
+ async #noteOutput(rows, limit) {
7576
+ const page = rows.slice(0, limit);
8017
7577
  return {
8018
- records: records.slice(0, limit),
8019
- nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
7578
+ notes: await this.#hydrateNotes(this.#read, page),
7579
+ ...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].createdAt), String(page[page.length - 1].id)]) } : {}
8020
7580
  };
8021
7581
  }
8022
- async removeKnowledge(input) {
7582
+ async #assertScopes(tx, ids) {
7583
+ if (ids.length === 0) throw new Error("A note needs at least one stamp scope");
7584
+ const found = await this.#query(tx, `SELECT id FROM "${T.pages}" WHERE isScope = 1 AND id IN (SELECT value FROM json_each(?))`, [jsonList(ids)]);
7585
+ const known = new Set(found.map((row) => String(row.id)));
7586
+ const missing = ids.find((id) => !known.has(id));
7587
+ if (missing) throw new Error(`Note stamp ${missing} is not a scope page`);
7588
+ }
7589
+ async addNote(input) {
7590
+ await assertKnowledgeNoteTextWithinBound(input.text);
8023
7591
  return this.#transaction(async (tx) => {
8024
- const record = await this.#getKnowledge(tx, input.id, true);
8025
- if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
8026
- if (record.deletedAt) return record;
8027
- const deletedAt = /* @__PURE__ */ new Date();
7592
+ const page = await this.#livePage(tx, input.pageId);
7593
+ const stamps = [...new Set(input.stampScopeIds)];
7594
+ await this.#assertScopes(tx, stamps);
7595
+ const mentions = [...new Set(input.mentions)];
7596
+ for (const mention of mentions) await this.#livePage(tx, mention);
7597
+ const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
7598
+ if ((await this.#query(tx, `SELECT 1 FROM "${T.notes}" WHERE id = ?`, [id])).length > 0) throw new _mastra_core_storage.KnowledgeConflictError(id);
7599
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8028
7600
  await tx.execute({
8029
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
7601
+ sql: `INSERT INTO "${T.notes}" (id, pageId, text, "when", provenance, version, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, 1, ?, ?)`,
8030
7602
  args: [
8031
- deletedAt.toISOString(),
8032
- input.deletedBy,
8033
- deletedAt.toISOString(),
8034
- input.id
7603
+ id,
7604
+ input.pageId,
7605
+ input.text,
7606
+ input.when ? input.when.toISOString() : null,
7607
+ JSON.stringify(input.provenance),
7608
+ now,
7609
+ now
8035
7610
  ]
8036
7611
  });
8037
- await this.#activity(tx, "record-deleted", "record", input.id, record.scope, record.sourceThreadId);
8038
- await this.#outbox(tx, "record", input.id, "delete", deletedAt.toISOString(), record.scope);
8039
- return {
8040
- ...record,
8041
- deletedAt,
8042
- deletedBy: input.deletedBy
8043
- };
7612
+ for (const stamp of stamps) await tx.execute({
7613
+ sql: `INSERT INTO "${T.stamps}" (noteId, scopeId) VALUES (?, ?)`,
7614
+ args: [id, stamp]
7615
+ });
7616
+ for (const mention of mentions) await tx.execute({
7617
+ sql: `INSERT INTO "${T.mentions}" (noteId, pageId) VALUES (?, ?)`,
7618
+ args: [id, mention]
7619
+ });
7620
+ await this.#logActivity(tx, "note-added", {
7621
+ scopeId: String(page.scopeId),
7622
+ pageId: input.pageId,
7623
+ noteId: id,
7624
+ provenance: input.provenance
7625
+ }, now);
7626
+ return await this.#noteById(tx, id);
8044
7627
  });
8045
7628
  }
8046
- async restoreKnowledge(input) {
7629
+ async #noteWithPage(tx, id) {
7630
+ const note = (await this.#query(tx, `SELECT * FROM "${T.notes}" WHERE id = ?`, [id]))[0];
7631
+ if (!note) throw new _mastra_core_storage.KnowledgeNotFoundError("note", id);
7632
+ const page = await this.#pageRow(tx, String(note.pageId));
7633
+ if (!page) throw new _mastra_core_storage.KnowledgeNotFoundError("page", String(note.pageId));
7634
+ return {
7635
+ note,
7636
+ page
7637
+ };
7638
+ }
7639
+ restampNote(input) {
8047
7640
  return this.#transaction(async (tx) => {
8048
- const record = await this.#getKnowledge(tx, input.id, true);
8049
- if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
8050
- if (!record.deletedAt) return record;
7641
+ const { note, page } = await this.#noteWithPage(tx, input.id);
7642
+ if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
7643
+ if (Number(note.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
7644
+ const stamps = [...new Set(input.stampScopeIds)];
7645
+ await this.#assertScopes(tx, stamps);
7646
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8051
7647
  await tx.execute({
8052
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
8053
- args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
7648
+ sql: `DELETE FROM "${T.stamps}" WHERE noteId = ?`,
7649
+ args: [input.id]
8054
7650
  });
8055
- await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
8056
- await this.#outbox(tx, "record", input.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), record.scope);
8057
- return {
8058
- ...record,
8059
- deletedAt: void 0,
8060
- deletedBy: void 0
8061
- };
7651
+ for (const stamp of stamps) await tx.execute({
7652
+ sql: `INSERT INTO "${T.stamps}" (noteId, scopeId) VALUES (?, ?)`,
7653
+ args: [input.id, stamp]
7654
+ });
7655
+ await tx.execute({
7656
+ sql: `UPDATE "${T.notes}" SET version = version + 1, updatedAt = ? WHERE id = ?`,
7657
+ args: [now, input.id]
7658
+ });
7659
+ await this.#logActivity(tx, "note-restamped", {
7660
+ scopeId: String(page.scopeId),
7661
+ pageId: String(page.id),
7662
+ noteId: input.id,
7663
+ provenance: input.provenance
7664
+ }, now);
7665
+ return await this.#noteById(tx, input.id);
8062
7666
  });
8063
7667
  }
8064
- async rescopeKnowledge(input) {
8065
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
7668
+ removeNote(input) {
8066
7669
  return this.#transaction(async (tx) => {
8067
- const record = await this.#getKnowledge(tx, input.id, true);
8068
- if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
8069
- const updatedAt = /* @__PURE__ */ new Date();
7670
+ const { note, page } = await this.#noteWithPage(tx, input.id);
7671
+ if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
7672
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8070
7673
  await tx.execute({
8071
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
7674
+ sql: `UPDATE "${T.notes}" SET deletedAt = ?, deletedBy = ?, version = version + 1, updatedAt = ? WHERE id = ?`,
8072
7675
  args: [
8073
- JSON.stringify(scope),
8074
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
8075
- updatedAt.toISOString(),
7676
+ now,
7677
+ input.deletedBy,
7678
+ now,
8076
7679
  input.id
8077
7680
  ]
8078
7681
  });
8079
- await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
8080
- await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
8081
- if ((0, _mastra_core_storage.knowledgeScopeKey)(record.scope) !== (0, _mastra_core_storage.knowledgeScopeKey)(scope)) await this.#outbox(tx, "record", input.id, "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), record.scope);
8082
- if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), scope);
8083
- return {
8084
- ...record,
8085
- scope
8086
- };
7682
+ await this.#logActivity(tx, "note-removed", {
7683
+ scopeId: String(page.scopeId),
7684
+ pageId: String(page.id),
7685
+ noteId: input.id,
7686
+ provenance: input.provenance
7687
+ }, now);
7688
+ return await this.#noteById(tx, input.id);
8087
7689
  });
8088
7690
  }
8089
- async search(input) {
8090
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
8091
- const keys = visibleScopeKeys(scope);
8092
- const normalizedQuery = input.query.trim().toLocaleLowerCase();
8093
- if (!normalizedQuery) return [];
8094
- const query = `%${escapeLikePattern(normalizedQuery)}%`;
8095
- const results = (await this.#client.execute({
8096
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE mergedInto IS NULL AND ${visibleSql(keys)} AND (canonicalName LIKE ? ESCAPE '=' OR lower(COALESCE(kind,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(content,'')) LIKE ? ESCAPE '=' OR lower(COALESCE(description,'')) LIKE ? ESCAPE '=') ORDER BY updatedAt DESC LIMIT ?`,
8097
- args: [
8098
- ...keys,
8099
- query,
8100
- query,
8101
- query,
8102
- query,
8103
- input.limit ?? 20
8104
- ]
8105
- })).rows.map((row) => ({
8106
- type: String(row.type),
8107
- id: String(row.id),
8108
- recordId: String(row.id),
8109
- name: String(row.name),
8110
- text: [
8111
- String(row.name),
8112
- ...row.description ? [String(row.description)] : [],
8113
- ...row.content ? [String(row.content)] : []
8114
- ].join("\n"),
8115
- scope: parseJson$3(row.scopeJson)
8116
- }));
8117
- if (results.length < (input.limit ?? 20)) {
8118
- const knowledge = await this.#client.execute({
8119
- sql: `SELECT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson,n.name,json(n.scope) AS parentScopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" k CROSS JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n ON n.id=k.node AND n.type='node' AND n.mergedInto IS NULL WHERE k.deletedAt IS NULL AND ${visibleSql(keys, "k.scopeKey")} AND lower(k.text) LIKE ? ESCAPE '=' ORDER BY k.id DESC LIMIT ?`,
8120
- args: [
8121
- ...keys,
8122
- query,
8123
- (input.limit ?? 20) - results.length
8124
- ]
7691
+ restoreNote(input) {
7692
+ return this.#transaction(async (tx) => {
7693
+ const { note, page } = await this.#noteWithPage(tx, input.id);
7694
+ if (!note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
7695
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7696
+ await tx.execute({
7697
+ sql: `UPDATE "${T.notes}" SET deletedAt = NULL, deletedBy = NULL, version = version + 1, updatedAt = ? WHERE id = ?`,
7698
+ args: [now, input.id]
8125
7699
  });
8126
- results.push(...knowledge.rows.map((row) => {
8127
- const parentVisible = (0, _mastra_core_storage.isKnowledgeScopeVisible)(parseJson$3(row.parentScopeJson), scope);
8128
- return {
8129
- type: "record",
8130
- id: String(row.id),
8131
- recordId: parentVisible ? String(row.node) : String(row.id),
8132
- name: parentVisible ? String(row.name) : "(private node)",
8133
- text: String(row.text),
8134
- scope: parseJson$3(row.scopeJson)
8135
- };
8136
- }));
8137
- }
8138
- return results;
8139
- }
8140
- /**
8141
- * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
8142
- * cursor. Always throws.
8143
- */
8144
- async getCurationCursor(_input) {
8145
- throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
8146
- }
8147
- /**
8148
- * @deprecated Curation cursors were removed. Observation-time curate is the only Knowledge writer and needs no
8149
- * cursor. Always throws.
8150
- */
8151
- async advanceCurationCursor(_input) {
8152
- throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
7700
+ await this.#logActivity(tx, "note-restored", {
7701
+ scopeId: String(page.scopeId),
7702
+ pageId: String(page.id),
7703
+ noteId: input.id,
7704
+ provenance: input.provenance
7705
+ }, now);
7706
+ return await this.#noteById(tx, input.id);
7707
+ });
8153
7708
  }
8154
- async listActivity(input) {
8155
- const keys = visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
8156
- return (await this.#client.execute({
8157
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
8158
- args: [
8159
- ...keys,
8160
- ...input.after ? [input.after] : [],
8161
- input.limit ?? 100
8162
- ]
8163
- })).rows.map((row) => ({
8164
- id: String(row.id),
8165
- action: String(row.action),
8166
- recordType: String(row.recordType),
8167
- recordId: String(row.recordId),
8168
- scope: parseJson$3(row.scopeJson),
8169
- sourceThreadId: row.sourceThreadId == null ? void 0 : String(row.sourceThreadId),
8170
- createdAt: toDate(row.createdAt)
7709
+ async searchPages(input) {
7710
+ const words = knowledgeSearchWords(input.query);
7711
+ if (!words.length) return [];
7712
+ const phrase = input.query.trim().toLowerCase().replace(/\s+/g, " ");
7713
+ const limit = Math.max(1, input.limit ?? 20);
7714
+ const likes = words.map((word) => `%${escapeLike(word)}%`);
7715
+ const readable = pageReadable("p", input.read);
7716
+ const scopeFilter = input.scopeIds ? ` AND p.scopeId IN (SELECT value FROM json_each(?))` : "";
7717
+ const scopeArgs = input.scopeIds ? [jsonList(input.scopeIds)] : [];
7718
+ const pageMatch = words.map(() => `(lower(p.name) LIKE ? ESCAPE '=' OR lower(COALESCE(p.description, '')) LIKE ? ESCAPE '=' OR lower(COALESCE(p.body, '')) LIKE ? ESCAPE '=')`).join(" AND ");
7719
+ const nameMatch = words.map(() => `lower(p.name) LIKE ? ESCAPE '='`).join(" AND ");
7720
+ const hits = (await this.#query(this.#read, `SELECT p.* FROM "${T.pages}" p WHERE p.deletedAt IS NULL AND p.isScope = 0 AND ${readable.sql}${scopeFilter} AND ${pageMatch} ORDER BY CASE WHEN instr(p.canonicalName, ?) > 0 THEN 0 WHEN ${nameMatch} THEN 1 ELSE 2 END, p.updatedAt DESC, p.id ASC LIMIT ?`, [
7721
+ ...readable.args,
7722
+ ...scopeArgs,
7723
+ ...likes.flatMap((like) => [
7724
+ like,
7725
+ like,
7726
+ like
7727
+ ]),
7728
+ phrase,
7729
+ ...likes,
7730
+ limit
7731
+ ])).map((row) => ({
7732
+ type: "page",
7733
+ page: toPage(row)
8171
7734
  }));
8172
- }
8173
- async listSemanticOutbox(input = {}) {
8174
- const clauses = [];
8175
- const args = [];
8176
- if (input.status) {
8177
- clauses.push("status=?");
8178
- args.push(input.status);
8179
- }
8180
- if (input.scope) {
8181
- const keys = visibleScopeKeys(input.scope);
8182
- clauses.push(visibleSql(keys));
8183
- args.push(...keys);
7735
+ if (hits.length >= limit) return hits;
7736
+ const noteVisible = noteReadable("n", input.read);
7737
+ const noteMatch = words.map(() => `lower(n.text) LIKE ? ESCAPE '='`).join(" AND ");
7738
+ const noteRows = await this.#query(this.#read, `SELECT n.* FROM "${T.notes}" n JOIN "${T.pages}" p ON p.id = n.pageId WHERE n.deletedAt IS NULL AND p.deletedAt IS NULL${scopeFilter} AND ${noteMatch} AND ${noteVisible.sql} ORDER BY n.updatedAt DESC, n.id DESC LIMIT ?`, [
7739
+ ...scopeArgs,
7740
+ ...likes,
7741
+ ...noteVisible.args,
7742
+ limit - hits.length
7743
+ ]);
7744
+ const notes = await this.#hydrateNotes(this.#read, noteRows);
7745
+ for (const note of notes) {
7746
+ const page = await this.#pageRow(this.#read, note.pageId);
7747
+ if (page) hits.push({
7748
+ type: "note",
7749
+ note,
7750
+ page: toPage(page)
7751
+ });
8184
7752
  }
8185
- args.push(input.limit ?? 100);
8186
- return (await this.#client.execute({
8187
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}"${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY createdAt ASC,id ASC LIMIT ?`,
8188
- args
8189
- })).rows.map(parseOutbox);
7753
+ return hits;
7754
+ }
7755
+ async listPageActivity(input) {
7756
+ const core = await loadPagesCore();
7757
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7758
+ const cursor = decodeCursor(input.cursor, 1);
7759
+ const readable = pageReadable("p", input.read);
7760
+ const noteVisible = noteReadable("n", input.read);
7761
+ const args = [...readable.args];
7762
+ let filters = "";
7763
+ if (input.scopeId !== void 0) {
7764
+ filters += ` AND p.scopeId = ?`;
7765
+ args.push(input.scopeId);
7766
+ }
7767
+ if (input.pageId !== void 0) {
7768
+ filters += ` AND e.pageId = ?`;
7769
+ args.push(input.pageId);
7770
+ }
7771
+ if (cursor) {
7772
+ filters += ` AND e.id < ?`;
7773
+ args.push(cursor[0]);
7774
+ }
7775
+ args.push(...noteVisible.args, limit + 1);
7776
+ const rows = await this.#query(this.#read, `SELECT e.* FROM "${T.activity}" e JOIN "${T.pages}" p ON p.id = e.pageId WHERE ${readable.sql}${filters} AND (e.noteId IS NULL OR EXISTS (SELECT 1 FROM "${T.notes}" n WHERE n.id = e.noteId AND ${noteVisible.sql})) ORDER BY e.id DESC LIMIT ?`, args);
7777
+ const page = rows.slice(0, limit);
7778
+ return {
7779
+ events: page.map((row) => ({
7780
+ id: String(row.id),
7781
+ action: String(row.action),
7782
+ scopeId: String(row.scopeId),
7783
+ pageId: String(row.pageId),
7784
+ ...row.noteId ? { noteId: String(row.noteId) } : {},
7785
+ provenance: provenance(row.provenance),
7786
+ createdAt: new Date(String(row.createdAt))
7787
+ })),
7788
+ ...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].id)]) } : {}
7789
+ };
7790
+ }
7791
+ /** Rewrites the audience index rows of one pin to match `audience`. */
7792
+ async #indexAudience(tx, pinId, audience) {
7793
+ await tx.execute({
7794
+ sql: `DELETE FROM "${T.pinAudiences}" WHERE pinId = ?`,
7795
+ args: [pinId]
7796
+ });
7797
+ for (const entry of audience) await tx.execute({
7798
+ sql: `INSERT OR IGNORE INTO "${T.pinAudiences}" (pinId, audience) VALUES (?, ?)`,
7799
+ args: [pinId, entry]
7800
+ });
8190
7801
  }
8191
- async claimSemanticOutbox(input) {
8192
- const now = input.now ?? /* @__PURE__ */ new Date();
8193
- const stale = new Date(now.getTime() - (input.claimTimeoutMs ?? 6e4));
7802
+ addPinAudience(input) {
8194
7803
  return this.#transaction(async (tx) => {
8195
- const clauses = [
8196
- `availableAt <= ?`,
8197
- `(status='pending' OR (status='processing' AND claimedAt <= ?))`,
8198
- `NOT EXISTS (SELECT 1 FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" AS earlier WHERE earlier.documentId = "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".documentId AND earlier.status != 'completed' AND (earlier.createdAt < "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".createdAt OR (earlier.createdAt = "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".createdAt AND earlier.id < "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}".id)))`
8199
- ];
8200
- const args = [now.toISOString(), stale.toISOString()];
8201
- if (input.scope) {
8202
- const keys = visibleScopeKeys(input.scope);
8203
- clauses.push(visibleSql(keys));
8204
- args.push(...keys);
7804
+ const page = await this.#livePage(tx, input.pageId);
7805
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7806
+ const existing = (await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE pageId = ?`, [input.pageId]))[0];
7807
+ if (!existing) {
7808
+ const id = (0, _mastra_core_storage.createKnowledgeUlid)();
7809
+ await tx.execute({
7810
+ sql: `INSERT INTO "${T.pins}" (id, pageId, audience, createdBy, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`,
7811
+ args: [
7812
+ id,
7813
+ input.pageId,
7814
+ JSON.stringify([...new Set(input.audience)]),
7815
+ input.createdBy,
7816
+ now,
7817
+ now
7818
+ ]
7819
+ });
7820
+ await this.#indexAudience(tx, id, [...new Set(input.audience)]);
7821
+ await this.#logActivity(tx, "pin-added", {
7822
+ scopeId: String(page.scopeId),
7823
+ pageId: input.pageId,
7824
+ provenance: { author: input.createdBy }
7825
+ }, now);
7826
+ return toPin((await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE id = ?`, [id]))[0]);
8205
7827
  }
8206
- args.push(input.limit ?? 100);
8207
- const ids = (await tx.execute({
8208
- sql: `SELECT id FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE ${clauses.join(" AND ")} ORDER BY createdAt ASC,id ASC LIMIT ?`,
8209
- args
8210
- })).rows.map((row) => String(row.id));
8211
- for (const id of ids) await tx.execute({
8212
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='processing',attempts=attempts+1,claimedAt=?,claimedBy=? WHERE id=?`,
8213
- args: [
8214
- now.toISOString(),
8215
- input.workerId,
8216
- id
8217
- ]
8218
- });
8219
- if (!ids.length) return [];
8220
- return (await tx.execute({
8221
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE id IN (${ids.map(() => "?").join(",")}) ORDER BY createdAt ASC,id ASC`,
8222
- args: ids
8223
- })).rows.map(parseOutbox);
7828
+ const current = JSON.parse(String(existing.audience));
7829
+ const merged = [.../* @__PURE__ */ new Set([...current, ...input.audience])];
7830
+ if (merged.length !== current.length) {
7831
+ await tx.execute({
7832
+ sql: `UPDATE "${T.pins}" SET audience = ?, updatedAt = ? WHERE id = ?`,
7833
+ args: [
7834
+ JSON.stringify(merged),
7835
+ now,
7836
+ String(existing.id)
7837
+ ]
7838
+ });
7839
+ await this.#indexAudience(tx, String(existing.id), merged);
7840
+ await this.#logActivity(tx, "pin-added", {
7841
+ scopeId: String(page.scopeId),
7842
+ pageId: input.pageId,
7843
+ provenance: { author: input.createdBy }
7844
+ }, now);
7845
+ }
7846
+ return toPin((await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE id = ?`, [String(existing.id)]))[0]);
8224
7847
  });
8225
7848
  }
8226
- async completeSemanticOutbox(input) {
8227
- if (!input.ids.length) return;
8228
- const now = (/* @__PURE__ */ new Date()).toISOString();
8229
- await this.#transaction(async (tx) => {
8230
- for (const id of input.ids) await tx.execute({
8231
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='completed',completedAt=? WHERE id=? AND status='processing' AND claimedBy=?`,
7849
+ removePinAudience(input) {
7850
+ return this.#transaction(async (tx) => {
7851
+ const existing = (await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE pageId = ?`, [input.pageId]))[0];
7852
+ if (!existing) return null;
7853
+ const current = JSON.parse(String(existing.audience));
7854
+ const remove = new Set(input.audience);
7855
+ const remaining = current.filter((entry) => !remove.has(entry));
7856
+ if (remaining.length === current.length) return toPin(existing);
7857
+ const page = await this.#pageRow(tx, input.pageId);
7858
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7859
+ if (page) await this.#logActivity(tx, "pin-removed", {
7860
+ scopeId: String(page.scopeId),
7861
+ pageId: input.pageId,
7862
+ provenance: { author: input.removedBy }
7863
+ }, now);
7864
+ if (remaining.length === 0) {
7865
+ await this.#indexAudience(tx, String(existing.id), []);
7866
+ await tx.execute({
7867
+ sql: `DELETE FROM "${T.pins}" WHERE id = ?`,
7868
+ args: [String(existing.id)]
7869
+ });
7870
+ return null;
7871
+ }
7872
+ await tx.execute({
7873
+ sql: `UPDATE "${T.pins}" SET audience = ?, updatedAt = ? WHERE id = ?`,
8232
7874
  args: [
7875
+ JSON.stringify(remaining),
8233
7876
  now,
8234
- id,
8235
- input.workerId
8236
- ]
8237
- });
8238
- });
8239
- }
8240
- async releaseSemanticOutbox(input) {
8241
- if (!input.ids.length) return;
8242
- await this.#transaction(async (tx) => {
8243
- for (const id of input.ids) await tx.execute({
8244
- sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='pending',availableAt=?,claimedAt=NULL,claimedBy=NULL WHERE id=? AND status='processing' AND claimedBy=?`,
8245
- args: [
8246
- (input.retryAt ?? /* @__PURE__ */ new Date()).toISOString(),
8247
- id,
8248
- input.workerId
7877
+ String(existing.id)
8249
7878
  ]
8250
7879
  });
7880
+ await this.#indexAudience(tx, String(existing.id), remaining);
7881
+ return toPin((await this.#query(tx, `SELECT * FROM "${T.pins}" WHERE id = ?`, [String(existing.id)]))[0]);
8251
7882
  });
8252
7883
  }
8253
- async #transaction(operation) {
8254
- return this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, async () => {
8255
- const tx = await this.#client.transaction("write");
8256
- try {
8257
- const result = await operation(tx);
8258
- await tx.commit();
8259
- return result;
8260
- } catch (error) {
8261
- if (!tx.closed) await tx.rollback();
8262
- throw error;
8263
- }
8264
- }), "write knowledge state");
8265
- }
8266
- async #getNode(executor, id) {
8267
- const result = await executor.execute({
8268
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE id=? AND type='node'`,
8269
- args: [id]
8270
- });
8271
- return result.rows[0] ? parseNode(result.rows[0]) : null;
7884
+ async listPins(input) {
7885
+ const core = await loadPagesCore();
7886
+ const limit = limitOf(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
7887
+ const cursor = decodeCursor(input.cursor, 2);
7888
+ const readable = pageReadable("p", input.read);
7889
+ const args = [...readable.args];
7890
+ let filters = "";
7891
+ if (input.audience !== "all") {
7892
+ filters += ` AND pin.id IN (SELECT pa.pinId FROM json_each(?) a JOIN "${T.pinAudiences}" pa ON pa.audience = a.value)`;
7893
+ args.push(jsonList(input.audience));
7894
+ }
7895
+ if (input.pageIds) {
7896
+ filters += ` AND pin.pageId IN (SELECT value FROM json_each(?))`;
7897
+ args.push(jsonList(input.pageIds));
7898
+ }
7899
+ if (input.excludePageIds?.length) {
7900
+ filters += ` AND pin.pageId NOT IN (SELECT value FROM json_each(?))`;
7901
+ args.push(jsonList(input.excludePageIds));
7902
+ }
7903
+ if (input.withBody) filters += ` AND p.body IS NOT NULL AND trim(p.body, char(32, 9, 10, 11, 12, 13)) <> ''`;
7904
+ const from = `FROM "${T.pins}" pin JOIN "${T.pages}" p ON p.id = pin.pageId WHERE p.deletedAt IS NULL AND ${readable.sql}${filters}`;
7905
+ let total;
7906
+ if (input.count) total = Number((await this.#query(this.#read, `SELECT COUNT(*) AS n ${from}`, args))[0].n);
7907
+ const pageArgs = [...args];
7908
+ let cursorFilter = "";
7909
+ if (cursor) {
7910
+ cursorFilter = ` AND (pin.createdAt < ? OR (pin.createdAt = ? AND pin.id < ?))`;
7911
+ pageArgs.push(cursor[0], cursor[0], cursor[1]);
7912
+ }
7913
+ pageArgs.push(limit + 1);
7914
+ const rows = await this.#query(this.#read, `SELECT pin.*, p.name AS pageName ${from}${cursorFilter} ORDER BY pin.createdAt DESC, pin.id DESC LIMIT ?`, pageArgs);
7915
+ const page = rows.slice(0, limit);
7916
+ return {
7917
+ pins: page.map(toListedPin),
7918
+ ...rows.length > limit ? { nextCursor: encodeCursor([String(page[page.length - 1].createdAt), String(page[page.length - 1].id)]) } : {},
7919
+ ...total === void 0 ? {} : { total }
7920
+ };
8272
7921
  }
8273
- async #getNodeByName(executor, name, scope) {
8274
- const result = await executor.execute({
8275
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND scopeKey=? AND canonicalName=?`,
8276
- args: [(0, _mastra_core_storage.knowledgeScopeKey)(scope), canonicalName(name)]
8277
- });
8278
- return result.rows[0] ? parseNode(result.rows[0]) : null;
8279
- }
8280
- async #resolveNode(executor, name, scope) {
8281
- const keys = visibleScopeKeys(scope);
8282
- const candidates = (await executor.execute({
8283
- sql: `SELECT *,json(scope) AS scopeJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND (scopeKey IN (${keys.map(() => "?").join(",")}) OR mergedInto IS NOT NULL)`,
8284
- args: [canonicalName(name), ...keys]
8285
- })).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
8286
- for (const candidate of candidates) {
8287
- const terminal = await this.#resolveTerminalNode(executor, candidate.id);
8288
- if (terminal && (0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) return terminal;
8289
- }
8290
- return null;
8291
- }
8292
- async #resolveTerminalNode(executor, id) {
8293
- let node = await this.#getNode(executor, id);
8294
- const seen = /* @__PURE__ */ new Set();
8295
- while (node?.mergedInto) {
8296
- if (seen.has(node.id)) throw new Error(`Knowledge merge cycle detected at ${node.id}`);
8297
- seen.add(node.id);
8298
- node = await this.#getNode(executor, node.mergedInto);
7922
+ };
7923
+ // src/storage/domains/knowledge/index.ts
7924
+ function canonicalizeLibSQLUrl(url) {
7925
+ if (url.includes(":memory:")) return /[?&]cache=shared(?:&|$)/.test(url) ? "file::memory:?cache=shared" : void 0;
7926
+ if (url.startsWith("file:")) {
7927
+ const [path$1] = url.slice(5).split("?");
7928
+ if (url.startsWith("file://")) {
7929
+ const parsed = new URL(url);
7930
+ const host = parsed.hostname.toLocaleLowerCase();
7931
+ const filePath = host && host !== "localhost" ? `//${host}${parsed.pathname}` : parsed.pathname;
7932
+ return `file:${(0, path.resolve)(decodeURIComponent(filePath))}`;
8299
7933
  }
8300
- return node;
7934
+ return `file:${(0, path.resolve)(decodeURIComponent(path$1))}`;
8301
7935
  }
8302
- async #getKnowledge(executor, id, includeDeleted) {
8303
- const result = await executor.execute({
8304
- sql: `SELECT *,json(scope) AS scopeJson,json(metadata) AS metadataJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${includeDeleted ? "" : " AND deletedAt IS NULL"}`,
8305
- args: [id]
7936
+ try {
7937
+ const parsed = new URL(url);
7938
+ const port = parsed.port || (parsed.protocol === "libsql:" || parsed.protocol === "https:" ? "443" : "80");
7939
+ return `${parsed.protocol}//${parsed.hostname.toLocaleLowerCase()}:${port}${parsed.pathname}`;
7940
+ } catch {
7941
+ return url;
7942
+ }
7943
+ }
7944
+ const unidentifiedClientIsolationKey = {};
7945
+ function getLibSQLKnowledgeIsolationKey(config, client) {
7946
+ if (config.storageIsolationKey !== void 0) return config.storageIsolationKey;
7947
+ if (!config.url) return unidentifiedClientIsolationKey;
7948
+ const urlKey = canonicalizeLibSQLUrl(config.url);
7949
+ return urlKey ? `libsql:${urlKey}` : client ?? config.client ?? config;
7950
+ }
7951
+ var KnowledgeLibSQL = class extends _mastra_core_storage.KnowledgeStorage {
7952
+ #client;
7953
+ #db;
7954
+ #pages;
7955
+ constructor(config) {
7956
+ const client = resolveClient(config);
7957
+ const storageIsolationKey = config.storageIsolationKey ?? getLibSQLKnowledgeIsolationKey(config, client);
7958
+ super({ storageIsolationKey });
7959
+ this.#client = client;
7960
+ this.#db = new LibSQLDB({
7961
+ client: this.#client,
7962
+ maxRetries: config.maxRetries,
7963
+ initialBackoffMs: config.initialBackoffMs
8306
7964
  });
8307
- return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
8308
- }
8309
- async #queryKnowledge(input, relationship) {
8310
- const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
8311
- const node = await this.#resolveTerminalNode(this.#client, nodeReferenceId(input.node));
8312
- if (!node) return { records: [] };
8313
- const keys = visibleScopeKeys(scope);
8314
- const includesMentions = relationship !== "about";
8315
- const relationSql = relationship === "about" ? "k.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(k.node=? OR m.recordId=?)";
8316
- const args = [
8317
- node.id,
8318
- ...relationship === "related" ? [node.id] : [],
8319
- ...keys
8320
- ];
8321
- if (input.after) args.push(input.after);
8322
- args.push((input.limit ?? 100) + 1);
8323
- const result = await this.#client.execute({
8324
- sql: `SELECT DISTINCT k.*,json(k.scope) AS scopeJson,json(k.metadata) AS metadataJson FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" k${includesMentions ? ` LEFT JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=k.id` : ""} WHERE ${relationSql} AND ${visibleSql(keys, "k.scopeKey")}${input.includeDeleted ? "" : " AND k.deletedAt IS NULL"}${input.after ? " AND k.id < ?" : ""} ORDER BY k.id DESC LIMIT ?`,
8325
- args
7965
+ this.#pages = new LibSQLKnowledgePages({
7966
+ read: this.#client,
7967
+ transaction: (operation) => this.#transaction(operation)
8326
7968
  });
8327
- const limit = input.limit ?? 100;
8328
- const rows = result.rows.slice(0, limit);
7969
+ }
7970
+ getCapabilities() {
8329
7971
  return {
8330
- records: rows.map(parseKnowledge),
8331
- nextCursor: result.rows.length > limit ? String(rows.at(-1)?.id) : void 0
7972
+ contractVersion: 4,
7973
+ schemaVersion: 4,
7974
+ supportsSchemaInspection: true,
7975
+ supportsExplicitReset: true
8332
7976
  };
8333
7977
  }
8334
- async #resolveScopeNodeIds(executor, addresses) {
8335
- const scopeNodeIds = /* @__PURE__ */ new Set();
8336
- for (const address of addresses) {
8337
- const result = await executor.execute({
8338
- sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
8339
- args: [address]
8340
- });
8341
- if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
8342
- }
8343
- return [...scopeNodeIds];
8344
- }
8345
- async #replaceNodeScopes(executor, nodeId, addresses, addedAt) {
8346
- await executor.execute({
8347
- sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
8348
- args: [nodeId]
8349
- });
8350
- for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
8351
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8352
- args: [
8353
- nodeId,
8354
- scopeNodeId,
8355
- addedAt.toISOString()
8356
- ]
8357
- });
7978
+ async dangerouslyClearAll() {
7979
+ await this.#pages.clearAll();
8358
7980
  }
8359
- /**
8360
- * Move a node's identity memberships from `from` to `to` without touching structural
8361
- * placements added by `#placeNodeInScopes`, which a scope change must not erase.
8362
- */
8363
- async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
8364
- const next = await this.#resolveScopeNodeIds(executor, to);
8365
- for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
8366
- if (next.includes(scopeNodeId)) continue;
8367
- await executor.execute({
8368
- sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
8369
- args: [nodeId, scopeNodeId]
8370
- });
8371
- }
8372
- for (const scopeNodeId of next) await executor.execute({
8373
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8374
- args: [
8375
- nodeId,
8376
- scopeNodeId,
8377
- addedAt.toISOString()
8378
- ]
8379
- });
7981
+ async #inspectPages(tx) {
7982
+ return inspectKnowledgePagesSchema(await this.#pagesSnapshot(tx));
8380
7983
  }
8381
- /**
8382
- * Additive structural placement: every address must resolve to a live scope node
8383
- * (unlike identity membership, an unknown address is a caller error, not a lazy
8384
- * materialization miss). Runs inside the caller's transaction, so a throw aborts
8385
- * the whole mutation.
8386
- */
8387
- async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
8388
- for (const address of addresses ?? []) {
8389
- const scopeNodeId = (await executor.execute({
8390
- sql: `SELECT sa.scopeNodeId AS scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" sa JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" n ON n.id=sa.scopeNodeId WHERE sa.address=? AND n.isScope AND n.deletedAt IS NULL`,
8391
- args: [address]
8392
- })).rows[0]?.scopeNodeId;
8393
- if (scopeNodeId == null) throw new _mastra_core_storage.KnowledgeNotFoundError("scope", address);
8394
- await executor.execute({
8395
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8396
- args: [
8397
- nodeId,
8398
- String(scopeNodeId),
8399
- addedAt.toISOString()
8400
- ]
8401
- });
7984
+ async #pagesSnapshot(tx) {
7985
+ const tableNames = (await tx.execute({ sql: "SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'mastra\\_knowledge\\_%' ESCAPE '\\'" })).rows.map((row) => String(row.name));
7986
+ let schemaVersion;
7987
+ if (tableNames.includes("mastra_knowledge_access_state")) {
7988
+ const state = await tx.execute({ sql: `SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_PAGES_STATE}" WHERE id = 'knowledge'` });
7989
+ if (state.rows[0]) schemaVersion = Number(state.rows[0].schemaVersion);
8402
7990
  }
7991
+ return {
7992
+ tableNames,
7993
+ schemaVersion
7994
+ };
8403
7995
  }
8404
- async #replaceRecordScopes(executor, recordId, addresses, addedAt) {
8405
- await executor.execute({
8406
- sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
8407
- args: [recordId]
8408
- });
8409
- for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
8410
- sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?)`,
8411
- args: [
8412
- recordId,
8413
- scopeNodeId,
8414
- addedAt.toISOString()
8415
- ]
8416
- });
7996
+ async inspectSchema() {
7997
+ return this.#inspectPages(this.#client);
8417
7998
  }
8418
- async #replaceMentions(tx, sourceType, sourceId, text, resolutionScope, defaultScope) {
8419
- await tx.execute({
8420
- sql: `DELETE FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
8421
- args: [sourceType, sourceId]
8422
- });
8423
- for (const name of (0, _mastra_core_storage.parseKnowledgeWikilinks)(text)) {
8424
- let node = await this.#resolveNode(tx, name, resolutionScope);
8425
- if (!node) {
8426
- node = await this.#getNodeByName(tx, name, defaultScope);
8427
- if (node) node = await this.#resolveTerminalNode(tx, node.id);
8428
- if (!node) {
8429
- const now = /* @__PURE__ */ new Date();
8430
- node = {
8431
- id: crypto.randomUUID(),
8432
- type: "node",
8433
- name,
8434
- kind: "node",
8435
- scope: defaultScope,
8436
- version: 1,
8437
- createdAt: now,
8438
- updatedAt: now
8439
- };
8440
- await tx.execute({
8441
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,canonicalName,kind,content,isScope,scope,scopeKey,version,mergedInto,createdAt,updatedAt) VALUES (?,?,?,?,?,NULL,FALSE,jsonb(?),?,?,NULL,?,?)`,
8442
- args: [
8443
- node.id,
8444
- "node",
8445
- node.name,
8446
- canonicalName(node.name),
8447
- node.kind,
8448
- JSON.stringify(defaultScope),
8449
- (0, _mastra_core_storage.knowledgeScopeKey)(defaultScope),
8450
- 1,
8451
- now.toISOString(),
8452
- now.toISOString()
8453
- ]
8454
- });
8455
- await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
8456
- await this.#activity(tx, "node-created", "node", node.id, defaultScope);
8457
- await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
8458
- }
8459
- }
7999
+ async init() {
8000
+ await this.#transaction(async (tx) => {
8001
+ for (const table of legacyKnowledgeTablesToDrop(await this.#pagesSnapshot(tx))) await tx.execute(`DROP TABLE IF EXISTS "${table}"`);
8002
+ const inspection = await this.#inspectPages(tx);
8003
+ if (inspection.status === "incompatible-reset-required") throw new _mastra_core_storage.KnowledgeSchemaResetRequiredError(inspection);
8004
+ for (const statement of KNOWLEDGE_PAGE_DDL) await tx.execute(statement);
8460
8005
  await tx.execute({
8461
- sql: `INSERT OR IGNORE INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" (sourceType,sourceId,recordId) VALUES (?,?,?)`,
8462
- args: [
8463
- sourceType,
8464
- sourceId,
8465
- node.id
8466
- ]
8006
+ sql: `INSERT OR IGNORE INTO "${TABLE_KNOWLEDGE_PAGES_STATE}" (id, schemaVersion) VALUES ('knowledge', ?)`,
8007
+ args: [4]
8467
8008
  });
8468
- }
8469
- }
8470
- async #activity(executor, action, recordType, recordId, scope, sourceThreadId) {
8471
- const now = /* @__PURE__ */ new Date();
8472
- const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
8473
- await executor.execute({
8474
- sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
8475
- args: [
8476
- (0, _mastra_core_storage.createKnowledgeUlid)(),
8477
- action,
8478
- recordType,
8479
- recordId,
8480
- contextScopeId ?? null,
8481
- recordType,
8482
- recordId,
8483
- JSON.stringify(scope),
8484
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
8485
- sourceThreadId ?? null,
8486
- now.toISOString()
8487
- ]
8488
8009
  });
8489
8010
  }
8490
- async #outbox(executor, documentType, id, operation, version, scope) {
8491
- const documentId = (0, _mastra_core_storage.knowledgeSemanticDocumentId)(documentType, id);
8492
- const idempotencyKey = (0, _mastra_core_storage.knowledgeSemanticIdempotencyKey)(documentId, operation, version);
8493
- const now = /* @__PURE__ */ new Date();
8494
- await executor.execute({
8495
- sql: `INSERT OR IGNORE INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" (id,idempotencyKey,documentId,documentType,operation,scope,scopeKey,status,attempts,availableAt,claimedAt,claimedBy,createdAt,completedAt) VALUES (?,?,?,?,?,jsonb(?),?,'pending',0,?,NULL,NULL,?,NULL)`,
8496
- args: [
8497
- (0, _mastra_core_storage.createKnowledgeUlid)(),
8498
- idempotencyKey,
8499
- documentId,
8500
- documentType,
8501
- operation,
8502
- JSON.stringify(scope),
8503
- (0, _mastra_core_storage.knowledgeScopeKey)(scope),
8504
- now.toISOString(),
8505
- now.toISOString()
8506
- ]
8011
+ async dangerouslyReset() {
8012
+ await withClientWriteLock(this.#client, async () => {
8013
+ await this.#client.batch([...KNOWLEDGE_RESETTABLE_TABLE_NAMES, KNOWLEDGE_PAGE_TABLES.pinAudiences].map((table) => ({
8014
+ sql: `DROP TABLE IF EXISTS "${table}"`,
8015
+ args: []
8016
+ })), "write");
8507
8017
  });
8018
+ await this.init();
8019
+ }
8020
+ materializeScope(input) {
8021
+ return this.#pages.materializeScope(input);
8022
+ }
8023
+ listScopeDeclarations() {
8024
+ return this.#pages.listScopeDeclarations();
8025
+ }
8026
+ renameScopeDeclaration(input) {
8027
+ return this.#pages.renameScopeDeclaration(input);
8028
+ }
8029
+ getScopesByAddress(input) {
8030
+ return this.#pages.getScopesByAddress(input);
8031
+ }
8032
+ listChildScopes(input) {
8033
+ return this.#pages.listChildScopes(input);
8034
+ }
8035
+ getPage(input) {
8036
+ return this.#pages.getPage(input);
8037
+ }
8038
+ getPageByPath(input) {
8039
+ return this.#pages.getPageByPath(input);
8040
+ }
8041
+ findPagesByName(input) {
8042
+ return this.#pages.findPagesByName(input);
8043
+ }
8044
+ listChildPages(input) {
8045
+ return this.#pages.listChildPages(input);
8046
+ }
8047
+ listPages(input) {
8048
+ return this.#pages.listPages(input);
8049
+ }
8050
+ createPage(input) {
8051
+ return this.#pages.createPage(input);
8052
+ }
8053
+ updatePage(input) {
8054
+ return this.#pages.updatePage(input);
8055
+ }
8056
+ movePage(input) {
8057
+ return this.#pages.movePage(input);
8058
+ }
8059
+ mergePages(input) {
8060
+ return this.#pages.mergePages(input);
8061
+ }
8062
+ deletePage(input) {
8063
+ return this.#pages.deletePage(input);
8064
+ }
8065
+ getNote(input) {
8066
+ return this.#pages.getNote(input);
8067
+ }
8068
+ listNotes(input) {
8069
+ return this.#pages.listNotes(input);
8070
+ }
8071
+ countNotes(input) {
8072
+ return this.#pages.countNotes(input);
8073
+ }
8074
+ listNotesMentioning(input) {
8075
+ return this.#pages.listNotesMentioning(input);
8076
+ }
8077
+ addNote(input) {
8078
+ return this.#pages.addNote(input);
8079
+ }
8080
+ restampNote(input) {
8081
+ return this.#pages.restampNote(input);
8082
+ }
8083
+ removeNote(input) {
8084
+ return this.#pages.removeNote(input);
8085
+ }
8086
+ restoreNote(input) {
8087
+ return this.#pages.restoreNote(input);
8088
+ }
8089
+ searchPages(input) {
8090
+ return this.#pages.searchPages(input);
8091
+ }
8092
+ listPageActivity(input) {
8093
+ return this.#pages.listPageActivity(input);
8094
+ }
8095
+ addPinAudience(input) {
8096
+ return this.#pages.addPinAudience(input);
8097
+ }
8098
+ removePinAudience(input) {
8099
+ return this.#pages.removePinAudience(input);
8100
+ }
8101
+ getPages(input) {
8102
+ return this.#pages.getPages(input);
8103
+ }
8104
+ listPins(input) {
8105
+ return this.#pages.listPins(input);
8106
+ }
8107
+ async #transaction(operation) {
8108
+ return this.#db.executeWriteOperationWithRetry(() => withClientWriteLock(this.#client, async () => {
8109
+ const tx = await this.#client.transaction("write");
8110
+ try {
8111
+ const result = await operation(tx);
8112
+ await tx.commit();
8113
+ return result;
8114
+ } catch (error) {
8115
+ if (!tx.closed) await tx.rollback();
8116
+ throw error;
8117
+ }
8118
+ }), "write knowledge state");
8508
8119
  }
8509
8120
  };
8510
8121
  // src/storage/domains/mcp-clients/index.ts