@mastra/pg 1.34.0 → 1.35.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core/dist/knowledge-compat/index.d.ts +152 -406
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/index.cjs +1454 -2018
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1456 -2020
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/knowledge/index.d.ts +35 -87
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/pages-schema.d.ts +20 -0
- package/dist/storage/domains/knowledge/pages-schema.d.ts.map +1 -0
- package/dist/storage/domains/knowledge/pages.d.ts +167 -0
- package/dist/storage/domains/knowledge/pages.d.ts.map +1 -0
- package/dist/storage/domains/observability/v-next/index.d.ts +1 -1
- package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts +5 -0
- package/dist/storage/domains/observability/v-next/trace-aggregate.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -410,7 +410,7 @@ const FILTER_OPERATORS = {
|
|
|
410
410
|
return {
|
|
411
411
|
sql,
|
|
412
412
|
needsValue: true,
|
|
413
|
-
transformValue: () => Array.isArray(value) ? value.map(String) : typeof value === "string" ? escapeLikePattern
|
|
413
|
+
transformValue: () => Array.isArray(value) ? value.map(String) : typeof value === "string" ? escapeLikePattern(value) : value
|
|
414
414
|
};
|
|
415
415
|
},
|
|
416
416
|
/**
|
|
@@ -434,7 +434,7 @@ const FILTER_OPERATORS = {
|
|
|
434
434
|
const parseJsonPathKey = (key) => {
|
|
435
435
|
return (key !== "" ? (0, _mastra_core_utils.parseFieldKey)(key) : "").replace(/\./g, ",");
|
|
436
436
|
};
|
|
437
|
-
function escapeLikePattern
|
|
437
|
+
function escapeLikePattern(str) {
|
|
438
438
|
return str.replace(/([%_\\])/g, "\\$1");
|
|
439
439
|
}
|
|
440
440
|
/**
|
|
@@ -8541,976 +8541,1403 @@ var FavoritesPG = class FavoritesPG extends _mastra_core_storage.FavoritesStorag
|
|
|
8541
8541
|
}
|
|
8542
8542
|
}
|
|
8543
8543
|
};
|
|
8544
|
-
// ../../packages/_internal-core/dist/knowledge-compat/index.js
|
|
8545
|
-
const KNOWLEDGE_V2_CORE_FEATURE = "knowledge-v2";
|
|
8546
|
-
/** Hard cap on scope nodes returned by one `listScopeNodes` read. */
|
|
8547
|
-
const MAX_KNOWLEDGE_SCOPE_NODES = 1e3;
|
|
8548
|
-
function knowledgeScopeNodeFilterKey(input) {
|
|
8549
|
-
return JSON.stringify([
|
|
8550
|
-
input.withinAddress ?? null,
|
|
8551
|
-
input.addresses ? [...input.addresses].sort() : null,
|
|
8552
|
-
input.ids ? [...input.ids].sort() : null
|
|
8553
|
-
]);
|
|
8554
|
-
}
|
|
8555
|
-
function createKnowledgeScopeNodeCursor(scope, input) {
|
|
8556
|
-
return encodeURIComponent(JSON.stringify({
|
|
8557
|
-
version: 1,
|
|
8558
|
-
type: "scope",
|
|
8559
|
-
name: scope.name,
|
|
8560
|
-
id: scope.id,
|
|
8561
|
-
filter: knowledgeScopeNodeFilterKey(input)
|
|
8562
|
-
}));
|
|
8563
|
-
}
|
|
8564
|
-
/** Validates the page size and cursor of a `listScopeNodes` query; throws on a cursor from a different query. */
|
|
8565
|
-
function parseListKnowledgeScopeNodesInput(input = {}) {
|
|
8566
|
-
const requested = Number.isFinite(input.limit) ? Math.trunc(input.limit) : MAX_KNOWLEDGE_SCOPE_NODES;
|
|
8567
|
-
const limit = Math.min(Math.max(requested, 1), MAX_KNOWLEDGE_SCOPE_NODES);
|
|
8568
|
-
if (!input.cursor) return {
|
|
8569
|
-
limit,
|
|
8570
|
-
after: null
|
|
8571
|
-
};
|
|
8572
|
-
let value;
|
|
8573
|
-
try {
|
|
8574
|
-
value = JSON.parse(decodeURIComponent(input.cursor));
|
|
8575
|
-
} catch {
|
|
8576
|
-
throw new Error("Invalid Knowledge scope node cursor.");
|
|
8577
|
-
}
|
|
8578
|
-
const parsed = value && typeof value === "object" ? value : {};
|
|
8579
|
-
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.");
|
|
8580
|
-
return {
|
|
8581
|
-
limit,
|
|
8582
|
-
after: {
|
|
8583
|
-
name: parsed.name,
|
|
8584
|
-
id: parsed.id
|
|
8585
|
-
}
|
|
8586
|
-
};
|
|
8587
|
-
}
|
|
8588
|
-
/** Builds a page from up to `limit + 1` name/id-ordered scope nodes. */
|
|
8589
|
-
function pageKnowledgeScopeNodes(rows, limit, input) {
|
|
8590
|
-
const scopes = rows.slice(0, limit);
|
|
8591
|
-
const last = scopes.at(-1);
|
|
8592
|
-
return {
|
|
8593
|
-
scopes,
|
|
8594
|
-
nextCursor: rows.length > limit && last ? createKnowledgeScopeNodeCursor(last, input) : null
|
|
8595
|
-
};
|
|
8596
|
-
}
|
|
8597
|
-
/** Clamps a requested member limit to 1–{@link MAX_KNOWLEDGE_SCOPE_MEMBERS_LIMIT}. */
|
|
8598
|
-
function knowledgeScopeMembersLimit(limit) {
|
|
8599
|
-
if (limit === void 0 || !Number.isFinite(limit)) return 500;
|
|
8600
|
-
return Math.min(Math.max(Math.trunc(limit), 1), 500);
|
|
8601
|
-
}
|
|
8602
|
-
/** Validates the page size and cursor of a `listScopeMembers` query; throws on a cursor from another scope. */
|
|
8603
|
-
function parseListKnowledgeScopeMembersInput(input) {
|
|
8604
|
-
const limit = knowledgeScopeMembersLimit(input.limit);
|
|
8605
|
-
if (!input.cursor) return {
|
|
8606
|
-
limit,
|
|
8607
|
-
after: null
|
|
8608
|
-
};
|
|
8609
|
-
let value;
|
|
8610
|
-
try {
|
|
8611
|
-
value = JSON.parse(decodeURIComponent(input.cursor));
|
|
8612
|
-
} catch {
|
|
8613
|
-
throw new Error("Invalid Knowledge scope member cursor.");
|
|
8614
|
-
}
|
|
8615
|
-
const parsed = value && typeof value === "object" ? value : {};
|
|
8616
|
-
const updatedAt = typeof parsed.updatedAt === "string" ? new Date(parsed.updatedAt) : /* @__PURE__ */ new Date(NaN);
|
|
8617
|
-
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.");
|
|
8618
|
-
return {
|
|
8619
|
-
limit,
|
|
8620
|
-
after: {
|
|
8621
|
-
updatedAt,
|
|
8622
|
-
name: parsed.name,
|
|
8623
|
-
id: parsed.id
|
|
8624
|
-
}
|
|
8625
|
-
};
|
|
8626
|
-
}
|
|
8627
|
-
/** Builds a page from up to `limit + 1` members in `listScopeMembers` order. */
|
|
8628
|
-
function pageKnowledgeScopeMembers(rows, limit, input) {
|
|
8629
|
-
const members = rows.slice(0, limit);
|
|
8630
|
-
const last = members.at(-1);
|
|
8631
|
-
const hasMore = rows.length > limit;
|
|
8632
|
-
return {
|
|
8633
|
-
members,
|
|
8634
|
-
hasMore,
|
|
8635
|
-
nextCursor: hasMore && last ? encodeURIComponent(JSON.stringify({
|
|
8636
|
-
version: 1,
|
|
8637
|
-
type: "scope-member",
|
|
8638
|
-
scopeNodeId: input.scopeNodeId,
|
|
8639
|
-
updatedAt: last.updatedAt.toISOString(),
|
|
8640
|
-
name: last.name,
|
|
8641
|
-
id: last.id
|
|
8642
|
-
})) : null
|
|
8643
|
-
};
|
|
8644
|
-
}
|
|
8645
|
-
const TABLE_KNOWLEDGE_NODES$1 = "mastra_knowledge_nodes";
|
|
8646
|
-
const TABLE_KNOWLEDGE_RECORDS$1 = "mastra_knowledge_records";
|
|
8647
|
-
const TABLE_KNOWLEDGE_MENTIONS$1 = "mastra_knowledge_mentions";
|
|
8648
|
-
const TABLE_KNOWLEDGE_ACTIVITY$1 = "mastra_knowledge_activity";
|
|
8649
|
-
const TABLE_KNOWLEDGE_SEMANTIC_OUTBOX$1 = "mastra_knowledge_semantic_outbox";
|
|
8650
|
-
const TABLE_KNOWLEDGE_NODE_SCOPES = "mastra_knowledge_node_scopes";
|
|
8651
|
-
const TABLE_KNOWLEDGE_RECORD_SCOPES = "mastra_knowledge_record_scopes";
|
|
8652
|
-
const TABLE_KNOWLEDGE_SCOPE_GRANTS = "mastra_knowledge_scope_grants";
|
|
8653
|
-
const TABLE_KNOWLEDGE_ACCESS_STATE = "mastra_knowledge_access_state";
|
|
8654
|
-
const TABLE_KNOWLEDGE_SCOPE_ADDRESSES = "mastra_knowledge_scope_addresses";
|
|
8655
|
-
const TABLE_KNOWLEDGE_NODE_ADDRESSES = "mastra_knowledge_node_addresses";
|
|
8656
|
-
const TABLE_KNOWLEDGE_IMPORT_STATE = "mastra_knowledge_import_state";
|
|
8657
|
-
const TABLE_KNOWLEDGE_IMPORT_RUNS = "mastra_knowledge_import_runs";
|
|
8658
|
-
const TABLE_KNOWLEDGE_PROPOSALS = "mastra_knowledge_proposals";
|
|
8659
8544
|
const KNOWLEDGE_TABLE_NAMES = [
|
|
8660
|
-
|
|
8661
|
-
|
|
8662
|
-
|
|
8663
|
-
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8545
|
+
"mastra_knowledge_nodes",
|
|
8546
|
+
"mastra_knowledge_records",
|
|
8547
|
+
"mastra_knowledge_mentions",
|
|
8548
|
+
"mastra_knowledge_activity",
|
|
8549
|
+
"mastra_knowledge_semantic_outbox",
|
|
8550
|
+
"mastra_knowledge_node_scopes",
|
|
8551
|
+
"mastra_knowledge_record_scopes",
|
|
8552
|
+
"mastra_knowledge_scope_grants",
|
|
8553
|
+
"mastra_knowledge_access_state",
|
|
8554
|
+
"mastra_knowledge_scope_addresses",
|
|
8555
|
+
"mastra_knowledge_node_addresses",
|
|
8556
|
+
"mastra_knowledge_import_state",
|
|
8557
|
+
"mastra_knowledge_import_runs",
|
|
8558
|
+
"mastra_knowledge_proposals"
|
|
8674
8559
|
];
|
|
8675
8560
|
/** Knowledge v1 tables with no v2 equivalent; an explicit schema reset drops them. */
|
|
8676
8561
|
const RETIRED_KNOWLEDGE_TABLE_NAMES = ["mastra_knowledge_cursors"];
|
|
8677
|
-
|
|
8678
|
-
const
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8562
|
+
const KNOWLEDGE_PAGES_CORE_FEATURE = "knowledge-pages";
|
|
8563
|
+
const KNOWLEDGE_PAGES_TABLE_NAMES = [
|
|
8564
|
+
"mastra_knowledge_pages",
|
|
8565
|
+
"mastra_knowledge_notes",
|
|
8566
|
+
"mastra_knowledge_note_stamps",
|
|
8567
|
+
"mastra_knowledge_mentions",
|
|
8568
|
+
"mastra_knowledge_pins",
|
|
8569
|
+
"mastra_knowledge_activity",
|
|
8570
|
+
"mastra_knowledge_access_state"
|
|
8571
|
+
];
|
|
8572
|
+
/** Every Knowledge table any schema version created. Reset drops these and nothing else. */
|
|
8573
|
+
const KNOWLEDGE_RESETTABLE_TABLE_NAMES = [.../* @__PURE__ */ new Set([
|
|
8574
|
+
...KNOWLEDGE_PAGES_TABLE_NAMES,
|
|
8575
|
+
...KNOWLEDGE_TABLE_NAMES,
|
|
8684
8576
|
...RETIRED_KNOWLEDGE_TABLE_NAMES
|
|
8685
|
-
]);
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
metadata: {
|
|
8715
|
-
type: "jsonb",
|
|
8716
|
-
nullable: true
|
|
8717
|
-
},
|
|
8718
|
-
version: {
|
|
8719
|
-
type: "integer",
|
|
8720
|
-
nullable: false
|
|
8721
|
-
},
|
|
8722
|
-
createdAt: {
|
|
8723
|
-
type: "timestamp",
|
|
8724
|
-
nullable: false
|
|
8725
|
-
},
|
|
8726
|
-
updatedAt: {
|
|
8727
|
-
type: "timestamp",
|
|
8728
|
-
nullable: false
|
|
8729
|
-
},
|
|
8730
|
-
deletedAt: {
|
|
8731
|
-
type: "timestamp",
|
|
8732
|
-
nullable: true
|
|
8733
|
-
},
|
|
8734
|
-
deletedBy: {
|
|
8735
|
-
type: "text",
|
|
8736
|
-
nullable: true
|
|
8737
|
-
},
|
|
8738
|
-
type: {
|
|
8739
|
-
type: "text",
|
|
8740
|
-
nullable: true
|
|
8741
|
-
},
|
|
8742
|
-
canonicalName: {
|
|
8743
|
-
type: "text",
|
|
8744
|
-
nullable: true
|
|
8745
|
-
},
|
|
8746
|
-
content: {
|
|
8747
|
-
type: "text",
|
|
8748
|
-
nullable: true
|
|
8749
|
-
},
|
|
8750
|
-
description: {
|
|
8751
|
-
type: "text",
|
|
8752
|
-
nullable: true
|
|
8753
|
-
},
|
|
8754
|
-
scope: {
|
|
8755
|
-
type: "jsonb",
|
|
8756
|
-
nullable: true
|
|
8757
|
-
},
|
|
8758
|
-
scopeKey: {
|
|
8759
|
-
type: "text",
|
|
8760
|
-
nullable: true
|
|
8761
|
-
},
|
|
8762
|
-
mergedInto: {
|
|
8763
|
-
type: "text",
|
|
8764
|
-
nullable: true
|
|
8765
|
-
}
|
|
8766
|
-
};
|
|
8767
|
-
const KNOWLEDGE_V2_RECORDS_SCHEMA = {
|
|
8768
|
-
id: {
|
|
8769
|
-
type: "text",
|
|
8770
|
-
nullable: false,
|
|
8771
|
-
primaryKey: true
|
|
8772
|
-
},
|
|
8773
|
-
node: {
|
|
8774
|
-
type: "text",
|
|
8775
|
-
nullable: false,
|
|
8776
|
-
references: {
|
|
8777
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8778
|
-
column: "id"
|
|
8779
|
-
}
|
|
8780
|
-
},
|
|
8781
|
-
text: {
|
|
8782
|
-
type: "text",
|
|
8783
|
-
nullable: false
|
|
8784
|
-
},
|
|
8785
|
-
metadata: {
|
|
8786
|
-
type: "jsonb",
|
|
8787
|
-
nullable: true
|
|
8788
|
-
},
|
|
8789
|
-
version: {
|
|
8790
|
-
type: "integer",
|
|
8791
|
-
nullable: false
|
|
8792
|
-
},
|
|
8793
|
-
capturedAt: {
|
|
8794
|
-
type: "timestamp",
|
|
8795
|
-
nullable: false
|
|
8796
|
-
},
|
|
8797
|
-
updatedAt: {
|
|
8798
|
-
type: "timestamp",
|
|
8799
|
-
nullable: false
|
|
8800
|
-
},
|
|
8801
|
-
deletedAt: {
|
|
8802
|
-
type: "timestamp",
|
|
8803
|
-
nullable: true
|
|
8804
|
-
},
|
|
8805
|
-
deletedBy: {
|
|
8806
|
-
type: "text",
|
|
8807
|
-
nullable: true
|
|
8808
|
-
},
|
|
8809
|
-
scope: {
|
|
8810
|
-
type: "jsonb",
|
|
8811
|
-
nullable: true
|
|
8812
|
-
},
|
|
8813
|
-
scopeKey: {
|
|
8814
|
-
type: "text",
|
|
8815
|
-
nullable: true
|
|
8816
|
-
},
|
|
8817
|
-
sourceThreadId: {
|
|
8818
|
-
type: "text",
|
|
8819
|
-
nullable: true
|
|
8820
|
-
},
|
|
8821
|
-
when: {
|
|
8822
|
-
type: "timestamp",
|
|
8823
|
-
nullable: true
|
|
8824
|
-
}
|
|
8825
|
-
};
|
|
8826
|
-
const KNOWLEDGE_V2_MENTIONS_SCHEMA = {
|
|
8827
|
-
recordId: {
|
|
8828
|
-
type: "text",
|
|
8829
|
-
nullable: false,
|
|
8830
|
-
references: {
|
|
8831
|
-
table: TABLE_KNOWLEDGE_RECORDS$1,
|
|
8832
|
-
column: "id"
|
|
8833
|
-
}
|
|
8834
|
-
},
|
|
8835
|
-
targetNodeId: {
|
|
8836
|
-
type: "text",
|
|
8837
|
-
nullable: true,
|
|
8838
|
-
references: {
|
|
8839
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8840
|
-
column: "id"
|
|
8841
|
-
}
|
|
8842
|
-
},
|
|
8843
|
-
sourceType: {
|
|
8844
|
-
type: "text",
|
|
8845
|
-
nullable: true
|
|
8846
|
-
},
|
|
8847
|
-
sourceId: {
|
|
8848
|
-
type: "text",
|
|
8849
|
-
nullable: true
|
|
8850
|
-
}
|
|
8851
|
-
};
|
|
8852
|
-
const KNOWLEDGE_NODE_SCOPES_SCHEMA = {
|
|
8853
|
-
nodeId: {
|
|
8854
|
-
type: "text",
|
|
8855
|
-
nullable: false,
|
|
8856
|
-
references: {
|
|
8857
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8858
|
-
column: "id"
|
|
8859
|
-
}
|
|
8860
|
-
},
|
|
8861
|
-
scopeNodeId: {
|
|
8862
|
-
type: "text",
|
|
8863
|
-
nullable: false,
|
|
8864
|
-
references: {
|
|
8865
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8866
|
-
column: "id"
|
|
8867
|
-
}
|
|
8868
|
-
},
|
|
8869
|
-
addedAt: {
|
|
8870
|
-
type: "timestamp",
|
|
8871
|
-
nullable: false
|
|
8872
|
-
}
|
|
8873
|
-
};
|
|
8874
|
-
const KNOWLEDGE_RECORD_SCOPES_SCHEMA = {
|
|
8875
|
-
recordId: {
|
|
8876
|
-
type: "text",
|
|
8877
|
-
nullable: false,
|
|
8878
|
-
references: {
|
|
8879
|
-
table: TABLE_KNOWLEDGE_RECORDS$1,
|
|
8880
|
-
column: "id"
|
|
8881
|
-
}
|
|
8882
|
-
},
|
|
8883
|
-
scopeNodeId: {
|
|
8884
|
-
type: "text",
|
|
8885
|
-
nullable: false,
|
|
8886
|
-
references: {
|
|
8887
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8888
|
-
column: "id"
|
|
8889
|
-
}
|
|
8890
|
-
},
|
|
8891
|
-
addedAt: {
|
|
8892
|
-
type: "timestamp",
|
|
8893
|
-
nullable: false
|
|
8894
|
-
}
|
|
8895
|
-
};
|
|
8896
|
-
const KNOWLEDGE_SCOPE_GRANTS_SCHEMA = {
|
|
8897
|
-
scopeNodeId: {
|
|
8898
|
-
type: "text",
|
|
8899
|
-
nullable: false,
|
|
8900
|
-
references: {
|
|
8901
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8902
|
-
column: "id"
|
|
8903
|
-
}
|
|
8904
|
-
},
|
|
8905
|
-
scopeRefId: {
|
|
8906
|
-
type: "text",
|
|
8907
|
-
nullable: false,
|
|
8908
|
-
references: {
|
|
8909
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8910
|
-
column: "id"
|
|
8911
|
-
}
|
|
8912
|
-
},
|
|
8913
|
-
role: {
|
|
8914
|
-
type: "text",
|
|
8915
|
-
nullable: false
|
|
8916
|
-
},
|
|
8917
|
-
canSuggest: {
|
|
8918
|
-
type: "boolean",
|
|
8919
|
-
nullable: true
|
|
8920
|
-
}
|
|
8921
|
-
};
|
|
8922
|
-
const KNOWLEDGE_ACCESS_STATE_SCHEMA = {
|
|
8923
|
-
id: {
|
|
8924
|
-
type: "text",
|
|
8925
|
-
nullable: false,
|
|
8926
|
-
primaryKey: true
|
|
8927
|
-
},
|
|
8928
|
-
epoch: {
|
|
8929
|
-
type: "integer",
|
|
8930
|
-
nullable: false
|
|
8931
|
-
},
|
|
8932
|
-
schemaVersion: {
|
|
8933
|
-
type: "integer",
|
|
8934
|
-
nullable: false
|
|
8935
|
-
}
|
|
8936
|
-
};
|
|
8937
|
-
const KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA = {
|
|
8938
|
-
address: {
|
|
8939
|
-
type: "text",
|
|
8940
|
-
nullable: false,
|
|
8941
|
-
primaryKey: true
|
|
8942
|
-
},
|
|
8943
|
-
scopeNodeId: {
|
|
8944
|
-
type: "text",
|
|
8945
|
-
nullable: false,
|
|
8946
|
-
references: {
|
|
8947
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8948
|
-
column: "id"
|
|
8949
|
-
}
|
|
8950
|
-
}
|
|
8951
|
-
};
|
|
8952
|
-
const KNOWLEDGE_NODE_ADDRESSES_SCHEMA = {
|
|
8953
|
-
source: {
|
|
8954
|
-
type: "text",
|
|
8955
|
-
nullable: false
|
|
8956
|
-
},
|
|
8957
|
-
address: {
|
|
8958
|
-
type: "text",
|
|
8959
|
-
nullable: false
|
|
8960
|
-
},
|
|
8961
|
-
nodeId: {
|
|
8962
|
-
type: "text",
|
|
8963
|
-
nullable: false,
|
|
8964
|
-
references: {
|
|
8965
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
8966
|
-
column: "id"
|
|
8967
|
-
}
|
|
8968
|
-
}
|
|
8969
|
-
};
|
|
8970
|
-
const KNOWLEDGE_IMPORT_STATE_SCHEMA = {
|
|
8971
|
-
importerId: {
|
|
8972
|
-
type: "text",
|
|
8973
|
-
nullable: false
|
|
8974
|
-
},
|
|
8975
|
-
binding: {
|
|
8976
|
-
type: "text",
|
|
8977
|
-
nullable: false
|
|
8978
|
-
},
|
|
8979
|
-
key: {
|
|
8980
|
-
type: "text",
|
|
8981
|
-
nullable: false
|
|
8982
|
-
},
|
|
8983
|
-
value: {
|
|
8984
|
-
type: "text",
|
|
8985
|
-
nullable: false
|
|
8986
|
-
}
|
|
8987
|
-
};
|
|
8988
|
-
const KNOWLEDGE_IMPORT_RUNS_SCHEMA = {
|
|
8989
|
-
id: {
|
|
8990
|
-
type: "text",
|
|
8991
|
-
nullable: false,
|
|
8992
|
-
primaryKey: true
|
|
8993
|
-
},
|
|
8994
|
-
importerId: {
|
|
8995
|
-
type: "text",
|
|
8996
|
-
nullable: false
|
|
8997
|
-
},
|
|
8998
|
-
binding: {
|
|
8999
|
-
type: "text",
|
|
9000
|
-
nullable: false
|
|
9001
|
-
},
|
|
9002
|
-
importKind: {
|
|
9003
|
-
type: "text",
|
|
9004
|
-
nullable: false
|
|
9005
|
-
},
|
|
9006
|
-
triggerKind: {
|
|
9007
|
-
type: "text",
|
|
9008
|
-
nullable: false
|
|
9009
|
-
},
|
|
9010
|
-
status: {
|
|
9011
|
-
type: "text",
|
|
9012
|
-
nullable: false
|
|
9013
|
-
},
|
|
9014
|
-
error: {
|
|
9015
|
-
type: "text",
|
|
9016
|
-
nullable: true
|
|
9017
|
-
},
|
|
9018
|
-
transcriptThreadId: {
|
|
9019
|
-
type: "text",
|
|
9020
|
-
nullable: true
|
|
9021
|
-
},
|
|
9022
|
-
traceId: {
|
|
9023
|
-
type: "text",
|
|
9024
|
-
nullable: true
|
|
9025
|
-
},
|
|
9026
|
-
queuedAt: {
|
|
9027
|
-
type: "timestamp",
|
|
9028
|
-
nullable: false
|
|
9029
|
-
},
|
|
9030
|
-
startedAt: {
|
|
9031
|
-
type: "timestamp",
|
|
9032
|
-
nullable: true
|
|
9033
|
-
},
|
|
9034
|
-
completedAt: {
|
|
9035
|
-
type: "timestamp",
|
|
9036
|
-
nullable: true
|
|
9037
|
-
}
|
|
9038
|
-
};
|
|
9039
|
-
const KNOWLEDGE_V2_ACTIVITY_SCHEMA = {
|
|
9040
|
-
id: {
|
|
9041
|
-
type: "text",
|
|
9042
|
-
nullable: false,
|
|
9043
|
-
primaryKey: true
|
|
9044
|
-
},
|
|
9045
|
-
action: {
|
|
9046
|
-
type: "text",
|
|
9047
|
-
nullable: false
|
|
9048
|
-
},
|
|
9049
|
-
targetType: {
|
|
9050
|
-
type: "text",
|
|
9051
|
-
nullable: true
|
|
9052
|
-
},
|
|
9053
|
-
targetId: {
|
|
9054
|
-
type: "text",
|
|
9055
|
-
nullable: true
|
|
9056
|
-
},
|
|
9057
|
-
contextScopeId: {
|
|
9058
|
-
type: "text",
|
|
9059
|
-
nullable: true,
|
|
9060
|
-
references: {
|
|
9061
|
-
table: TABLE_KNOWLEDGE_NODES$1,
|
|
9062
|
-
column: "id"
|
|
9063
|
-
}
|
|
9064
|
-
},
|
|
9065
|
-
importRunId: {
|
|
9066
|
-
type: "text",
|
|
9067
|
-
nullable: true,
|
|
9068
|
-
references: {
|
|
9069
|
-
table: TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
9070
|
-
column: "id"
|
|
9071
|
-
}
|
|
9072
|
-
},
|
|
9073
|
-
details: {
|
|
9074
|
-
type: "jsonb",
|
|
9075
|
-
nullable: true
|
|
9076
|
-
},
|
|
9077
|
-
createdAt: {
|
|
9078
|
-
type: "timestamp",
|
|
9079
|
-
nullable: false
|
|
9080
|
-
},
|
|
9081
|
-
recordType: {
|
|
9082
|
-
type: "text",
|
|
9083
|
-
nullable: true
|
|
9084
|
-
},
|
|
9085
|
-
recordId: {
|
|
9086
|
-
type: "text",
|
|
9087
|
-
nullable: true
|
|
9088
|
-
},
|
|
9089
|
-
scope: {
|
|
9090
|
-
type: "jsonb",
|
|
9091
|
-
nullable: true
|
|
9092
|
-
},
|
|
9093
|
-
scopeKey: {
|
|
9094
|
-
type: "text",
|
|
9095
|
-
nullable: true
|
|
9096
|
-
},
|
|
9097
|
-
sourceThreadId: {
|
|
9098
|
-
type: "text",
|
|
9099
|
-
nullable: true
|
|
9100
|
-
}
|
|
9101
|
-
};
|
|
9102
|
-
const KNOWLEDGE_PROPOSALS_SCHEMA = {
|
|
9103
|
-
id: {
|
|
9104
|
-
type: "text",
|
|
9105
|
-
nullable: false,
|
|
9106
|
-
primaryKey: true
|
|
9107
|
-
},
|
|
9108
|
-
targetType: {
|
|
9109
|
-
type: "text",
|
|
9110
|
-
nullable: false
|
|
9111
|
-
},
|
|
9112
|
-
targetId: {
|
|
9113
|
-
type: "text",
|
|
9114
|
-
nullable: false
|
|
9115
|
-
},
|
|
9116
|
-
action: {
|
|
9117
|
-
type: "text",
|
|
9118
|
-
nullable: false
|
|
9119
|
-
},
|
|
9120
|
-
changes: {
|
|
9121
|
-
type: "jsonb",
|
|
9122
|
-
nullable: false
|
|
9123
|
-
},
|
|
9124
|
-
reason: {
|
|
9125
|
-
type: "text",
|
|
9126
|
-
nullable: true
|
|
9127
|
-
},
|
|
9128
|
-
proposerContextScopeId: {
|
|
9129
|
-
type: "text",
|
|
9130
|
-
nullable: false
|
|
9131
|
-
},
|
|
9132
|
-
expectedVersion: {
|
|
9133
|
-
type: "integer",
|
|
9134
|
-
nullable: false
|
|
9135
|
-
},
|
|
9136
|
-
status: {
|
|
9137
|
-
type: "text",
|
|
9138
|
-
nullable: false
|
|
9139
|
-
},
|
|
9140
|
-
reviewerContextScopeId: {
|
|
9141
|
-
type: "text",
|
|
9142
|
-
nullable: true
|
|
9143
|
-
},
|
|
9144
|
-
reviewedAt: {
|
|
9145
|
-
type: "timestamp",
|
|
9146
|
-
nullable: true
|
|
9147
|
-
},
|
|
9148
|
-
createdAt: {
|
|
9149
|
-
type: "timestamp",
|
|
9150
|
-
nullable: false
|
|
8577
|
+
])];
|
|
8578
|
+
/** Schema-1/2 tables whose presence means the store needs a reset before schema 4 can run. */
|
|
8579
|
+
const LEGACY_KNOWLEDGE_TABLE_NAMES = new Set([...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES].filter((name) => !KNOWLEDGE_PAGES_TABLE_NAMES.includes(name)));
|
|
8580
|
+
/**
|
|
8581
|
+
* Classifies what an adapter found before creating schema-4 tables. `tableNames` is every
|
|
8582
|
+
* `mastra_knowledge_*` table present; `schemaVersion` is the state row's version, if any.
|
|
8583
|
+
*/
|
|
8584
|
+
function inspectKnowledgePagesSchema(snapshot) {
|
|
8585
|
+
const legacy = snapshot.tableNames.filter((name) => LEGACY_KNOWLEDGE_TABLE_NAMES.has(name));
|
|
8586
|
+
if (legacy.length > 0) return {
|
|
8587
|
+
status: "incompatible-reset-required",
|
|
8588
|
+
schemaVersion: snapshot.schemaVersion ?? null,
|
|
8589
|
+
reason: `found Knowledge tables from an earlier schema (${legacy.join(", ")})`
|
|
8590
|
+
};
|
|
8591
|
+
if (snapshot.schemaVersion === 4) return {
|
|
8592
|
+
status: "compatible",
|
|
8593
|
+
schemaVersion: 4
|
|
8594
|
+
};
|
|
8595
|
+
if (snapshot.schemaVersion === void 0) {
|
|
8596
|
+
const present = snapshot.tableNames.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
8597
|
+
if (present.length === 0) return {
|
|
8598
|
+
status: "uninitialized",
|
|
8599
|
+
schemaVersion: null
|
|
8600
|
+
};
|
|
8601
|
+
return {
|
|
8602
|
+
status: "incompatible-reset-required",
|
|
8603
|
+
schemaVersion: null,
|
|
8604
|
+
reason: `found Knowledge tables (${present.join(", ")}) without a schema version`
|
|
8605
|
+
};
|
|
9151
8606
|
}
|
|
9152
|
-
};
|
|
9153
|
-
function assertKnowledgeV2CoreSupport(features) {
|
|
9154
|
-
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.`);
|
|
9155
|
-
}
|
|
9156
|
-
function resolveKnowledgeV2Core(storageModule) {
|
|
9157
|
-
if (typeof storageModule !== "object" || storageModule === null) throw new Error("@mastra/core advertises Knowledge v2 without the required storage API");
|
|
9158
|
-
const { assertKnowledgeDescriptionWithinBound: assertDescription, assertKnowledgeRecordTextWithinBound: assertRecordText, assertKnowledgeSchemaCompatible: assertCompatible, inspectKnowledgeSchema: inspectSchema } = storageModule;
|
|
9159
|
-
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");
|
|
9160
8607
|
return {
|
|
9161
|
-
|
|
9162
|
-
|
|
9163
|
-
|
|
9164
|
-
inspectKnowledgeSchema: (snapshot) => inspectSchema(snapshot)
|
|
8608
|
+
status: "incompatible-reset-required",
|
|
8609
|
+
schemaVersion: snapshot.schemaVersion,
|
|
8610
|
+
reason: `Knowledge schema version ${snapshot.schemaVersion} is not 4`
|
|
9165
8611
|
};
|
|
9166
8612
|
}
|
|
9167
|
-
|
|
9168
|
-
|
|
8613
|
+
/** Schema-2 table names schema 4 reuses with a different shape. */
|
|
8614
|
+
const REUSED_KNOWLEDGE_TABLE_NAMES = KNOWLEDGE_TABLE_NAMES.filter((name) => KNOWLEDGE_PAGES_TABLE_NAMES.includes(name));
|
|
8615
|
+
/**
|
|
8616
|
+
* The old Knowledge tables an adapter drops on init, rows included: Knowledge before schema 4 was an
|
|
8617
|
+
* experimental store with no migration. Every schema-1/2-only table present is dropped. The table
|
|
8618
|
+
* names schema 4 reuses (mentions, activity, the state table) are dropped too, but only when an old
|
|
8619
|
+
* table is present and no schema-4 state row exists, so they are old-shaped. Schema-4-only tables
|
|
8620
|
+
* are never listed. On a schema-4 store this lists only tables an older process recreated during a
|
|
8621
|
+
* rolling deploy. Drop order is safe for adapters that enforce references.
|
|
8622
|
+
*/
|
|
8623
|
+
function legacyKnowledgeTablesToDrop(snapshot) {
|
|
8624
|
+
const present = new Set(snapshot.tableNames);
|
|
8625
|
+
const legacy = [...LEGACY_KNOWLEDGE_TABLE_NAMES].filter((name) => present.has(name));
|
|
8626
|
+
if (legacy.length === 0) return [];
|
|
8627
|
+
const reused = snapshot.schemaVersion === 4 ? [] : REUSED_KNOWLEDGE_TABLE_NAMES.filter((name) => present.has(name));
|
|
8628
|
+
const drop = /* @__PURE__ */ new Set([...legacy, ...reused]);
|
|
8629
|
+
return [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].filter((name) => drop.has(name));
|
|
8630
|
+
}
|
|
8631
|
+
/** Message for the error an adapter throws on `incompatible-reset-required`. */
|
|
8632
|
+
function knowledgePagesResetMessage(reason) {
|
|
8633
|
+
return `Knowledge storage needs a reset before this version can use it: ${reason}. Knowledge data has no migration; run \`await storage.stores?.knowledge?.dangerouslyReset()\` to drop the Knowledge tables (Memory tables are untouched).`;
|
|
8634
|
+
}
|
|
8635
|
+
const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
|
|
8636
|
+
"assertKnowledgePageDescriptionWithinBound",
|
|
8637
|
+
"assertKnowledgeNoteTextWithinBound",
|
|
8638
|
+
"canonicalKnowledgeName",
|
|
8639
|
+
"knowledgePositionBetween",
|
|
8640
|
+
"KnowledgeMoveTooLargeError",
|
|
8641
|
+
"KnowledgePageDepthError",
|
|
8642
|
+
"KnowledgePageHasChildrenError",
|
|
8643
|
+
"KnowledgePageKeyConflictError"
|
|
8644
|
+
];
|
|
8645
|
+
const KNOWLEDGE_PAGES_CORE_NUMBERS = [
|
|
8646
|
+
"MAX_KNOWLEDGE_MOVE_PAGES",
|
|
8647
|
+
"MAX_KNOWLEDGE_PAGE_DEPTH",
|
|
8648
|
+
"MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
|
|
8649
|
+
];
|
|
8650
|
+
function resolveKnowledgePagesCore(storageModule) {
|
|
8651
|
+
const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
|
|
8652
|
+
const missing = [...KNOWLEDGE_PAGES_CORE_FUNCTIONS.filter((name) => typeof module[name] !== "function"), ...KNOWLEDGE_PAGES_CORE_NUMBERS.filter((name) => typeof module[name] !== "number")];
|
|
8653
|
+
if (missing.length > 0) throw new Error(`@mastra/core advertises Knowledge pages without the required storage API (missing ${missing.join(", ")})`);
|
|
8654
|
+
return module;
|
|
8655
|
+
}
|
|
8656
|
+
/** Loads core's page-store runtime once, after checking core advertises `knowledge-pages`. */
|
|
8657
|
+
function createKnowledgePagesCoreLoader(features, loadStorageModule) {
|
|
8658
|
+
let core;
|
|
9169
8659
|
return async () => {
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
8660
|
+
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.`);
|
|
8661
|
+
core ??= loadStorageModule().then(resolveKnowledgePagesCore).catch((error) => {
|
|
8662
|
+
core = void 0;
|
|
9173
8663
|
throw error;
|
|
9174
8664
|
});
|
|
9175
|
-
return
|
|
8665
|
+
return core;
|
|
9176
8666
|
};
|
|
9177
8667
|
}
|
|
9178
|
-
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
async function assertKnowledgeRecordTextWithinBoundCompat(text) {
|
|
9185
|
-
const { assertKnowledgeRecordTextWithinBound } = await loadKnowledgeV2Core();
|
|
9186
|
-
assertKnowledgeRecordTextWithinBound(text);
|
|
9187
|
-
}
|
|
9188
|
-
const camelCaseColumns = [
|
|
9189
|
-
"canonicalName",
|
|
9190
|
-
"scopeKey",
|
|
9191
|
-
"mergedInto",
|
|
9192
|
-
"createdAt",
|
|
9193
|
-
"updatedAt",
|
|
9194
|
-
"node",
|
|
9195
|
-
"sourceThreadId",
|
|
9196
|
-
"capturedAt",
|
|
9197
|
-
"deletedAt",
|
|
9198
|
-
"deletedBy",
|
|
9199
|
-
"sourceType",
|
|
9200
|
-
"sourceId",
|
|
9201
|
-
"recordId",
|
|
9202
|
-
"lastKnowledgeId",
|
|
9203
|
-
"recordType",
|
|
9204
|
-
"idempotencyKey",
|
|
9205
|
-
"documentId",
|
|
9206
|
-
"documentType",
|
|
9207
|
-
"availableAt",
|
|
9208
|
-
"claimedAt",
|
|
9209
|
-
"claimedBy",
|
|
9210
|
-
"completedAt",
|
|
9211
|
-
"isScope",
|
|
9212
|
-
"nodeId",
|
|
9213
|
-
"targetNodeId",
|
|
9214
|
-
"scopeNodeId",
|
|
9215
|
-
"scopeRefId",
|
|
9216
|
-
"canSuggest",
|
|
9217
|
-
"schemaVersion",
|
|
9218
|
-
"importerId",
|
|
9219
|
-
"importKind",
|
|
9220
|
-
"triggerKind",
|
|
9221
|
-
"transcriptThreadId",
|
|
9222
|
-
"traceId",
|
|
9223
|
-
"queuedAt",
|
|
9224
|
-
"startedAt",
|
|
9225
|
-
"targetType",
|
|
9226
|
-
"targetId",
|
|
9227
|
-
"contextScopeId",
|
|
9228
|
-
"importRunId",
|
|
9229
|
-
"proposerContextScopeId",
|
|
9230
|
-
"expectedVersion",
|
|
9231
|
-
"reviewerContextScopeId",
|
|
9232
|
-
"reviewedAt",
|
|
9233
|
-
"addedAt"
|
|
9234
|
-
];
|
|
9235
|
-
function transformSqlCode(sql, transform) {
|
|
9236
|
-
return sql.split(/('(?:''|[^'])*')/g).map((part, index) => index % 2 === 0 ? transform(part) : part).join("");
|
|
9237
|
-
}
|
|
9238
|
-
function postgresSql(sql, schemaName) {
|
|
9239
|
-
let normalized = transformSqlCode(sql, (code) => {
|
|
9240
|
-
let transformed = code.replace(/jsonb\(\?\)/g, "?::jsonb");
|
|
9241
|
-
for (const column of camelCaseColumns) transformed = transformed.replace(new RegExp(`(?<!")\\b${column}\\b(?!")`, "g"), `"${column}"`);
|
|
9242
|
-
return transformed;
|
|
9243
|
-
});
|
|
9244
|
-
if (schemaName) {
|
|
9245
|
-
const quotedSchema = `"${parseSchemaName(schemaName)}"`;
|
|
9246
|
-
normalized = transformSqlCode(normalized, (code) => {
|
|
9247
|
-
let transformed = code;
|
|
9248
|
-
for (const table of [...KNOWLEDGE_TABLE_NAMES, ...RETIRED_KNOWLEDGE_TABLE_NAMES]) transformed = transformed.replaceAll(`"${table}"`, `${quotedSchema}."${table}"`);
|
|
9249
|
-
return transformed;
|
|
9250
|
-
});
|
|
9251
|
-
}
|
|
9252
|
-
let index = 0;
|
|
9253
|
-
return transformSqlCode(normalized, (code) => code.replace(/\?/g, () => `$${++index}`));
|
|
9254
|
-
}
|
|
9255
|
-
function createExecutor(client, schemaName) {
|
|
9256
|
-
const execute = async (statement, qualifyWithSchema) => {
|
|
9257
|
-
const sql = typeof statement === "string" ? statement : statement.sql;
|
|
9258
|
-
const args = typeof statement === "string" ? [] : statement.args ?? [];
|
|
9259
|
-
const result = await client.query(postgresSql(sql, qualifyWithSchema ? schemaName : void 0), args);
|
|
9260
|
-
return {
|
|
9261
|
-
rows: result.rows,
|
|
9262
|
-
rowsAffected: result.rowCount ?? 0
|
|
9263
|
-
};
|
|
9264
|
-
};
|
|
9265
|
-
return {
|
|
9266
|
-
execute: (statement) => execute(statement, true),
|
|
9267
|
-
executeRaw: (statement) => execute(statement, false)
|
|
9268
|
-
};
|
|
8668
|
+
/**
|
|
8669
|
+
* Keyword-search words: the query split on whitespace, lowercased, deduplicated. Every word must
|
|
8670
|
+
* appear (as a case-insensitive substring) in a hit; an empty list means the search returns nothing.
|
|
8671
|
+
*/
|
|
8672
|
+
function knowledgeSearchWords(query) {
|
|
8673
|
+
return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
|
|
9269
8674
|
}
|
|
9270
8675
|
/**
|
|
9271
|
-
*
|
|
9272
|
-
*
|
|
9273
|
-
* Matching rows by `scopeKey` keeps visibility filters on an index; a caller scope has only a few
|
|
9274
|
-
* entries, so the list stays small.
|
|
8676
|
+
* Why `key` can't be a Knowledge page key, or undefined when it can. Keys are one path segment:
|
|
8677
|
+
* non-empty, with no `/` (path separator), `#` (scope/path separator) or `%` (escape character).
|
|
9275
8678
|
*/
|
|
9276
|
-
function
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
8679
|
+
function knowledgePageKeyProblem(key) {
|
|
8680
|
+
if (!key) return "Knowledge page key must not be empty";
|
|
8681
|
+
if (/[/#%]/.test(key)) return `Knowledge page key "${key}" must not contain "/", "#" or "%"`;
|
|
8682
|
+
}
|
|
8683
|
+
// src/storage/domains/knowledge/pages.ts
|
|
8684
|
+
const loadPagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
|
|
8685
|
+
const DEFAULT_LIST_LIMIT = 50;
|
|
8686
|
+
const PG_UNIQUE_VIOLATION$1 = "23505";
|
|
8687
|
+
const PG_KNOWLEDGE_PAGES_TABLES = {
|
|
8688
|
+
pages: "mastra_knowledge_pages",
|
|
8689
|
+
notes: "mastra_knowledge_notes",
|
|
8690
|
+
stamps: "mastra_knowledge_note_stamps",
|
|
8691
|
+
mentions: "mastra_knowledge_mentions",
|
|
8692
|
+
pins: "mastra_knowledge_pins",
|
|
8693
|
+
activity: "mastra_knowledge_activity",
|
|
8694
|
+
state: "mastra_knowledge_access_state"
|
|
8695
|
+
};
|
|
8696
|
+
/**
|
|
8697
|
+
* Schema-4 DDL. `COLLATE "C"` on every column that is ordered, prefix-matched or compared for
|
|
8698
|
+
* equality makes Postgres compare bytes, not locale rules, so address prefixes are case-sensitive and
|
|
8699
|
+
* names/positions sort in binary order regardless of the database's default collation.
|
|
8700
|
+
*/
|
|
8701
|
+
function pgKnowledgePagesDDL(schemaName) {
|
|
8702
|
+
const q = qualifier(schemaName);
|
|
8703
|
+
const t = PG_KNOWLEDGE_PAGES_TABLES;
|
|
8704
|
+
const index = (name, table, columns, where = "", unique = false) => `CREATE ${unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS "${name}" ON ${q(table)} (${columns})${where ? ` WHERE ${where}` : ""}`;
|
|
8705
|
+
return [
|
|
8706
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.pages)} (
|
|
8707
|
+
"id" text PRIMARY KEY,
|
|
8708
|
+
"scopeId" text NOT NULL,
|
|
8709
|
+
"parentId" text,
|
|
8710
|
+
"isScope" boolean NOT NULL,
|
|
8711
|
+
"address" text COLLATE "C",
|
|
8712
|
+
"declaration" text COLLATE "C",
|
|
8713
|
+
"key" text COLLATE "C",
|
|
8714
|
+
"position" text COLLATE "C" NOT NULL,
|
|
8715
|
+
"name" text COLLATE "C" NOT NULL,
|
|
8716
|
+
"canonicalName" text COLLATE "C" NOT NULL,
|
|
8717
|
+
"description" text,
|
|
8718
|
+
"body" text,
|
|
8719
|
+
"depth" integer NOT NULL,
|
|
8720
|
+
"version" integer NOT NULL,
|
|
8721
|
+
"mergedInto" text,
|
|
8722
|
+
"createdBy" jsonb NOT NULL,
|
|
8723
|
+
"updatedBy" jsonb NOT NULL,
|
|
8724
|
+
"createdAt" timestamptz NOT NULL,
|
|
8725
|
+
"updatedAt" timestamptz NOT NULL,
|
|
8726
|
+
"deletedAt" timestamptz,
|
|
8727
|
+
"deletedBy" text
|
|
8728
|
+
)`,
|
|
8729
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.notes)} (
|
|
8730
|
+
"id" text PRIMARY KEY,
|
|
8731
|
+
"pageId" text NOT NULL REFERENCES ${q(t.pages)} ("id"),
|
|
8732
|
+
"text" text NOT NULL,
|
|
8733
|
+
"when" timestamptz,
|
|
8734
|
+
"provenance" jsonb NOT NULL,
|
|
8735
|
+
"version" integer NOT NULL,
|
|
8736
|
+
"createdAt" timestamptz NOT NULL,
|
|
8737
|
+
"updatedAt" timestamptz NOT NULL,
|
|
8738
|
+
"deletedAt" timestamptz,
|
|
8739
|
+
"deletedBy" text
|
|
8740
|
+
)`,
|
|
8741
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.stamps)} (
|
|
8742
|
+
"noteId" text NOT NULL REFERENCES ${q(t.notes)} ("id"),
|
|
8743
|
+
"scopeId" text NOT NULL REFERENCES ${q(t.pages)} ("id"),
|
|
8744
|
+
PRIMARY KEY ("noteId", "scopeId")
|
|
8745
|
+
)`,
|
|
8746
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.mentions)} (
|
|
8747
|
+
"noteId" text NOT NULL REFERENCES ${q(t.notes)} ("id"),
|
|
8748
|
+
"pageId" text NOT NULL REFERENCES ${q(t.pages)} ("id"),
|
|
8749
|
+
PRIMARY KEY ("noteId", "pageId")
|
|
8750
|
+
)`,
|
|
8751
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.pins)} (
|
|
8752
|
+
"id" text PRIMARY KEY,
|
|
8753
|
+
"pageId" text NOT NULL UNIQUE REFERENCES ${q(t.pages)} ("id"),
|
|
8754
|
+
"audience" text[] NOT NULL,
|
|
8755
|
+
"createdBy" text NOT NULL,
|
|
8756
|
+
"createdAt" timestamptz NOT NULL,
|
|
8757
|
+
"updatedAt" timestamptz NOT NULL
|
|
8758
|
+
)`,
|
|
8759
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.activity)} (
|
|
8760
|
+
"id" text PRIMARY KEY,
|
|
8761
|
+
"action" text NOT NULL,
|
|
8762
|
+
"scopeId" text NOT NULL,
|
|
8763
|
+
"pageId" text NOT NULL,
|
|
8764
|
+
"noteId" text,
|
|
8765
|
+
"provenance" jsonb NOT NULL,
|
|
8766
|
+
"createdAt" timestamptz NOT NULL
|
|
8767
|
+
)`,
|
|
8768
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.state)} (
|
|
8769
|
+
"id" text PRIMARY KEY,
|
|
8770
|
+
"schemaVersion" integer NOT NULL
|
|
8771
|
+
)`,
|
|
8772
|
+
index("mastra_knowledge_pages_address_uq", t.pages, "\"address\"", "\"isScope\"", true),
|
|
8773
|
+
`DROP INDEX IF EXISTS ${q("mastra_knowledge_pages_key_uq")}`,
|
|
8774
|
+
index("mastra_knowledge_pages_parent_key_uq", t.pages, "\"parentId\",\"key\"", "\"key\" IS NOT NULL AND \"deletedAt\" IS NULL", true),
|
|
8775
|
+
index("mastra_knowledge_pages_scope_decl_parent_idx", t.pages, "\"declaration\",\"parentId\"", "\"isScope\""),
|
|
8776
|
+
index("mastra_knowledge_pages_parent_idx", t.pages, "\"parentId\",\"position\",\"id\""),
|
|
8777
|
+
index("mastra_knowledge_pages_scope_name_idx", t.pages, "\"scopeId\",\"name\",\"id\"", "\"deletedAt\" IS NULL"),
|
|
8778
|
+
index("mastra_knowledge_pages_name_idx", t.pages, "\"name\",\"id\"", "\"deletedAt\" IS NULL AND NOT \"isScope\""),
|
|
8779
|
+
index("mastra_knowledge_pages_canonical_idx", t.pages, "\"canonicalName\"", "\"deletedAt\" IS NULL"),
|
|
8780
|
+
index("mastra_knowledge_notes_page_idx", t.notes, "\"pageId\",\"createdAt\",\"id\""),
|
|
8781
|
+
index("mastra_knowledge_stamps_scope_idx", t.stamps, "\"scopeId\""),
|
|
8782
|
+
index("mastra_knowledge_mentions_page_idx", t.mentions, "\"pageId\""),
|
|
8783
|
+
`CREATE INDEX IF NOT EXISTS "mastra_knowledge_pins_audience_gin" ON ${q(t.pins)} USING gin ("audience")`,
|
|
8784
|
+
index("mastra_knowledge_pins_created_idx", t.pins, "\"createdAt\",\"id\""),
|
|
8785
|
+
index("mastra_knowledge_activity_page_idx", t.activity, "\"pageId\",\"createdAt\",\"id\""),
|
|
8786
|
+
index("mastra_knowledge_activity_created_idx", t.activity, "\"createdAt\",\"id\"")
|
|
8787
|
+
];
|
|
9283
8788
|
}
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
if (typeof value === "string") return JSON.parse(value);
|
|
9287
|
-
if (value instanceof Uint8Array) return JSON.parse(new TextDecoder().decode(value));
|
|
9288
|
-
if (value instanceof ArrayBuffer) return JSON.parse(new TextDecoder().decode(value));
|
|
9289
|
-
return value;
|
|
8789
|
+
function qualifier(schemaName) {
|
|
8790
|
+
return (table) => schemaName ? `"${schemaName}"."${table}"` : `"${table}"`;
|
|
9290
8791
|
}
|
|
8792
|
+
/** Collects bind parameters and hands back their `$n` placeholders. */
|
|
8793
|
+
var Params = class {
|
|
8794
|
+
values = [];
|
|
8795
|
+
add(value) {
|
|
8796
|
+
this.values.push(value);
|
|
8797
|
+
return `$${this.values.length}`;
|
|
8798
|
+
}
|
|
8799
|
+
};
|
|
9291
8800
|
/**
|
|
9292
|
-
*
|
|
9293
|
-
*
|
|
9294
|
-
*
|
|
9295
|
-
* parsed components as UTC so reads return the digits that were stored.
|
|
8801
|
+
* The read scope as bound arrays, registered once per query so the same `$n` is reused for every
|
|
8802
|
+
* check in it. A large frontier travels as a handful of array parameters (`= ANY($n::text[])`,
|
|
8803
|
+
* `unnest`), never as a long `IN (...)` list.
|
|
9296
8804
|
*/
|
|
9297
|
-
|
|
9298
|
-
|
|
9299
|
-
|
|
9300
|
-
|
|
9301
|
-
|
|
8805
|
+
var ReadFilter = class {
|
|
8806
|
+
#all;
|
|
8807
|
+
#pages;
|
|
8808
|
+
#addresses;
|
|
8809
|
+
#prefixes;
|
|
8810
|
+
#openPrefixes;
|
|
8811
|
+
#children;
|
|
8812
|
+
constructor(read, params, pages) {
|
|
8813
|
+
this.#all = read === "all";
|
|
8814
|
+
this.#pages = pages;
|
|
8815
|
+
if (read === "all") return;
|
|
8816
|
+
if (read.addresses.length) this.#addresses = `${params.add(read.addresses)}::text[]`;
|
|
8817
|
+
const bounded = read.prefixes.filter((entry) => prefixUpperBound(entry.prefix) !== null);
|
|
8818
|
+
const open = read.prefixes.filter((entry) => prefixUpperBound(entry.prefix) === null);
|
|
8819
|
+
if (bounded.length) this.#prefixes = [
|
|
8820
|
+
`${params.add(bounded.map((entry) => entry.declaration))}::text[]`,
|
|
8821
|
+
`${params.add(bounded.map((entry) => entry.prefix))}::text[]`,
|
|
8822
|
+
`${params.add(bounded.map((entry) => prefixUpperBound(entry.prefix)))}::text[]`
|
|
8823
|
+
];
|
|
8824
|
+
if (open.length) this.#openPrefixes = [`${params.add(open.map((entry) => entry.declaration))}::text[]`, `${params.add(open.map((entry) => entry.prefix))}::text[]`];
|
|
8825
|
+
if (read.children.length) this.#children = [`${params.add(read.children.map((entry) => entry.declaration))}::text[]`, `${params.add(read.children.map((entry) => entry.parentId))}::text[]`];
|
|
8826
|
+
}
|
|
8827
|
+
/** Whether the filter admits every scope (no restriction to apply). */
|
|
8828
|
+
get all() {
|
|
8829
|
+
return this.#all;
|
|
8830
|
+
}
|
|
8831
|
+
/**
|
|
8832
|
+
* The ids of every readable scope page, resolved through indexes: exact addresses hit the
|
|
8833
|
+
* address index, prefixes become a binary range on it (`address >= prefix AND address < upper`,
|
|
8834
|
+
* rechecked with `starts_with`; a prefix with no finite upper bound uses `>=` alone; the declaration test is `IS NOT DISTINCT FROM` on purpose so the
|
|
8835
|
+
* planner walks the address range instead of every scope of that declaration, so `_` and `%` stay literal and matching stays case-sensitive),
|
|
8836
|
+
* and one-hop children hit `(declaration, parentId)`.
|
|
8837
|
+
*/
|
|
8838
|
+
readableIds() {
|
|
8839
|
+
if (this.#all) return `SELECT "id" FROM ${this.#pages} WHERE "isScope"`;
|
|
8840
|
+
const branches = [];
|
|
8841
|
+
if (this.#addresses) branches.push(`SELECT "id" FROM ${this.#pages} WHERE "isScope" AND "address" = ANY(${this.#addresses})`);
|
|
8842
|
+
if (this.#prefixes) branches.push(`SELECT ps."id" FROM unnest(${this.#prefixes[0]}, ${this.#prefixes[1]}, ${this.#prefixes[2]}) AS pr("declaration", "prefix", "upper") JOIN ${this.#pages} ps ON ps."isScope" AND ps."address" >= pr."prefix" AND ps."address" < pr."upper" AND ps."declaration" IS NOT DISTINCT FROM pr."declaration" AND starts_with(ps."address", pr."prefix")`);
|
|
8843
|
+
if (this.#openPrefixes) branches.push(`SELECT os."id" FROM unnest(${this.#openPrefixes[0]}, ${this.#openPrefixes[1]}) AS po("declaration", "prefix") JOIN ${this.#pages} os ON os."isScope" AND os."address" >= po."prefix" AND os."declaration" IS NOT DISTINCT FROM po."declaration" AND starts_with(os."address", po."prefix")`);
|
|
8844
|
+
if (this.#children) branches.push(`SELECT cs."id" FROM unnest(${this.#children[0]}, ${this.#children[1]}) AS ch("declaration", "parentId") JOIN ${this.#pages} cs ON cs."isScope" AND cs."declaration" = ch."declaration" AND cs."parentId" = ch."parentId"`);
|
|
8845
|
+
return branches.length ? branches.join(" UNION ") : `SELECT "id" FROM ${this.#pages} WHERE FALSE`;
|
|
8846
|
+
}
|
|
8847
|
+
/** True when a scope page aliased `scope` is readable. Matching is binary and case-sensitive. */
|
|
8848
|
+
readable(scope) {
|
|
8849
|
+
if (this.#all) return "TRUE";
|
|
8850
|
+
return `${scope}."id" IN (${this.readableIds()})`;
|
|
8851
|
+
}
|
|
8852
|
+
};
|
|
8853
|
+
/** Smallest string greater than every string starting with `prefix`, or null when none exists. */
|
|
8854
|
+
function prefixUpperBound(prefix) {
|
|
8855
|
+
const points = Array.from(prefix);
|
|
8856
|
+
const last = points.pop()?.codePointAt(0);
|
|
8857
|
+
if (last === void 0 || last >= 1114111 || last >= 55295 && last < 57344) return null;
|
|
8858
|
+
return points.join("") + String.fromCodePoint(last + 1);
|
|
9302
8859
|
}
|
|
9303
|
-
function
|
|
9304
|
-
return
|
|
8860
|
+
function encodeCursor(parts) {
|
|
8861
|
+
return Buffer.from(JSON.stringify(parts)).toString("base64url");
|
|
9305
8862
|
}
|
|
9306
|
-
function
|
|
9307
|
-
|
|
9308
|
-
|
|
8863
|
+
function decodeCursor(cursor, arity) {
|
|
8864
|
+
try {
|
|
8865
|
+
const parts = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
8866
|
+
if (Array.isArray(parts) && parts.length === arity && parts.every((part) => typeof part === "string")) return parts;
|
|
8867
|
+
} catch {}
|
|
8868
|
+
throw new Error("Invalid Knowledge cursor");
|
|
9309
8869
|
}
|
|
9310
|
-
function
|
|
9311
|
-
|
|
8870
|
+
function clampLimit(limit, max) {
|
|
8871
|
+
const value = limit ?? DEFAULT_LIST_LIMIT;
|
|
8872
|
+
if (!Number.isInteger(value) || value < 1) throw new Error("Knowledge list limit must be a positive integer");
|
|
8873
|
+
return Math.min(value, max);
|
|
9312
8874
|
}
|
|
9313
|
-
function
|
|
9314
|
-
return
|
|
8875
|
+
function iso(value) {
|
|
8876
|
+
return value.toISOString();
|
|
8877
|
+
}
|
|
8878
|
+
function optionalDate(value) {
|
|
8879
|
+
return value == null ? void 0 : new Date(value);
|
|
9315
8880
|
}
|
|
9316
|
-
function
|
|
9317
|
-
return value
|
|
8881
|
+
function parseJson$1(value) {
|
|
8882
|
+
return typeof value === "string" ? JSON.parse(value) : value;
|
|
9318
8883
|
}
|
|
9319
|
-
function
|
|
8884
|
+
function toPage(row) {
|
|
9320
8885
|
return {
|
|
9321
8886
|
id: String(row.id),
|
|
9322
|
-
|
|
8887
|
+
scopeId: String(row.scopeId),
|
|
8888
|
+
parentId: row.parentId == null ? null : String(row.parentId),
|
|
8889
|
+
isScope: row.isScope === true,
|
|
8890
|
+
...row.address == null ? {} : { address: String(row.address) },
|
|
8891
|
+
...row.declaration == null ? {} : { declaration: String(row.declaration) },
|
|
8892
|
+
...row.key == null ? {} : { key: String(row.key) },
|
|
8893
|
+
position: String(row.position),
|
|
9323
8894
|
name: String(row.name),
|
|
9324
|
-
|
|
9325
|
-
|
|
9326
|
-
description: row.description == null ? void 0 : String(row.description),
|
|
9327
|
-
isScope: row.isScope === true || row.isScope === 1 ? true : void 0,
|
|
9328
|
-
scope: parseJson$1(row.scopeJson ?? row.scope),
|
|
8895
|
+
...row.description == null ? {} : { description: String(row.description) },
|
|
8896
|
+
...row.body == null ? {} : { body: String(row.body) },
|
|
9329
8897
|
version: Number(row.version),
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
8898
|
+
...row.mergedInto == null ? {} : { mergedInto: String(row.mergedInto) },
|
|
8899
|
+
createdBy: parseJson$1(row.createdBy),
|
|
8900
|
+
updatedBy: parseJson$1(row.updatedBy),
|
|
8901
|
+
createdAt: new Date(row.createdAt),
|
|
8902
|
+
updatedAt: new Date(row.updatedAt),
|
|
8903
|
+
...row.deletedAt == null ? {} : { deletedAt: new Date(row.deletedAt) },
|
|
8904
|
+
...row.deletedBy == null ? {} : { deletedBy: String(row.deletedBy) }
|
|
9333
8905
|
};
|
|
9334
8906
|
}
|
|
9335
|
-
function
|
|
8907
|
+
function toNote(row) {
|
|
8908
|
+
const when = optionalDate(row.when);
|
|
8909
|
+
const deletedAt = optionalDate(row.deletedAt);
|
|
9336
8910
|
return {
|
|
9337
8911
|
id: String(row.id),
|
|
9338
|
-
|
|
8912
|
+
pageId: String(row.pageId),
|
|
9339
8913
|
text: String(row.text),
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
8914
|
+
...when ? { when } : {},
|
|
8915
|
+
stampScopeIds: row.stampScopeIds ?? [],
|
|
8916
|
+
mentions: row.mentions ?? [],
|
|
8917
|
+
provenance: parseJson$1(row.provenance),
|
|
8918
|
+
version: Number(row.version),
|
|
8919
|
+
createdAt: new Date(row.createdAt),
|
|
8920
|
+
updatedAt: new Date(row.updatedAt),
|
|
8921
|
+
...deletedAt ? { deletedAt } : {},
|
|
8922
|
+
...row.deletedBy == null ? {} : { deletedBy: String(row.deletedBy) }
|
|
9347
8923
|
};
|
|
9348
8924
|
}
|
|
9349
|
-
function
|
|
8925
|
+
function toPin(row) {
|
|
9350
8926
|
return {
|
|
9351
8927
|
id: String(row.id),
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
status: String(row.status),
|
|
9358
|
-
attempts: Number(row.attempts),
|
|
9359
|
-
availableAt: toDate$1(row.availableAt),
|
|
9360
|
-
claimedAt: optionalDate(row.claimedAt),
|
|
9361
|
-
claimedBy: row.claimedBy == null ? void 0 : String(row.claimedBy),
|
|
9362
|
-
createdAt: toDate$1(row.createdAt),
|
|
9363
|
-
completedAt: optionalDate(row.completedAt)
|
|
8928
|
+
pageId: String(row.pageId),
|
|
8929
|
+
audience: row.audience ?? [],
|
|
8930
|
+
createdBy: String(row.createdBy),
|
|
8931
|
+
createdAt: new Date(row.createdAt),
|
|
8932
|
+
updatedAt: new Date(row.updatedAt)
|
|
9364
8933
|
};
|
|
9365
8934
|
}
|
|
9366
|
-
function
|
|
9367
|
-
|
|
9368
|
-
|
|
9369
|
-
|
|
8935
|
+
function toListedPin(row) {
|
|
8936
|
+
return {
|
|
8937
|
+
...toPin(row),
|
|
8938
|
+
pageName: String(row.pageName)
|
|
9370
8939
|
};
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
}
|
|
8940
|
+
}
|
|
8941
|
+
function escapeLike(term) {
|
|
8942
|
+
return term.replace(/[\\%_]/g, (match) => `\\${match}`);
|
|
8943
|
+
}
|
|
8944
|
+
function isUniqueViolation$1(error) {
|
|
8945
|
+
return typeof error === "object" && error !== null && error.code === PG_UNIQUE_VIOLATION$1;
|
|
8946
|
+
}
|
|
8947
|
+
/**
|
|
8948
|
+
* Postgres implementation of the schema-4 {@link KnowledgePageStore}.
|
|
8949
|
+
*
|
|
8950
|
+
* Visibility is computed with live joins (a note's page home scope, its stamps, its mentioned pages'
|
|
8951
|
+
* current home scopes), so a move or merge, which rewrites `scopeId` on the moved pages in the same
|
|
8952
|
+
* transaction, can never leave a stale denormalized copy.
|
|
8953
|
+
*/
|
|
8954
|
+
var PgKnowledgePages = class {
|
|
8955
|
+
#client;
|
|
8956
|
+
#schemaName;
|
|
8957
|
+
#bounds;
|
|
8958
|
+
#q;
|
|
8959
|
+
constructor(options) {
|
|
8960
|
+
this.#client = options.client;
|
|
8961
|
+
this.#schemaName = options.schemaName;
|
|
8962
|
+
this.#bounds = options.bounds;
|
|
8963
|
+
this.#q = qualifier(options.schemaName);
|
|
8964
|
+
}
|
|
8965
|
+
get #pages() {
|
|
8966
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.pages);
|
|
8967
|
+
}
|
|
8968
|
+
get #notes() {
|
|
8969
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.notes);
|
|
8970
|
+
}
|
|
8971
|
+
get #stamps() {
|
|
8972
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.stamps);
|
|
8973
|
+
}
|
|
8974
|
+
get #mentions() {
|
|
8975
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.mentions);
|
|
8976
|
+
}
|
|
8977
|
+
get #pins() {
|
|
8978
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.pins);
|
|
8979
|
+
}
|
|
8980
|
+
get #activity() {
|
|
8981
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.activity);
|
|
8982
|
+
}
|
|
8983
|
+
async #rows(db, sql, params = []) {
|
|
8984
|
+
return (await db.query(sql, params)).rows;
|
|
8985
|
+
}
|
|
8986
|
+
#tx(operation) {
|
|
8987
|
+
return this.#client.tx(operation);
|
|
8988
|
+
}
|
|
8989
|
+
/** The read-side `FROM` for notes: the note, its page, and the page's home scope. */
|
|
8990
|
+
#visibleNotes(filter, alias = "n") {
|
|
8991
|
+
return `${this.#notes} ${alias}
|
|
8992
|
+
JOIN ${this.#pages} np ON np."id" = ${alias}."pageId"
|
|
8993
|
+
JOIN ${this.#pages} nps ON nps."id" = np."scopeId"
|
|
8994
|
+
WHERE ${filter.readable("nps")}
|
|
8995
|
+
AND EXISTS (SELECT 1 FROM ${this.#stamps} st JOIN ${this.#pages} sts ON sts."id" = st."scopeId" WHERE st."noteId" = ${alias}."id" AND ${filter.readable("sts")})
|
|
8996
|
+
AND NOT EXISTS (SELECT 1 FROM ${this.#mentions} mn JOIN ${this.#pages} mp ON mp."id" = mn."pageId" JOIN ${this.#pages} mps ON mps."id" = mp."scopeId" WHERE mn."noteId" = ${alias}."id" AND NOT ${filter.readable("mps")})`;
|
|
8997
|
+
}
|
|
8998
|
+
/** Same note visibility as a bare predicate, for filters that start from another table. */
|
|
8999
|
+
#noteVisible(filter, noteIdColumn) {
|
|
9000
|
+
return `EXISTS (SELECT 1 FROM ${this.#notes} vn JOIN ${this.#pages} vp ON vp."id" = vn."pageId" JOIN ${this.#pages} vps ON vps."id" = vp."scopeId"
|
|
9001
|
+
WHERE vn."id" = ${noteIdColumn} AND ${filter.readable("vps")}
|
|
9002
|
+
AND EXISTS (SELECT 1 FROM ${this.#stamps} vst JOIN ${this.#pages} vsts ON vsts."id" = vst."scopeId" WHERE vst."noteId" = vn."id" AND ${filter.readable("vsts")})
|
|
9003
|
+
AND NOT EXISTS (SELECT 1 FROM ${this.#mentions} vmn JOIN ${this.#pages} vmp ON vmp."id" = vmn."pageId" JOIN ${this.#pages} vmps ON vmps."id" = vmp."scopeId" WHERE vmn."noteId" = vn."id" AND NOT ${filter.readable("vmps")}))`;
|
|
9004
|
+
}
|
|
9005
|
+
#noteColumns(alias = "n") {
|
|
9006
|
+
return `${alias}.*,
|
|
9007
|
+
ARRAY(SELECT st."scopeId" FROM ${this.#stamps} st WHERE st."noteId" = ${alias}."id" ORDER BY st."scopeId") AS "stampScopeIds",
|
|
9008
|
+
ARRAY(SELECT mn."pageId" FROM ${this.#mentions} mn WHERE mn."noteId" = ${alias}."id" ORDER BY mn."pageId") AS "mentions"`;
|
|
9009
|
+
}
|
|
9010
|
+
async #logActivity(db, event) {
|
|
9011
|
+
await db.query(`INSERT INTO ${this.#activity} ("id","action","scopeId","pageId","noteId","provenance","createdAt") VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7)`, [
|
|
9012
|
+
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
9013
|
+
event.action,
|
|
9014
|
+
event.scopeId,
|
|
9015
|
+
event.pageId,
|
|
9016
|
+
event.noteId ?? null,
|
|
9017
|
+
JSON.stringify(event.provenance),
|
|
9018
|
+
iso(event.at)
|
|
9019
|
+
]);
|
|
9020
|
+
}
|
|
9021
|
+
async #lockPage(db, id) {
|
|
9022
|
+
const rows = await this.#rows(db, `SELECT * FROM ${this.#pages} WHERE "id" = $1 FOR UPDATE`, [id]);
|
|
9023
|
+
return rows[0] ? toPage(rows[0]) : null;
|
|
9024
|
+
}
|
|
9025
|
+
async #requireLivePage(db, id, lock = true) {
|
|
9026
|
+
const rows = await this.#rows(db, `SELECT * FROM ${this.#pages} WHERE "id" = $1${lock ? " FOR UPDATE" : ""}`, [id]);
|
|
9027
|
+
const page = rows[0] ? toPage(rows[0]) : null;
|
|
9028
|
+
if (!page || page.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("page", id);
|
|
9029
|
+
return page;
|
|
9030
|
+
}
|
|
9031
|
+
/** Position after the last live child of `parentId`. */
|
|
9032
|
+
async #lastPosition(db, parentId) {
|
|
9033
|
+
const rows = await this.#rows(db, parentId === null ? `SELECT "position" FROM ${this.#pages} WHERE "parentId" IS NULL AND "deletedAt" IS NULL ORDER BY "position" DESC LIMIT 1` : `SELECT "position" FROM ${this.#pages} WHERE "parentId" = $1 AND "deletedAt" IS NULL ORDER BY "position" DESC LIMIT 1`, parentId === null ? [] : [parentId]);
|
|
9034
|
+
return rows[0] ? String(rows[0].position) : void 0;
|
|
9035
|
+
}
|
|
9036
|
+
/** Ordinary descendants of `rootId` (excluding it), as a recursive CTE named `sub`. */
|
|
9037
|
+
#subtree(rootParam) {
|
|
9038
|
+
return `WITH RECURSIVE sub("id") AS (
|
|
9039
|
+
SELECT c."id" FROM ${this.#pages} c WHERE c."parentId" = ${rootParam} AND NOT c."isScope"
|
|
9040
|
+
UNION ALL
|
|
9041
|
+
SELECT c."id" FROM ${this.#pages} c JOIN sub ON c."parentId" = sub."id" WHERE NOT c."isScope"
|
|
9042
|
+
)`;
|
|
9043
|
+
}
|
|
9044
|
+
/** Live descendants, capped, and the deepest live level. */
|
|
9045
|
+
async #subtreeStats(db, rootId) {
|
|
9046
|
+
const core = await loadPagesCore();
|
|
9047
|
+
const rows = await this.#rows(db, `${this.#subtree("$1")}
|
|
9048
|
+
SELECT (SELECT count(*) FROM (SELECT 1 FROM sub JOIN ${this.#pages} p ON p."id" = sub."id" WHERE p."deletedAt" IS NULL LIMIT ${core.MAX_KNOWLEDGE_MOVE_PAGES + 1}) l) AS live,
|
|
9049
|
+
(SELECT COALESCE(max(p."depth"), 0) FROM sub JOIN ${this.#pages} p ON p."id" = sub."id" WHERE p."deletedAt" IS NULL) AS "maxDepth"`, [rootId]);
|
|
9050
|
+
return {
|
|
9051
|
+
live: Number(rows[0]?.live ?? 0),
|
|
9052
|
+
maxDepth: Number(rows[0]?.maxDepth ?? 0)
|
|
9053
|
+
};
|
|
9054
|
+
}
|
|
9055
|
+
/**
|
|
9056
|
+
* Refuses before any write when a live page arriving under `parentId` has a key a live child of
|
|
9057
|
+
* `parentId` already uses: the moved root itself, or, for a merge, the source's live children.
|
|
9058
|
+
*/
|
|
9059
|
+
async #assertNoKeyConflicts(db, arriving, parentId, exceptId) {
|
|
9060
|
+
const core = await loadPagesCore();
|
|
9061
|
+
const arrivingWhere = arriving.rootId ? `c."id" = $1` : `c."parentId" = $1 AND NOT c."isScope"`;
|
|
9062
|
+
const rows = await this.#rows(db, `SELECT c."key" FROM ${this.#pages} c
|
|
9063
|
+
JOIN ${this.#pages} t ON t."parentId" = $2 AND t."key" = c."key" AND t."deletedAt" IS NULL AND t."id" <> c."id" AND t."id" IS DISTINCT FROM $3::text
|
|
9064
|
+
WHERE ${arrivingWhere} AND c."key" IS NOT NULL AND c."deletedAt" IS NULL
|
|
9065
|
+
LIMIT 1`, [
|
|
9066
|
+
arriving.rootId ?? arriving.childrenOf,
|
|
9067
|
+
parentId,
|
|
9068
|
+
exceptId ?? null
|
|
9069
|
+
]);
|
|
9070
|
+
if (rows[0]) throw new core.KnowledgePageKeyConflictError(String(rows[0].key), parentId);
|
|
9071
|
+
}
|
|
9072
|
+
async #isInSubtree(db, rootId, candidateId) {
|
|
9073
|
+
return (await this.#rows(db, `${this.#subtree("$1")} SELECT 1 FROM sub WHERE "id" = $2 LIMIT 1`, [rootId, candidateId])).length > 0;
|
|
9074
|
+
}
|
|
9075
|
+
async materializeScope(input) {
|
|
9076
|
+
const bounds = await this.#bounds();
|
|
9077
|
+
bounds.assertKnowledgePageDescriptionWithinBound(input.description);
|
|
9078
|
+
for (const child of input.children ?? []) bounds.assertKnowledgePageDescriptionWithinBound(child.description);
|
|
9079
|
+
const checkTemplates = (templates) => {
|
|
9080
|
+
for (const template of templates) {
|
|
9081
|
+
bounds.assertKnowledgePageDescriptionWithinBound(template.description);
|
|
9082
|
+
checkTemplates(template.pages ?? []);
|
|
9083
|
+
}
|
|
9084
|
+
};
|
|
9085
|
+
checkTemplates(input.pages ?? []);
|
|
9086
|
+
return this.#tx(async (tx) => {
|
|
9087
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtext($1))", [`mastra-knowledge-scope:${this.#schemaName ?? ""}:${input.address}`]);
|
|
9088
|
+
const now = /* @__PURE__ */ new Date();
|
|
9089
|
+
let scope = await this.#scopeByAddress(tx, input.address);
|
|
9090
|
+
let created = false;
|
|
9091
|
+
let parentConflict;
|
|
9092
|
+
if (!scope) {
|
|
9093
|
+
if (input.parentId) await this.#requireLivePage(tx, input.parentId, false);
|
|
9094
|
+
scope = await this.#insertScope(tx, {
|
|
9095
|
+
address: input.address,
|
|
9096
|
+
declaration: input.declaration,
|
|
9097
|
+
name: input.name,
|
|
9098
|
+
description: input.description,
|
|
9099
|
+
parentId: input.parentId ?? null,
|
|
9100
|
+
provenance: input.provenance,
|
|
9101
|
+
at: now
|
|
9102
|
+
});
|
|
9103
|
+
created = true;
|
|
9104
|
+
} else if (input.parentId && scope.parentId === null) {
|
|
9105
|
+
await this.#requireLivePage(tx, input.parentId, false);
|
|
9106
|
+
scope = toPage((await this.#rows(tx, `UPDATE ${this.#pages} SET "parentId" = $2 WHERE "id" = $1 AND "parentId" IS NULL RETURNING *`, [scope.id, input.parentId]))[0]);
|
|
9107
|
+
} else if (input.parentId !== void 0 && (input.parentId ?? null) !== scope.parentId) parentConflict = {
|
|
9108
|
+
existingParentId: scope.parentId,
|
|
9109
|
+
requestedParentId: input.parentId ?? null
|
|
9110
|
+
};
|
|
9111
|
+
for (const child of input.children ?? []) {
|
|
9112
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtext($1))", [`mastra-knowledge-scope:${this.#schemaName ?? ""}:${child.address}`]);
|
|
9113
|
+
if (await this.#scopeByAddress(tx, child.address)) continue;
|
|
9114
|
+
await this.#insertScope(tx, {
|
|
9115
|
+
address: child.address,
|
|
9116
|
+
declaration: child.declaration,
|
|
9117
|
+
name: child.name,
|
|
9118
|
+
description: child.description,
|
|
9119
|
+
parentId: scope.id,
|
|
9120
|
+
provenance: input.provenance,
|
|
9121
|
+
at: now
|
|
9122
|
+
});
|
|
9123
|
+
}
|
|
9124
|
+
await this.#materializeTemplates(tx, scope, input.pages ?? [], input.provenance, now);
|
|
9125
|
+
return {
|
|
9126
|
+
scope,
|
|
9127
|
+
created,
|
|
9128
|
+
...parentConflict ? { parentConflict } : {}
|
|
9129
|
+
};
|
|
9130
|
+
});
|
|
9131
|
+
}
|
|
9132
|
+
async #materializeTemplates(db, parent, templates, provenance, at) {
|
|
9133
|
+
await loadPagesCore();
|
|
9134
|
+
for (const template of templates) {
|
|
9135
|
+
const problem = knowledgePageKeyProblem(template.key);
|
|
9136
|
+
if (problem) throw new Error(problem);
|
|
9137
|
+
const rows = await this.#rows(db, `SELECT * FROM ${this.#pages} WHERE "parentId" = $1 AND "key" = $2 AND "deletedAt" IS NULL`, [parent.id, template.key]);
|
|
9138
|
+
const page = rows[0] === void 0 ? await this.#insertPage(db, {
|
|
9139
|
+
parent,
|
|
9140
|
+
key: template.key,
|
|
9141
|
+
name: template.name,
|
|
9142
|
+
description: template.description,
|
|
9143
|
+
provenance,
|
|
9144
|
+
at
|
|
9145
|
+
}) : toPage(rows[0]);
|
|
9146
|
+
if (template.pages?.length) await this.#materializeTemplates(db, page, template.pages, provenance, at);
|
|
9147
|
+
}
|
|
9148
|
+
}
|
|
9149
|
+
async #scopeByAddress(db, address) {
|
|
9150
|
+
const rows = await this.#rows(db, `SELECT * FROM ${this.#pages} WHERE "isScope" AND "address" = $1`, [address]);
|
|
9151
|
+
return rows[0] ? toPage(rows[0]) : null;
|
|
9152
|
+
}
|
|
9153
|
+
async #insertScope(db, input) {
|
|
9154
|
+
const core = await loadPagesCore();
|
|
9155
|
+
const id = (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
9156
|
+
const position = core.knowledgePositionBetween(await this.#lastPosition(db, input.parentId));
|
|
9157
|
+
const rows = await this.#rows(db, `INSERT INTO ${this.#pages} ("id","scopeId","parentId","isScope","address","declaration","position","name","canonicalName","description","depth","version","createdBy","updatedBy","createdAt","updatedAt")
|
|
9158
|
+
VALUES ($1,$1,$2,TRUE,$3,$4,$5,$6,$7,$8,0,1,$9::jsonb,$9::jsonb,$10,$10) RETURNING *`, [
|
|
9159
|
+
id,
|
|
9160
|
+
input.parentId,
|
|
9161
|
+
input.address,
|
|
9162
|
+
input.declaration,
|
|
9163
|
+
position,
|
|
9164
|
+
input.name,
|
|
9165
|
+
core.canonicalKnowledgeName(input.name),
|
|
9166
|
+
input.description ?? null,
|
|
9167
|
+
JSON.stringify(input.provenance),
|
|
9168
|
+
iso(input.at)
|
|
9169
|
+
]);
|
|
9170
|
+
await this.#logActivity(db, {
|
|
9171
|
+
action: "scope-created",
|
|
9172
|
+
scopeId: id,
|
|
9173
|
+
pageId: id,
|
|
9174
|
+
provenance: input.provenance,
|
|
9175
|
+
at: input.at
|
|
9176
|
+
});
|
|
9177
|
+
return toPage(rows[0]);
|
|
9178
|
+
}
|
|
9179
|
+
async getScopesByAddress(addresses) {
|
|
9180
|
+
if (addresses.length === 0) return [];
|
|
9181
|
+
const rows = await this.#rows(this.#client, `SELECT * FROM ${this.#pages} WHERE "isScope" AND "address" = ANY($1::text[])`, [addresses]);
|
|
9182
|
+
const byAddress = new Map(rows.map((row) => [String(row.address), toPage(row)]));
|
|
9183
|
+
return [...new Set(addresses)].flatMap((address) => byAddress.get(address) ?? []);
|
|
9184
|
+
}
|
|
9185
|
+
async listScopeDeclarations() {
|
|
9186
|
+
return (await this.#rows(this.#client, `SELECT "declaration", COUNT(*)::int AS "scopes" FROM ${this.#pages}
|
|
9187
|
+
WHERE "isScope" AND "declaration" IS NOT NULL
|
|
9188
|
+
GROUP BY "declaration" ORDER BY "declaration" COLLATE "C"`, [])).map((row) => ({
|
|
9189
|
+
declaration: String(row.declaration),
|
|
9190
|
+
scopes: Number(row.scopes)
|
|
9191
|
+
}));
|
|
9192
|
+
}
|
|
9193
|
+
async renameScopeDeclaration(input) {
|
|
9194
|
+
return this.#tx(async (tx) => {
|
|
9195
|
+
const rows = await this.#rows(tx, `SELECT "id", "address", "declaration" FROM ${this.#pages}
|
|
9196
|
+
WHERE "isScope" AND ("declaration" = $1 OR starts_with("declaration", $2))
|
|
9197
|
+
ORDER BY "id" FOR UPDATE`, [input.from, `${input.from}/`]);
|
|
9198
|
+
if (rows.length === 0) return 0;
|
|
9199
|
+
const renames = rows.map((row) => ({
|
|
9200
|
+
address: String(row.address),
|
|
9201
|
+
from: String(row.declaration),
|
|
9202
|
+
to: input.to + String(row.declaration).slice(input.from.length)
|
|
9203
|
+
}));
|
|
9204
|
+
input.check(renames);
|
|
9205
|
+
await tx.query(`UPDATE ${this.#pages} p SET "declaration" = r."to"
|
|
9206
|
+
FROM unnest($1::text[], $2::text[]) AS r("id", "to") WHERE p."id" = r."id"`, [rows.map((row) => String(row.id)), renames.map((rename) => rename.to)]);
|
|
9207
|
+
return rows.length;
|
|
9208
|
+
});
|
|
9209
|
+
}
|
|
9210
|
+
async listChildScopes(input) {
|
|
9211
|
+
const core = await loadPagesCore();
|
|
9212
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9213
|
+
const params = new Params();
|
|
9214
|
+
const where = [`"isScope"`, `"deletedAt" IS NULL`];
|
|
9215
|
+
where.push(input.parentId === null ? `"parentId" IS NULL` : `"parentId" = ${params.add(input.parentId)}`);
|
|
9216
|
+
if (input.cursor) {
|
|
9217
|
+
const [position, id] = decodeCursor(input.cursor, 2);
|
|
9218
|
+
where.push(`("position", "id") > (${params.add(position)}, ${params.add(id)})`);
|
|
9219
|
+
}
|
|
9220
|
+
const rows = await this.#rows(this.#client, `SELECT * FROM ${this.#pages} WHERE ${where.join(" AND ")} ORDER BY "position", "id" LIMIT ${limit + 1}`, params.values);
|
|
9221
|
+
return this.#pageOutput(rows, limit, (page) => [page.position, page.id]);
|
|
9222
|
+
}
|
|
9223
|
+
#pageOutput(rows, limit, cursorOf) {
|
|
9224
|
+
const pages = rows.slice(0, limit).map(toPage);
|
|
9225
|
+
const last = pages[pages.length - 1];
|
|
9226
|
+
return {
|
|
9227
|
+
pages,
|
|
9228
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor(cursorOf(last)) } : {}
|
|
9229
|
+
};
|
|
9230
|
+
}
|
|
9231
|
+
async getPage(input) {
|
|
9232
|
+
const params = new Params();
|
|
9233
|
+
const id = params.add(input.id);
|
|
9234
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9235
|
+
const rows = await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId" WHERE p."id" = ${id} AND ${filter.readable("s")}`, params.values);
|
|
9236
|
+
return rows[0] ? toPage(rows[0]) : null;
|
|
9237
|
+
}
|
|
9238
|
+
async getPageByPath(input) {
|
|
9239
|
+
if (!input.path.length) return null;
|
|
9240
|
+
let page;
|
|
9241
|
+
let parentId = input.scopeId;
|
|
9242
|
+
for (const key of input.path) {
|
|
9243
|
+
const params = new Params();
|
|
9244
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9245
|
+
const rows = await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId"
|
|
9246
|
+
WHERE p."parentId" = ${params.add(parentId)} AND p."key" = ${params.add(key)} AND p."deletedAt" IS NULL AND NOT p."isScope" AND ${filter.readable("s")}`, params.values);
|
|
9247
|
+
if (!rows[0]) return null;
|
|
9248
|
+
page = toPage(rows[0]);
|
|
9249
|
+
parentId = page.id;
|
|
9250
|
+
}
|
|
9251
|
+
return page ?? null;
|
|
9252
|
+
}
|
|
9253
|
+
async findPagesByName(input) {
|
|
9254
|
+
const core = await loadPagesCore();
|
|
9255
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9256
|
+
const params = new Params();
|
|
9257
|
+
const where = [`p."canonicalName" = ${params.add(core.canonicalKnowledgeName(input.name))}`, `p."deletedAt" IS NULL`];
|
|
9258
|
+
if (input.scopeId) where.push(`p."scopeId" = ${params.add(input.scopeId)}`);
|
|
9259
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9260
|
+
where.push(filter.readable("s"));
|
|
9261
|
+
return (await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId"
|
|
9262
|
+
WHERE ${where.join(" AND ")} ORDER BY p."createdAt" DESC, p."id" DESC LIMIT ${limit}`, params.values)).map(toPage);
|
|
9263
|
+
}
|
|
9264
|
+
async listChildPages(input) {
|
|
9265
|
+
const core = await loadPagesCore();
|
|
9266
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9267
|
+
const params = new Params();
|
|
9268
|
+
const where = [
|
|
9269
|
+
`p."parentId" = ${params.add(input.parentId)}`,
|
|
9270
|
+
`NOT p."isScope"`,
|
|
9271
|
+
`p."deletedAt" IS NULL`
|
|
9272
|
+
];
|
|
9273
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9274
|
+
where.push(filter.readable("s"));
|
|
9275
|
+
if (input.cursor) {
|
|
9276
|
+
const [position, id] = decodeCursor(input.cursor, 2);
|
|
9277
|
+
where.push(`(p."position", p."id") > (${params.add(position)}, ${params.add(id)})`);
|
|
9278
|
+
}
|
|
9279
|
+
const rows = await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId"
|
|
9280
|
+
WHERE ${where.join(" AND ")} ORDER BY p."position", p."id" LIMIT ${limit + 1}`, params.values);
|
|
9281
|
+
return this.#pageOutput(rows, limit, (page) => [page.position, page.id]);
|
|
9282
|
+
}
|
|
9283
|
+
async listPages(input) {
|
|
9284
|
+
const core = await loadPagesCore();
|
|
9285
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9286
|
+
const params = new Params();
|
|
9287
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9288
|
+
const where = [
|
|
9289
|
+
`p."scopeId" = r."id"`,
|
|
9290
|
+
`NOT p."isScope"`,
|
|
9291
|
+
`p."deletedAt" IS NULL`
|
|
9292
|
+
];
|
|
9293
|
+
if (input.namePrefix) where.push(`starts_with(p."canonicalName", ${params.add(core.canonicalKnowledgeName(input.namePrefix))})`);
|
|
9294
|
+
if (input.cursor) {
|
|
9295
|
+
const [name, id] = decodeCursor(input.cursor, 2);
|
|
9296
|
+
where.push(`(p."name", p."id") > (${params.add(name)}, ${params.add(id)})`);
|
|
9297
|
+
}
|
|
9298
|
+
if (filter.all) {
|
|
9299
|
+
const direct = where.slice(1);
|
|
9300
|
+
if (input.scopeId) direct.unshift(`p."scopeId" = ${params.add(input.scopeId)}`);
|
|
9301
|
+
const rows = await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p WHERE ${direct.join(" AND ")} ORDER BY p."name", p."id" LIMIT ${limit + 1}`, params.values);
|
|
9302
|
+
return this.#pageOutput(rows, limit, (page) => [page.name, page.id]);
|
|
9303
|
+
}
|
|
9304
|
+
const scopes = input.scopeId ? `SELECT "id" FROM (${filter.readableIds()}) ri WHERE "id" = ${params.add(input.scopeId)}` : filter.readableIds();
|
|
9305
|
+
const rows = await this.#rows(this.#client, `SELECT x.* FROM (${scopes}) r CROSS JOIN LATERAL (
|
|
9306
|
+
SELECT p.* FROM ${this.#pages} p WHERE ${where.join(" AND ")} ORDER BY p."name", p."id" LIMIT ${limit + 1}
|
|
9307
|
+
) x ORDER BY x."name", x."id" LIMIT ${limit + 1}`, params.values);
|
|
9308
|
+
return this.#pageOutput(rows, limit, (page) => [page.name, page.id]);
|
|
9309
|
+
}
|
|
9310
|
+
async createPage(input) {
|
|
9311
|
+
(await this.#bounds()).assertKnowledgePageDescriptionWithinBound(input.description);
|
|
9312
|
+
await loadPagesCore();
|
|
9313
|
+
if (input.key !== void 0) {
|
|
9314
|
+
const problem = knowledgePageKeyProblem(input.key);
|
|
9315
|
+
if (problem) throw new Error(problem);
|
|
9316
|
+
}
|
|
9317
|
+
return this.#tx(async (tx) => {
|
|
9318
|
+
const parent = await this.#requireLivePage(tx, input.parentId);
|
|
9319
|
+
return this.#insertPage(tx, {
|
|
9320
|
+
id: input.id,
|
|
9321
|
+
parent,
|
|
9322
|
+
key: input.key,
|
|
9323
|
+
name: input.name,
|
|
9324
|
+
description: input.description,
|
|
9325
|
+
body: input.body,
|
|
9326
|
+
position: input.position,
|
|
9327
|
+
provenance: input.provenance,
|
|
9328
|
+
at: /* @__PURE__ */ new Date()
|
|
9329
|
+
});
|
|
9330
|
+
});
|
|
9331
|
+
}
|
|
9332
|
+
async #insertPage(db, input) {
|
|
9333
|
+
const core = await loadPagesCore();
|
|
9334
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
9335
|
+
const scopeId = input.parent.isScope ? input.parent.id : input.parent.scopeId;
|
|
9336
|
+
const depth = input.parent.isScope ? 1 : await this.#depthOf(db, input.parent.id) + 1;
|
|
9337
|
+
if (depth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(id);
|
|
9338
|
+
const position = input.position ?? core.knowledgePositionBetween(await this.#lastPosition(db, input.parent.id));
|
|
9339
|
+
try {
|
|
9340
|
+
const rows = await this.#rows(db, `INSERT INTO ${this.#pages} ("id","scopeId","parentId","isScope","key","position","name","canonicalName","description","body","depth","version","createdBy","updatedBy","createdAt","updatedAt")
|
|
9341
|
+
VALUES ($1,$2,$3,FALSE,$4,$5,$6,$7,$8,$9,$10,1,$11::jsonb,$11::jsonb,$12,$12) RETURNING *`, [
|
|
9342
|
+
id,
|
|
9343
|
+
scopeId,
|
|
9344
|
+
input.parent.id,
|
|
9345
|
+
input.key ?? null,
|
|
9346
|
+
position,
|
|
9347
|
+
input.name,
|
|
9348
|
+
core.canonicalKnowledgeName(input.name),
|
|
9349
|
+
input.description ?? null,
|
|
9350
|
+
input.body ?? null,
|
|
9351
|
+
depth,
|
|
9352
|
+
JSON.stringify(input.provenance),
|
|
9353
|
+
iso(input.at)
|
|
9354
|
+
]);
|
|
9355
|
+
await this.#logActivity(db, {
|
|
9356
|
+
action: "page-created",
|
|
9357
|
+
scopeId,
|
|
9358
|
+
pageId: id,
|
|
9359
|
+
provenance: input.provenance,
|
|
9360
|
+
at: input.at
|
|
9361
|
+
});
|
|
9362
|
+
return toPage(rows[0]);
|
|
9363
|
+
} catch (error) {
|
|
9364
|
+
if (isUniqueViolation$1(error)) {
|
|
9365
|
+
if (input.key) throw new core.KnowledgePageKeyConflictError(input.key, input.parent.id);
|
|
9366
|
+
throw new _mastra_core_storage.KnowledgeConflictError(id);
|
|
9367
|
+
}
|
|
9368
|
+
throw error;
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
async #depthOf(db, id) {
|
|
9372
|
+
const rows = await this.#rows(db, `SELECT "depth" FROM ${this.#pages} WHERE "id" = $1`, [id]);
|
|
9373
|
+
return Number(rows[0]?.depth ?? 0);
|
|
9374
|
+
}
|
|
9375
|
+
async updatePage(input) {
|
|
9376
|
+
const core = await loadPagesCore();
|
|
9377
|
+
(await this.#bounds()).assertKnowledgePageDescriptionWithinBound(input.description);
|
|
9378
|
+
return this.#tx(async (tx) => {
|
|
9379
|
+
const page = await this.#requireLivePage(tx, input.id);
|
|
9380
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
9381
|
+
const now = /* @__PURE__ */ new Date();
|
|
9382
|
+
const sets = [
|
|
9383
|
+
`"version" = "version" + 1`,
|
|
9384
|
+
`"updatedAt" = $2`,
|
|
9385
|
+
`"updatedBy" = $3::jsonb`
|
|
9386
|
+
];
|
|
9387
|
+
const params = new Params();
|
|
9388
|
+
params.add(input.id);
|
|
9389
|
+
params.add(iso(now));
|
|
9390
|
+
params.add(JSON.stringify(input.provenance));
|
|
9391
|
+
if (input.name !== void 0) sets.push(`"name" = ${params.add(input.name)}`, `"canonicalName" = ${params.add(core.canonicalKnowledgeName(input.name))}`);
|
|
9392
|
+
if (input.description !== void 0) sets.push(`"description" = ${params.add(input.description === "" ? null : input.description)}`);
|
|
9393
|
+
if (input.body !== void 0) sets.push(`"body" = ${params.add(input.body)}`);
|
|
9394
|
+
const rows = await this.#rows(tx, `UPDATE ${this.#pages} SET ${sets.join(", ")} WHERE "id" = $1 RETURNING *`, params.values);
|
|
9395
|
+
await this.#logActivity(tx, {
|
|
9396
|
+
action: "page-updated",
|
|
9397
|
+
scopeId: page.scopeId,
|
|
9398
|
+
pageId: page.id,
|
|
9399
|
+
provenance: input.provenance,
|
|
9400
|
+
at: now
|
|
9401
|
+
});
|
|
9402
|
+
return toPage(rows[0]);
|
|
9403
|
+
});
|
|
9404
|
+
}
|
|
9405
|
+
async movePage(input) {
|
|
9406
|
+
const core = await loadPagesCore();
|
|
9407
|
+
return this.#tx(async (tx) => {
|
|
9408
|
+
const page = await this.#requireLivePage(tx, input.id);
|
|
9409
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
9410
|
+
if (page.isScope) throw new Error(`Knowledge scope page ${input.id} can't be moved`);
|
|
9411
|
+
if (input.parentId === input.id || await this.#isInSubtree(tx, input.id, input.parentId)) throw new Error(`Knowledge page ${input.id} can't move under itself or its own subtree`);
|
|
9412
|
+
const parent = await this.#requireLivePage(tx, input.parentId);
|
|
9413
|
+
const stats = await this.#subtreeStats(tx, input.id);
|
|
9414
|
+
if (stats.live + 1 > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(input.id, stats.live + 1);
|
|
9415
|
+
const newScopeId = parent.isScope ? parent.id : parent.scopeId;
|
|
9416
|
+
const newDepth = parent.isScope ? 1 : await this.#depthOf(tx, parent.id) + 1;
|
|
9417
|
+
const delta = newDepth - await this.#depthOf(tx, page.id);
|
|
9418
|
+
await this.#assertNoKeyConflicts(tx, { rootId: input.id }, parent.id);
|
|
9419
|
+
if (stats.maxDepth + delta > core.MAX_KNOWLEDGE_PAGE_DEPTH || newDepth > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(input.id);
|
|
9420
|
+
const position = input.position ?? core.knowledgePositionBetween(await this.#lastPosition(tx, parent.id));
|
|
9421
|
+
const now = /* @__PURE__ */ new Date();
|
|
9422
|
+
try {
|
|
9423
|
+
await tx.query(`${this.#subtree("$1")} UPDATE ${this.#pages} SET "scopeId" = $2, "depth" = "depth" + $3 WHERE "id" IN (SELECT "id" FROM sub)`, [
|
|
9424
|
+
input.id,
|
|
9425
|
+
newScopeId,
|
|
9426
|
+
delta
|
|
9427
|
+
]);
|
|
9428
|
+
const rows = await this.#rows(tx, `UPDATE ${this.#pages} SET "parentId" = $2, "scopeId" = $3, "depth" = $4, "position" = $5, "version" = "version" + 1, "updatedAt" = $6, "updatedBy" = $7::jsonb WHERE "id" = $1 RETURNING *`, [
|
|
9429
|
+
input.id,
|
|
9430
|
+
parent.id,
|
|
9431
|
+
newScopeId,
|
|
9432
|
+
newDepth,
|
|
9433
|
+
position,
|
|
9434
|
+
iso(now),
|
|
9435
|
+
JSON.stringify(input.provenance)
|
|
9436
|
+
]);
|
|
9437
|
+
await this.#logActivity(tx, {
|
|
9438
|
+
action: "page-moved",
|
|
9439
|
+
scopeId: newScopeId,
|
|
9440
|
+
pageId: input.id,
|
|
9441
|
+
provenance: input.provenance,
|
|
9442
|
+
at: now
|
|
9443
|
+
});
|
|
9444
|
+
return toPage(rows[0]);
|
|
9445
|
+
} catch (error) {
|
|
9446
|
+
if (isUniqueViolation$1(error)) throw new _mastra_core_storage.KnowledgeConflictError(page.key ? `${newScopeId}#${page.key}` : input.id);
|
|
9447
|
+
throw error;
|
|
9448
|
+
}
|
|
9449
|
+
});
|
|
9450
|
+
}
|
|
9451
|
+
async mergePages(input) {
|
|
9452
|
+
const core = await loadPagesCore();
|
|
9453
|
+
return this.#tx(async (tx) => {
|
|
9454
|
+
if (input.sourceId === input.targetId) throw new Error(`Knowledge page ${input.sourceId} can't merge into itself or its own subtree`);
|
|
9455
|
+
const [firstId, secondId] = [input.sourceId, input.targetId].sort();
|
|
9456
|
+
await this.#lockPage(tx, firstId);
|
|
9457
|
+
await this.#lockPage(tx, secondId);
|
|
9458
|
+
const source = await this.#requireLivePage(tx, input.sourceId, false);
|
|
9459
|
+
const target = await this.#requireLivePage(tx, input.targetId, false);
|
|
9460
|
+
if (source.version !== input.sourceVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.sourceId);
|
|
9461
|
+
if (source.isScope || target.isScope) throw new Error("Knowledge scope pages can't be merged");
|
|
9462
|
+
if (await this.#isInSubtree(tx, source.id, target.id)) throw new Error(`Knowledge page ${source.id} can't merge into itself or its own subtree`);
|
|
9463
|
+
const stats = await this.#subtreeStats(tx, source.id);
|
|
9464
|
+
if (stats.live > core.MAX_KNOWLEDGE_MOVE_PAGES) throw new core.KnowledgeMoveTooLargeError(source.id, stats.live);
|
|
9465
|
+
const delta = await this.#depthOf(tx, target.id) - await this.#depthOf(tx, source.id);
|
|
9466
|
+
await this.#assertNoKeyConflicts(tx, { childrenOf: source.id }, target.id, source.id);
|
|
9467
|
+
if (stats.maxDepth > 0 && stats.maxDepth + delta > core.MAX_KNOWLEDGE_PAGE_DEPTH) throw new core.KnowledgePageDepthError(source.id);
|
|
9468
|
+
const now = /* @__PURE__ */ new Date();
|
|
9469
|
+
await tx.query(`UPDATE ${this.#pages} SET "deletedAt" = $2, "deletedBy" = $3, "mergedInto" = $4, "scopeId" = $6, "version" = "version" + 1, "updatedAt" = $2, "updatedBy" = $5::jsonb WHERE "id" = $1`, [
|
|
9470
|
+
source.id,
|
|
9471
|
+
iso(now),
|
|
9472
|
+
input.provenance.author,
|
|
9473
|
+
target.id,
|
|
9474
|
+
JSON.stringify(input.provenance),
|
|
9475
|
+
target.scopeId
|
|
9476
|
+
]);
|
|
9477
|
+
try {
|
|
9478
|
+
await tx.query(`${this.#subtree("$1")} UPDATE ${this.#pages} SET "scopeId" = $2, "depth" = "depth" + $3 WHERE "id" IN (SELECT "id" FROM sub)`, [
|
|
9479
|
+
source.id,
|
|
9480
|
+
target.scopeId,
|
|
9481
|
+
delta
|
|
9482
|
+
]);
|
|
9483
|
+
await tx.query(`UPDATE ${this.#pages} SET "parentId" = $2 WHERE "parentId" = $1 AND NOT "isScope"`, [source.id, target.id]);
|
|
9484
|
+
} catch (error) {
|
|
9485
|
+
if (isUniqueViolation$1(error)) throw new _mastra_core_storage.KnowledgeConflictError(source.id);
|
|
9486
|
+
throw error;
|
|
9487
|
+
}
|
|
9488
|
+
await tx.query(`UPDATE ${this.#notes} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
|
|
9489
|
+
await tx.query(`DELETE FROM ${this.#mentions} m WHERE m."pageId" = $1 AND EXISTS (SELECT 1 FROM ${this.#mentions} t WHERE t."noteId" = m."noteId" AND t."pageId" = $2)`, [source.id, target.id]);
|
|
9490
|
+
await tx.query(`UPDATE ${this.#mentions} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
|
|
9491
|
+
const sourcePin = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [source.id]))[0];
|
|
9492
|
+
if (sourcePin) {
|
|
9493
|
+
await this.#upsertPinAudience(tx, target.id, sourcePin.audience ?? [], String(sourcePin.createdBy), now);
|
|
9494
|
+
await tx.query(`DELETE FROM ${this.#pins} WHERE "id" = $1`, [String(sourcePin.id)]);
|
|
9495
|
+
}
|
|
9496
|
+
const rows = await this.#rows(tx, `SELECT * FROM ${this.#pages} WHERE "id" = $1`, [target.id]);
|
|
9497
|
+
await this.#logActivity(tx, {
|
|
9498
|
+
action: "page-merged",
|
|
9499
|
+
scopeId: target.scopeId,
|
|
9500
|
+
pageId: source.id,
|
|
9501
|
+
provenance: input.provenance,
|
|
9502
|
+
at: now
|
|
9503
|
+
});
|
|
9504
|
+
return toPage(rows[0]);
|
|
9505
|
+
});
|
|
9506
|
+
}
|
|
9507
|
+
async deletePage(input) {
|
|
9508
|
+
const core = await loadPagesCore();
|
|
9509
|
+
return this.#tx(async (tx) => {
|
|
9510
|
+
const page = await this.#requireLivePage(tx, input.id);
|
|
9511
|
+
if (page.version !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
9512
|
+
if (page.isScope) throw new Error(`Knowledge scope page ${input.id} can't be deleted`);
|
|
9513
|
+
if ((await this.#rows(tx, `SELECT 1 FROM ${this.#pages} WHERE "parentId" = $1 AND "deletedAt" IS NULL LIMIT 1`, [input.id])).length) throw new core.KnowledgePageHasChildrenError(input.id);
|
|
9514
|
+
const now = /* @__PURE__ */ new Date();
|
|
9515
|
+
const rows = await this.#rows(tx, `UPDATE ${this.#pages} SET "deletedAt" = $2, "deletedBy" = $3, "version" = "version" + 1, "updatedAt" = $2, "updatedBy" = $4::jsonb WHERE "id" = $1 RETURNING *`, [
|
|
9516
|
+
input.id,
|
|
9517
|
+
iso(now),
|
|
9518
|
+
input.deletedBy,
|
|
9519
|
+
JSON.stringify({ author: input.deletedBy })
|
|
9520
|
+
]);
|
|
9521
|
+
await tx.query(`DELETE FROM ${this.#pins} WHERE "pageId" = $1`, [input.id]);
|
|
9522
|
+
await this.#logActivity(tx, {
|
|
9523
|
+
action: "page-deleted",
|
|
9524
|
+
scopeId: page.scopeId,
|
|
9525
|
+
pageId: page.id,
|
|
9526
|
+
provenance: { author: input.deletedBy },
|
|
9527
|
+
at: now
|
|
9528
|
+
});
|
|
9529
|
+
return toPage(rows[0]);
|
|
9530
|
+
});
|
|
9531
|
+
}
|
|
9532
|
+
async getNote(input) {
|
|
9533
|
+
const params = new Params();
|
|
9534
|
+
const id = params.add(input.id);
|
|
9535
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9536
|
+
const rows = await this.#rows(this.#client, `SELECT ${this.#noteColumns()} FROM ${this.#visibleNotes(filter)} AND n."id" = ${id}${input.includeDeleted ? "" : " AND n.\"deletedAt\" IS NULL"}`, params.values);
|
|
9537
|
+
return rows[0] ? toNote(rows[0]) : null;
|
|
9538
|
+
}
|
|
9539
|
+
async listNotes(input) {
|
|
9540
|
+
const core = await loadPagesCore();
|
|
9541
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9542
|
+
const params = new Params();
|
|
9543
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9544
|
+
const extra = [`n."pageId" = ${params.add(input.pageId)}`];
|
|
9545
|
+
if (!input.includeDeleted) extra.push(`n."deletedAt" IS NULL`);
|
|
9546
|
+
if (input.cursor) {
|
|
9547
|
+
const [createdAt, id] = decodeCursor(input.cursor, 2);
|
|
9548
|
+
extra.push(`(n."createdAt", n."id") > (${params.add(createdAt)}::timestamptz, ${params.add(id)})`);
|
|
9549
|
+
}
|
|
9550
|
+
const rows = await this.#rows(this.#client, `SELECT ${this.#noteColumns()} FROM ${this.#visibleNotes(filter)} AND ${extra.join(" AND ")} ORDER BY n."createdAt", n."id" LIMIT ${limit + 1}`, params.values);
|
|
9551
|
+
return this.#noteOutput(rows, limit);
|
|
9552
|
+
}
|
|
9553
|
+
async countNotes(input) {
|
|
9554
|
+
const ids = [...new Set(input.pageIds)];
|
|
9555
|
+
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
9556
|
+
if (!ids.length) return counts;
|
|
9557
|
+
const params = new Params();
|
|
9558
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9559
|
+
const rows = await this.#rows(this.#client, `SELECT n."pageId" AS "pageId", count(*)::int AS "count" FROM ${this.#visibleNotes(filter)} AND n."deletedAt" IS NULL AND n."pageId" = ANY(${params.add(ids)}::text[]) GROUP BY n."pageId"`, params.values);
|
|
9560
|
+
for (const row of rows) counts[row.pageId] = Number(row.count);
|
|
9561
|
+
return counts;
|
|
9562
|
+
}
|
|
9563
|
+
async listNotesMentioning(input) {
|
|
9564
|
+
const core = await loadPagesCore();
|
|
9565
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9566
|
+
const params = new Params();
|
|
9567
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9568
|
+
const pageParam = params.add(input.pageId);
|
|
9569
|
+
const extra = [
|
|
9570
|
+
`n."deletedAt" IS NULL`,
|
|
9571
|
+
`n."pageId" <> ${pageParam}`,
|
|
9572
|
+
`np."deletedAt" IS NULL`,
|
|
9573
|
+
`EXISTS (SELECT 1 FROM ${this.#mentions} tm WHERE tm."noteId" = n."id" AND tm."pageId" = ${pageParam})`
|
|
9574
|
+
];
|
|
9575
|
+
if (input.cursor) {
|
|
9576
|
+
const [createdAt, id] = decodeCursor(input.cursor, 2);
|
|
9577
|
+
extra.push(`(n."createdAt", n."id") < (${params.add(createdAt)}::timestamptz, ${params.add(id)})`);
|
|
9578
|
+
}
|
|
9579
|
+
const rows = await this.#rows(this.#client, `SELECT ${this.#noteColumns()} FROM ${this.#visibleNotes(filter)} AND ${extra.join(" AND ")} ORDER BY n."createdAt" DESC, n."id" DESC LIMIT ${limit + 1}`, params.values);
|
|
9580
|
+
return this.#noteOutput(rows, limit);
|
|
9581
|
+
}
|
|
9582
|
+
#noteOutput(rows, limit) {
|
|
9583
|
+
const notes = rows.slice(0, limit).map(toNote);
|
|
9584
|
+
const last = notes[notes.length - 1];
|
|
9585
|
+
return {
|
|
9586
|
+
notes,
|
|
9587
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor([iso(last.createdAt), last.id]) } : {}
|
|
9588
|
+
};
|
|
9589
|
+
}
|
|
9590
|
+
async addNote(input) {
|
|
9591
|
+
(await this.#bounds()).assertKnowledgeNoteTextWithinBound(input.text);
|
|
9592
|
+
if (input.stampScopeIds.length === 0) throw new Error("A Knowledge note needs at least one stamp scope");
|
|
9593
|
+
return this.#tx(async (tx) => {
|
|
9594
|
+
const page = await this.#requireLivePage(tx, input.pageId, false);
|
|
9595
|
+
const stampIds = [...new Set(input.stampScopeIds)];
|
|
9596
|
+
await this.#assertScopePages(tx, stampIds);
|
|
9597
|
+
const mentionIds = [...new Set(input.mentions)];
|
|
9598
|
+
if (mentionIds.length) {
|
|
9599
|
+
const found = await this.#rows(tx, `SELECT "id" FROM ${this.#pages} WHERE "id" = ANY($1::text[])`, [mentionIds]);
|
|
9600
|
+
const missing = mentionIds.find((id) => !found.some((row) => row.id === id));
|
|
9601
|
+
if (missing) throw new _mastra_core_storage.KnowledgeNotFoundError("page", missing);
|
|
9602
|
+
}
|
|
9603
|
+
const id = input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)();
|
|
9604
|
+
const now = /* @__PURE__ */ new Date();
|
|
9605
|
+
await tx.query(`INSERT INTO ${this.#notes} ("id","pageId","text","when","provenance","version","createdAt","updatedAt") VALUES ($1,$2,$3,$4,$5::jsonb,1,$6,$6)`, [
|
|
9606
|
+
id,
|
|
9607
|
+
page.id,
|
|
9608
|
+
input.text,
|
|
9609
|
+
input.when ? iso(input.when) : null,
|
|
9610
|
+
JSON.stringify(input.provenance),
|
|
9611
|
+
iso(now)
|
|
9612
|
+
]);
|
|
9613
|
+
await tx.query(`INSERT INTO ${this.#stamps} ("noteId","scopeId") SELECT $1, unnest($2::text[])`, [id, stampIds]);
|
|
9614
|
+
if (mentionIds.length) await tx.query(`INSERT INTO ${this.#mentions} ("noteId","pageId") SELECT $1, unnest($2::text[])`, [id, mentionIds]);
|
|
9615
|
+
await this.#logActivity(tx, {
|
|
9616
|
+
action: "note-added",
|
|
9617
|
+
scopeId: page.scopeId,
|
|
9618
|
+
pageId: page.id,
|
|
9619
|
+
noteId: id,
|
|
9620
|
+
provenance: input.provenance,
|
|
9621
|
+
at: now
|
|
9622
|
+
});
|
|
9623
|
+
return this.#noteById(tx, id);
|
|
9624
|
+
});
|
|
9625
|
+
}
|
|
9626
|
+
async #assertScopePages(db, ids) {
|
|
9627
|
+
const rows = await this.#rows(db, `SELECT "id" FROM ${this.#pages} WHERE "id" = ANY($1::text[]) AND "isScope"`, [ids]);
|
|
9628
|
+
const missing = ids.find((id) => !rows.some((row) => row.id === id));
|
|
9629
|
+
if (missing) throw new Error(`Knowledge note stamp ${missing} is not a scope page`);
|
|
9630
|
+
}
|
|
9631
|
+
async #noteById(db, id, lock = false) {
|
|
9632
|
+
const rows = await this.#rows(db, `SELECT ${this.#noteColumns()} FROM ${this.#notes} n WHERE n."id" = $1${lock ? " FOR UPDATE" : ""}`, [id]);
|
|
9633
|
+
if (!rows[0]) throw new _mastra_core_storage.KnowledgeNotFoundError("note", id);
|
|
9634
|
+
return toNote(rows[0]);
|
|
9635
|
+
}
|
|
9636
|
+
async restampNote(input) {
|
|
9637
|
+
if (input.stampScopeIds.length === 0) throw new Error("A Knowledge note needs at least one stamp scope");
|
|
9638
|
+
return this.#tx(async (tx) => {
|
|
9639
|
+
const note = (await this.#rows(tx, `SELECT * FROM ${this.#notes} WHERE "id" = $1 FOR UPDATE`, [input.id]))[0];
|
|
9640
|
+
if (!note || note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
|
|
9641
|
+
if (Number(note.version) !== input.expectedVersion) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
9642
|
+
const stampIds = [...new Set(input.stampScopeIds)];
|
|
9643
|
+
await this.#assertScopePages(tx, stampIds);
|
|
9644
|
+
const now = /* @__PURE__ */ new Date();
|
|
9645
|
+
await tx.query(`DELETE FROM ${this.#stamps} WHERE "noteId" = $1`, [input.id]);
|
|
9646
|
+
await tx.query(`INSERT INTO ${this.#stamps} ("noteId","scopeId") SELECT $1, unnest($2::text[])`, [input.id, stampIds]);
|
|
9647
|
+
await tx.query(`UPDATE ${this.#notes} SET "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [input.id, iso(now)]);
|
|
9648
|
+
const page = await this.#pageScope(tx, String(note.pageId));
|
|
9649
|
+
await this.#logActivity(tx, {
|
|
9650
|
+
action: "note-restamped",
|
|
9651
|
+
scopeId: page,
|
|
9652
|
+
pageId: String(note.pageId),
|
|
9653
|
+
noteId: input.id,
|
|
9654
|
+
provenance: input.provenance,
|
|
9655
|
+
at: now
|
|
9656
|
+
});
|
|
9657
|
+
return this.#noteById(tx, input.id);
|
|
9658
|
+
});
|
|
9659
|
+
}
|
|
9660
|
+
async #pageScope(db, pageId) {
|
|
9661
|
+
const rows = await this.#rows(db, `SELECT "scopeId" FROM ${this.#pages} WHERE "id" = $1`, [pageId]);
|
|
9662
|
+
return String(rows[0]?.scopeId);
|
|
9663
|
+
}
|
|
9664
|
+
async removeNote(input) {
|
|
9665
|
+
return this.#tx(async (tx) => {
|
|
9666
|
+
const note = await this.#noteById(tx, input.id, true);
|
|
9667
|
+
if (note.deletedAt) return note;
|
|
9668
|
+
const now = /* @__PURE__ */ new Date();
|
|
9669
|
+
await tx.query(`UPDATE ${this.#notes} SET "deletedAt" = $2, "deletedBy" = $3, "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [
|
|
9670
|
+
input.id,
|
|
9671
|
+
iso(now),
|
|
9672
|
+
input.deletedBy
|
|
9673
|
+
]);
|
|
9674
|
+
await this.#logActivity(tx, {
|
|
9675
|
+
action: "note-removed",
|
|
9676
|
+
scopeId: await this.#pageScope(tx, note.pageId),
|
|
9677
|
+
pageId: note.pageId,
|
|
9678
|
+
noteId: input.id,
|
|
9679
|
+
provenance: input.provenance,
|
|
9680
|
+
at: now
|
|
9681
|
+
});
|
|
9682
|
+
return this.#noteById(tx, input.id);
|
|
9683
|
+
});
|
|
9684
|
+
}
|
|
9685
|
+
async restoreNote(input) {
|
|
9686
|
+
return this.#tx(async (tx) => {
|
|
9687
|
+
const note = await this.#noteById(tx, input.id, true);
|
|
9688
|
+
if (!note.deletedAt) return note;
|
|
9689
|
+
const now = /* @__PURE__ */ new Date();
|
|
9690
|
+
await tx.query(`UPDATE ${this.#notes} SET "deletedAt" = NULL, "deletedBy" = NULL, "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [input.id, iso(now)]);
|
|
9691
|
+
await this.#logActivity(tx, {
|
|
9692
|
+
action: "note-restored",
|
|
9693
|
+
scopeId: await this.#pageScope(tx, note.pageId),
|
|
9694
|
+
pageId: note.pageId,
|
|
9695
|
+
noteId: input.id,
|
|
9696
|
+
provenance: input.provenance,
|
|
9697
|
+
at: now
|
|
9698
|
+
});
|
|
9699
|
+
return this.#noteById(tx, input.id);
|
|
9700
|
+
});
|
|
9701
|
+
}
|
|
9702
|
+
async searchPages(input) {
|
|
9703
|
+
const core = await loadPagesCore();
|
|
9704
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9705
|
+
const words = knowledgeSearchWords(input.query);
|
|
9706
|
+
if (!words.length) return [];
|
|
9707
|
+
const phrase = input.query.trim().toLowerCase().replace(/\s+/g, " ");
|
|
9708
|
+
const hits = [];
|
|
9432
9709
|
{
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9710
|
+
const params = new Params();
|
|
9711
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9712
|
+
const patterns = words.map((word) => params.add(`%${escapeLike(word)}%`));
|
|
9713
|
+
const phrasePattern = params.add(`%${escapeLike(phrase)}%`);
|
|
9714
|
+
const where = [
|
|
9715
|
+
`p."deletedAt" IS NULL`,
|
|
9716
|
+
`NOT p."isScope"`,
|
|
9717
|
+
filter.readable("s")
|
|
9718
|
+
];
|
|
9719
|
+
if (input.scopeIds?.length) where.push(`p."scopeId" = ANY(${params.add(input.scopeIds)}::text[])`);
|
|
9720
|
+
for (const pattern of patterns) where.push(`(p."name" ILIKE ${pattern} OR p."description" ILIKE ${pattern} OR p."body" ILIKE ${pattern})`);
|
|
9721
|
+
const tier = `CASE WHEN p."canonicalName" LIKE ${phrasePattern} THEN 0 WHEN ${patterns.map((pattern) => `p."name" ILIKE ${pattern}`).join(" AND ")} THEN 1 ELSE 2 END`;
|
|
9722
|
+
const rows = await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId" WHERE ${where.join(" AND ")} ORDER BY ${tier}, p."updatedAt" DESC, p."id" ASC LIMIT ${limit}`, params.values);
|
|
9723
|
+
for (const row of rows) hits.push({
|
|
9724
|
+
type: "page",
|
|
9725
|
+
page: toPage(row)
|
|
9726
|
+
});
|
|
9727
|
+
}
|
|
9728
|
+
if (hits.length >= limit) return hits;
|
|
9436
9729
|
{
|
|
9437
|
-
|
|
9438
|
-
|
|
9730
|
+
const params = new Params();
|
|
9731
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9732
|
+
const extra = [`n."deletedAt" IS NULL`, `np."deletedAt" IS NULL`];
|
|
9733
|
+
if (input.scopeIds?.length) extra.push(`np."scopeId" = ANY(${params.add(input.scopeIds)}::text[])`);
|
|
9734
|
+
for (const word of words) extra.push(`n."text" ILIKE ${params.add(`%${escapeLike(word)}%`)}`);
|
|
9735
|
+
const rows = await this.#rows(this.#client, `SELECT ${this.#noteColumns()}, row_to_json(np) AS "pageJson" FROM ${this.#visibleNotes(filter)} AND ${extra.join(" AND ")} ORDER BY n."updatedAt" DESC, n."id" DESC LIMIT ${limit - hits.length}`, params.values);
|
|
9736
|
+
for (const row of rows) hits.push({
|
|
9737
|
+
type: "note",
|
|
9738
|
+
note: toNote(row),
|
|
9739
|
+
page: toPage(parseJson$1(row.pageJson))
|
|
9740
|
+
});
|
|
9741
|
+
}
|
|
9742
|
+
return hits;
|
|
9743
|
+
}
|
|
9744
|
+
async listPageActivity(input) {
|
|
9745
|
+
const core = await loadPagesCore();
|
|
9746
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9747
|
+
const params = new Params();
|
|
9748
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9749
|
+
const where = [filter.readable("s"), `(a."noteId" IS NULL OR ${this.#noteVisible(filter, "a.\"noteId\"")})`];
|
|
9750
|
+
if (input.scopeId) where.push(`s."id" = ${params.add(input.scopeId)}`);
|
|
9751
|
+
if (input.pageId) where.push(`a."pageId" = ${params.add(input.pageId)}`);
|
|
9752
|
+
if (input.cursor) {
|
|
9753
|
+
const [createdAt, id] = decodeCursor(input.cursor, 2);
|
|
9754
|
+
where.push(`(a."createdAt", a."id") < (${params.add(createdAt)}::timestamptz, ${params.add(id)})`);
|
|
9439
9755
|
}
|
|
9440
|
-
|
|
9756
|
+
const rows = await this.#rows(this.#client, `SELECT a.* FROM ${this.#activity} a JOIN ${this.#pages} p ON p."id" = a."pageId" JOIN ${this.#pages} s ON s."id" = p."scopeId"
|
|
9757
|
+
WHERE ${where.join(" AND ")} ORDER BY a."createdAt" DESC, a."id" DESC LIMIT ${limit + 1}`, params.values);
|
|
9758
|
+
const events = rows.slice(0, limit).map((row) => ({
|
|
9759
|
+
id: String(row.id),
|
|
9760
|
+
action: String(row.action),
|
|
9761
|
+
scopeId: String(row.scopeId),
|
|
9762
|
+
pageId: String(row.pageId),
|
|
9763
|
+
...row.noteId == null ? {} : { noteId: String(row.noteId) },
|
|
9764
|
+
provenance: parseJson$1(row.provenance),
|
|
9765
|
+
createdAt: new Date(row.createdAt)
|
|
9766
|
+
}));
|
|
9767
|
+
const last = events[events.length - 1];
|
|
9768
|
+
return {
|
|
9769
|
+
events,
|
|
9770
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor([iso(last.createdAt), last.id]) } : {}
|
|
9771
|
+
};
|
|
9772
|
+
}
|
|
9773
|
+
async addPinAudience(input) {
|
|
9774
|
+
return this.#tx(async (tx) => {
|
|
9775
|
+
const page = await this.#requireLivePage(tx, input.pageId, false);
|
|
9776
|
+
const now = /* @__PURE__ */ new Date();
|
|
9777
|
+
const { pin, changed } = await this.#upsertPinAudience(tx, page.id, input.audience, input.createdBy, now);
|
|
9778
|
+
if (changed) await this.#logActivity(tx, {
|
|
9779
|
+
action: "pin-added",
|
|
9780
|
+
scopeId: page.scopeId,
|
|
9781
|
+
pageId: page.id,
|
|
9782
|
+
provenance: { author: input.createdBy },
|
|
9783
|
+
at: now
|
|
9784
|
+
});
|
|
9785
|
+
return pin;
|
|
9786
|
+
});
|
|
9787
|
+
}
|
|
9788
|
+
async #upsertPinAudience(db, pageId, audience, createdBy, now) {
|
|
9789
|
+
const existing = (await this.#rows(db, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [pageId]))[0];
|
|
9790
|
+
if (!existing) return {
|
|
9791
|
+
pin: toPin((await this.#rows(db, `INSERT INTO ${this.#pins} ("id","pageId","audience","createdBy","createdAt","updatedAt") VALUES ($1,$2,$3::text[],$4,$5,$5) RETURNING *`, [
|
|
9792
|
+
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
9793
|
+
pageId,
|
|
9794
|
+
[...new Set(audience)],
|
|
9795
|
+
createdBy,
|
|
9796
|
+
iso(now)
|
|
9797
|
+
]))[0]),
|
|
9798
|
+
changed: true
|
|
9799
|
+
};
|
|
9800
|
+
const current = existing.audience ?? [];
|
|
9801
|
+
const merged = [.../* @__PURE__ */ new Set([...current, ...audience])];
|
|
9802
|
+
if (merged.length === current.length) return {
|
|
9803
|
+
pin: toPin(existing),
|
|
9804
|
+
changed: false
|
|
9805
|
+
};
|
|
9806
|
+
return {
|
|
9807
|
+
pin: toPin((await this.#rows(db, `UPDATE ${this.#pins} SET "audience" = $2::text[], "updatedAt" = $3 WHERE "id" = $1 RETURNING *`, [
|
|
9808
|
+
String(existing.id),
|
|
9809
|
+
merged,
|
|
9810
|
+
iso(now)
|
|
9811
|
+
]))[0]),
|
|
9812
|
+
changed: true
|
|
9813
|
+
};
|
|
9814
|
+
}
|
|
9815
|
+
async removePinAudience(input) {
|
|
9816
|
+
return this.#tx(async (tx) => {
|
|
9817
|
+
const existing = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [input.pageId]))[0];
|
|
9818
|
+
if (!existing) return null;
|
|
9819
|
+
const current = existing.audience ?? [];
|
|
9820
|
+
const remaining = current.filter((entry) => !input.audience.includes(entry));
|
|
9821
|
+
if (remaining.length === current.length) return toPin(existing);
|
|
9822
|
+
const now = /* @__PURE__ */ new Date();
|
|
9823
|
+
let pin = null;
|
|
9824
|
+
if (remaining.length === 0) await tx.query(`DELETE FROM ${this.#pins} WHERE "id" = $1`, [String(existing.id)]);
|
|
9825
|
+
else pin = toPin((await this.#rows(tx, `UPDATE ${this.#pins} SET "audience" = $2::text[], "updatedAt" = $3 WHERE "id" = $1 RETURNING *`, [
|
|
9826
|
+
String(existing.id),
|
|
9827
|
+
remaining,
|
|
9828
|
+
iso(now)
|
|
9829
|
+
]))[0]);
|
|
9830
|
+
await this.#logActivity(tx, {
|
|
9831
|
+
action: "pin-removed",
|
|
9832
|
+
scopeId: await this.#pageScope(tx, input.pageId),
|
|
9833
|
+
pageId: input.pageId,
|
|
9834
|
+
provenance: { author: input.removedBy },
|
|
9835
|
+
at: now
|
|
9836
|
+
});
|
|
9837
|
+
return pin;
|
|
9838
|
+
});
|
|
9839
|
+
}
|
|
9840
|
+
/** Deletes every row but keeps the tables and the schema-version marker. */
|
|
9841
|
+
async clearAll() {
|
|
9842
|
+
const t = PG_KNOWLEDGE_PAGES_TABLES;
|
|
9843
|
+
const tables = [
|
|
9844
|
+
t.activity,
|
|
9845
|
+
t.pins,
|
|
9846
|
+
t.mentions,
|
|
9847
|
+
t.stamps,
|
|
9848
|
+
t.notes,
|
|
9849
|
+
t.pages
|
|
9850
|
+
].map((name) => this.#q(name)).join(", ");
|
|
9851
|
+
await this.#client.query(`TRUNCATE ${tables}`);
|
|
9852
|
+
}
|
|
9853
|
+
async getPages(input) {
|
|
9854
|
+
if (!input.ids.length) return [];
|
|
9855
|
+
const params = new Params();
|
|
9856
|
+
const ids = params.add([...new Set(input.ids)]);
|
|
9857
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9858
|
+
return (await this.#rows(this.#client, `SELECT p.* FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId"
|
|
9859
|
+
WHERE p."id" = ANY(${ids}::text[]) AND p."deletedAt" IS NULL AND ${filter.readable("s")}`, params.values)).map(toPage);
|
|
9860
|
+
}
|
|
9861
|
+
async listPins(input) {
|
|
9862
|
+
const core = await loadPagesCore();
|
|
9863
|
+
const limit = clampLimit(input.limit, core.MAX_KNOWLEDGE_PAGE_LIST_LIMIT);
|
|
9864
|
+
const params = new Params();
|
|
9865
|
+
const where = [`p."deletedAt" IS NULL`, new ReadFilter(input.read, params, this.#pages).readable("s")];
|
|
9866
|
+
if (input.audience !== "all") where.push(`pn."audience" && ${params.add(input.audience)}::text[]`);
|
|
9867
|
+
if (input.pageIds) where.push(`pn."pageId" = ANY(${params.add(input.pageIds)}::text[])`);
|
|
9868
|
+
if (input.excludePageIds?.length) where.push(`pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[])`);
|
|
9869
|
+
if (input.withBody) where.push(`p."body" ~ '\\S'`);
|
|
9870
|
+
const from = `FROM ${this.#pins} pn JOIN ${this.#pages} p ON p."id" = pn."pageId" JOIN ${this.#pages} s ON s."id" = p."scopeId"`;
|
|
9871
|
+
const totalWhere = where.join(" AND ");
|
|
9872
|
+
if (input.cursor) {
|
|
9873
|
+
const [createdAt, id] = decodeCursor(input.cursor, 2);
|
|
9874
|
+
where.push(`(pn."createdAt", pn."id") < (${params.add(createdAt)}::timestamptz, ${params.add(id)})`);
|
|
9875
|
+
}
|
|
9876
|
+
const rows = await this.#rows(this.#client, `SELECT pn.*, p."name" AS "pageName" ${from}
|
|
9877
|
+
WHERE ${where.join(" AND ")} ORDER BY pn."createdAt" DESC, pn."id" DESC LIMIT ${limit + 1}`, params.values);
|
|
9878
|
+
let total;
|
|
9879
|
+
if (input.count) {
|
|
9880
|
+
const counted = await this.#rows(this.#client, `SELECT count(*)::int AS "total" ${from} WHERE ${totalWhere}`, params.values.slice(0, params.values.length - (input.cursor ? 2 : 0)));
|
|
9881
|
+
total = Number(counted[0]?.total ?? 0);
|
|
9882
|
+
}
|
|
9883
|
+
const pins = rows.slice(0, limit).map(toListedPin);
|
|
9884
|
+
const last = pins[pins.length - 1];
|
|
9885
|
+
return {
|
|
9886
|
+
pins,
|
|
9887
|
+
...rows.length > limit && last ? { nextCursor: encodeCursor([iso(last.createdAt), last.id]) } : {},
|
|
9888
|
+
...total === void 0 ? {} : { total }
|
|
9889
|
+
};
|
|
9890
|
+
}
|
|
9891
|
+
};
|
|
9892
|
+
// src/storage/domains/knowledge/pages-schema.ts
|
|
9893
|
+
const qualify = (schemaName, table) => schemaName ? `"${schemaName}"."${table}"` : `"${table}"`;
|
|
9894
|
+
/** Thrown when existing Knowledge tables can't be used as schema 4 without a reset. */
|
|
9895
|
+
var PgKnowledgeResetRequiredError = class extends Error {
|
|
9896
|
+
inspection;
|
|
9897
|
+
constructor(inspection) {
|
|
9898
|
+
super(knowledgePagesResetMessage(inspection.reason));
|
|
9899
|
+
this.name = "KnowledgeSchemaResetRequiredError";
|
|
9900
|
+
this.inspection = inspection;
|
|
9901
|
+
}
|
|
9902
|
+
};
|
|
9903
|
+
/** Looks at every `mastra_knowledge_*` table and the version row without changing anything. */
|
|
9904
|
+
async function inspectPgKnowledgePagesSchema(db, schemaName) {
|
|
9905
|
+
return inspectKnowledgePagesSchema(await pgKnowledgeSnapshot(db, schemaName));
|
|
9906
|
+
}
|
|
9907
|
+
/** Creates the schema-4 tables, refusing (with the reset message) over anything older. */
|
|
9908
|
+
async function initPgKnowledgePagesSchema(client, schemaName) {
|
|
9909
|
+
await client.tx(async (tx) => {
|
|
9910
|
+
await tx.query("SELECT pg_advisory_xact_lock(hashtext($1))", [`mastra-knowledge-schema:${schemaName ?? ""}`]);
|
|
9911
|
+
const drop = legacyKnowledgeTablesToDrop(await pgKnowledgeSnapshot(tx, schemaName));
|
|
9912
|
+
if (drop.length > 0) await tx.query(`DROP TABLE IF EXISTS ${drop.map((table) => qualify(schemaName, table)).join(", ")} CASCADE`);
|
|
9913
|
+
const inspection = await inspectPgKnowledgePagesSchema(tx, schemaName);
|
|
9914
|
+
if (inspection.status === "incompatible-reset-required") throw new PgKnowledgeResetRequiredError(inspection);
|
|
9915
|
+
for (const statement of pgKnowledgePagesDDL(schemaName)) await tx.query(statement);
|
|
9916
|
+
await tx.query(`INSERT INTO ${qualify(schemaName, PG_KNOWLEDGE_PAGES_TABLES.state)} ("id","schemaVersion") VALUES ('knowledge', $1) ON CONFLICT ("id") DO NOTHING`, [4]);
|
|
9917
|
+
});
|
|
9441
9918
|
}
|
|
9442
|
-
|
|
9443
|
-
|
|
9919
|
+
/**
|
|
9920
|
+
* Every `mastra_knowledge_*` table and the schema-4 version, if any. The state table is read as
|
|
9921
|
+
* whole rows because schema 2 used the same name with a different shape, and a failed query would
|
|
9922
|
+
* abort the init transaction.
|
|
9923
|
+
*/
|
|
9924
|
+
async function pgKnowledgeSnapshot(db, schemaName) {
|
|
9925
|
+
const tableNames = (await db.query(`SELECT table_name FROM information_schema.tables WHERE table_schema = COALESCE($1, current_schema()) AND table_name LIKE 'mastra\\_knowledge\\_%'`, [schemaName ?? null])).rows.map((row) => String(row.table_name));
|
|
9926
|
+
if (!tableNames.includes(PG_KNOWLEDGE_PAGES_TABLES.state)) return { tableNames };
|
|
9927
|
+
const row = (await db.query(`SELECT to_jsonb(s) AS row FROM ${qualify(schemaName, PG_KNOWLEDGE_PAGES_TABLES.state)} s`)).rows.map((r) => r.row).find((r) => r.id === "knowledge");
|
|
9928
|
+
return {
|
|
9929
|
+
tableNames,
|
|
9930
|
+
schemaVersion: typeof row?.schemaVersion === "number" ? row.schemaVersion : void 0
|
|
9931
|
+
};
|
|
9444
9932
|
}
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
schema: KNOWLEDGE_V2_RECORDS_SCHEMA
|
|
9454
|
-
},
|
|
9455
|
-
{
|
|
9456
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS,
|
|
9457
|
-
schema: KNOWLEDGE_V2_MENTIONS_SCHEMA,
|
|
9458
|
-
compositePrimaryKey: ["recordId", "sourceId"]
|
|
9459
|
-
},
|
|
9460
|
-
{
|
|
9461
|
-
tableName: TABLE_KNOWLEDGE_NODE_SCOPES,
|
|
9462
|
-
schema: KNOWLEDGE_NODE_SCOPES_SCHEMA,
|
|
9463
|
-
compositePrimaryKey: ["nodeId", "scopeNodeId"]
|
|
9464
|
-
},
|
|
9465
|
-
{
|
|
9466
|
-
tableName: TABLE_KNOWLEDGE_RECORD_SCOPES,
|
|
9467
|
-
schema: KNOWLEDGE_RECORD_SCOPES_SCHEMA,
|
|
9468
|
-
compositePrimaryKey: ["recordId", "scopeNodeId"]
|
|
9469
|
-
},
|
|
9470
|
-
{
|
|
9471
|
-
tableName: TABLE_KNOWLEDGE_SCOPE_GRANTS,
|
|
9472
|
-
schema: KNOWLEDGE_SCOPE_GRANTS_SCHEMA,
|
|
9473
|
-
compositePrimaryKey: ["scopeNodeId", "scopeRefId"]
|
|
9474
|
-
},
|
|
9475
|
-
{
|
|
9476
|
-
tableName: TABLE_KNOWLEDGE_ACCESS_STATE,
|
|
9477
|
-
schema: KNOWLEDGE_ACCESS_STATE_SCHEMA
|
|
9478
|
-
},
|
|
9479
|
-
{
|
|
9480
|
-
tableName: TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
|
|
9481
|
-
schema: KNOWLEDGE_SCOPE_ADDRESSES_SCHEMA
|
|
9482
|
-
},
|
|
9483
|
-
{
|
|
9484
|
-
tableName: TABLE_KNOWLEDGE_NODE_ADDRESSES,
|
|
9485
|
-
schema: KNOWLEDGE_NODE_ADDRESSES_SCHEMA,
|
|
9486
|
-
compositePrimaryKey: ["source", "address"]
|
|
9487
|
-
},
|
|
9488
|
-
{
|
|
9489
|
-
tableName: TABLE_KNOWLEDGE_IMPORT_STATE,
|
|
9490
|
-
schema: KNOWLEDGE_IMPORT_STATE_SCHEMA,
|
|
9491
|
-
compositePrimaryKey: [
|
|
9492
|
-
"importerId",
|
|
9493
|
-
"binding",
|
|
9494
|
-
"key"
|
|
9495
|
-
]
|
|
9496
|
-
},
|
|
9497
|
-
{
|
|
9498
|
-
tableName: TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
9499
|
-
schema: KNOWLEDGE_IMPORT_RUNS_SCHEMA
|
|
9500
|
-
},
|
|
9501
|
-
{
|
|
9502
|
-
tableName: TABLE_KNOWLEDGE_PROPOSALS,
|
|
9503
|
-
schema: KNOWLEDGE_PROPOSALS_SCHEMA
|
|
9504
|
-
},
|
|
9505
|
-
{
|
|
9506
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
|
|
9507
|
-
schema: KNOWLEDGE_V2_ACTIVITY_SCHEMA
|
|
9508
|
-
},
|
|
9509
|
-
{
|
|
9510
|
-
tableName: _mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
9511
|
-
schema: _mastra_core_storage.KNOWLEDGE_SEMANTIC_OUTBOX_SCHEMA
|
|
9512
|
-
}
|
|
9513
|
-
];
|
|
9933
|
+
/** Drops exactly the Knowledge tables (never Memory's) and recreates schema 4. */
|
|
9934
|
+
async function resetPgKnowledgePagesSchema(client, schemaName) {
|
|
9935
|
+
const tables = KNOWLEDGE_RESETTABLE_TABLE_NAMES.map((table) => qualify(schemaName, table)).join(", ");
|
|
9936
|
+
await client.query(`DROP TABLE IF EXISTS ${tables} CASCADE`);
|
|
9937
|
+
await initPgKnowledgePagesSchema(client, schemaName);
|
|
9938
|
+
}
|
|
9939
|
+
// src/storage/domains/knowledge/index.ts
|
|
9940
|
+
const loadKnowledgePagesCore = createKnowledgePagesCoreLoader(_mastra_core_features.coreFeatures, () => import("@mastra/core/storage"));
|
|
9514
9941
|
function pgSetting(config, key) {
|
|
9515
9942
|
const value = config[key] || process.env[`PG${key.toUpperCase()}`] || pg.defaults[key];
|
|
9516
9943
|
return value === void 0 || value === null || value === "" ? void 0 : String(value);
|
|
@@ -9550,1153 +9977,137 @@ function getPgKnowledgeIsolationKey(config) {
|
|
|
9550
9977
|
if (target) return `pg:${target}:schema:${schema}`;
|
|
9551
9978
|
return `pg:unidentified:schema:${schema}`;
|
|
9552
9979
|
}
|
|
9980
|
+
/** @experimental Knowledge APIs are experimental and may change without notice. */
|
|
9553
9981
|
var KnowledgePG = class extends _mastra_core_storage.KnowledgeStorage {
|
|
9554
|
-
static MANAGED_TABLES = KNOWLEDGE_TABLE_NAMES;
|
|
9555
9982
|
static getExportDDL(schemaName) {
|
|
9556
|
-
return
|
|
9557
|
-
...definition,
|
|
9558
|
-
schemaName,
|
|
9559
|
-
includeAllConstraints: true
|
|
9560
|
-
})), ...knowledgeIndexDDL(schemaName)];
|
|
9983
|
+
return pgKnowledgePagesDDL(schemaName).map((statement) => `${statement};`);
|
|
9561
9984
|
}
|
|
9562
9985
|
#client;
|
|
9563
|
-
#executor;
|
|
9564
|
-
/** Reader-backed executor for standalone reads; mutations and read-modify-write stay on #executor. */
|
|
9565
|
-
#readExecutor;
|
|
9566
|
-
#db;
|
|
9567
9986
|
#schemaName;
|
|
9987
|
+
#pages;
|
|
9568
9988
|
constructor(config) {
|
|
9569
9989
|
super({ storageIsolationKey: config.storageIsolationKey ?? getPgKnowledgeIsolationKey(config) });
|
|
9570
|
-
const { client,
|
|
9990
|
+
const { client, schemaName } = resolvePgConfig(config);
|
|
9571
9991
|
this.#client = client;
|
|
9572
9992
|
this.#schemaName = schemaName;
|
|
9573
|
-
this.#
|
|
9574
|
-
this.#readExecutor = createExecutor(readClient, schemaName);
|
|
9575
|
-
this.#db = new PgDB({
|
|
9993
|
+
this.#pages = new PgKnowledgePages({
|
|
9576
9994
|
client,
|
|
9577
|
-
readClient,
|
|
9578
9995
|
schemaName,
|
|
9579
|
-
|
|
9996
|
+
bounds: async () => {
|
|
9997
|
+
const core = await loadKnowledgePagesCore();
|
|
9998
|
+
return {
|
|
9999
|
+
assertKnowledgePageDescriptionWithinBound: core.assertKnowledgePageDescriptionWithinBound,
|
|
10000
|
+
assertKnowledgeNoteTextWithinBound: core.assertKnowledgeNoteTextWithinBound
|
|
10001
|
+
};
|
|
10002
|
+
}
|
|
9580
10003
|
});
|
|
9581
10004
|
}
|
|
9582
10005
|
getCapabilities() {
|
|
9583
10006
|
return {
|
|
9584
|
-
|
|
9585
|
-
schemaVersion:
|
|
9586
|
-
supportsV2: true,
|
|
10007
|
+
...super.getCapabilities(),
|
|
10008
|
+
schemaVersion: 4,
|
|
9587
10009
|
supportsSchemaInspection: true,
|
|
9588
10010
|
supportsExplicitReset: true
|
|
9589
10011
|
};
|
|
9590
10012
|
}
|
|
9591
|
-
async inspectSchema() {
|
|
9592
|
-
const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
|
|
9593
|
-
try {
|
|
9594
|
-
const snapshot = getSchemaSnapshot(this.#client, this.#schemaName);
|
|
9595
|
-
if (snapshot) {
|
|
9596
|
-
const tableNames = KNOWLEDGE_TABLE_NAMES.filter((table) => snapshot.tables.has(table));
|
|
9597
|
-
return this.#classifySchema(this.#executor, tableNames, snapshot.columns);
|
|
9598
|
-
}
|
|
9599
|
-
return this.#inspectSchemaWithExecutor(this.#executor);
|
|
9600
|
-
} catch (error) {
|
|
9601
|
-
return inspectKnowledgeSchema({
|
|
9602
|
-
available: false,
|
|
9603
|
-
tableNames: [],
|
|
9604
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
9605
|
-
});
|
|
9606
|
-
}
|
|
9607
|
-
}
|
|
9608
|
-
async #inspectSchemaWithExecutor(executor) {
|
|
9609
|
-
const tableNames = (await executor.execute({
|
|
9610
|
-
sql: `SELECT table_name FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
|
|
9611
|
-
args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
|
|
9612
|
-
})).rows.map((row) => String(row.table_name));
|
|
9613
|
-
const columnResult = await executor.execute({
|
|
9614
|
-
sql: `SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[])`,
|
|
9615
|
-
args: [this.#schemaName ?? null, [...KNOWLEDGE_TABLE_NAMES]]
|
|
9616
|
-
});
|
|
9617
|
-
const columns = /* @__PURE__ */ new Map();
|
|
9618
|
-
for (const row of columnResult.rows) {
|
|
9619
|
-
const table = String(row.table_name);
|
|
9620
|
-
const names = columns.get(table) ?? /* @__PURE__ */ new Set();
|
|
9621
|
-
names.add(String(row.column_name));
|
|
9622
|
-
columns.set(table, names);
|
|
9623
|
-
}
|
|
9624
|
-
return this.#classifySchema(executor, tableNames, columns);
|
|
9625
|
-
}
|
|
9626
|
-
async #classifySchema(executor, tableNames, columns) {
|
|
9627
|
-
const { inspectKnowledgeSchema } = await loadKnowledgeV2Core();
|
|
9628
|
-
if (tableNames.length === 0) return inspectKnowledgeSchema({
|
|
9629
|
-
available: true,
|
|
9630
|
-
tableNames
|
|
9631
|
-
});
|
|
9632
|
-
const missingTables = KNOWLEDGE_TABLE_NAMES.filter((table) => !tableNames.includes(table));
|
|
9633
|
-
if (missingTables.length > 0) return inspectKnowledgeSchema({
|
|
9634
|
-
available: true,
|
|
9635
|
-
tableNames,
|
|
9636
|
-
reason: `Missing Knowledge v2 tables: ${missingTables.join(", ")}`
|
|
9637
|
-
});
|
|
9638
|
-
for (const { tableName: table, schema } of knowledgeTableDefinitions) {
|
|
9639
|
-
const actual = columns.get(table) ?? /* @__PURE__ */ new Set();
|
|
9640
|
-
const missing = Object.keys(schema).filter((column) => !actual.has(column));
|
|
9641
|
-
if (missing.length > 0) return inspectKnowledgeSchema({
|
|
9642
|
-
available: true,
|
|
9643
|
-
tableNames,
|
|
9644
|
-
reason: `Knowledge table ${table} is missing v2 columns: ${missing.join(", ")}`
|
|
9645
|
-
});
|
|
9646
|
-
}
|
|
9647
|
-
const accessState = await executor.execute(`SELECT schemaVersion FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
|
|
9648
|
-
if (Number(accessState.rows[0]?.schemaVersion) !== 2) return inspectKnowledgeSchema({
|
|
9649
|
-
available: true,
|
|
9650
|
-
tableNames,
|
|
9651
|
-
reason: "Knowledge schema version marker is missing or incompatible"
|
|
9652
|
-
});
|
|
9653
|
-
return inspectKnowledgeSchema({
|
|
9654
|
-
available: true,
|
|
9655
|
-
tableNames,
|
|
9656
|
-
schemaVersion: 2
|
|
9657
|
-
});
|
|
9658
|
-
}
|
|
9659
10013
|
async init() {
|
|
9660
|
-
await this.#
|
|
9661
|
-
await tx.execute({
|
|
9662
|
-
sql: "SELECT pg_advisory_xact_lock(hashtext(?))",
|
|
9663
|
-
args: [`mastra-knowledge-v2:${this.#schemaName ?? "current_schema"}`]
|
|
9664
|
-
});
|
|
9665
|
-
const { assertKnowledgeSchemaCompatible } = await loadKnowledgeV2Core();
|
|
9666
|
-
let inspection = await this.#inspectSchemaWithExecutor(tx);
|
|
9667
|
-
if (inspection.status === "incompatible-reset-required" && await this.#isPublishedV1Layout(tx)) {
|
|
9668
|
-
const tables = [...RETIRED_KNOWLEDGE_TABLE_NAMES, ...[...KNOWLEDGE_TABLE_NAMES].reverse()].map((table) => `"${table}"`).join(", ");
|
|
9669
|
-
await tx.execute(`DROP TABLE IF EXISTS ${tables}`);
|
|
9670
|
-
inspection = await this.#inspectSchemaWithExecutor(tx);
|
|
9671
|
-
}
|
|
9672
|
-
assertKnowledgeSchemaCompatible(inspection);
|
|
9673
|
-
for (const definition of knowledgeTableDefinitions) await tx.executeRaw(generateTableSQL({
|
|
9674
|
-
...definition,
|
|
9675
|
-
schemaName: this.#schemaName,
|
|
9676
|
-
includeAllConstraints: true
|
|
9677
|
-
}));
|
|
9678
|
-
for (const index of knowledgeIndexes(this.#schemaName)) await tx.executeRaw(index.sql);
|
|
9679
|
-
await tx.execute({
|
|
9680
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_ACCESS_STATE}" (id, epoch, schemaVersion) VALUES ('global', 0, ?) ON CONFLICT (id) DO NOTHING`,
|
|
9681
|
-
args: [2]
|
|
9682
|
-
});
|
|
9683
|
-
});
|
|
9684
|
-
}
|
|
9685
|
-
/**
|
|
9686
|
-
* True when the only Knowledge objects are the tables and indexes published v1 adapters created,
|
|
9687
|
-
* with or without rows. Anything else (v2 tables, unknown tables, views, triggers, extra indexes)
|
|
9688
|
-
* needs an explicit reset.
|
|
9689
|
-
*/
|
|
9690
|
-
async #isPublishedV1Layout(executor) {
|
|
9691
|
-
const schema = this.#schemaName ?? null;
|
|
9692
|
-
const relations = await executor.execute({
|
|
9693
|
-
sql: `SELECT table_name, table_type FROM information_schema.tables WHERE table_schema = COALESCE(?, current_schema()) AND table_name LIKE 'mastra\\_knowledge\\_%'`,
|
|
9694
|
-
args: [schema]
|
|
9695
|
-
});
|
|
9696
|
-
const tables = [];
|
|
9697
|
-
for (const row of relations.rows) {
|
|
9698
|
-
const name = String(row.table_name);
|
|
9699
|
-
if (row.table_type !== "BASE TABLE" || !PUBLISHED_KNOWLEDGE_V1_TABLE_NAMES.has(name)) return false;
|
|
9700
|
-
tables.push(name);
|
|
9701
|
-
}
|
|
9702
|
-
const indexes = await executor.execute({
|
|
9703
|
-
sql: `SELECT indexname FROM pg_indexes WHERE schemaname = COALESCE(?, current_schema()) AND tablename = ANY(?::text[])`,
|
|
9704
|
-
args: [schema, tables]
|
|
9705
|
-
});
|
|
9706
|
-
for (const row of indexes.rows) {
|
|
9707
|
-
const name = String(row.indexname);
|
|
9708
|
-
if (!PUBLISHED_KNOWLEDGE_V1_INDEX_NAMES.has(name) && !name.endsWith("_pkey")) return false;
|
|
9709
|
-
}
|
|
9710
|
-
if ((await executor.execute({
|
|
9711
|
-
sql: `SELECT 1 FROM information_schema.view_table_usage WHERE table_schema = COALESCE(?, current_schema()) AND table_name = ANY(?::text[]) UNION ALL SELECT 1 FROM information_schema.triggers WHERE event_object_schema = COALESCE(?, current_schema()) AND event_object_table = ANY(?::text[]) LIMIT 1`,
|
|
9712
|
-
args: [
|
|
9713
|
-
schema,
|
|
9714
|
-
tables,
|
|
9715
|
-
schema,
|
|
9716
|
-
tables
|
|
9717
|
-
]
|
|
9718
|
-
})).rows.length > 0) return false;
|
|
9719
|
-
return true;
|
|
10014
|
+
await initPgKnowledgePagesSchema(this.#client, this.#schemaName);
|
|
9720
10015
|
}
|
|
9721
|
-
async
|
|
9722
|
-
|
|
9723
|
-
for (const table of [
|
|
9724
|
-
TABLE_KNOWLEDGE_RECORD_SCOPES,
|
|
9725
|
-
TABLE_KNOWLEDGE_NODE_SCOPES,
|
|
9726
|
-
TABLE_KNOWLEDGE_SCOPE_GRANTS,
|
|
9727
|
-
TABLE_KNOWLEDGE_SCOPE_ADDRESSES,
|
|
9728
|
-
TABLE_KNOWLEDGE_NODE_ADDRESSES,
|
|
9729
|
-
_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS,
|
|
9730
|
-
TABLE_KNOWLEDGE_PROPOSALS,
|
|
9731
|
-
_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY,
|
|
9732
|
-
TABLE_KNOWLEDGE_IMPORT_STATE,
|
|
9733
|
-
TABLE_KNOWLEDGE_IMPORT_RUNS,
|
|
9734
|
-
_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX,
|
|
9735
|
-
_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS,
|
|
9736
|
-
_mastra_core_storage.TABLE_KNOWLEDGE_NODES
|
|
9737
|
-
]) await tx.execute(`DELETE FROM "${table}"`);
|
|
9738
|
-
await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch = 0 WHERE id = 'global'`);
|
|
9739
|
-
});
|
|
10016
|
+
async inspectSchema() {
|
|
10017
|
+
return inspectPgKnowledgePagesSchema(this.#client, this.#schemaName);
|
|
9740
10018
|
}
|
|
9741
10019
|
async dangerouslyReset() {
|
|
9742
|
-
|
|
9743
|
-
await this.#executor.execute(`DROP TABLE IF EXISTS ${tables}`);
|
|
9744
|
-
await this.init();
|
|
9745
|
-
}
|
|
9746
|
-
async reconcileStructure(plan) {
|
|
9747
|
-
return this.#transaction(async (tx) => {
|
|
9748
|
-
await tx.execute({
|
|
9749
|
-
sql: `SELECT pg_advisory_xact_lock(hashtext(?))`,
|
|
9750
|
-
args: [`mastra-knowledge-reconcile:${this.#schemaName}`]
|
|
9751
|
-
});
|
|
9752
|
-
const scopes = {};
|
|
9753
|
-
const createdScopeIds = [];
|
|
9754
|
-
const createdAddresses = /* @__PURE__ */ new Set();
|
|
9755
|
-
const retrofit = plan.retrofit ?? true;
|
|
9756
|
-
let structureChanged = false;
|
|
9757
|
-
const deletedScopeAddresses = /* @__PURE__ */ new Set();
|
|
9758
|
-
const resolveAddress = async (address) => {
|
|
9759
|
-
if (scopes[address]) return scopes[address];
|
|
9760
|
-
const row = (await tx.execute({
|
|
9761
|
-
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=?`,
|
|
9762
|
-
args: [address]
|
|
9763
|
-
})).rows[0];
|
|
9764
|
-
if (!row) return void 0;
|
|
9765
|
-
if (!row.isScope) throw new Error(`Knowledge address ${address} does not reference a scope`);
|
|
9766
|
-
if (row.deletedAt) deletedScopeAddresses.add(address);
|
|
9767
|
-
scopes[address] = String(row.scopeNodeId);
|
|
9768
|
-
return scopes[address];
|
|
9769
|
-
};
|
|
9770
|
-
for (const scope of plan.scopes) {
|
|
9771
|
-
if (await resolveAddress(scope.address)) continue;
|
|
9772
|
-
const id = (0, crypto$1.randomUUID)();
|
|
9773
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9774
|
-
await tx.execute({
|
|
9775
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" (id,type,name,"canonicalName",kind,description,"isScope",metadata,version,"createdAt","updatedAt") VALUES (?,'node',?,?,?,?,TRUE,?,1,?,?)`,
|
|
9776
|
-
args: [
|
|
9777
|
-
id,
|
|
9778
|
-
scope.name,
|
|
9779
|
-
canonicalName(scope.name),
|
|
9780
|
-
scope.kind ?? null,
|
|
9781
|
-
scope.description ?? null,
|
|
9782
|
-
scope.metadata ?? null,
|
|
9783
|
-
now,
|
|
9784
|
-
now
|
|
9785
|
-
]
|
|
9786
|
-
});
|
|
9787
|
-
await tx.execute({
|
|
9788
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" (address,"scopeNodeId") VALUES (?,?)`,
|
|
9789
|
-
args: [scope.address, id]
|
|
9790
|
-
});
|
|
9791
|
-
scopes[scope.address] = id;
|
|
9792
|
-
createdAddresses.add(scope.address);
|
|
9793
|
-
createdScopeIds.push(id);
|
|
9794
|
-
structureChanged = true;
|
|
9795
|
-
}
|
|
9796
|
-
for (const scope of plan.scopes) {
|
|
9797
|
-
if (!createdAddresses.has(scope.address) && (!retrofit || deletedScopeAddresses.has(scope.address))) continue;
|
|
9798
|
-
const scopeNodeId = scopes[scope.address];
|
|
9799
|
-
for (const parentAddress of scope.parentAddresses ?? []) {
|
|
9800
|
-
const parentId = await resolveAddress(parentAddress);
|
|
9801
|
-
if (!parentId || deletedScopeAddresses.has(parentAddress)) {
|
|
9802
|
-
const deletedParentId = scopes[parentAddress];
|
|
9803
|
-
if ((deletedParentId ? await tx.execute({
|
|
9804
|
-
sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
|
|
9805
|
-
args: [scopeNodeId, deletedParentId]
|
|
9806
|
-
}) : void 0)?.rows.length) continue;
|
|
9807
|
-
throw new Error(`Knowledge parent scope does not exist: ${parentAddress}`);
|
|
9808
|
-
}
|
|
9809
|
-
if ((await tx.execute({
|
|
9810
|
-
sql: `SELECT 1 FROM "mastra_knowledge_node_scopes" WHERE "nodeId"=? AND "scopeNodeId"=?`,
|
|
9811
|
-
args: [scopeNodeId, parentId]
|
|
9812
|
-
})).rows.length) continue;
|
|
9813
|
-
if ((await tx.execute({
|
|
9814
|
-
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`,
|
|
9815
|
-
args: [
|
|
9816
|
-
parentId,
|
|
9817
|
-
canonicalName(scope.name),
|
|
9818
|
-
scopeNodeId
|
|
9819
|
-
]
|
|
9820
|
-
})).rows.length) throw new Error(`Knowledge scope name ${scope.name} already exists under ${parentAddress}`);
|
|
9821
|
-
const inserted = await tx.execute({
|
|
9822
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
9823
|
-
args: [
|
|
9824
|
-
scopeNodeId,
|
|
9825
|
-
parentId,
|
|
9826
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
9827
|
-
]
|
|
9828
|
-
});
|
|
9829
|
-
structureChanged ||= inserted.rowsAffected > 0;
|
|
9830
|
-
}
|
|
9831
|
-
for (const grant of scope.grants ?? []) {
|
|
9832
|
-
const scopeRefId = await resolveAddress(grant.scopeRefAddress);
|
|
9833
|
-
if (!scopeRefId || deletedScopeAddresses.has(grant.scopeRefAddress)) {
|
|
9834
|
-
const deletedScopeRefId = scopes[grant.scopeRefAddress];
|
|
9835
|
-
if ((deletedScopeRefId ? await tx.execute({
|
|
9836
|
-
sql: `SELECT 1 FROM "mastra_knowledge_scope_grants" WHERE "scopeNodeId"=? AND "scopeRefId"=?`,
|
|
9837
|
-
args: [scopeNodeId, deletedScopeRefId]
|
|
9838
|
-
}) : void 0)?.rows.length) continue;
|
|
9839
|
-
throw new Error(`Knowledge grant scope does not exist: ${grant.scopeRefAddress}`);
|
|
9840
|
-
}
|
|
9841
|
-
const inserted = await tx.execute({
|
|
9842
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_SCOPE_GRANTS}" ("scopeNodeId","scopeRefId",role,"canSuggest") VALUES (?,?,?,?) ON CONFLICT DO NOTHING`,
|
|
9843
|
-
args: [
|
|
9844
|
-
scopeNodeId,
|
|
9845
|
-
scopeRefId,
|
|
9846
|
-
grant.role,
|
|
9847
|
-
grant.canSuggest ?? null
|
|
9848
|
-
]
|
|
9849
|
-
});
|
|
9850
|
-
structureChanged ||= inserted.rowsAffected > 0;
|
|
9851
|
-
}
|
|
9852
|
-
}
|
|
9853
|
-
if (structureChanged) await tx.execute(`UPDATE "${TABLE_KNOWLEDGE_ACCESS_STATE}" SET epoch=epoch+1 WHERE id='global'`);
|
|
9854
|
-
const state = await tx.execute(`SELECT epoch FROM "${TABLE_KNOWLEDGE_ACCESS_STATE}" WHERE id='global'`);
|
|
9855
|
-
return {
|
|
9856
|
-
scopes,
|
|
9857
|
-
createdScopeIds,
|
|
9858
|
-
deletedScopeAddresses: [...deletedScopeAddresses],
|
|
9859
|
-
changed: structureChanged,
|
|
9860
|
-
accessEpoch: Number(state.rows[0]?.epoch ?? 0)
|
|
9861
|
-
};
|
|
9862
|
-
});
|
|
9863
|
-
}
|
|
9864
|
-
async listScopeNodes(input = {}) {
|
|
9865
|
-
const { limit, after } = parseListKnowledgeScopeNodesInput(input);
|
|
9866
|
-
if (input.addresses?.length === 0 || input.ids?.length === 0) return {
|
|
9867
|
-
scopes: [],
|
|
9868
|
-
nextCursor: null
|
|
9869
|
-
};
|
|
9870
|
-
const args = [];
|
|
9871
|
-
const where = [`n."isScope"`, `n."deletedAt" IS NULL`];
|
|
9872
|
-
let within = "";
|
|
9873
|
-
if (input.withinAddress !== void 0) {
|
|
9874
|
-
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) `;
|
|
9875
|
-
args.push(input.withinAddress);
|
|
9876
|
-
where.push("n.id IN (SELECT id FROM within_scope)");
|
|
9877
|
-
}
|
|
9878
|
-
if (input.addresses) {
|
|
9879
|
-
where.push(`a.address IN (${input.addresses.map(() => "?").join(",")})`);
|
|
9880
|
-
args.push(...input.addresses);
|
|
9881
|
-
}
|
|
9882
|
-
if (input.ids) {
|
|
9883
|
-
where.push(`n.id IN (${input.ids.map(() => "?").join(",")})`);
|
|
9884
|
-
args.push(...input.ids);
|
|
9885
|
-
}
|
|
9886
|
-
if (after) {
|
|
9887
|
-
where.push("(n.name COLLATE \"C\" > ? OR (n.name = ? AND n.id > ?))");
|
|
9888
|
-
args.push(after.name, after.name, after.id);
|
|
9889
|
-
}
|
|
9890
|
-
args.push(limit + 1);
|
|
9891
|
-
const rows = (await this.#readExecutor.execute({
|
|
9892
|
-
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 COLLATE "C", n.id LIMIT ?`,
|
|
9893
|
-
args
|
|
9894
|
-
})).rows;
|
|
9895
|
-
if (rows.length === 0) return {
|
|
9896
|
-
scopes: [],
|
|
9897
|
-
nextCursor: null
|
|
9898
|
-
};
|
|
9899
|
-
const parents = await this.#readExecutor.execute({
|
|
9900
|
-
sql: `SELECT ns."nodeId",ns."scopeNodeId" FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" ns WHERE ns."nodeId" IN (${rows.map(() => "?").join(",")}) ORDER BY ns."scopeNodeId" COLLATE "C"`,
|
|
9901
|
-
args: rows.map((row) => String(row.id))
|
|
9902
|
-
});
|
|
9903
|
-
const parentsByScopeId = /* @__PURE__ */ new Map();
|
|
9904
|
-
for (const row of parents.rows) {
|
|
9905
|
-
const scopeId = String(row.nodeId);
|
|
9906
|
-
const parentId = String(row.scopeNodeId);
|
|
9907
|
-
const existing = parentsByScopeId.get(scopeId);
|
|
9908
|
-
if (existing) existing.push(parentId);
|
|
9909
|
-
else parentsByScopeId.set(scopeId, [parentId]);
|
|
9910
|
-
}
|
|
9911
|
-
return pageKnowledgeScopeNodes(rows.map((row) => ({
|
|
9912
|
-
id: String(row.id),
|
|
9913
|
-
address: String(row.address),
|
|
9914
|
-
name: String(row.name),
|
|
9915
|
-
...row.kind == null ? {} : { kind: String(row.kind) },
|
|
9916
|
-
...row.description == null ? {} : { description: String(row.description) },
|
|
9917
|
-
parentIds: parentsByScopeId.get(String(row.id)) ?? []
|
|
9918
|
-
})), limit, input);
|
|
9919
|
-
}
|
|
9920
|
-
async listScopeMembers(input) {
|
|
9921
|
-
const { limit, after } = parseListKnowledgeScopeMembersInput(input);
|
|
9922
|
-
const args = [input.scopeNodeId];
|
|
9923
|
-
let keyset = "";
|
|
9924
|
-
if (after) {
|
|
9925
|
-
const updatedAt = postgresTimestamp(after.updatedAt);
|
|
9926
|
-
keyset = ` AND (n."updatedAt" < ? OR (n."updatedAt" = ? AND (n.name > ? OR (n.name = ? AND n.id > ?))))`;
|
|
9927
|
-
args.push(updatedAt, updatedAt, after.name, after.name, after.id);
|
|
9928
|
-
}
|
|
9929
|
-
args.push(limit + 1);
|
|
9930
|
-
return pageKnowledgeScopeMembers((await this.#readExecutor.execute({
|
|
9931
|
-
sql: `SELECT *, 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 ?`,
|
|
9932
|
-
args
|
|
9933
|
-
})).rows.map(parseNode), limit, input);
|
|
9934
|
-
}
|
|
9935
|
-
async createNode(input) {
|
|
9936
|
-
await assertKnowledgeDescriptionWithinBoundCompat(input.description);
|
|
9937
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
|
|
9938
|
-
return this.#transaction(async (tx) => {
|
|
9939
|
-
const existing = await this.#getNodeByName(tx, input.name, scope);
|
|
9940
|
-
if (existing) {
|
|
9941
|
-
const terminal = await this.#resolveTerminalNode(tx, existing.id);
|
|
9942
|
-
if (!(0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) throw new Error(`Merged knowledge node is not visible from scope: ${input.name}`);
|
|
9943
|
-
await this.#placeNodeInScopes(tx, terminal.id, input.scopeAddresses, /* @__PURE__ */ new Date());
|
|
9944
|
-
return terminal;
|
|
9945
|
-
}
|
|
9946
|
-
const now = /* @__PURE__ */ new Date();
|
|
9947
|
-
const node = {
|
|
9948
|
-
id: input.id ?? crypto.randomUUID(),
|
|
9949
|
-
type: "node",
|
|
9950
|
-
name: input.name.trim(),
|
|
9951
|
-
kind: input.kind,
|
|
9952
|
-
content: input.content,
|
|
9953
|
-
description: input.description,
|
|
9954
|
-
scope,
|
|
9955
|
-
version: 1,
|
|
9956
|
-
createdAt: now,
|
|
9957
|
-
updatedAt: now
|
|
9958
|
-
};
|
|
9959
|
-
await tx.execute({
|
|
9960
|
-
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,?,?)`,
|
|
9961
|
-
args: [
|
|
9962
|
-
node.id,
|
|
9963
|
-
"node",
|
|
9964
|
-
node.name,
|
|
9965
|
-
canonicalName(node.name),
|
|
9966
|
-
node.kind,
|
|
9967
|
-
node.content ?? null,
|
|
9968
|
-
node.description ?? null,
|
|
9969
|
-
JSON.stringify(scope),
|
|
9970
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
9971
|
-
node.version,
|
|
9972
|
-
now.toISOString(),
|
|
9973
|
-
now.toISOString()
|
|
9974
|
-
]
|
|
9975
|
-
});
|
|
9976
|
-
await this.#replaceNodeScopes(tx, node.id, scope, now);
|
|
9977
|
-
await this.#placeNodeInScopes(tx, node.id, input.scopeAddresses, now);
|
|
9978
|
-
await this.#replaceMentions(tx, "node", node.id, node.content ?? "", input.resolutionScope ?? scope, scope);
|
|
9979
|
-
await this.#activity(tx, "node-created", "node", node.id, scope);
|
|
9980
|
-
await this.#outbox(tx, "node", node.id, "upsert", node.version, scope);
|
|
9981
|
-
return node;
|
|
9982
|
-
});
|
|
9983
|
-
}
|
|
9984
|
-
async getNode(id) {
|
|
9985
|
-
return this.#getNode(this.#readExecutor, id);
|
|
9986
|
-
}
|
|
9987
|
-
async getNodeByName(input) {
|
|
9988
|
-
return this.#getNodeByName(this.#readExecutor, input.name, (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
|
|
9989
|
-
}
|
|
9990
|
-
async resolveNode(input) {
|
|
9991
|
-
return this.#resolveNode(this.#readExecutor, input.name, (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
|
|
9992
|
-
}
|
|
9993
|
-
async listNodes(input) {
|
|
9994
|
-
const keys = visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
|
|
9995
|
-
const clauses = [
|
|
9996
|
-
`type = 'node'`,
|
|
9997
|
-
"mergedInto IS NULL",
|
|
9998
|
-
visibleSql(keys)
|
|
9999
|
-
];
|
|
10000
|
-
const args = [...keys];
|
|
10001
|
-
if (input.namePrefix) {
|
|
10002
|
-
clauses.push("canonicalName LIKE ? ESCAPE '='");
|
|
10003
|
-
args.push(`${escapeLikePattern(canonicalName(input.namePrefix))}%`);
|
|
10004
|
-
}
|
|
10005
|
-
if (input.kind) {
|
|
10006
|
-
clauses.push("kind = ?");
|
|
10007
|
-
args.push(input.kind);
|
|
10008
|
-
}
|
|
10009
|
-
if (input.hasContent !== void 0) clauses.push(input.hasContent ? "content IS NOT NULL AND content <> ''" : "(content IS NULL OR content = '')");
|
|
10010
|
-
if (input.cursor) {
|
|
10011
|
-
const cursor = (0, _mastra_core_storage.parseKnowledgeNodeCursor)(input.cursor, {
|
|
10012
|
-
namePrefix: input.namePrefix,
|
|
10013
|
-
kind: input.kind,
|
|
10014
|
-
hasContent: input.hasContent
|
|
10015
|
-
});
|
|
10016
|
-
const updatedAt = postgresTimestamp(cursor.updatedAt);
|
|
10017
|
-
clauses.push("(updatedAt < ? OR (updatedAt = ? AND (name > ? OR (name = ? AND id > ?))))");
|
|
10018
|
-
args.push(updatedAt, updatedAt, cursor.name, cursor.name, cursor.id);
|
|
10019
|
-
}
|
|
10020
|
-
args.push(input.limit ?? 100);
|
|
10021
|
-
return (await this.#readExecutor.execute({
|
|
10022
|
-
sql: `SELECT *, scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE ${clauses.join(" AND ")} ORDER BY updatedAt DESC, name ASC, id ASC LIMIT ?`,
|
|
10023
|
-
args
|
|
10024
|
-
})).rows.map(parseNode);
|
|
10025
|
-
}
|
|
10026
|
-
async updateNode(input) {
|
|
10027
|
-
await assertKnowledgeDescriptionWithinBoundCompat(input.description);
|
|
10028
|
-
return this.#transaction(async (tx) => {
|
|
10029
|
-
const existing = await this.#getNode(tx, input.id);
|
|
10030
|
-
if (!existing) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.id);
|
|
10031
|
-
if (existing.mergedInto) throw new Error(`Cannot update merged knowledge node: ${input.id}`);
|
|
10032
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope ?? existing.scope);
|
|
10033
|
-
const name = (input.name ?? existing.name).trim();
|
|
10034
|
-
const content = input.content ?? existing.content;
|
|
10035
|
-
const description = input.description ?? existing.description;
|
|
10036
|
-
const now = /* @__PURE__ */ new Date();
|
|
10037
|
-
if ((await tx.execute({
|
|
10038
|
-
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=?`,
|
|
10039
|
-
args: [
|
|
10040
|
-
name,
|
|
10041
|
-
canonicalName(name),
|
|
10042
|
-
input.kind ?? existing.kind,
|
|
10043
|
-
content ?? null,
|
|
10044
|
-
description ?? null,
|
|
10045
|
-
JSON.stringify(scope),
|
|
10046
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
10047
|
-
now.toISOString(),
|
|
10048
|
-
input.id,
|
|
10049
|
-
input.version
|
|
10050
|
-
]
|
|
10051
|
-
})).rowsAffected === 0) throw new _mastra_core_storage.KnowledgeConflictError(input.id);
|
|
10052
|
-
if ((0, _mastra_core_storage.knowledgeScopeKey)(existing.scope) !== (0, _mastra_core_storage.knowledgeScopeKey)(scope)) await this.#swapIdentityScopes(tx, input.id, existing.scope, scope, now);
|
|
10053
|
-
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);
|
|
10054
|
-
await this.#activity(tx, "node-updated", "node", input.id, scope);
|
|
10055
|
-
if ((0, _mastra_core_storage.knowledgeScopeKey)(existing.scope) !== (0, _mastra_core_storage.knowledgeScopeKey)(scope)) {
|
|
10056
|
-
await this.#outbox(tx, "node", input.id, "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), existing.scope);
|
|
10057
|
-
const records = await tx.execute({
|
|
10058
|
-
sql: `SELECT id,scope AS "scopeJson",deletedAt FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
|
|
10059
|
-
args: [input.id]
|
|
10060
|
-
});
|
|
10061
|
-
for (const row of records.rows) {
|
|
10062
|
-
const factScope = parseJson$1(row.scopeJson);
|
|
10063
|
-
await this.#outbox(tx, "record", String(row.id), "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), factScope);
|
|
10064
|
-
if (row.deletedAt == null) await this.#outbox(tx, "record", String(row.id), "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), factScope);
|
|
10065
|
-
}
|
|
10066
|
-
}
|
|
10067
|
-
await this.#outbox(tx, "node", input.id, "upsert", input.version + 1, scope);
|
|
10068
|
-
return {
|
|
10069
|
-
...existing,
|
|
10070
|
-
name,
|
|
10071
|
-
kind: input.kind ?? existing.kind,
|
|
10072
|
-
content,
|
|
10073
|
-
description,
|
|
10074
|
-
scope,
|
|
10075
|
-
version: input.version + 1,
|
|
10076
|
-
updatedAt: now
|
|
10077
|
-
};
|
|
10078
|
-
});
|
|
10020
|
+
await resetPgKnowledgePagesSchema(this.#client, this.#schemaName);
|
|
10079
10021
|
}
|
|
10080
|
-
async
|
|
10081
|
-
|
|
10082
|
-
return this.#transaction(async (tx) => {
|
|
10083
|
-
const source = await this.#getNode(tx, input.sourceId);
|
|
10084
|
-
if (!source) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.sourceId);
|
|
10085
|
-
const target = await this.#resolveTerminalNode(tx, input.targetId);
|
|
10086
|
-
if (!target) throw new _mastra_core_storage.KnowledgeNotFoundError("node", input.targetId);
|
|
10087
|
-
if (target.id === source.id) throw new Error("Cannot create a knowledge merge cycle");
|
|
10088
|
-
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");
|
|
10089
|
-
const affected = await tx.execute({
|
|
10090
|
-
sql: `SELECT DISTINCT m.sourceType,m.sourceId,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=?`,
|
|
10091
|
-
args: [source.id]
|
|
10092
|
-
});
|
|
10093
|
-
const movedFacts = await tx.execute({
|
|
10094
|
-
sql: `SELECT id,scope AS "scopeJson",deletedAt FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE node=?`,
|
|
10095
|
-
args: [source.id]
|
|
10096
|
-
});
|
|
10097
|
-
if ((await tx.execute({
|
|
10098
|
-
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`,
|
|
10099
|
-
args: [
|
|
10100
|
-
target.id,
|
|
10101
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
10102
|
-
source.id,
|
|
10103
|
-
input.sourceVersion
|
|
10104
|
-
]
|
|
10105
|
-
})).rowsAffected === 0) throw new _mastra_core_storage.KnowledgeConflictError(source.id);
|
|
10106
|
-
await tx.execute({
|
|
10107
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET node=?,version=version+1,updatedAt=? WHERE node=?`,
|
|
10108
|
-
args: [
|
|
10109
|
-
target.id,
|
|
10110
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
10111
|
-
source.id
|
|
10112
|
-
]
|
|
10113
|
-
});
|
|
10114
|
-
await tx.execute({
|
|
10115
|
-
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=?)`,
|
|
10116
|
-
args: [source.id, target.id]
|
|
10117
|
-
});
|
|
10118
|
-
await tx.execute({
|
|
10119
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" SET recordId=? WHERE recordId=?`,
|
|
10120
|
-
args: [target.id, source.id]
|
|
10121
|
-
});
|
|
10122
|
-
for (const row of movedFacts.rows) await this.#outbox(tx, "record", String(row.id), row.deletedAt == null ? "upsert" : "delete", (0, _mastra_core_storage.createKnowledgeUlid)(), parseJson$1(row.scopeJson));
|
|
10123
|
-
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$1(row.scopeJson));
|
|
10124
|
-
let mergedTarget = target;
|
|
10125
|
-
if (target.description === void 0 && source.description) {
|
|
10126
|
-
const adoptedAt = /* @__PURE__ */ new Date();
|
|
10127
|
-
if ((await tx.execute({
|
|
10128
|
-
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`,
|
|
10129
|
-
args: [
|
|
10130
|
-
source.description,
|
|
10131
|
-
adoptedAt.toISOString(),
|
|
10132
|
-
target.id,
|
|
10133
|
-
target.version
|
|
10134
|
-
]
|
|
10135
|
-
})).rowsAffected > 0) {
|
|
10136
|
-
mergedTarget = {
|
|
10137
|
-
...target,
|
|
10138
|
-
description: source.description,
|
|
10139
|
-
version: target.version + 1,
|
|
10140
|
-
updatedAt: adoptedAt
|
|
10141
|
-
};
|
|
10142
|
-
await this.#activity(tx, "node-updated", "node", target.id, target.scope);
|
|
10143
|
-
}
|
|
10144
|
-
}
|
|
10145
|
-
await this.#activity(tx, "node-merged", "node", source.id, source.scope);
|
|
10146
|
-
await this.#outbox(tx, "node", source.id, "delete", input.sourceVersion + 1, source.scope);
|
|
10147
|
-
await this.#outbox(tx, "node", target.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), mergedTarget.scope);
|
|
10148
|
-
return mergedTarget;
|
|
10149
|
-
});
|
|
10150
|
-
}
|
|
10151
|
-
async appendKnowledge(input) {
|
|
10152
|
-
await assertKnowledgeRecordTextWithinBoundCompat(input.text);
|
|
10153
|
-
const scope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope);
|
|
10154
|
-
const resolutionScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.resolutionScope);
|
|
10155
|
-
const defaultScope = (0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.defaultScope);
|
|
10156
|
-
return this.#transaction(async (tx) => {
|
|
10157
|
-
const parent = await this.#resolveTerminalNode(tx, nodeReferenceId(input.node));
|
|
10158
|
-
if (!parent) throw new _mastra_core_storage.KnowledgeNotFoundError("node", nodeReferenceId(input.node));
|
|
10159
|
-
const metadataJson = input.metadata ? toPgJson(input.metadata) : null;
|
|
10160
|
-
const record = {
|
|
10161
|
-
id: input.id ?? (0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
10162
|
-
node: parent.id,
|
|
10163
|
-
text: input.text,
|
|
10164
|
-
scope,
|
|
10165
|
-
sourceThreadId: input.sourceThreadId,
|
|
10166
|
-
capturedAt: /* @__PURE__ */ new Date(),
|
|
10167
|
-
when: input.when ? new Date(input.when) : void 0,
|
|
10168
|
-
metadata: input.metadata
|
|
10169
|
-
};
|
|
10170
|
-
await tx.execute({
|
|
10171
|
-
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)`,
|
|
10172
|
-
args: [
|
|
10173
|
-
record.id,
|
|
10174
|
-
record.node,
|
|
10175
|
-
record.text,
|
|
10176
|
-
JSON.stringify(scope),
|
|
10177
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
10178
|
-
record.sourceThreadId,
|
|
10179
|
-
record.capturedAt.toISOString(),
|
|
10180
|
-
record.when?.toISOString() ?? null,
|
|
10181
|
-
metadataJson,
|
|
10182
|
-
1,
|
|
10183
|
-
record.capturedAt.toISOString()
|
|
10184
|
-
]
|
|
10185
|
-
});
|
|
10186
|
-
await this.#replaceRecordScopes(tx, record.id, scope, record.capturedAt);
|
|
10187
|
-
await this.#replaceMentions(tx, "record", record.id, record.text, resolutionScope, defaultScope);
|
|
10188
|
-
await this.#activity(tx, "record-created", "record", record.id, scope, record.sourceThreadId);
|
|
10189
|
-
await this.#outbox(tx, "record", record.id, "upsert", record.id, scope);
|
|
10190
|
-
return metadataJson ? {
|
|
10191
|
-
...record,
|
|
10192
|
-
metadata: JSON.parse(metadataJson)
|
|
10193
|
-
} : record;
|
|
10194
|
-
});
|
|
10022
|
+
async dangerouslyClearAll() {
|
|
10023
|
+
await this.#pages.clearAll();
|
|
10195
10024
|
}
|
|
10196
|
-
|
|
10197
|
-
|
|
10198
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${input.includeDeleted ? "" : " AND deletedAt IS NULL"}`,
|
|
10199
|
-
args: [input.id]
|
|
10200
|
-
});
|
|
10201
|
-
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
10025
|
+
materializeScope(input) {
|
|
10026
|
+
return this.#pages.materializeScope(input);
|
|
10202
10027
|
}
|
|
10203
|
-
|
|
10204
|
-
return this.#
|
|
10028
|
+
getScopesByAddress(input) {
|
|
10029
|
+
return this.#pages.getScopesByAddress(input);
|
|
10205
10030
|
}
|
|
10206
|
-
|
|
10207
|
-
return this.#
|
|
10031
|
+
listScopeDeclarations() {
|
|
10032
|
+
return this.#pages.listScopeDeclarations();
|
|
10208
10033
|
}
|
|
10209
|
-
|
|
10210
|
-
return this.#
|
|
10034
|
+
renameScopeDeclaration(input) {
|
|
10035
|
+
return this.#pages.renameScopeDeclaration(input);
|
|
10211
10036
|
}
|
|
10212
|
-
|
|
10213
|
-
|
|
10214
|
-
const args = [input.sourceThreadId, ...keys];
|
|
10215
|
-
if (input.after) args.push(input.after);
|
|
10216
|
-
const limit = input.limit ?? 100;
|
|
10217
|
-
args.push(limit + 1);
|
|
10218
|
-
const records = (await this.#readExecutor.execute({
|
|
10219
|
-
sql: `SELECT *,scope AS "scopeJson" 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 ?`,
|
|
10220
|
-
args
|
|
10221
|
-
})).rows.map(parseKnowledge);
|
|
10222
|
-
return {
|
|
10223
|
-
records: records.slice(0, limit),
|
|
10224
|
-
nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
|
|
10225
|
-
};
|
|
10037
|
+
listChildScopes(input) {
|
|
10038
|
+
return this.#pages.listChildScopes(input);
|
|
10226
10039
|
}
|
|
10227
|
-
|
|
10228
|
-
return this.#
|
|
10229
|
-
const record = await this.#getKnowledge(tx, input.id, true);
|
|
10230
|
-
if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
|
|
10231
|
-
if (record.deletedAt) return record;
|
|
10232
|
-
const deletedAt = /* @__PURE__ */ new Date();
|
|
10233
|
-
await tx.execute({
|
|
10234
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=?,deletedBy=?,version=version+1,updatedAt=? WHERE id=? AND deletedAt IS NULL`,
|
|
10235
|
-
args: [
|
|
10236
|
-
deletedAt.toISOString(),
|
|
10237
|
-
input.deletedBy,
|
|
10238
|
-
deletedAt.toISOString(),
|
|
10239
|
-
input.id
|
|
10240
|
-
]
|
|
10241
|
-
});
|
|
10242
|
-
await this.#activity(tx, "record-deleted", "record", input.id, record.scope, record.sourceThreadId);
|
|
10243
|
-
await this.#outbox(tx, "record", input.id, "delete", deletedAt.toISOString(), record.scope);
|
|
10244
|
-
return {
|
|
10245
|
-
...record,
|
|
10246
|
-
deletedAt,
|
|
10247
|
-
deletedBy: input.deletedBy
|
|
10248
|
-
};
|
|
10249
|
-
});
|
|
10040
|
+
getPages(input) {
|
|
10041
|
+
return this.#pages.getPages(input);
|
|
10250
10042
|
}
|
|
10251
|
-
|
|
10252
|
-
return this.#
|
|
10253
|
-
const record = await this.#getKnowledge(tx, input.id, true);
|
|
10254
|
-
if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
|
|
10255
|
-
if (!record.deletedAt) return record;
|
|
10256
|
-
await tx.execute({
|
|
10257
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET deletedAt=NULL,deletedBy=NULL,version=version+1,updatedAt=? WHERE id=?`,
|
|
10258
|
-
args: [(/* @__PURE__ */ new Date()).toISOString(), input.id]
|
|
10259
|
-
});
|
|
10260
|
-
await this.#activity(tx, "record-restored", "record", input.id, record.scope, record.sourceThreadId);
|
|
10261
|
-
await this.#outbox(tx, "record", input.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), record.scope);
|
|
10262
|
-
return {
|
|
10263
|
-
...record,
|
|
10264
|
-
deletedAt: void 0,
|
|
10265
|
-
deletedBy: void 0
|
|
10266
|
-
};
|
|
10267
|
-
});
|
|
10043
|
+
getPage(input) {
|
|
10044
|
+
return this.#pages.getPage(input);
|
|
10268
10045
|
}
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
return this.#transaction(async (tx) => {
|
|
10272
|
-
const record = await this.#getKnowledge(tx, input.id, true);
|
|
10273
|
-
if (!record) throw new _mastra_core_storage.KnowledgeNotFoundError("record", input.id);
|
|
10274
|
-
const updatedAt = /* @__PURE__ */ new Date();
|
|
10275
|
-
await tx.execute({
|
|
10276
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" SET scope=jsonb(?),scopeKey=?,version=version+1,updatedAt=? WHERE id=?`,
|
|
10277
|
-
args: [
|
|
10278
|
-
JSON.stringify(scope),
|
|
10279
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
10280
|
-
updatedAt.toISOString(),
|
|
10281
|
-
input.id
|
|
10282
|
-
]
|
|
10283
|
-
});
|
|
10284
|
-
await this.#replaceRecordScopes(tx, input.id, scope, updatedAt);
|
|
10285
|
-
await this.#activity(tx, "record-rescoped", "record", input.id, scope, record.sourceThreadId);
|
|
10286
|
-
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);
|
|
10287
|
-
if (!record.deletedAt) await this.#outbox(tx, "record", input.id, "upsert", (0, _mastra_core_storage.createKnowledgeUlid)(), scope);
|
|
10288
|
-
return {
|
|
10289
|
-
...record,
|
|
10290
|
-
scope
|
|
10291
|
-
};
|
|
10292
|
-
});
|
|
10046
|
+
getPageByPath(input) {
|
|
10047
|
+
return this.#pages.getPageByPath(input);
|
|
10293
10048
|
}
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
const keys = visibleScopeKeys(scope);
|
|
10297
|
-
const normalizedQuery = input.query.trim().toLocaleLowerCase();
|
|
10298
|
-
if (!normalizedQuery) return [];
|
|
10299
|
-
const query = `%${escapeLikePattern(normalizedQuery)}%`;
|
|
10300
|
-
const results = (await this.#readExecutor.execute({
|
|
10301
|
-
sql: `SELECT *,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 ?`,
|
|
10302
|
-
args: [
|
|
10303
|
-
...keys,
|
|
10304
|
-
query,
|
|
10305
|
-
query,
|
|
10306
|
-
query,
|
|
10307
|
-
query,
|
|
10308
|
-
input.limit ?? 20
|
|
10309
|
-
]
|
|
10310
|
-
})).rows.map((row) => ({
|
|
10311
|
-
type: String(row.type),
|
|
10312
|
-
id: String(row.id),
|
|
10313
|
-
recordId: String(row.id),
|
|
10314
|
-
name: String(row.name),
|
|
10315
|
-
text: [
|
|
10316
|
-
String(row.name),
|
|
10317
|
-
...row.description ? [String(row.description)] : [],
|
|
10318
|
-
...row.content ? [String(row.content)] : []
|
|
10319
|
-
].join("\n"),
|
|
10320
|
-
scope: parseJson$1(row.scopeJson)
|
|
10321
|
-
}));
|
|
10322
|
-
if (results.length < (input.limit ?? 20)) {
|
|
10323
|
-
const records = await this.#readExecutor.execute({
|
|
10324
|
-
sql: `SELECT f.*,f.scope AS "scopeJson",r.name,r.scope AS "parentScopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" f JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" r ON r.id=f.node AND r.type='node' AND r.mergedInto IS NULL WHERE f.deletedAt IS NULL AND ${visibleSql(keys, "f.scopeKey")} AND lower(f.text) LIKE ? ESCAPE '=' ORDER BY f.id DESC LIMIT ?`,
|
|
10325
|
-
args: [
|
|
10326
|
-
...keys,
|
|
10327
|
-
query,
|
|
10328
|
-
(input.limit ?? 20) - results.length
|
|
10329
|
-
]
|
|
10330
|
-
});
|
|
10331
|
-
results.push(...records.rows.map((row) => {
|
|
10332
|
-
const parentVisible = (0, _mastra_core_storage.isKnowledgeScopeVisible)(parseJson$1(row.parentScopeJson), scope);
|
|
10333
|
-
return {
|
|
10334
|
-
type: "record",
|
|
10335
|
-
id: String(row.id),
|
|
10336
|
-
recordId: parentVisible ? String(row.node) : String(row.id),
|
|
10337
|
-
name: parentVisible ? String(row.name) : "(private node)",
|
|
10338
|
-
text: String(row.text),
|
|
10339
|
-
scope: parseJson$1(row.scopeJson)
|
|
10340
|
-
};
|
|
10341
|
-
}));
|
|
10342
|
-
}
|
|
10343
|
-
return results;
|
|
10049
|
+
findPagesByName(input) {
|
|
10050
|
+
return this.#pages.findPagesByName(input);
|
|
10344
10051
|
}
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
* cursor. Always throws.
|
|
10348
|
-
*/
|
|
10349
|
-
async getCurationCursor(_input) {
|
|
10350
|
-
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
10052
|
+
listChildPages(input) {
|
|
10053
|
+
return this.#pages.listChildPages(input);
|
|
10351
10054
|
}
|
|
10352
|
-
|
|
10353
|
-
|
|
10354
|
-
* cursor. Always throws.
|
|
10355
|
-
*/
|
|
10356
|
-
async advanceCurationCursor(_input) {
|
|
10357
|
-
throw new Error(KNOWLEDGE_CURATION_CURSOR_REMOVED_MESSAGE);
|
|
10358
|
-
}
|
|
10359
|
-
async listActivity(input) {
|
|
10360
|
-
const keys = visibleScopeKeys((0, _mastra_core_storage.canonicalizeKnowledgeScope)(input.scope));
|
|
10361
|
-
return (await this.#readExecutor.execute({
|
|
10362
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" WHERE ${visibleSql(keys)}${input.after ? " AND id < ?" : ""} ORDER BY id DESC LIMIT ?`,
|
|
10363
|
-
args: [
|
|
10364
|
-
...keys,
|
|
10365
|
-
...input.after ? [input.after] : [],
|
|
10366
|
-
input.limit ?? 100
|
|
10367
|
-
]
|
|
10368
|
-
})).rows.map((row) => ({
|
|
10369
|
-
id: String(row.id),
|
|
10370
|
-
action: String(row.action),
|
|
10371
|
-
recordType: String(row.recordType),
|
|
10372
|
-
recordId: String(row.recordId),
|
|
10373
|
-
scope: parseJson$1(row.scopeJson),
|
|
10374
|
-
sourceThreadId: row.sourceThreadId == null ? void 0 : String(row.sourceThreadId),
|
|
10375
|
-
createdAt: toDate$1(row.createdAt)
|
|
10376
|
-
}));
|
|
10055
|
+
listPages(input) {
|
|
10056
|
+
return this.#pages.listPages(input);
|
|
10377
10057
|
}
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
const args = [];
|
|
10381
|
-
if (input.status) {
|
|
10382
|
-
clauses.push("status=?");
|
|
10383
|
-
args.push(input.status);
|
|
10384
|
-
}
|
|
10385
|
-
if (input.scope) {
|
|
10386
|
-
const keys = visibleScopeKeys(input.scope);
|
|
10387
|
-
clauses.push(visibleSql(keys));
|
|
10388
|
-
args.push(...keys);
|
|
10389
|
-
}
|
|
10390
|
-
args.push(input.limit ?? 100);
|
|
10391
|
-
return (await this.#executor.execute({
|
|
10392
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}"${clauses.length ? ` WHERE ${clauses.join(" AND ")}` : ""} ORDER BY createdAt ASC,id ASC LIMIT ?`,
|
|
10393
|
-
args
|
|
10394
|
-
})).rows.map(parseOutbox);
|
|
10395
|
-
}
|
|
10396
|
-
async claimSemanticOutbox(input) {
|
|
10397
|
-
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
10398
|
-
const stale = new Date(now.getTime() - (input.claimTimeoutMs ?? 6e4));
|
|
10399
|
-
return this.#transaction(async (tx) => {
|
|
10400
|
-
const clauses = [
|
|
10401
|
-
`availableAt <= ?`,
|
|
10402
|
-
`(status='pending' OR (status='processing' AND claimedAt <= ?))`,
|
|
10403
|
-
`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)))`
|
|
10404
|
-
];
|
|
10405
|
-
const args = [now.toISOString(), stale.toISOString()];
|
|
10406
|
-
if (input.scope) {
|
|
10407
|
-
const keys = visibleScopeKeys(input.scope);
|
|
10408
|
-
clauses.push(visibleSql(keys));
|
|
10409
|
-
args.push(...keys);
|
|
10410
|
-
}
|
|
10411
|
-
args.push(input.limit ?? 100);
|
|
10412
|
-
const ids = (await tx.execute({
|
|
10413
|
-
sql: `SELECT id FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE ${clauses.join(" AND ")} ORDER BY createdAt ASC,id ASC LIMIT ? FOR UPDATE SKIP LOCKED`,
|
|
10414
|
-
args
|
|
10415
|
-
})).rows.map((row) => String(row.id));
|
|
10416
|
-
for (const id of ids) await tx.execute({
|
|
10417
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='processing',attempts=attempts+1,claimedAt=?,claimedBy=? WHERE id=?`,
|
|
10418
|
-
args: [
|
|
10419
|
-
now.toISOString(),
|
|
10420
|
-
input.workerId,
|
|
10421
|
-
id
|
|
10422
|
-
]
|
|
10423
|
-
});
|
|
10424
|
-
if (!ids.length) return [];
|
|
10425
|
-
return (await tx.execute({
|
|
10426
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" WHERE id IN (${ids.map(() => "?").join(",")}) ORDER BY createdAt ASC,id ASC`,
|
|
10427
|
-
args: ids
|
|
10428
|
-
})).rows.map(parseOutbox);
|
|
10429
|
-
});
|
|
10058
|
+
createPage(input) {
|
|
10059
|
+
return this.#pages.createPage(input);
|
|
10430
10060
|
}
|
|
10431
|
-
|
|
10432
|
-
|
|
10433
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10434
|
-
await this.#transaction(async (tx) => {
|
|
10435
|
-
for (const id of input.ids) await tx.execute({
|
|
10436
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='completed',completedAt=? WHERE id=? AND status='processing' AND claimedBy=?`,
|
|
10437
|
-
args: [
|
|
10438
|
-
now,
|
|
10439
|
-
id,
|
|
10440
|
-
input.workerId
|
|
10441
|
-
]
|
|
10442
|
-
});
|
|
10443
|
-
});
|
|
10061
|
+
updatePage(input) {
|
|
10062
|
+
return this.#pages.updatePage(input);
|
|
10444
10063
|
}
|
|
10445
|
-
|
|
10446
|
-
|
|
10447
|
-
await this.#transaction(async (tx) => {
|
|
10448
|
-
for (const id of input.ids) await tx.execute({
|
|
10449
|
-
sql: `UPDATE "${_mastra_core_storage.TABLE_KNOWLEDGE_SEMANTIC_OUTBOX}" SET status='pending',availableAt=?,claimedAt=NULL,claimedBy=NULL WHERE id=? AND status='processing' AND claimedBy=?`,
|
|
10450
|
-
args: [
|
|
10451
|
-
(input.retryAt ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
10452
|
-
id,
|
|
10453
|
-
input.workerId
|
|
10454
|
-
]
|
|
10455
|
-
});
|
|
10456
|
-
});
|
|
10064
|
+
movePage(input) {
|
|
10065
|
+
return this.#pages.movePage(input);
|
|
10457
10066
|
}
|
|
10458
|
-
|
|
10459
|
-
return this.#
|
|
10067
|
+
mergePages(input) {
|
|
10068
|
+
return this.#pages.mergePages(input);
|
|
10460
10069
|
}
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE id=? AND type='node'`,
|
|
10464
|
-
args: [id]
|
|
10465
|
-
});
|
|
10466
|
-
return result.rows[0] ? parseNode(result.rows[0]) : null;
|
|
10070
|
+
deletePage(input) {
|
|
10071
|
+
return this.#pages.deletePage(input);
|
|
10467
10072
|
}
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND scopeKey=? AND canonicalName=?`,
|
|
10471
|
-
args: [(0, _mastra_core_storage.knowledgeScopeKey)(scope), canonicalName(name)]
|
|
10472
|
-
});
|
|
10473
|
-
return result.rows[0] ? parseNode(result.rows[0]) : null;
|
|
10073
|
+
getNote(input) {
|
|
10074
|
+
return this.#pages.getNote(input);
|
|
10474
10075
|
}
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
const candidates = (await executor.execute({
|
|
10478
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_NODES}" WHERE type='node' AND canonicalName=? AND ("scopeKey" IN (${keys.map(() => "?").join(",")}) OR "mergedInto" IS NOT NULL)`,
|
|
10479
|
-
args: [canonicalName(name), ...keys]
|
|
10480
|
-
})).rows.map(parseNode).sort((left, right) => right.scope.length - left.scope.length);
|
|
10481
|
-
for (const candidate of candidates) {
|
|
10482
|
-
const terminal = await this.#resolveTerminalNode(executor, candidate.id);
|
|
10483
|
-
if (terminal && (0, _mastra_core_storage.isKnowledgeScopeVisible)(terminal.scope, scope)) return terminal;
|
|
10484
|
-
}
|
|
10485
|
-
return null;
|
|
10076
|
+
listNotes(input) {
|
|
10077
|
+
return this.#pages.listNotes(input);
|
|
10486
10078
|
}
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
const seen = /* @__PURE__ */ new Set();
|
|
10490
|
-
while (node?.mergedInto) {
|
|
10491
|
-
if (seen.has(node.id)) throw new Error(`Knowledge merge cycle detected at ${node.id}`);
|
|
10492
|
-
seen.add(node.id);
|
|
10493
|
-
node = await this.#getNode(executor, node.mergedInto);
|
|
10494
|
-
}
|
|
10495
|
-
return node;
|
|
10079
|
+
countNotes(input) {
|
|
10080
|
+
return this.#pages.countNotes(input);
|
|
10496
10081
|
}
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
sql: `SELECT *,scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" WHERE id=?${includeDeleted ? "" : " AND deletedAt IS NULL"}`,
|
|
10500
|
-
args: [id]
|
|
10501
|
-
});
|
|
10502
|
-
return result.rows[0] ? parseKnowledge(result.rows[0]) : null;
|
|
10082
|
+
listNotesMentioning(input) {
|
|
10083
|
+
return this.#pages.listNotesMentioning(input);
|
|
10503
10084
|
}
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
const node = await this.#resolveTerminalNode(this.#readExecutor, nodeReferenceId(input.node));
|
|
10507
|
-
if (!node) return { records: [] };
|
|
10508
|
-
const keys = visibleScopeKeys(scope);
|
|
10509
|
-
const args = [
|
|
10510
|
-
node.id,
|
|
10511
|
-
...relationship === "related" ? [node.id] : [],
|
|
10512
|
-
...keys
|
|
10513
|
-
];
|
|
10514
|
-
if (input.after) args.push(input.after);
|
|
10515
|
-
args.push((input.limit ?? 100) + 1);
|
|
10516
|
-
const records = (await this.#readExecutor.execute({
|
|
10517
|
-
sql: `SELECT DISTINCT f.*,f.scope AS "scopeJson" FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_RECORDS}" f${relationship === "about" ? "" : ` LEFT JOIN "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" m ON m.sourceType='record' AND m.sourceId=f.id`} WHERE ${relationship === "about" ? "f.node=?" : relationship === "mentioning" ? "m.recordId=?" : "(f.node=? OR m.recordId=?)"} AND ${visibleSql(keys, "f.scopeKey")}${input.includeDeleted ? "" : " AND f.deletedAt IS NULL"}${input.after ? " AND f.id < ?" : ""} ORDER BY f.id DESC LIMIT ?`,
|
|
10518
|
-
args
|
|
10519
|
-
})).rows.map(parseKnowledge);
|
|
10520
|
-
const limit = input.limit ?? 100;
|
|
10521
|
-
return {
|
|
10522
|
-
records: records.slice(0, limit),
|
|
10523
|
-
nextCursor: records.length > limit ? records[limit - 1]?.id : void 0
|
|
10524
|
-
};
|
|
10085
|
+
addNote(input) {
|
|
10086
|
+
return this.#pages.addNote(input);
|
|
10525
10087
|
}
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
for (const address of addresses) {
|
|
10529
|
-
const result = await executor.execute({
|
|
10530
|
-
sql: `SELECT scopeNodeId FROM "${TABLE_KNOWLEDGE_SCOPE_ADDRESSES}" WHERE address=?`,
|
|
10531
|
-
args: [address]
|
|
10532
|
-
});
|
|
10533
|
-
if (result.rows[0]?.scopeNodeId != null) scopeNodeIds.add(String(result.rows[0].scopeNodeId));
|
|
10534
|
-
}
|
|
10535
|
-
return [...scopeNodeIds];
|
|
10088
|
+
restampNote(input) {
|
|
10089
|
+
return this.#pages.restampNote(input);
|
|
10536
10090
|
}
|
|
10537
|
-
|
|
10538
|
-
|
|
10539
|
-
sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=?`,
|
|
10540
|
-
args: [nodeId]
|
|
10541
|
-
});
|
|
10542
|
-
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
|
|
10543
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
10544
|
-
args: [
|
|
10545
|
-
nodeId,
|
|
10546
|
-
scopeNodeId,
|
|
10547
|
-
addedAt.toISOString()
|
|
10548
|
-
]
|
|
10549
|
-
});
|
|
10091
|
+
removeNote(input) {
|
|
10092
|
+
return this.#pages.removeNote(input);
|
|
10550
10093
|
}
|
|
10551
|
-
|
|
10552
|
-
|
|
10553
|
-
* placements added by `#placeNodeInScopes`, which a scope change must not erase.
|
|
10554
|
-
*/
|
|
10555
|
-
async #swapIdentityScopes(executor, nodeId, from, to, addedAt) {
|
|
10556
|
-
const next = await this.#resolveScopeNodeIds(executor, to);
|
|
10557
|
-
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, from)) {
|
|
10558
|
-
if (next.includes(scopeNodeId)) continue;
|
|
10559
|
-
await executor.execute({
|
|
10560
|
-
sql: `DELETE FROM "${TABLE_KNOWLEDGE_NODE_SCOPES}" WHERE nodeId=? AND scopeNodeId=?`,
|
|
10561
|
-
args: [nodeId, scopeNodeId]
|
|
10562
|
-
});
|
|
10563
|
-
}
|
|
10564
|
-
for (const scopeNodeId of next) await executor.execute({
|
|
10565
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" (nodeId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
10566
|
-
args: [
|
|
10567
|
-
nodeId,
|
|
10568
|
-
scopeNodeId,
|
|
10569
|
-
addedAt.toISOString()
|
|
10570
|
-
]
|
|
10571
|
-
});
|
|
10094
|
+
restoreNote(input) {
|
|
10095
|
+
return this.#pages.restoreNote(input);
|
|
10572
10096
|
}
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
* (unlike identity membership, an unknown address is a caller error, not a lazy
|
|
10576
|
-
* materialization miss). Runs inside the caller's transaction, so a throw aborts
|
|
10577
|
-
* the whole mutation.
|
|
10578
|
-
*/
|
|
10579
|
-
async #placeNodeInScopes(executor, nodeId, addresses, addedAt) {
|
|
10580
|
-
for (const address of addresses ?? []) {
|
|
10581
|
-
const scopeNodeId = (await executor.execute({
|
|
10582
|
-
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`,
|
|
10583
|
-
args: [address]
|
|
10584
|
-
})).rows[0]?.scopeNodeId;
|
|
10585
|
-
if (scopeNodeId == null) throw new _mastra_core_storage.KnowledgeNotFoundError("scope", address);
|
|
10586
|
-
await executor.execute({
|
|
10587
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_NODE_SCOPES}" ("nodeId","scopeNodeId","addedAt") VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
10588
|
-
args: [
|
|
10589
|
-
nodeId,
|
|
10590
|
-
String(scopeNodeId),
|
|
10591
|
-
addedAt.toISOString()
|
|
10592
|
-
]
|
|
10593
|
-
});
|
|
10594
|
-
}
|
|
10097
|
+
searchPages(input) {
|
|
10098
|
+
return this.#pages.searchPages(input);
|
|
10595
10099
|
}
|
|
10596
|
-
|
|
10597
|
-
|
|
10598
|
-
sql: `DELETE FROM "${TABLE_KNOWLEDGE_RECORD_SCOPES}" WHERE recordId=?`,
|
|
10599
|
-
args: [recordId]
|
|
10600
|
-
});
|
|
10601
|
-
for (const scopeNodeId of await this.#resolveScopeNodeIds(executor, addresses)) await executor.execute({
|
|
10602
|
-
sql: `INSERT INTO "${TABLE_KNOWLEDGE_RECORD_SCOPES}" (recordId,scopeNodeId,addedAt) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
10603
|
-
args: [
|
|
10604
|
-
recordId,
|
|
10605
|
-
scopeNodeId,
|
|
10606
|
-
addedAt.toISOString()
|
|
10607
|
-
]
|
|
10608
|
-
});
|
|
10100
|
+
listPageActivity(input) {
|
|
10101
|
+
return this.#pages.listPageActivity(input);
|
|
10609
10102
|
}
|
|
10610
|
-
|
|
10611
|
-
|
|
10612
|
-
sql: `DELETE FROM "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" WHERE sourceType=? AND sourceId=?`,
|
|
10613
|
-
args: [sourceType, sourceId]
|
|
10614
|
-
});
|
|
10615
|
-
for (const name of (0, _mastra_core_storage.parseKnowledgeWikilinks)(text)) {
|
|
10616
|
-
let node = await this.#resolveNode(tx, name, resolutionScope);
|
|
10617
|
-
if (!node) {
|
|
10618
|
-
node = await this.#getNodeByName(tx, name, defaultScope);
|
|
10619
|
-
if (node) node = await this.#resolveTerminalNode(tx, node.id);
|
|
10620
|
-
if (!node) {
|
|
10621
|
-
const now = /* @__PURE__ */ new Date();
|
|
10622
|
-
node = {
|
|
10623
|
-
id: crypto.randomUUID(),
|
|
10624
|
-
type: "node",
|
|
10625
|
-
name,
|
|
10626
|
-
kind: "node",
|
|
10627
|
-
scope: defaultScope,
|
|
10628
|
-
version: 1,
|
|
10629
|
-
createdAt: now,
|
|
10630
|
-
updatedAt: now
|
|
10631
|
-
};
|
|
10632
|
-
await tx.execute({
|
|
10633
|
-
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,?,?)`,
|
|
10634
|
-
args: [
|
|
10635
|
-
node.id,
|
|
10636
|
-
"node",
|
|
10637
|
-
node.name,
|
|
10638
|
-
canonicalName(node.name),
|
|
10639
|
-
node.kind,
|
|
10640
|
-
JSON.stringify(defaultScope),
|
|
10641
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(defaultScope),
|
|
10642
|
-
1,
|
|
10643
|
-
now.toISOString(),
|
|
10644
|
-
now.toISOString()
|
|
10645
|
-
]
|
|
10646
|
-
});
|
|
10647
|
-
await this.#replaceNodeScopes(tx, node.id, defaultScope, now);
|
|
10648
|
-
await this.#activity(tx, "node-created", "node", node.id, defaultScope);
|
|
10649
|
-
await this.#outbox(tx, "node", node.id, "upsert", 1, defaultScope);
|
|
10650
|
-
}
|
|
10651
|
-
}
|
|
10652
|
-
await tx.execute({
|
|
10653
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_MENTIONS}" (sourceType,sourceId,recordId) VALUES (?,?,?) ON CONFLICT DO NOTHING`,
|
|
10654
|
-
args: [
|
|
10655
|
-
sourceType,
|
|
10656
|
-
sourceId,
|
|
10657
|
-
node.id
|
|
10658
|
-
]
|
|
10659
|
-
});
|
|
10660
|
-
}
|
|
10103
|
+
addPinAudience(input) {
|
|
10104
|
+
return this.#pages.addPinAudience(input);
|
|
10661
10105
|
}
|
|
10662
|
-
|
|
10663
|
-
|
|
10664
|
-
const [contextScopeId] = await this.#resolveScopeNodeIds(executor, scope);
|
|
10665
|
-
await executor.execute({
|
|
10666
|
-
sql: `INSERT INTO "${_mastra_core_storage.TABLE_KNOWLEDGE_ACTIVITY}" (id,action,targetType,targetId,contextScopeId,recordType,recordId,scope,scopeKey,sourceThreadId,createdAt) VALUES (?,?,?,?,?,?,?,jsonb(?),?,?,?)`,
|
|
10667
|
-
args: [
|
|
10668
|
-
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
10669
|
-
action,
|
|
10670
|
-
recordType,
|
|
10671
|
-
recordId,
|
|
10672
|
-
contextScopeId ?? null,
|
|
10673
|
-
recordType,
|
|
10674
|
-
recordId,
|
|
10675
|
-
JSON.stringify(scope),
|
|
10676
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
10677
|
-
sourceThreadId ?? null,
|
|
10678
|
-
now.toISOString()
|
|
10679
|
-
]
|
|
10680
|
-
});
|
|
10106
|
+
removePinAudience(input) {
|
|
10107
|
+
return this.#pages.removePinAudience(input);
|
|
10681
10108
|
}
|
|
10682
|
-
|
|
10683
|
-
|
|
10684
|
-
const idempotencyKey = (0, _mastra_core_storage.knowledgeSemanticIdempotencyKey)(documentId, operation, version);
|
|
10685
|
-
const now = /* @__PURE__ */ new Date();
|
|
10686
|
-
await executor.execute({
|
|
10687
|
-
sql: `INSERT 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) ON CONFLICT DO NOTHING`,
|
|
10688
|
-
args: [
|
|
10689
|
-
(0, _mastra_core_storage.createKnowledgeUlid)(),
|
|
10690
|
-
idempotencyKey,
|
|
10691
|
-
documentId,
|
|
10692
|
-
documentType,
|
|
10693
|
-
operation,
|
|
10694
|
-
JSON.stringify(scope),
|
|
10695
|
-
(0, _mastra_core_storage.knowledgeScopeKey)(scope),
|
|
10696
|
-
now.toISOString(),
|
|
10697
|
-
now.toISOString()
|
|
10698
|
-
]
|
|
10699
|
-
});
|
|
10109
|
+
listPins(input) {
|
|
10110
|
+
return this.#pages.listPins(input);
|
|
10700
10111
|
}
|
|
10701
10112
|
};
|
|
10702
10113
|
// src/storage/domains/mcp-clients/index.ts
|
|
@@ -21115,6 +20526,18 @@ const PERCENTILES = {
|
|
|
21115
20526
|
"duration.p95": .95,
|
|
21116
20527
|
"duration.p99": .99
|
|
21117
20528
|
};
|
|
20529
|
+
/** Quantile for each percentile statistic of a time-to-first measure. */
|
|
20530
|
+
const STATISTIC_PERCENTILES = {
|
|
20531
|
+
p50: .5,
|
|
20532
|
+
p90: .9,
|
|
20533
|
+
p95: .95,
|
|
20534
|
+
p99: .99
|
|
20535
|
+
};
|
|
20536
|
+
/**
|
|
20537
|
+
* Store features this compiler supports beyond `trace-aggregate` / `span-aggregate`. Core before
|
|
20538
|
+
* the time-to-first measures rejects them, so they are only declared when core plans them.
|
|
20539
|
+
*/
|
|
20540
|
+
const aggregateTimeToFirstFeatures = "timeToFirstToken.p50" in _mastra_core_storage.TRACE_AGGREGATE_MEASURE_REGISTRY ? ["aggregate-time-to-first-measures"] : [];
|
|
21118
20541
|
const COMPARISON_SQL = {
|
|
21119
20542
|
eq: "=",
|
|
21120
20543
|
ne: "<>",
|
|
@@ -21182,6 +20605,8 @@ function compilePostgresAggregate(schema, plan, source) {
|
|
|
21182
20605
|
factColumns.push(`(${distinctSql(measure.field)})::text AS ${column}`);
|
|
21183
20606
|
}
|
|
21184
20607
|
factColumns.push(`(${durationMsSql("r.\"startedAt\"", "r.\"endedAt\"")})::float8 AS "durationMs"`, `r."error" IS NOT NULL AS "isError"`);
|
|
20608
|
+
const timeToFirstFields = new Set(plan.measures.map((measure) => measureRule(measure.name)?.field));
|
|
20609
|
+
for (const field of timeToFirstFields) if (field) factColumns.push(`r."${field}"::float8 AS "${field}"`);
|
|
21185
20610
|
if (plan.interval !== void 0) {
|
|
21186
20611
|
const intervalMs = _mastra_core_storage.TRACE_AGGREGATE_INTERVAL_MS[plan.interval];
|
|
21187
20612
|
if (intervalMs === void 0) throw new Error(`Unsupported trusted trace-aggregate interval: ${plan.interval}`);
|
|
@@ -21213,6 +20638,15 @@ function compilePostgresAggregate(schema, plan, source) {
|
|
|
21213
20638
|
const aggregate = rule.statistic === "avg" ? "AVG" : "SUM";
|
|
21214
20639
|
return `(CASE WHEN ${mixedUnitsSql(alias)} THEN NULL ELSE ${aggregate}(${column("traceCost")}) END)::float8`;
|
|
21215
20640
|
}
|
|
20641
|
+
if (rule?.field) {
|
|
20642
|
+
const value = column(rule.field);
|
|
20643
|
+
if (rule.statistic === "count") return `COUNT(${value})::float8`;
|
|
20644
|
+
if (rule.statistic === "avg") return `AVG(${value})`;
|
|
20645
|
+
if (rule.statistic === "min") return `MIN(${value})`;
|
|
20646
|
+
if (rule.statistic === "max") return `MAX(${value})`;
|
|
20647
|
+
const quantile = rule.statistic && STATISTIC_PERCENTILES[rule.statistic];
|
|
20648
|
+
if (quantile !== void 0) return `percentile_cont(${quantile}) WITHIN GROUP (ORDER BY ${value})`;
|
|
20649
|
+
}
|
|
21216
20650
|
if (name === "errorCount") return `(COUNT(*) FILTER (WHERE ${column("isError")}))::float8`;
|
|
21217
20651
|
if (name === "errorRate") return `(COUNT(*) FILTER (WHERE ${column("isError")}))::float8 / COUNT(*)`;
|
|
21218
20652
|
if (name === "duration.avg") return `AVG(${column("durationMs")})`;
|
|
@@ -22367,6 +21801,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22367
21801
|
"metric-discovery",
|
|
22368
21802
|
"trace-query",
|
|
22369
21803
|
"trace-aggregate",
|
|
21804
|
+
...aggregateTimeToFirstFeatures,
|
|
22370
21805
|
...spanQueryFeatures,
|
|
22371
21806
|
...spanAggregateFeatures,
|
|
22372
21807
|
...typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
|
|
@@ -22393,6 +21828,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
|
|
|
22393
21828
|
"delta-polling",
|
|
22394
21829
|
"trace-query",
|
|
22395
21830
|
"trace-aggregate",
|
|
21831
|
+
...aggregateTimeToFirstFeatures,
|
|
22396
21832
|
...spanQueryFeatures,
|
|
22397
21833
|
...spanAggregateFeatures,
|
|
22398
21834
|
...typeof _mastra_core_storage.planThreadRowsQuery === "function" ? ["thread-query-rows"] : [],
|