@mastra/pg 1.35.0-alpha.2 → 1.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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. A hit needs at
8651
- * least one of them (as a case-insensitive substring); an empty list means the search returns nothing.
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. Pages rank by how many query words they match first; these points only
8659
- * break ties between pages that match the same number of words.
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 UNIQUE REFERENCES ${q(t.pages)} ("id"),
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 sourcePin = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [source.id]))[0];
9483
- if (sourcePin) {
9484
- await this.#upsertPinAudience(tx, target.id, sourcePin.audience ?? [], String(sourcePin.createdBy), now);
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) return note;
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, page.id, input.audience, input.createdBy, now);
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
- async #upsertPinAudience(db, pageId, audience, createdBy, now) {
9797
- const existing = (await this.#rows(db, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [pageId]))[0];
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[],$4,$5,$5) RETURNING *`, [
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
- if (merged.length === current.length) return {
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[], "updatedAt" = $3 WHERE "id" = $1 RETURNING *`, [
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
  }