@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.
@@ -318,6 +318,7 @@ export interface KnowledgePagesCore {
318
318
  MAX_KNOWLEDGE_MOVE_PAGES: number;
319
319
  MAX_KNOWLEDGE_PAGE_DEPTH: number;
320
320
  MAX_KNOWLEDGE_PAGE_LIST_LIMIT: number;
321
+ MAX_KNOWLEDGE_PAGE_LINKS: number;
321
322
  KnowledgeMoveTooLargeError: new (pageId: string, pages: number) => Error;
322
323
  KnowledgePageDepthError: new (pageId: string) => Error;
323
324
  KnowledgePageHasChildrenError: new (pageId: string) => Error;
@@ -325,15 +326,19 @@ export interface KnowledgePagesCore {
325
326
  }
326
327
  /** Loads core's page-store runtime once, after checking core advertises `knowledge-pages`. */
327
328
  export declare function createKnowledgePagesCoreLoader(features: ReadonlySet<string>, loadStorageModule: () => Promise<unknown>): () => Promise<KnowledgePagesCore>;
329
+ /** Most words one keyword search uses; every word adds a substring check per page in each store. */
330
+ export declare const KNOWLEDGE_SEARCH_MAX_WORDS = 16;
328
331
  /**
329
- * Keyword-search words: the query split on whitespace, lowercased, deduplicated. A hit needs at
330
- * least one of them (as a case-insensitive substring); an empty list means the search returns nothing.
332
+ * Keyword-search words: the query split on whitespace, lowercased, deduplicated, keeping the first
333
+ * {@link KNOWLEDGE_SEARCH_MAX_WORDS}. A hit needs at least one of them (as a case-insensitive
334
+ * substring); an empty list means the search returns nothing.
331
335
  */
332
336
  export declare function knowledgeSearchWords(query: string): string[];
333
337
  /**
334
338
  * Points one matched search word earns on a page, by the best field it appears in: name, then
335
- * description, then body. Pages rank by how many query words they match first; these points only
336
- * break ties between pages that match the same number of words.
339
+ * description, then body. A page whose name contains the whole query ranks first; below that, pages
340
+ * rank by how many query words they match, and these points only break ties between pages that
341
+ * match the same number of words.
337
342
  */
338
343
  export declare const KNOWLEDGE_SEARCH_WEIGHTS: {
339
344
  readonly name: 3;
@@ -3,7 +3,7 @@ name: mastra-pg
3
3
  description: Documentation for @mastra/pg. Use when working with @mastra/pg APIs, configuration, or implementation.
4
4
  metadata:
5
5
  package: "@mastra/pg"
6
- version: "1.35.0-alpha.2"
6
+ version: "1.35.0"
7
7
  ---
8
8
 
9
9
  ## When to use
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.35.0-alpha.2",
2
+ "version": "1.35.0",
3
3
  "package": "@mastra/pg",
4
4
  "exports": {},
5
5
  "modules": {}
package/dist/index.cjs CHANGED
@@ -8649,7 +8649,8 @@ const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
8649
8649
  const KNOWLEDGE_PAGES_CORE_NUMBERS = [
8650
8650
  "MAX_KNOWLEDGE_MOVE_PAGES",
8651
8651
  "MAX_KNOWLEDGE_PAGE_DEPTH",
8652
- "MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
8652
+ "MAX_KNOWLEDGE_PAGE_LIST_LIMIT",
8653
+ "MAX_KNOWLEDGE_PAGE_LINKS"
8653
8654
  ];
8654
8655
  function resolveKnowledgePagesCore(storageModule) {
8655
8656
  const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
@@ -8670,16 +8671,18 @@ function createKnowledgePagesCoreLoader(features, loadStorageModule) {
8670
8671
  };
8671
8672
  }
8672
8673
  /**
8673
- * Keyword-search words: the query split on whitespace, lowercased, deduplicated. A hit needs at
8674
- * least one of them (as a case-insensitive substring); an empty list means the search returns nothing.
8674
+ * Keyword-search words: the query split on whitespace, lowercased, deduplicated, keeping the first
8675
+ * {@link KNOWLEDGE_SEARCH_MAX_WORDS}. A hit needs at least one of them (as a case-insensitive
8676
+ * substring); an empty list means the search returns nothing.
8675
8677
  */
8676
8678
  function knowledgeSearchWords(query) {
8677
- return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
8679
+ return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))].slice(0, 16);
8678
8680
  }
