@mastra/pg 1.35.0-alpha.2 → 1.35.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_types/@internal_core@0.1.4/dist/knowledge-compat/index.d.ts +9 -4
- package/dist/index.cjs +190 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +190 -24
- package/dist/index.js.map +1 -1
- package/dist/storage/domains/knowledge/index.d.ts +6 -0
- package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/pages-schema.d.ts.map +1 -1
- package/dist/storage/domains/knowledge/pages.d.ts +30 -11
- package/dist/storage/domains/knowledge/pages.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/docs/SKILL.md +0 -48
- package/dist/docs/assets/SOURCE_MAP.json +0 -6
- package/dist/docs/references/docs-deployment-workers.md +0 -404
- package/dist/docs/references/docs-memory-semantic-recall.md +0 -485
- package/dist/docs/references/docs-memory-working-memory.md +0 -450
- package/dist/docs/references/docs-storage.md +0 -224
- package/dist/docs/references/integrations-databases-clickhouse-managed-postgres.md +0 -82
- package/dist/docs/references/integrations-databases-dynamodb.md +0 -286
- package/dist/docs/references/integrations-databases-neon.md +0 -222
- package/dist/docs/references/integrations-databases-postgresql.md +0 -592
- package/dist/docs/references/integrations-vectors-pg.md +0 -451
- package/dist/docs/references/reference-memory-memory-class.md +0 -237
- package/dist/docs/references/reference-processors-message-history-processor.md +0 -89
- package/dist/docs/references/reference-processors-semantic-recall-processor.md +0 -119
- package/dist/docs/references/reference-processors-working-memory-processor.md +0 -154
- package/dist/docs/references/reference-rag-chunking-and-embedding.md +0 -184
- package/dist/docs/references/reference-rag-metadata-filters.md +0 -238
- package/dist/docs/references/reference-rag-overview.md +0 -76
- package/dist/docs/references/reference-rag-retrieval.md +0 -672
- package/dist/docs/references/reference-rag-vector-databases.md +0 -808
- package/dist/docs/references/reference-storage-composite.md +0 -433
- package/dist/docs/references/reference-storage-retention.md +0 -310
- package/dist/docs/references/reference-tools-vector-query-tool.md +0 -500
package/dist/index.js
CHANGED
|
@@ -8626,7 +8626,8 @@ const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
|
|
|
8626
8626
|
const KNOWLEDGE_PAGES_CORE_NUMBERS = [
|
|
8627
8627
|
"MAX_KNOWLEDGE_MOVE_PAGES",
|
|
8628
8628
|
"MAX_KNOWLEDGE_PAGE_DEPTH",
|
|
8629
|
-
"MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
|
|
8629
|
+
"MAX_KNOWLEDGE_PAGE_LIST_LIMIT",
|
|
8630
|
+
"MAX_KNOWLEDGE_PAGE_LINKS"
|
|
8630
8631
|
];
|
|
8631
8632
|
function resolveKnowledgePagesCore(storageModule) {
|
|
8632
8633
|
const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
|
|
@@ -8647,16 +8648,18 @@ function createKnowledgePagesCoreLoader(features, loadStorageModule) {
|
|
|
8647
8648
|
};
|
|
8648
8649
|
}
|
|
8649
8650
|
/**
|
|
8650
|
-
* Keyword-search words: the query split on whitespace, lowercased, deduplicated
|
|
8651
|
-
* least one of them (as a case-insensitive
|
|
8651
|
+
* Keyword-search words: the query split on whitespace, lowercased, deduplicated, keeping the first
|
|
8652
|
+
* {@link KNOWLEDGE_SEARCH_MAX_WORDS}. A hit needs at least one of them (as a case-insensitive
|
|
8653
|
+
* substring); an empty list means the search returns nothing.
|
|
8652
8654
|
*/
|
|
8653
8655
|
function knowledgeSearchWords(query) {
|
|
8654
|
-
return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
|
|
8656
|
+
return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))].slice(0, 16);
|
|
8655
8657
|
}
|
|
8656
8658
|
/**
|
|
8657
8659
|
* Points one matched search word earns on a page, by the best field it appears in: name, then
|
|
8658
|
-
* description, then body.
|
|
8659
|
-
*
|
|
8660
|
+
* description, then body. A page whose name contains the whole query ranks first; below that, pages
|
|
8661
|
+
* rank by how many query words they match, and these points only break ties between pages that
|
|
8662
|
+
* match the same number of words.
|
|
8660
8663
|
*/
|
|
8661
8664
|
const KNOWLEDGE_SEARCH_WEIGHTS = {
|
|
8662
8665
|
name: 3,
|
|
@@ -8682,7 +8685,8 @@ const PG_KNOWLEDGE_PAGES_TABLES = {
|
|
|
8682
8685
|
mentions: "mastra_knowledge_mentions",
|
|
8683
8686
|
pins: "mastra_knowledge_pins",
|
|
8684
8687
|
activity: "mastra_knowledge_activity",
|
|
8685
|
-
state: "mastra_knowledge_access_state"
|
|
8688
|
+
state: "mastra_knowledge_access_state",
|
|
8689
|
+
links: "mastra_knowledge_page_links"
|
|
8686
8690
|
};
|
|
8687
8691
|
/**
|
|
8688
8692
|
* Schema-4 DDL. `COLLATE "C"` on every column that is ordered, prefix-matched or compared for
|
|
@@ -8741,7 +8745,9 @@ function pgKnowledgePagesDDL(schemaName) {
|
|
|
8741
8745
|
)`,
|
|
8742
8746
|
`CREATE TABLE IF NOT EXISTS ${q(t.pins)} (
|
|
8743
8747
|
"id" text PRIMARY KEY,
|
|
8744
|
-
"pageId" text NOT NULL
|
|
8748
|
+
"pageId" text NOT NULL REFERENCES ${q(t.pages)} ("id"),
|
|
8749
|
+
"kind" text NOT NULL DEFAULT 'page',
|
|
8750
|
+
"depth" integer,
|
|
8745
8751
|
"audience" text[] NOT NULL,
|
|
8746
8752
|
"createdBy" text NOT NULL,
|
|
8747
8753
|
"createdAt" timestamptz NOT NULL,
|
|
@@ -8773,6 +8779,19 @@ function pgKnowledgePagesDDL(schemaName) {
|
|
|
8773
8779
|
index("mastra_knowledge_mentions_page_idx", t.mentions, "\"pageId\""),
|
|
8774
8780
|
`CREATE INDEX IF NOT EXISTS "mastra_knowledge_pins_audience_gin" ON ${q(t.pins)} USING gin ("audience")`,
|
|
8775
8781
|
index("mastra_knowledge_pins_created_idx", t.pins, "\"createdAt\",\"id\""),
|
|
8782
|
+
`ALTER TABLE ${q(t.pins)} ADD COLUMN IF NOT EXISTS "kind" text NOT NULL DEFAULT 'page'`,
|
|
8783
|
+
`ALTER TABLE ${q(t.pins)} ADD COLUMN IF NOT EXISTS "depth" integer`,
|
|
8784
|
+
`ALTER TABLE ${q(t.pins)} DROP CONSTRAINT IF EXISTS "${t.pins}_pageId_key"`,
|
|
8785
|
+
index("mastra_knowledge_pins_page_kind_idx", t.pins, "\"pageId\",\"kind\"", "", true),
|
|
8786
|
+
`CREATE TABLE IF NOT EXISTS ${q(t.links)} (
|
|
8787
|
+
"fromPageId" text NOT NULL,
|
|
8788
|
+
"ref" text COLLATE "C" NOT NULL,
|
|
8789
|
+
"toPageId" text,
|
|
8790
|
+
"count" integer NOT NULL,
|
|
8791
|
+
PRIMARY KEY ("fromPageId", "ref")
|
|
8792
|
+
)`,
|
|
8793
|
+
index("mastra_knowledge_page_links_to_idx", t.links, "\"toPageId\"", "\"toPageId\" IS NOT NULL"),
|
|
8794
|
+
index("mastra_knowledge_page_links_ref_idx", t.links, "\"ref\"", "\"toPageId\" IS NULL"),
|
|
8776
8795
|
index("mastra_knowledge_activity_page_idx", t.activity, "\"pageId\",\"createdAt\",\"id\""),
|
|
8777
8796
|
index("mastra_knowledge_activity_created_idx", t.activity, "\"createdAt\",\"id\"")
|
|
8778
8797
|
];
|
|
@@ -8917,6 +8936,8 @@ function toPin(row) {
|
|
|
8917
8936
|
return {
|
|
8918
8937
|
id: String(row.id),
|
|
8919
8938
|
pageId: String(row.pageId),
|
|
8939
|
+
kind: String(row.kind),
|
|
8940
|
+
...row.depth == null ? {} : { depth: Number(row.depth) },
|
|
8920
8941
|
audience: row.audience ?? [],
|
|
8921
8942
|
createdBy: String(row.createdBy),
|
|
8922
8943
|
createdAt: new Date(row.createdAt),
|
|
@@ -8968,6 +8989,9 @@ var PgKnowledgePages = class {
|
|
|
8968
8989
|
get #pins() {
|
|
8969
8990
|
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.pins);
|
|
8970
8991
|
}
|
|
8992
|
+
get #links() {
|
|
8993
|
+
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.links);
|
|
8994
|
+
}
|
|
8971
8995
|
get #activity() {
|
|
8972
8996
|
return this.#q(PG_KNOWLEDGE_PAGES_TABLES.activity);
|
|
8973
8997
|
}
|
|
@@ -9204,6 +9228,7 @@ var PgKnowledgePages = class {
|
|
|
9204
9228
|
const params = new Params();
|
|
9205
9229
|
const where = [`"isScope"`, `"deletedAt" IS NULL`];
|
|
9206
9230
|
where.push(input.parentId === null ? `"parentId" IS NULL` : `"parentId" = ${params.add(input.parentId)}`);
|
|
9231
|
+
if (input.read && input.read !== "all") where.push(`"id" IN (${new ReadFilter(input.read, params, this.#pages).readableIds()})`);
|
|
9207
9232
|
if (input.cursor) {
|
|
9208
9233
|
const [position, id] = decodeCursor(input.cursor, 2);
|
|
9209
9234
|
where.push(`("position", "id") > (${params.add(position)}, ${params.add(id)})`);
|
|
@@ -9479,9 +9504,17 @@ var PgKnowledgePages = class {
|
|
|
9479
9504
|
await tx.query(`UPDATE ${this.#notes} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
|
|
9480
9505
|
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]);
|
|
9481
9506
|
await tx.query(`UPDATE ${this.#mentions} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
|
|
9482
|
-
const
|
|
9483
|
-
|
|
9484
|
-
await this.#upsertPinAudience(tx,
|
|
9507
|
+
const sourcePins = await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 ORDER BY "kind" FOR UPDATE`, [source.id]);
|
|
9508
|
+
for (const sourcePin of sourcePins) {
|
|
9509
|
+
await this.#upsertPinAudience(tx, {
|
|
9510
|
+
pageId: target.id,
|
|
9511
|
+
kind: String(sourcePin.kind),
|
|
9512
|
+
depth: sourcePin.depth == null ? null : Number(sourcePin.depth),
|
|
9513
|
+
audience: sourcePin.audience ?? [],
|
|
9514
|
+
createdBy: String(sourcePin.createdBy),
|
|
9515
|
+
now,
|
|
9516
|
+
keepDepth: true
|
|
9517
|
+
});
|
|
9485
9518
|
await tx.query(`DELETE FROM ${this.#pins} WHERE "id" = $1`, [String(sourcePin.id)]);
|
|
9486
9519
|
}
|
|
9487
9520
|
const rows = await this.#rows(tx, `SELECT * FROM ${this.#pages} WHERE "id" = $1`, [target.id]);
|
|
@@ -9541,6 +9574,18 @@ var PgKnowledgePages = class {
|
|
|
9541
9574
|
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);
|
|
9542
9575
|
return this.#noteOutput(rows, limit);
|
|
9543
9576
|
}
|
|
9577
|
+
async countChildScopes(input) {
|
|
9578
|
+
const ids = [...new Set(input.parentIds)];
|
|
9579
|
+
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
9580
|
+
if (!ids.length) return counts;
|
|
9581
|
+
const params = new Params();
|
|
9582
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9583
|
+
const rows = await this.#rows(this.#client, `SELECT s."parentId" AS "parentId", count(*)::int AS "count" FROM ${this.#pages} s
|
|
9584
|
+
WHERE s."parentId" = ANY(${params.add(ids)}::text[]) AND s."isScope" AND s."deletedAt" IS NULL AND ${filter.readable("s")}
|
|
9585
|
+
GROUP BY s."parentId"`, params.values);
|
|
9586
|
+
for (const row of rows) counts[row.parentId] = Number(row.count);
|
|
9587
|
+
return counts;
|
|
9588
|
+
}
|
|
9544
9589
|
async countChildPages(input) {
|
|
9545
9590
|
const ids = [...new Set(input.parentIds)];
|
|
9546
9591
|
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
@@ -9553,6 +9598,94 @@ var PgKnowledgePages = class {
|
|
|
9553
9598
|
for (const row of rows) counts[row.parentId] = Number(row.count);
|
|
9554
9599
|
return counts;
|
|
9555
9600
|
}
|
|
9601
|
+
async replacePageBodyLinks(input) {
|
|
9602
|
+
await this.#tx(async (tx) => {
|
|
9603
|
+
await tx.query(`DELETE FROM ${this.#links} WHERE "fromPageId" = $1`, [input.pageId]);
|
|
9604
|
+
if (!input.links.length) return;
|
|
9605
|
+
await tx.query(`INSERT INTO ${this.#links} ("fromPageId","ref","toPageId","count") SELECT $1, r, t, c FROM unnest($2::text[], $3::text[], $4::int[]) AS x(r, t, c)`, [
|
|
9606
|
+
input.pageId,
|
|
9607
|
+
input.links.map((l) => l.ref),
|
|
9608
|
+
input.links.map((l) => l.toPageId),
|
|
9609
|
+
input.links.map((l) => l.count)
|
|
9610
|
+
]);
|
|
9611
|
+
});
|
|
9612
|
+
}
|
|
9613
|
+
async listPageBodyLinkSources(input) {
|
|
9614
|
+
const refs = [...new Set(input.refs ?? [])];
|
|
9615
|
+
const targets = [...new Set(input.toPageIds ?? [])];
|
|
9616
|
+
if (!refs.length && !targets.length) return [];
|
|
9617
|
+
const parts = [];
|
|
9618
|
+
const params = new Params();
|
|
9619
|
+
if (refs.length) parts.push(`("toPageId" IS NULL AND "ref" = ANY(${params.add(refs)}::text[]))`);
|
|
9620
|
+
if (targets.length) parts.push(`"toPageId" = ANY(${params.add(targets)}::text[])`);
|
|
9621
|
+
return (await this.#rows(this.#client, `SELECT DISTINCT "fromPageId" FROM ${this.#links} WHERE ${parts.join(" OR ")}`, params.values)).map((row) => String(row.fromPageId));
|
|
9622
|
+
}
|
|
9623
|
+
/**
|
|
9624
|
+
* Every resolved link touching `ids` whose both ends are live and readable, one row per
|
|
9625
|
+
* (from, to, kind); no self-links. Note links go through the same visible-note rule as listNotes.
|
|
9626
|
+
*/
|
|
9627
|
+
#visibleLinkEdges(filter, params, ids, direction, kinds) {
|
|
9628
|
+
const wanted = new Set(kinds ?? ["body", "note"]);
|
|
9629
|
+
const idsParam = `${params.add(ids)}::text[]`;
|
|
9630
|
+
const touch = (from, to) => direction === "out" ? `${from} = ANY(${idsParam})` : direction === "in" ? `${to} = ANY(${idsParam})` : `(${from} = ANY(${idsParam}) OR ${to} = ANY(${idsParam}))`;
|
|
9631
|
+
const branches = [];
|
|
9632
|
+
if (wanted.has("body")) branches.push(`SELECT l."fromPageId" AS "fromPageId", l."toPageId" AS "toPageId", 'body'::text AS "kind", sum(l."count")::int AS "count"
|
|
9633
|
+
FROM ${this.#links} l
|
|
9634
|
+
JOIN ${this.#pages} fp ON fp."id" = l."fromPageId" AND fp."deletedAt" IS NULL
|
|
9635
|
+
JOIN ${this.#pages} fs ON fs."id" = fp."scopeId"
|
|
9636
|
+
JOIN ${this.#pages} tp ON tp."id" = l."toPageId" AND tp."deletedAt" IS NULL
|
|
9637
|
+
JOIN ${this.#pages} ts ON ts."id" = tp."scopeId"
|
|
9638
|
+
WHERE l."toPageId" IS NOT NULL AND l."fromPageId" <> l."toPageId" AND ${touch("l.\"fromPageId\"", "l.\"toPageId\"")}
|
|
9639
|
+
AND ${filter.readable("fs")} AND ${filter.readable("ts")}
|
|
9640
|
+
GROUP BY l."fromPageId", l."toPageId"`);
|
|
9641
|
+
if (wanted.has("note")) branches.push(`SELECT vn."pageId" AS "fromPageId", mn."pageId" AS "toPageId", 'note'::text AS "kind", count(*)::int AS "count"
|
|
9642
|
+
FROM (SELECT n."id", n."pageId" FROM ${this.#visibleNotes(filter)}
|
|
9643
|
+
AND n."deletedAt" IS NULL AND np."deletedAt" IS NULL
|
|
9644
|
+
AND (n."pageId" = ANY(${idsParam}) OR n."id" IN (SELECT mm."noteId" FROM ${this.#mentions} mm WHERE mm."pageId" = ANY(${idsParam})))) vn
|
|
9645
|
+
JOIN ${this.#mentions} mn ON mn."noteId" = vn."id"
|
|
9646
|
+
JOIN ${this.#pages} tp ON tp."id" = mn."pageId" AND tp."deletedAt" IS NULL
|
|
9647
|
+
JOIN ${this.#pages} ts ON ts."id" = tp."scopeId"
|
|
9648
|
+
WHERE mn."pageId" <> vn."pageId" AND ${touch("vn.\"pageId\"", "mn.\"pageId\"")} AND ${filter.readable("ts")}
|
|
9649
|
+
GROUP BY vn."pageId", mn."pageId"`);
|
|
9650
|
+
return branches.length ? branches.join(" UNION ALL ") : `SELECT NULL::text AS "fromPageId", NULL::text AS "toPageId", NULL::text AS "kind", NULL::int AS "count" WHERE FALSE`;
|
|
9651
|
+
}
|
|
9652
|
+
async countPageLinks(input) {
|
|
9653
|
+
const ids = [...new Set(input.pageIds)];
|
|
9654
|
+
const counts = Object.fromEntries(ids.map((id) => [id, {
|
|
9655
|
+
in: 0,
|
|
9656
|
+
out: 0
|
|
9657
|
+
}]));
|
|
9658
|
+
if (!ids.length) return counts;
|
|
9659
|
+
const params = new Params();
|
|
9660
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9661
|
+
const edges = this.#visibleLinkEdges(filter, params, ids, "both", input.kinds);
|
|
9662
|
+
const rows = await this.#rows(this.#client, `WITH e AS (${edges}), r AS (
|
|
9663
|
+
SELECT "fromPageId" AS "id", count(DISTINCT "toPageId")::int AS "out", 0 AS "in" FROM e WHERE "fromPageId" = ANY(${params.add(ids)}::text[]) GROUP BY "fromPageId"
|
|
9664
|
+
UNION ALL
|
|
9665
|
+
SELECT "toPageId", 0, count(DISTINCT "fromPageId")::int FROM e WHERE "toPageId" = ANY(${params.add(ids)}::text[]) GROUP BY "toPageId")
|
|
9666
|
+
SELECT "id", sum("out")::int AS "out", sum("in")::int AS "in" FROM r GROUP BY "id"`, params.values);
|
|
9667
|
+
for (const row of rows) counts[String(row.id)] = {
|
|
9668
|
+
in: Number(row.in),
|
|
9669
|
+
out: Number(row.out)
|
|
9670
|
+
};
|
|
9671
|
+
return counts;
|
|
9672
|
+
}
|
|
9673
|
+
async listPageLinks(input) {
|
|
9674
|
+
const limit = input.limit ?? 1e3;
|
|
9675
|
+
if (!Number.isInteger(limit) || limit < 1) throw new RangeError("Knowledge link limit must be a positive integer");
|
|
9676
|
+
const core = await loadPagesCore();
|
|
9677
|
+
const ids = [...new Set(input.pageIds)];
|
|
9678
|
+
if (!ids.length) return [];
|
|
9679
|
+
const params = new Params();
|
|
9680
|
+
const filter = new ReadFilter(input.read, params, this.#pages);
|
|
9681
|
+
const edges = this.#visibleLinkEdges(filter, params, ids, input.direction ?? "both", input.kinds);
|
|
9682
|
+
return (await this.#rows(this.#client, `SELECT * FROM (${edges}) e ORDER BY "fromPageId" COLLATE "C", "toPageId" COLLATE "C", CASE "kind" WHEN 'body' THEN 0 ELSE 1 END LIMIT ${Math.min(limit, core.MAX_KNOWLEDGE_PAGE_LINKS)}`, params.values)).map((row) => ({
|
|
9683
|
+
fromPageId: String(row.fromPageId),
|
|
9684
|
+
toPageId: String(row.toPageId),
|
|
9685
|
+
kind: row.kind,
|
|
9686
|
+
count: Number(row.count)
|
|
9687
|
+
}));
|
|
9688
|
+
}
|
|
9556
9689
|
async countNotes(input) {
|
|
9557
9690
|
const ids = [...new Set(input.pageIds)];
|
|
9558
9691
|
const counts = Object.fromEntries(ids.map((id) => [id, 0]));
|
|
@@ -9667,7 +9800,7 @@ var PgKnowledgePages = class {
|
|
|
9667
9800
|
async removeNote(input) {
|
|
9668
9801
|
return this.#tx(async (tx) => {
|
|
9669
9802
|
const note = await this.#noteById(tx, input.id, true);
|
|
9670
|
-
if (note.deletedAt)
|
|
9803
|
+
if (note.deletedAt) throw new KnowledgeNotFoundError("note", input.id);
|
|
9671
9804
|
const now = /* @__PURE__ */ new Date();
|
|
9672
9805
|
await tx.query(`UPDATE ${this.#notes} SET "deletedAt" = $2, "deletedBy" = $3, "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [
|
|
9673
9806
|
input.id,
|
|
@@ -9779,10 +9912,18 @@ var PgKnowledgePages = class {
|
|
|
9779
9912
|
};
|
|
9780
9913
|
}
|
|
9781
9914
|
async addPinAudience(input) {
|
|
9915
|
+
const kind = input.kind ?? "page";
|
|
9782
9916
|
return this.#tx(async (tx) => {
|
|
9783
9917
|
const page = await this.#requireLivePage(tx, input.pageId, false);
|
|
9784
9918
|
const now = /* @__PURE__ */ new Date();
|
|
9785
|
-
const { pin, changed } = await this.#upsertPinAudience(tx,
|
|
9919
|
+
const { pin, changed } = await this.#upsertPinAudience(tx, {
|
|
9920
|
+
pageId: page.id,
|
|
9921
|
+
kind,
|
|
9922
|
+
depth: kind === "list" ? input.depth ?? 1 : null,
|
|
9923
|
+
audience: input.audience,
|
|
9924
|
+
createdBy: input.createdBy,
|
|
9925
|
+
now
|
|
9926
|
+
});
|
|
9786
9927
|
if (changed) await this.#logActivity(tx, {
|
|
9787
9928
|
action: "pin-added",
|
|
9788
9929
|
scopeId: page.scopeId,
|
|
@@ -9793,28 +9934,35 @@ var PgKnowledgePages = class {
|
|
|
9793
9934
|
return pin;
|
|
9794
9935
|
});
|
|
9795
9936
|
}
|
|
9796
|
-
|
|
9797
|
-
|
|
9937
|
+
/** `keepDepth` (merges) leaves an existing pin's depth as it is instead of setting `depth`. */
|
|
9938
|
+
async #upsertPinAudience(db, input) {
|
|
9939
|
+
const { pageId, kind, audience, now } = input;
|
|
9940
|
+
const existing = (await this.#rows(db, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 AND "kind" = $2 FOR UPDATE`, [pageId, kind]))[0];
|
|
9798
9941
|
if (!existing) return {
|
|
9799
|
-
pin: toPin((await this.#rows(db, `INSERT INTO ${this.#pins} ("id","pageId","audience","createdBy","createdAt","updatedAt") VALUES ($1,$2,$3::text[],$
|
|
9942
|
+
pin: toPin((await this.#rows(db, `INSERT INTO ${this.#pins} ("id","pageId","kind","depth","audience","createdBy","createdAt","updatedAt") VALUES ($1,$2,$3,$4,$5::text[],$6,$7,$7) RETURNING *`, [
|
|
9800
9943
|
createKnowledgeUlid(),
|
|
9801
9944
|
pageId,
|
|
9945
|
+
kind,
|
|
9946
|
+
input.depth,
|
|
9802
9947
|
[...new Set(audience)],
|
|
9803
|
-
createdBy,
|
|
9948
|
+
input.createdBy,
|
|
9804
9949
|
iso(now)
|
|
9805
9950
|
]))[0]),
|
|
9806
9951
|
changed: true
|
|
9807
9952
|
};
|
|
9808
9953
|
const current = existing.audience ?? [];
|
|
9809
9954
|
const merged = [.../* @__PURE__ */ new Set([...current, ...audience])];
|
|
9810
|
-
|
|
9955
|
+
const currentDepth = existing.depth == null ? null : Number(existing.depth);
|
|
9956
|
+
const depth = input.keepDepth ? currentDepth : input.depth;
|
|
9957
|
+
if (merged.length === current.length && depth === currentDepth) return {
|
|
9811
9958
|
pin: toPin(existing),
|
|
9812
9959
|
changed: false
|
|
9813
9960
|
};
|
|
9814
9961
|
return {
|
|
9815
|
-
pin: toPin((await this.#rows(db, `UPDATE ${this.#pins} SET "audience" = $2::text[], "
|
|
9962
|
+
pin: toPin((await this.#rows(db, `UPDATE ${this.#pins} SET "audience" = $2::text[], "depth" = $3, "updatedAt" = $4 WHERE "id" = $1 RETURNING *`, [
|
|
9816
9963
|
String(existing.id),
|
|
9817
9964
|
merged,
|
|
9965
|
+
depth,
|
|
9818
9966
|
iso(now)
|
|
9819
9967
|
]))[0]),
|
|
9820
9968
|
changed: true
|
|
@@ -9822,7 +9970,7 @@ var PgKnowledgePages = class {
|
|
|
9822
9970
|
}
|
|
9823
9971
|
async removePinAudience(input) {
|
|
9824
9972
|
return this.#tx(async (tx) => {
|
|
9825
|
-
const existing = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [input.pageId]))[0];
|
|
9973
|
+
const existing = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 AND "kind" = $2 FOR UPDATE`, [input.pageId, input.kind ?? "page"]))[0];
|
|
9826
9974
|
if (!existing) return null;
|
|
9827
9975
|
const current = existing.audience ?? [];
|
|
9828
9976
|
const remaining = current.filter((entry) => !input.audience.includes(entry));
|
|
@@ -9854,6 +10002,7 @@ var PgKnowledgePages = class {
|
|
|
9854
10002
|
t.mentions,
|
|
9855
10003
|
t.stamps,
|
|
9856
10004
|
t.notes,
|
|
10005
|
+
t.links,
|
|
9857
10006
|
t.pages
|
|
9858
10007
|
].map((name) => this.#q(name)).join(", ");
|
|
9859
10008
|
await this.#client.query(`TRUNCATE ${tables}`);
|
|
@@ -9872,9 +10021,10 @@ var PgKnowledgePages = class {
|
|
|
9872
10021
|
const params = new Params();
|
|
9873
10022
|
const where = [`p."deletedAt" IS NULL`, new ReadFilter(input.read, params, this.#pages).readable("s")];
|
|
9874
10023
|
if (input.audience !== "all") where.push(`pn."audience" && ${params.add(input.audience)}::text[]`);
|
|
10024
|
+
if (input.kind) where.push(`pn."kind" = ${params.add(input.kind)}`);
|
|
9875
10025
|
if (input.pageIds) where.push(`pn."pageId" = ANY(${params.add(input.pageIds)}::text[])`);
|
|
9876
|
-
if (input.excludePageIds?.length) where.push(`pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[])`);
|
|
9877
|
-
if (input.withBody) where.push(`p."body" ~ '\\S'`);
|
|
10026
|
+
if (input.excludePageIds?.length) where.push(`(pn."kind" <> 'page' OR pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[]))`);
|
|
10027
|
+
if (input.withBody) where.push(`(pn."kind" <> 'page' OR p."body" ~ '\\S')`);
|
|
9878
10028
|
const from = `FROM ${this.#pins} pn JOIN ${this.#pages} p ON p."id" = pn."pageId" JOIN ${this.#pages} s ON s."id" = p."scopeId"`;
|
|
9879
10029
|
const totalWhere = where.join(" AND ");
|
|
9880
10030
|
if (input.cursor) {
|
|
@@ -9940,7 +10090,7 @@ async function pgKnowledgeSnapshot(db, schemaName) {
|
|
|
9940
10090
|
}
|
|
9941
10091
|
/** Drops exactly the Knowledge tables (never Memory's) and recreates schema 4. */
|
|
9942
10092
|
async function resetPgKnowledgePagesSchema(client, schemaName) {
|
|
9943
|
-
const tables = KNOWLEDGE_RESETTABLE_TABLE_NAMES.map((table) => qualify(schemaName, table)).join(", ");
|
|
10093
|
+
const tables = [...KNOWLEDGE_RESETTABLE_TABLE_NAMES, PG_KNOWLEDGE_PAGES_TABLES.links].map((table) => qualify(schemaName, table)).join(", ");
|
|
9944
10094
|
await client.query(`DROP TABLE IF EXISTS ${tables} CASCADE`);
|
|
9945
10095
|
await initPgKnowledgePagesSchema(client, schemaName);
|
|
9946
10096
|
}
|
|
@@ -10015,7 +10165,8 @@ var KnowledgePG = class extends KnowledgeStorage {
|
|
|
10015
10165
|
...super.getCapabilities(),
|
|
10016
10166
|
schemaVersion: 4,
|
|
10017
10167
|
supportsSchemaInspection: true,
|
|
10018
|
-
supportsExplicitReset: true
|
|
10168
|
+
supportsExplicitReset: true,
|
|
10169
|
+
supportsPinKinds: true
|
|
10019
10170
|
};
|
|
10020
10171
|
}
|
|
10021
10172
|
async init() {
|
|
@@ -10045,6 +10196,9 @@ var KnowledgePG = class extends KnowledgeStorage {
|
|
|
10045
10196
|
listChildScopes(input) {
|
|
10046
10197
|
return this.#pages.listChildScopes(input);
|
|
10047
10198
|
}
|
|
10199
|
+
countChildScopes(input) {
|
|
10200
|
+
return this.#pages.countChildScopes(input);
|
|
10201
|
+
}
|
|
10048
10202
|
getPages(input) {
|
|
10049
10203
|
return this.#pages.getPages(input);
|
|
10050
10204
|
}
|
|
@@ -10087,6 +10241,18 @@ var KnowledgePG = class extends KnowledgeStorage {
|
|
|
10087
10241
|
countChildPages(input) {
|
|
10088
10242
|
return this.#pages.countChildPages(input);
|
|
10089
10243
|
}
|
|
10244
|
+
replacePageBodyLinks(input) {
|
|
10245
|
+
return this.#pages.replacePageBodyLinks(input);
|
|
10246
|
+
}
|
|
10247
|
+
listPageBodyLinkSources(input) {
|
|
10248
|
+
return this.#pages.listPageBodyLinkSources(input);
|
|
10249
|
+
}
|
|
10250
|
+
countPageLinks(input) {
|
|
10251
|
+
return this.#pages.countPageLinks(input);
|
|
10252
|
+
}
|
|
10253
|
+
listPageLinks(input) {
|
|
10254
|
+
return this.#pages.listPageLinks(input);
|
|
10255
|
+
}
|
|
10090
10256
|
countNotes(input) {
|
|
10091
10257
|
return this.#pages.countNotes(input);
|
|
10092
10258
|
}
|