8679
8681
  /**
8680
8682
  * Points one matched search word earns on a page, by the best field it appears in: name, then
8681
- * description, then body. Pages rank by how many query words they match first; these points only
8682
- * break ties between pages that match the same number of words.
8683
+ * description, then body. A page whose name contains the whole query ranks first; below that, pages
8684
+ * rank by how many query words they match, and these points only break ties between pages that
8685
+ * match the same number of words.
8683
8686
  */
8684
8687
  const KNOWLEDGE_SEARCH_WEIGHTS = {
8685
8688
  name: 3,
@@ -8705,7 +8708,8 @@ const PG_KNOWLEDGE_PAGES_TABLES = {
8705
8708
  mentions: "mastra_knowledge_mentions",
8706
8709
  pins: "mastra_knowledge_pins",
8707
8710
  activity: "mastra_knowledge_activity",
8708
- state: "mastra_knowledge_access_state"
8711
+ state: "mastra_knowledge_access_state",
8712
+ links: "mastra_knowledge_page_links"
8709
8713
  };
8710
8714
  /**
8711
8715
  * Schema-4 DDL. `COLLATE "C"` on every column that is ordered, prefix-matched or compared for
@@ -8764,7 +8768,9 @@ function pgKnowledgePagesDDL(schemaName) {
8764
8768
  )`,
8765
8769
  `CREATE TABLE IF NOT EXISTS ${q(t.pins)} (
8766
8770
  "id" text PRIMARY KEY,
8767
- "pageId" text NOT NULL UNIQUE REFERENCES ${q(t.pages)} ("id"),
8771
+ "pageId" text NOT NULL REFERENCES ${q(t.pages)} ("id"),
8772
+ "kind" text NOT NULL DEFAULT 'page',
8773
+ "depth" integer,
8768
8774
  "audience" text[] NOT NULL,
8769
8775
  "createdBy" text NOT NULL,
8770
8776
  "createdAt" timestamptz NOT NULL,
@@ -8796,6 +8802,19 @@ function pgKnowledgePagesDDL(schemaName) {
8796
8802
  index("mastra_knowledge_mentions_page_idx", t.mentions, "\"pageId\""),
8797
8803
  `CREATE INDEX IF NOT EXISTS "mastra_knowledge_pins_audience_gin" ON ${q(t.pins)} USING gin ("audience")`,
8798
8804
  index("mastra_knowledge_pins_created_idx", t.pins, "\"createdAt\",\"id\""),
8805
+ `ALTER TABLE ${q(t.pins)} ADD COLUMN IF NOT EXISTS "kind" text NOT NULL DEFAULT 'page'`,
8806
+ `ALTER TABLE ${q(t.pins)} ADD COLUMN IF NOT EXISTS "depth" integer`,
8807
+ `ALTER TABLE ${q(t.pins)} DROP CONSTRAINT IF EXISTS "${t.pins}_pageId_key"`,
8808
+ index("mastra_knowledge_pins_page_kind_idx", t.pins, "\"pageId\",\"kind\"", "", true),
8809
+ `CREATE TABLE IF NOT EXISTS ${q(t.links)} (
8810
+ "fromPageId" text NOT NULL,
8811
+ "ref" text COLLATE "C" NOT NULL,
8812
+ "toPageId" text,
8813
+ "count" integer NOT NULL,
8814
+ PRIMARY KEY ("fromPageId", "ref")
8815
+ )`,
8816
+ index("mastra_knowledge_page_links_to_idx", t.links, "\"toPageId\"", "\"toPageId\" IS NOT NULL"),
8817
+ index("mastra_knowledge_page_links_ref_idx", t.links, "\"ref\"", "\"toPageId\" IS NULL"),
8799
8818
  index("mastra_knowledge_activity_page_idx", t.activity, "\"pageId\",\"createdAt\",\"id\""),
8800
8819
  index("mastra_knowledge_activity_created_idx", t.activity, "\"createdAt\",\"id\"")
8801
8820
  ];
@@ -8940,6 +8959,8 @@ function toPin(row) {
8940
8959
  return {
8941
8960
  id: String(row.id),
8942
8961
  pageId: String(row.pageId),
8962
+ kind: String(row.kind),
8963
+ ...row.depth == null ? {} : { depth: Number(row.depth) },
8943
8964
  audience: row.audience ?? [],
8944
8965
  createdBy: String(row.createdBy),
8945
8966
  createdAt: new Date(row.createdAt),
@@ -8991,6 +9012,9 @@ var PgKnowledgePages = class {
8991
9012
  get #pins() {
8992
9013
  return this.#q(PG_KNOWLEDGE_PAGES_TABLES.pins);
8993
9014
  }
9015
+ get #links() {
9016
+ return this.#q(PG_KNOWLEDGE_PAGES_TABLES.links);
9017
+ }
8994
9018
  get #activity() {
8995
9019
  return this.#q(PG_KNOWLEDGE_PAGES_TABLES.activity);
8996
9020
  }
@@ -9227,6 +9251,7 @@ var PgKnowledgePages = class {
9227
9251
  const params = new Params();
9228
9252
  const where = [`"isScope"`, `"deletedAt" IS NULL`];
9229
9253
  where.push(input.parentId === null ? `"parentId" IS NULL` : `"parentId" = ${params.add(input.parentId)}`);
9254
+ if (input.read && input.read !== "all") where.push(`"id" IN (${new ReadFilter(input.read, params, this.#pages).readableIds()})`);
9230
9255
  if (input.cursor) {
9231
9256
  const [position, id] = decodeCursor(input.cursor, 2);
9232
9257
  where.push(`("position", "id") > (${params.add(position)}, ${params.add(id)})`);
@@ -9502,9 +9527,17 @@ var PgKnowledgePages = class {
9502
9527
  await tx.query(`UPDATE ${this.#notes} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
9503
9528
  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]);
9504
9529
  await tx.query(`UPDATE ${this.#mentions} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
9505
- const sourcePin = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [source.id]))[0];
9506
- if (sourcePin) {
9507
- await this.#upsertPinAudience(tx, target.id, sourcePin.audience ?? [], String(sourcePin.createdBy), now);
9530
+ const sourcePins = await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 ORDER BY "kind" FOR UPDATE`, [source.id]);
9531
+ for (const sourcePin of sourcePins) {
9532
+ await this.#upsertPinAudience(tx, {
9533
+ pageId: target.id,
9534
+ kind: String(sourcePin.kind),
9535
+ depth: sourcePin.depth == null ? null : Number(sourcePin.depth),
9536
+ audience: sourcePin.audience ?? [],
9537
+ createdBy: String(sourcePin.createdBy),
9538
+ now,
9539
+ keepDepth: true
9540
+ });
9508
9541
  await tx.query(`DELETE FROM ${this.#pins} WHERE "id" = $1`, [String(sourcePin.id)]);
9509
9542
  }
9510
9543
  const rows = await this.#rows(tx, `SELECT * FROM ${this.#pages} WHERE "id" = $1`, [target.id]);
@@ -9564,6 +9597,18 @@ var PgKnowledgePages = class {
9564
9597
  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);
9565
9598
  return this.#noteOutput(rows, limit);
9566
9599
  }
9600
+ async countChildScopes(input) {
9601
+ const ids = [...new Set(input.parentIds)];
9602
+ const counts = Object.fromEntries(ids.map((id) => [id, 0]));
9603
+ if (!ids.length) return counts;
9604
+ const params = new Params();
9605
+ const filter = new ReadFilter(input.read, params, this.#pages);
9606
+ const rows = await this.#rows(this.#client, `SELECT s."parentId" AS "parentId", count(*)::int AS "count" FROM ${this.#pages} s
9607
+ WHERE s."parentId" = ANY(${params.add(ids)}::text[]) AND s."isScope" AND s."deletedAt" IS NULL AND ${filter.readable("s")}
9608
+ GROUP BY s."parentId"`, params.values);
9609
+ for (const row of rows) counts[row.parentId] = Number(row.count);
9610
+ return counts;
9611
+ }
9567
9612
  async countChildPages(input) {
9568
9613
  const ids = [...new Set(input.parentIds)];
9569
9614
  const counts = Object.fromEntries(ids.map((id) => [id, 0]));
@@ -9576,6 +9621,94 @@ var PgKnowledgePages = class {
9576
9621
  for (const row of rows) counts[row.parentId] = Number(row.count);
9577
9622
  return counts;
9578
9623
  }
9624
+ async replacePageBodyLinks(input) {
9625
+ await this.#tx(async (tx) => {
9626
+ await tx.query(`DELETE FROM ${this.#links} WHERE "fromPageId" = $1`, [input.pageId]);
9627
+ if (!input.links.length) return;
9628
+ 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)`, [
9629
+ input.pageId,
9630
+ input.links.map((l) => l.ref),
9631
+ input.links.map((l) => l.toPageId),
9632
+ input.links.map((l) => l.count)
9633
+ ]);
9634
+ });
9635
+ }
9636
+ async listPageBodyLinkSources(input) {
9637
+ const refs = [...new Set(input.refs ?? [])];
9638
+ const targets = [...new Set(input.toPageIds ?? [])];
9639
+ if (!refs.length && !targets.length) return [];
9640
+ const parts = [];
9641
+ const params = new Params();
9642
+ if (refs.length) parts.push(`("toPageId" IS NULL AND "ref" = ANY(${params.add(refs)}::text[]))`);
9643
+ if (targets.length) parts.push(`"toPageId" = ANY(${params.add(targets)}::text[])`);
9644
+ return (await this.#rows(this.#client, `SELECT DISTINCT "fromPageId" FROM ${this.#links} WHERE ${parts.join(" OR ")}`, params.values)).map((row) => String(row.fromPageId));
9645
+ }
9646
+ /**
9647
+ * Every resolved link touching `ids` whose both ends are live and readable, one row per
9648
+ * (from, to, kind); no self-links. Note links go through the same visible-note rule as listNotes.
9649
+ */
9650
+ #visibleLinkEdges(filter, params, ids, direction, kinds) {
9651
+ const wanted = new Set(kinds ?? ["body", "note"]);
9652
+ const idsParam = `${params.add(ids)}::text[]`;
9653
+ const touch = (from, to) => direction === "out" ? `${from} = ANY(${idsParam})` : direction === "in" ? `${to} = ANY(${idsParam})` : `(${from} = ANY(${idsParam}) OR ${to} = ANY(${idsParam}))`;
9654
+ const branches = [];
9655
+ 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"
9656
+ FROM ${this.#links} l
9657
+ JOIN ${this.#pages} fp ON fp."id" = l."fromPageId" AND fp."deletedAt" IS NULL
9658
+ JOIN ${this.#pages} fs ON fs."id" = fp."scopeId"
9659
+ JOIN ${this.#pages} tp ON tp."id" = l."toPageId" AND tp."deletedAt" IS NULL
9660
+ JOIN ${this.#pages} ts ON ts."id" = tp."scopeId"
9661
+ WHERE l."toPageId" IS NOT NULL AND l."fromPageId" <> l."toPageId" AND ${touch("l.\"fromPageId\"", "l.\"toPageId\"")}
9662
+ AND ${filter.readable("fs")} AND ${filter.readable("ts")}
9663
+ GROUP BY l."fromPageId", l."toPageId"`);
9664
+ if (wanted.has("note")) branches.push(`SELECT vn."pageId" AS "fromPageId", mn."pageId" AS "toPageId", 'note'::text AS "kind", count(*)::int AS "count"
9665
+ FROM (SELECT n."id", n."pageId" FROM ${this.#visibleNotes(filter)}
9666
+ AND n."deletedAt" IS NULL AND np."deletedAt" IS NULL
9667
+ AND (n."pageId" = ANY(${idsParam}) OR n."id" IN (SELECT mm."noteId" FROM ${this.#mentions} mm WHERE mm."pageId" = ANY(${idsParam})))) vn
9668
+ JOIN ${this.#mentions} mn ON mn."noteId" = vn."id"
9669
+ JOIN ${this.#pages} tp ON tp."id" = mn."pageId" AND tp."deletedAt" IS NULL
9670
+ JOIN ${this.#pages} ts ON ts."id" = tp."scopeId"
9671
+ WHERE mn."pageId" <> vn."pageId" AND ${touch("vn.\"pageId\"", "mn.\"pageId\"")} AND ${filter.readable("ts")}
9672
+ GROUP BY vn."pageId", mn."pageId"`);
9673
+ 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`;
9674
+ }
9675
+ async countPageLinks(input) {
9676
+ const ids = [...new Set(input.pageIds)];
9677
+ const counts = Object.fromEntries(ids.map((id) => [id, {
9678
+ in: 0,
9679
+ out: 0
9680
+ }]));
9681
+ if (!ids.length) return counts;
9682
+ const params = new Params();
9683
+ const filter = new ReadFilter(input.read, params, this.#pages);
9684
+ const edges = this.#visibleLinkEdges(filter, params, ids, "both", input.kinds);
9685
+ const rows = await this.#rows(this.#client, `WITH e AS (${edges}), r AS (
9686
+ 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"
9687
+ UNION ALL
9688
+ SELECT "toPageId", 0, count(DISTINCT "fromPageId")::int FROM e WHERE "toPageId" = ANY(${params.add(ids)}::text[]) GROUP BY "toPageId")
9689
+ SELECT "id", sum("out")::int AS "out", sum("in")::int AS "in" FROM r GROUP BY "id"`, params.values);
9690
+ for (const row of rows) counts[String(row.id)] = {
9691
+ in: Number(row.in),
9692
+ out: Number(row.out)
9693
+ };
9694
+ return counts;
9695
+ }
9696
+ async listPageLinks(input) {
9697
+ const limit = input.limit ?? 1e3;
9698
+ if (!Number.isInteger(limit) || limit < 1) throw new RangeError("Knowledge link limit must be a positive integer");
9699
+ const core = await loadPagesCore();
9700
+ const ids = [...new Set(input.pageIds)];
9701
+ if (!ids.length) return [];
9702
+ const params = new Params();
9703
+ const filter = new ReadFilter(input.read, params, this.#pages);
9704
+ const edges = this.#visibleLinkEdges(filter, params, ids, input.direction ?? "both", input.kinds);
9705
+ 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) => ({
9706
+ fromPageId: String(row.fromPageId),
9707
+ toPageId: String(row.toPageId),
9708
+ kind: row.kind,
9709
+ count: Number(row.count)
9710
+ }));
9711
+ }
9579
9712
  async countNotes(input) {
9580
9713
  const ids = [...new Set(input.pageIds)];
9581
9714
  const counts = Object.fromEntries(ids.map((id) => [id, 0]));
@@ -9690,7 +9823,7 @@ var PgKnowledgePages = class {
9690
9823
  async removeNote(input) {
9691
9824
  return this.#tx(async (tx) => {
9692
9825
  const note = await this.#noteById(tx, input.id, true);
9693
- if (note.deletedAt) return note;
9826
+ if (note.deletedAt) throw new _mastra_core_storage.KnowledgeNotFoundError("note", input.id);
9694
9827
  const now = /* @__PURE__ */ new Date();
9695
9828
  await tx.query(`UPDATE ${this.#notes} SET "deletedAt" = $2, "deletedBy" = $3, "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [
9696
9829
  input.id,
@@ -9802,10 +9935,18 @@ var PgKnowledgePages = class {
9802
9935
  };
9803
9936
  }
9804
9937
  async addPinAudience(input) {
9938
+ const kind = input.kind ?? "page";
9805
9939
  return this.#tx(async (tx) => {
9806
9940
  const page = await this.#requireLivePage(tx, input.pageId, false);
9807
9941
  const now = /* @__PURE__ */ new Date();
9808
- const { pin, changed } = await this.#upsertPinAudience(tx, page.id, input.audience, input.createdBy, now);
9942
+ const { pin, changed } = await this.#upsertPinAudience(tx, {
9943
+ pageId: page.id,
9944
+ kind,
9945
+ depth: kind === "list" ? input.depth ?? 1 : null,
9946
+ audience: input.audience,
9947
+ createdBy: input.createdBy,
9948
+ now
9949
+ });
9809
9950
  if (changed) await this.#logActivity(tx, {
9810
9951
  action: "pin-added",
9811
9952
  scopeId: page.scopeId,
@@ -9816,28 +9957,35 @@ var PgKnowledgePages = class {
9816
9957
  return pin;
9817
9958
  });
9818
9959
  }
9819
- async #upsertPinAudience(db, pageId, audience, createdBy, now) {
9820
- const existing = (await this.#rows(db, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [pageId]))[0];
9960
+ /** `keepDepth` (merges) leaves an existing pin's depth as it is instead of setting `depth`. */
9961
+ async #upsertPinAudience(db, input) {
9962
+ const { pageId, kind, audience, now } = input;
9963
+ const existing = (await this.#rows(db, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 AND "kind" = $2 FOR UPDATE`, [pageId, kind]))[0];
9821
9964
  if (!existing) return {
9822
- 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 *`, [
9965
+ 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 *`, [
9823
9966
  (0, _mastra_core_storage.createKnowledgeUlid)(),
9824
9967
  pageId,
9968
+ kind,
9969
+ input.depth,
9825
9970
  [...new Set(audience)],
9826
- createdBy,
9971
+ input.createdBy,
9827
9972
  iso(now)
9828
9973
  ]))[0]),
9829
9974
  changed: true
9830
9975
  };
9831
9976
  const current = existing.audience ?? [];
9832
9977
  const merged = [.../* @__PURE__ */ new Set([...current, ...audience])];
9833
- if (merged.length === current.length) return {
9978
+ const currentDepth = existing.depth == null ? null : Number(existing.depth);
9979
+ const depth = input.keepDepth ? currentDepth : input.depth;
9980
+ if (merged.length === current.length && depth === currentDepth) return {
9834
9981
  pin: toPin(existing),
9835
9982
  changed: false
9836
9983
  };
9837
9984
  return {
9838
- pin: toPin((await this.#rows(db, `UPDATE ${this.#pins} SET "audience" = $2::text[], "updatedAt" = $3 WHERE "id" = $1 RETURNING *`, [
9985
+ pin: toPin((await this.#rows(db, `UPDATE ${this.#pins} SET "audience" = $2::text[], "depth" = $3, "updatedAt" = $4 WHERE "id" = $1 RETURNING *`, [
9839
9986
  String(existing.id),
9840
9987
  merged,
9988
+ depth,
9841
9989
  iso(now)
9842
9990
  ]))[0]),
9843
9991
  changed: true
@@ -9845,7 +9993,7 @@ var PgKnowledgePages = class {
9845
9993
  }
9846
9994
  async removePinAudience(input) {
9847
9995
  return this.#tx(async (tx) => {
9848
- const existing = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [input.pageId]))[0];
9996
+ const existing = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 AND "kind" = $2 FOR UPDATE`, [input.pageId, input.kind ?? "page"]))[0];
9849
9997
  if (!existing) return null;
9850
9998
  const current = existing.audience ?? [];
9851
9999
  const remaining = current.filter((entry) => !input.audience.includes(entry));
@@ -9877,6 +10025,7 @@ var PgKnowledgePages = class {
9877
10025
  t.mentions,
9878
10026
  t.stamps,
9879
10027
  t.notes,
10028
+ t.links,
9880
10029
  t.pages
9881
10030
  ].map((name) => this.#q(name)).join(", ");
9882
10031
  await this.#client.query(`TRUNCATE ${tables}`);
@@ -9895,9 +10044,10 @@ var PgKnowledgePages = class {
9895
10044
  const params = new Params();
9896
10045
  const where = [`p."deletedAt" IS NULL`, new ReadFilter(input.read, params, this.#pages).readable("s")];
9897
10046
  if (input.audience !== "all") where.push(`pn."audience" && ${params.add(input.audience)}::text[]`);
10047
+ if (input.kind) where.push(`pn."kind" = ${params.add(input.kind)}`);
9898
10048
  if (input.pageIds) where.push(`pn."pageId" = ANY(${params.add(input.pageIds)}::text[])`);
9899
- if (input.excludePageIds?.length) where.push(`pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[])`);
9900
- if (input.withBody) where.push(`p."body" ~ '\\S'`);
10049
+ if (input.excludePageIds?.length) where.push(`(pn."kind" <> 'page' OR pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[]))`);
10050
+ if (input.withBody) where.push(`(pn."kind" <> 'page' OR p."body" ~ '\\S')`);
9901
10051
  const from = `FROM ${this.#pins} pn JOIN ${this.#pages} p ON p."id" = pn."pageId" JOIN ${this.#pages} s ON s."id" = p."scopeId"`;
9902
10052
  const totalWhere = where.join(" AND ");
9903
10053
  if (input.cursor) {
@@ -9963,7 +10113,7 @@ async function pgKnowledgeSnapshot(db, schemaName) {
9963
10113
  }
9964
10114
  /** Drops exactly the Knowledge tables (never Memory's) and recreates schema 4. */
9965
10115
  async function resetPgKnowledgePagesSchema(client, schemaName) {
9966
- const tables = KNOWLEDGE_RESETTABLE_TABLE_NAMES.map((table) => qualify(schemaName, table)).join(", ");
10116
+ const tables = [...KNOWLEDGE_RESETTABLE_TABLE_NAMES, PG_KNOWLEDGE_PAGES_TABLES.links].map((table) => qualify(schemaName, table)).join(", ");
9967
10117
  await client.query(`DROP TABLE IF EXISTS ${tables} CASCADE`);
9968
10118
  await initPgKnowledgePagesSchema(client, schemaName);
9969
10119
  }
@@ -10038,7 +10188,8 @@ var KnowledgePG = class extends _mastra_core_storage.KnowledgeStorage {
10038
10188
  ...super.getCapabilities(),
10039
10189
  schemaVersion: 4,
10040
10190
  supportsSchemaInspection: true,
10041
- supportsExplicitReset: true
10191
+ supportsExplicitReset: true,
10192
+ supportsPinKinds: true
10042
10193
  };
10043
10194
  }
10044
10195
  async init() {
@@ -10068,6 +10219,9 @@ var KnowledgePG = class extends _mastra_core_storage.KnowledgeStorage {
10068
10219
  listChildScopes(input) {
10069
10220
  return this.#pages.listChildScopes(input);
10070
10221
  }
10222
+ countChildScopes(input) {
10223
+ return this.#pages.countChildScopes(input);
10224
+ }
10071
10225
  getPages(input) {
10072
10226
  return this.#pages.getPages(input);
10073
10227
  }
@@ -10110,6 +10264,18 @@ var KnowledgePG = class extends _mastra_core_storage.KnowledgeStorage {
10110
10264
  countChildPages(input) {
10111
10265
  return this.#pages.countChildPages(input);
10112
10266
  }
10267
+ replacePageBodyLinks(input) {
10268
+ return this.#pages.replacePageBodyLinks(input);
10269
+ }
10270
+ listPageBodyLinkSources(input) {
10271
+ return this.#pages.listPageBodyLinkSources(input);
10272
+ }
10273
+ countPageLinks(input) {
10274
+ return this.#pages.countPageLinks(input);
10275
+ }
10276
+ listPageLinks(input) {
10277
+ return this.#pages.listPageLinks(input);
10278
+ }
10113
10279
  countNotes(input) {
10114
10280
  return this.#pages.countNotes(input);
10115
10281
  }