@mastra/pg 1.35.0-alpha.1 → 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.
Files changed (43) hide show
  1. package/dist/_types/{@internal_core → @internal_core@0.1.4}/dist/knowledge-compat/index.d.ts +17 -2
  2. package/dist/index.cjs +252 -39
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +252 -39
  5. package/dist/index.js.map +1 -1
  6. package/dist/storage/domains/agents/index.d.ts.map +1 -1
  7. package/dist/storage/domains/knowledge/index.d.ts +8 -1
  8. package/dist/storage/domains/knowledge/index.d.ts.map +1 -1
  9. package/dist/storage/domains/knowledge/pages-schema.d.ts +1 -1
  10. package/dist/storage/domains/knowledge/pages-schema.d.ts.map +1 -1
  11. package/dist/storage/domains/knowledge/pages.d.ts +34 -11
  12. package/dist/storage/domains/knowledge/pages.d.ts.map +1 -1
  13. package/dist/storage/domains/memory/index.d.ts +1 -1
  14. package/dist/storage/domains/observability/v-next/index.d.ts +1 -1
  15. package/dist/storage/domains/observability/v-next/index.d.ts.map +1 -1
  16. package/dist/storage/domains/observability/v-next/trace-query.d.ts +2 -0
  17. package/dist/storage/domains/observability/v-next/trace-query.d.ts.map +1 -1
  18. package/package.json +7 -7
  19. package/dist/docs/SKILL.md +0 -48
  20. package/dist/docs/assets/SOURCE_MAP.json +0 -6
  21. package/dist/docs/references/docs-deployment-workers.md +0 -404
  22. package/dist/docs/references/docs-memory-semantic-recall.md +0 -485
  23. package/dist/docs/references/docs-memory-working-memory.md +0 -450
  24. package/dist/docs/references/docs-storage.md +0 -224
  25. package/dist/docs/references/integrations-databases-clickhouse-managed-postgres.md +0 -82
  26. package/dist/docs/references/integrations-databases-dynamodb.md +0 -286
  27. package/dist/docs/references/integrations-databases-neon.md +0 -222
  28. package/dist/docs/references/integrations-databases-postgresql.md +0 -592
  29. package/dist/docs/references/integrations-vectors-pg.md +0 -451
  30. package/dist/docs/references/reference-memory-memory-class.md +0 -237
  31. package/dist/docs/references/reference-processors-message-history-processor.md +0 -89
  32. package/dist/docs/references/reference-processors-semantic-recall-processor.md +0 -119
  33. package/dist/docs/references/reference-processors-working-memory-processor.md +0 -154
  34. package/dist/docs/references/reference-rag-chunking-and-embedding.md +0 -184
  35. package/dist/docs/references/reference-rag-metadata-filters.md +0 -238
  36. package/dist/docs/references/reference-rag-overview.md +0 -76
  37. package/dist/docs/references/reference-rag-retrieval.md +0 -672
  38. package/dist/docs/references/reference-rag-vector-databases.md +0 -808
  39. package/dist/docs/references/reference-storage-composite.md +0 -433
  40. package/dist/docs/references/reference-storage-retention.md +0 -310
  41. package/dist/docs/references/reference-tools-vector-query-tool.md +0 -500
  42. /package/dist/_types/{@internal_core → @internal_core@0.1.4}/dist/memory/index.d.ts +0 -0
  43. /package/dist/_types/{@internal_core → @internal_core@0.1.4}/package.json +0 -0
package/dist/index.js CHANGED
@@ -4443,7 +4443,8 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4443
4443
  "skillsFormat",
4444
4444
  "durable",
4445
4445
  "browser",
4446
- "toolProviders"
4446
+ "toolProviders",
4447
+ "modelList"
4447
4448
  ]
4448
4449
  });
4449
4450
  await this.#migrateToolsToJsonbFormat();
@@ -4501,10 +4502,10 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4501
4502
  row.updatedAt ?? now
4502
4503
  ]);
4503
4504
  await this.#db.client.none(`INSERT INTO ${fullVersionsTableName}
4504
- (id, "agentId", "versionNumber", name, description, instructions, model, tools,
4505
+ (id, "agentId", "versionNumber", name, description, instructions, model, "modelList", tools,
4505
4506
  "defaultOptions", workflows, agents, "integrationTools", "toolProviders", "inputProcessors",
4506
4507
  "outputProcessors", memory, scorers, "changedFields", "changeMessage", "createdAt")
4507
- VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20)
4508
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21)
4508
4509
  ON CONFLICT (id) DO NOTHING`, [
4509
4510
  versionId,
4510
4511
  agentId,
@@ -4513,6 +4514,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4513
4514
  row.description ?? null,
4514
4515
  this.serializeInstructions(row.instructions ?? ""),
4515
4516
  row.model ? toPgJson(row.model) : "{}",
4517
+ row.modelList ? toPgJson(row.modelList) : null,
4516
4518
  row.tools ? toPgJson(row.tools) : null,
4517
4519
  row.defaultOptions ? toPgJson(row.defaultOptions) : null,
4518
4520
  row.workflows ? toPgJson(row.workflows) : null,
@@ -4926,21 +4928,22 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
4926
4928
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
4927
4929
  const row = await this.#db.client.one(`INSERT INTO ${tableName} (
4928
4930
  id, "agentId", "versionNumber",
4929
- name, description, instructions, model, tools,
4931
+ name, description, instructions, model, "modelList", tools,
4930
4932
  "defaultOptions", workflows, agents, "integrationTools", "toolProviders",
4931
4933
  "inputProcessors", "outputProcessors", memory, scorers,
4932
4934
  "mcpClients", "requestContextSchema", workspace, skills, "skillsFormat",
4933
4935
  durable, browser,
4934
4936
  "changedFields", "changeMessage",
4935
4937
  "createdAt", "createdAtZ"
4936
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28) RETURNING *`, [
4938
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29) RETURNING *`, [
4937
4939
  input.id,
4938
4940
  input.agentId,
4939
4941
  input.versionNumber,
4940
4942
  input.name,
4941
4943
  input.description ?? null,
4942
- this.serializeInstructions(input.instructions),
4943
- toPgJson(input.model),
4944
+ this.serializeInstructions(input.instructions) ?? "",
4945
+ toPgJson(input.model ?? {}),
4946
+ input.modelList ? toPgJson(input.modelList) : null,
4944
4947
  input.tools ? toPgJson(input.tools) : null,
4945
4948
  input.defaultOptions ? toPgJson(input.defaultOptions) : null,
4946
4949
  input.workflows ? toPgJson(input.workflows) : null,
@@ -5186,6 +5189,7 @@ var AgentsPG = class AgentsPG extends AgentsStorage {
5186
5189
  description: row.description,
5187
5190
  instructions: this.deserializeInstructions(row.instructions),
5188
5191
  model: parseJsonResilient(row.model, "model"),
5192
+ modelList: parseJsonResilient(row.modelList, "modelList"),
5189
5193
  tools: parseJsonResilient(row.tools, "tools"),
5190
5194
  defaultOptions: parseJsonResilient(row.defaultOptions, "defaultOptions"),
5191
5195
  workflows: parseJsonResilient(row.workflows, "workflows"),
@@ -8622,7 +8626,8 @@ const KNOWLEDGE_PAGES_CORE_FUNCTIONS = [
8622
8626
  const KNOWLEDGE_PAGES_CORE_NUMBERS = [
8623
8627
  "MAX_KNOWLEDGE_MOVE_PAGES",
8624
8628
  "MAX_KNOWLEDGE_PAGE_DEPTH",
8625
- "MAX_KNOWLEDGE_PAGE_LIST_LIMIT"
8629
+ "MAX_KNOWLEDGE_PAGE_LIST_LIMIT",
8630
+ "MAX_KNOWLEDGE_PAGE_LINKS"
8626
8631
  ];
8627
8632
  function resolveKnowledgePagesCore(storageModule) {
8628
8633
  const module = typeof storageModule === "object" && storageModule !== null ? storageModule : {};
@@ -8643,13 +8648,25 @@ function createKnowledgePagesCoreLoader(features, loadStorageModule) {
8643
8648
  };
8644
8649
  }
8645
8650
  /**
8646
- * Keyword-search words: the query split on whitespace, lowercased, deduplicated. Every word must
8647
- * appear (as a case-insensitive substring) in a hit; 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.
8648
8654
  */
8649
8655
  function knowledgeSearchWords(query) {
8650
- return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))];
8656
+ return [...new Set(query.toLowerCase().split(/\s+/).filter(Boolean))].slice(0, 16);
8651
8657
  }
8652
8658
  /**
8659
+ * Points one matched search word earns on a page, by the best field it appears in: name, then
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.
8663
+ */
8664
+ const KNOWLEDGE_SEARCH_WEIGHTS = {
8665
+ name: 3,
8666
+ description: 2,
8667
+ body: 1
8668
+ };
8669
+ /**
8653
8670
  * Why `key` can't be a Knowledge page key, or undefined when it can. Keys are one path segment:
8654
8671
  * non-empty, with no `/` (path separator), `#` (scope/path separator) or `%` (escape character).
8655
8672
  */
@@ -8668,7 +8685,8 @@ const PG_KNOWLEDGE_PAGES_TABLES = {
8668
8685
  mentions: "mastra_knowledge_mentions",
8669
8686
  pins: "mastra_knowledge_pins",
8670
8687
  activity: "mastra_knowledge_activity",
8671
- state: "mastra_knowledge_access_state"
8688
+ state: "mastra_knowledge_access_state",
8689
+ links: "mastra_knowledge_page_links"
8672
8690
  };
8673
8691
  /**
8674
8692
  * Schema-4 DDL. `COLLATE "C"` on every column that is ordered, prefix-matched or compared for
@@ -8727,7 +8745,9 @@ function pgKnowledgePagesDDL(schemaName) {
8727
8745
  )`,
8728
8746
  `CREATE TABLE IF NOT EXISTS ${q(t.pins)} (
8729
8747
  "id" text PRIMARY KEY,
8730
- "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,
8731
8751
  "audience" text[] NOT NULL,
8732
8752
  "createdBy" text NOT NULL,
8733
8753
  "createdAt" timestamptz NOT NULL,
@@ -8759,6 +8779,19 @@ function pgKnowledgePagesDDL(schemaName) {
8759
8779
  index("mastra_knowledge_mentions_page_idx", t.mentions, "\"pageId\""),
8760
8780
  `CREATE INDEX IF NOT EXISTS "mastra_knowledge_pins_audience_gin" ON ${q(t.pins)} USING gin ("audience")`,
8761
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"),
8762
8795
  index("mastra_knowledge_activity_page_idx", t.activity, "\"pageId\",\"createdAt\",\"id\""),
8763
8796
  index("mastra_knowledge_activity_created_idx", t.activity, "\"createdAt\",\"id\"")
8764
8797
  ];
@@ -8903,6 +8936,8 @@ function toPin(row) {
8903
8936
  return {
8904
8937
  id: String(row.id),
8905
8938
  pageId: String(row.pageId),
8939
+ kind: String(row.kind),
8940
+ ...row.depth == null ? {} : { depth: Number(row.depth) },
8906
8941
  audience: row.audience ?? [],
8907
8942
  createdBy: String(row.createdBy),
8908
8943
  createdAt: new Date(row.createdAt),
@@ -8954,6 +8989,9 @@ var PgKnowledgePages = class {
8954
8989
  get #pins() {
8955
8990
  return this.#q(PG_KNOWLEDGE_PAGES_TABLES.pins);
8956
8991
  }
8992
+ get #links() {
8993
+ return this.#q(PG_KNOWLEDGE_PAGES_TABLES.links);
8994
+ }
8957
8995
  get #activity() {
8958
8996
  return this.#q(PG_KNOWLEDGE_PAGES_TABLES.activity);
8959
8997
  }
@@ -9190,6 +9228,7 @@ var PgKnowledgePages = class {
9190
9228
  const params = new Params();
9191
9229
  const where = [`"isScope"`, `"deletedAt" IS NULL`];
9192
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()})`);
9193
9232
  if (input.cursor) {
9194
9233
  const [position, id] = decodeCursor(input.cursor, 2);
9195
9234
  where.push(`("position", "id") > (${params.add(position)}, ${params.add(id)})`);
@@ -9465,9 +9504,17 @@ var PgKnowledgePages = class {
9465
9504
  await tx.query(`UPDATE ${this.#notes} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
9466
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]);
9467
9506
  await tx.query(`UPDATE ${this.#mentions} SET "pageId" = $2 WHERE "pageId" = $1`, [source.id, target.id]);
9468
- const sourcePin = (await this.#rows(tx, `SELECT * FROM ${this.#pins} WHERE "pageId" = $1 FOR UPDATE`, [source.id]))[0];
9469
- if (sourcePin) {
9470
- 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
+ });
9471
9518
  await tx.query(`DELETE FROM ${this.#pins} WHERE "id" = $1`, [String(sourcePin.id)]);
9472
9519
  }
9473
9520
  const rows = await this.#rows(tx, `SELECT * FROM ${this.#pages} WHERE "id" = $1`, [target.id]);
@@ -9527,6 +9574,118 @@ var PgKnowledgePages = class {
9527
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);
9528
9575
  return this.#noteOutput(rows, limit);
9529
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
+ }
9589
+ async countChildPages(input) {
9590
+ const ids = [...new Set(input.parentIds)];
9591
+ const counts = Object.fromEntries(ids.map((id) => [id, 0]));
9592
+ if (!ids.length) return counts;
9593
+ const params = new Params();
9594
+ const filter = new ReadFilter(input.read, params, this.#pages);
9595
+ const rows = await this.#rows(this.#client, `SELECT p."parentId" AS "parentId", count(*)::int AS "count" FROM ${this.#pages} p JOIN ${this.#pages} s ON s."id" = p."scopeId"
9596
+ WHERE p."parentId" = ANY(${params.add(ids)}::text[]) AND NOT p."isScope" AND p."deletedAt" IS NULL AND ${filter.readable("s")}
9597
+ GROUP BY p."parentId"`, params.values);
9598
+ for (const row of rows) counts[row.parentId] = Number(row.count);
9599
+ return counts;
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
+ }
9530
9689
  async countNotes(input) {
9531
9690
  const ids = [...new Set(input.pageIds)];
9532
9691
  const counts = Object.fromEntries(ids.map((id) => [id, 0]));
@@ -9641,7 +9800,7 @@ var PgKnowledgePages = class {
9641
9800
  async removeNote(input) {
9642
9801
  return this.#tx(async (tx) => {
9643
9802
  const note = await this.#noteById(tx, input.id, true);
9644
- if (note.deletedAt) return note;
9803
+ if (note.deletedAt) throw new KnowledgeNotFoundError("note", input.id);
9645
9804
  const now = /* @__PURE__ */ new Date();
9646
9805
  await tx.query(`UPDATE ${this.#notes} SET "deletedAt" = $2, "deletedBy" = $3, "version" = "version" + 1, "updatedAt" = $2 WHERE "id" = $1`, [
9647
9806
  input.id,
@@ -9694,9 +9853,12 @@ var PgKnowledgePages = class {
9694
9853
  filter.readable("s")
9695
9854
  ];
9696
9855
  if (input.scopeIds?.length) where.push(`p."scopeId" = ANY(${params.add(input.scopeIds)}::text[])`);
9697
- for (const pattern of patterns) where.push(`(p."name" ILIKE ${pattern} OR p."description" ILIKE ${pattern} OR p."body" ILIKE ${pattern})`);
9698
- 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`;
9699
- 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);
9856
+ const anyField = patterns.map((pattern) => `(p."name" ILIKE ${pattern} OR p."description" ILIKE ${pattern} OR p."body" ILIKE ${pattern})`).join(" OR ");
9857
+ where.push(`(${anyField})`);
9858
+ const score = patterns.map((pattern) => `(CASE WHEN p."name" ILIKE ${pattern} THEN ${KNOWLEDGE_SEARCH_WEIGHTS.name} WHEN p."description" ILIKE ${pattern} THEN ${KNOWLEDGE_SEARCH_WEIGHTS.description} WHEN p."body" ILIKE ${pattern} THEN ${KNOWLEDGE_SEARCH_WEIGHTS.body} ELSE 0 END)`).join(" + ");
9859
+ const count = patterns.map((pattern) => `(CASE WHEN p."name" ILIKE ${pattern} OR p."description" ILIKE ${pattern} OR p."body" ILIKE ${pattern} THEN 1 ELSE 0 END)`).join(" + ");
9860
+ const tier = `CASE WHEN p."canonicalName" LIKE ${phrasePattern} THEN 0 ELSE 1 END`;
9861
+ 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}, (${count}) DESC, (${score}) DESC, p."updatedAt" DESC, p."id" ASC LIMIT ${limit}`, params.values);
9700
9862
  for (const row of rows) hits.push({
9701
9863
  type: "page",
9702
9864
  page: toPage(row)
@@ -9708,8 +9870,10 @@ var PgKnowledgePages = class {
9708
9870
  const filter = new ReadFilter(input.read, params, this.#pages);
9709
9871
  const extra = [`n."deletedAt" IS NULL`, `np."deletedAt" IS NULL`];
9710
9872
  if (input.scopeIds?.length) extra.push(`np."scopeId" = ANY(${params.add(input.scopeIds)}::text[])`);
9711
- for (const word of words) extra.push(`n."text" ILIKE ${params.add(`%${escapeLike(word)}%`)}`);
9712
- 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);
9873
+ const noteHas = words.map((word) => `n."text" ILIKE ${params.add(`%${escapeLike(word)}%`)}`);
9874
+ extra.push(`(${noteHas.join(" OR ")})`);
9875
+ const noteScore = noteHas.map((has) => `(CASE WHEN ${has} THEN 1 ELSE 0 END)`).join(" + ");
9876
+ 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 (${noteScore}) DESC, n."updatedAt" DESC, n."id" DESC LIMIT ${limit - hits.length}`, params.values);
9713
9877
  for (const row of rows) hits.push({
9714
9878
  type: "note",
9715
9879
  note: toNote(row),
@@ -9748,10 +9912,18 @@ var PgKnowledgePages = class {
9748
9912
  };
9749
9913
  }
9750
9914
  async addPinAudience(input) {
9915
+ const kind = input.kind ?? "page";
9751
9916
  return this.#tx(async (tx) => {
9752
9917
  const page = await this.#requireLivePage(tx, input.pageId, false);
9753
9918
  const now = /* @__PURE__ */ new Date();
9754
- 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
+ });
9755
9927
  if (changed) await this.#logActivity(tx, {
9756
9928
  action: "pin-added",
9757
9929
  scopeId: page.scopeId,
@@ -9762,28 +9934,35 @@ var PgKnowledgePages = class {
9762
9934
  return pin;
9763
9935
  });
9764
9936
  }
9765
- async #upsertPinAudience(db, pageId, audience, createdBy, now) {
9766
- 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];
9767
9941
  if (!existing) return {
9768
- 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 *`, [
9769
9943
  createKnowledgeUlid(),
9770
9944
  pageId,
9945
+ kind,
9946
+ input.depth,
9771
9947
  [...new Set(audience)],
9772
- createdBy,
9948
+ input.createdBy,
9773
9949
  iso(now)
9774
9950
  ]))[0]),
9775
9951
  changed: true
9776
9952
  };
9777
9953
  const current = existing.audience ?? [];
9778
9954
  const merged = [.../* @__PURE__ */ new Set([...current, ...audience])];
9779
- 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 {
9780
9958
  pin: toPin(existing),
9781
9959
  changed: false
9782
9960
  };
9783
9961
  return {
9784
- 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 *`, [
9785
9963
  String(existing.id),
9786
9964
  merged,
9965
+ depth,
9787
9966
  iso(now)
9788
9967
  ]))[0]),
9789
9968
  changed: true
@@ -9791,7 +9970,7 @@ var PgKnowledgePages = class {
9791
9970
  }
9792
9971
  async removePinAudience(input) {
9793
9972
  return this.#tx(async (tx) => {
9794
- 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];
9795
9974
  if (!existing) return null;
9796
9975
  const current = existing.audience ?? [];
9797
9976
  const remaining = current.filter((entry) => !input.audience.includes(entry));
@@ -9823,6 +10002,7 @@ var PgKnowledgePages = class {
9823
10002
  t.mentions,
9824
10003
  t.stamps,
9825
10004
  t.notes,
10005
+ t.links,
9826
10006
  t.pages
9827
10007
  ].map((name) => this.#q(name)).join(", ");
9828
10008
  await this.#client.query(`TRUNCATE ${tables}`);
@@ -9841,9 +10021,10 @@ var PgKnowledgePages = class {
9841
10021
  const params = new Params();
9842
10022
  const where = [`p."deletedAt" IS NULL`, new ReadFilter(input.read, params, this.#pages).readable("s")];
9843
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)}`);
9844
10025
  if (input.pageIds) where.push(`pn."pageId" = ANY(${params.add(input.pageIds)}::text[])`);
9845
- if (input.excludePageIds?.length) where.push(`pn."pageId" <> ALL(${params.add(input.excludePageIds)}::text[])`);
9846
- 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')`);
9847
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"`;
9848
10029
  const totalWhere = where.join(" AND ");
9849
10030
  if (input.cursor) {
@@ -9909,7 +10090,7 @@ async function pgKnowledgeSnapshot(db, schemaName) {
9909
10090
  }
9910
10091
  /** Drops exactly the Knowledge tables (never Memory's) and recreates schema 4. */
9911
10092
  async function resetPgKnowledgePagesSchema(client, schemaName) {
9912
- 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(", ");
9913
10094
  await client.query(`DROP TABLE IF EXISTS ${tables} CASCADE`);
9914
10095
  await initPgKnowledgePagesSchema(client, schemaName);
9915
10096
  }
@@ -9984,7 +10165,8 @@ var KnowledgePG = class extends KnowledgeStorage {
9984
10165
  ...super.getCapabilities(),
9985
10166
  schemaVersion: 4,
9986
10167
  supportsSchemaInspection: true,
9987
- supportsExplicitReset: true
10168
+ supportsExplicitReset: true,
10169
+ supportsPinKinds: true
9988
10170
  };
9989
10171
  }
9990
10172
  async init() {
@@ -10014,6 +10196,9 @@ var KnowledgePG = class extends KnowledgeStorage {
10014
10196
  listChildScopes(input) {
10015
10197
  return this.#pages.listChildScopes(input);
10016
10198
  }
10199
+ countChildScopes(input) {
10200
+ return this.#pages.countChildScopes(input);
10201
+ }
10017
10202
  getPages(input) {
10018
10203
  return this.#pages.getPages(input);
10019
10204
  }
@@ -10053,6 +10238,21 @@ var KnowledgePG = class extends KnowledgeStorage {
10053
10238
  listNotes(input) {
10054
10239
  return this.#pages.listNotes(input);
10055
10240
  }
10241
+ countChildPages(input) {
10242
+ return this.#pages.countChildPages(input);
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
+ }
10056
10256
  countNotes(input) {
10057
10257
  return this.#pages.countNotes(input);
10058
10258
  }
@@ -19002,6 +19202,8 @@ const timeToFirstTokenSortSupported = coreStorage.TRACE_QUERY_ORDER_FIELDS?.incl
19002
19202
  const spanQueryOutcomeFieldsSupported = "outcome" in (coreStorage.SPAN_QUERY_FIELD_REGISTRY ?? {});
19003
19203
  const traceQueryOutcomeFieldsSupported = "outcome" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {});
19004
19204
  const outcomeFeatures = [...spanQueryOutcomeFieldsSupported ? ["span-query-outcome-fields"] : [], ...traceQueryOutcomeFieldsSupported ? ["trace-query-outcome-fields"] : []];
19205
+ /** Core before the fields rejects their trace and span predicates, so the feature is only declared when core plans them. */
19206
+ const contextFieldFeatures = "requestId" in (coreStorage.TRACE_QUERY_FIELD_REGISTRY?.trace ?? {}) ? ["trace-query-context-fields"] : [];
19005
19207
  const timingFeatures = [
19006
19208
  ...timeToFirstChunkSupported ? ["trace-query-time-to-first-chunk"] : [],
19007
19209
  ...timeToFirstChunkSortSupported ? ["trace-query-time-to-first-chunk-sort"] : [],
@@ -19036,6 +19238,9 @@ const TRACE_FIELDS = {
19036
19238
  sessionId: "r.\"sessionId\"",
19037
19239
  userId: "r.\"userId\"",
19038
19240
  organizationId: "r.\"organizationId\"",
19241
+ requestId: "r.\"requestId\"",
19242
+ serviceName: "r.\"serviceName\"",
19243
+ experimentId: "r.\"experimentId\"",
19039
19244
  startedAt: "r.\"startedAt\"",
19040
19245
  endedAt: "r.\"endedAt\"",
19041
19246
  durationMs: durationMsSql("r.\"startedAt\"", "r.\"endedAt\""),
@@ -19075,7 +19280,10 @@ const SPAN_FIELDS = {
19075
19280
  runId: "s.\"runId\"",
19076
19281
  sessionId: "s.\"sessionId\"",
19077
19282
  userId: "s.\"userId\"",
19078
- organizationId: "s.\"organizationId\""
19283
+ organizationId: "s.\"organizationId\"",
19284
+ requestId: "s.\"requestId\"",
19285
+ serviceName: "s.\"serviceName\"",
19286
+ experimentId: "s.\"experimentId\""
19079
19287
  };
19080
19288
  const SPAN_QUERY_FIELDS = {
19081
19289
  ...SPAN_FIELDS,
@@ -19084,9 +19292,6 @@ const SPAN_QUERY_FIELDS = {
19084
19292
  parentSpanId: "s.\"parentSpanId\"",
19085
19293
  threadId: "s.\"threadId\"",
19086
19294
  resourceId: "s.\"resourceId\"",
19087
- requestId: "s.\"requestId\"",
19088
- serviceName: "s.\"serviceName\"",
19089
- experimentId: "s.\"experimentId\"",
19090
19295
  environment: "s.\"environment\"",
19091
19296
  executionSource: "s.\"executionSource\"",
19092
19297
  parentEntityType: "s.\"parentEntityType\"",
@@ -19350,6 +19555,9 @@ const ROOT_PUSHDOWN_FIELDS = /* @__PURE__ */ new Set([
19350
19555
  "sessionId",
19351
19556
  "userId",
19352
19557
  "organizationId",
19558
+ "requestId",
19559
+ "serviceName",
19560
+ "experimentId",
19353
19561
  "startedAt",
19354
19562
  "endedAt",
19355
19563
  "entityId",
@@ -19521,7 +19729,10 @@ function compilePostgresTraceScope(schema, selection, relationCollections, scope
19521
19729
  s."runId",
19522
19730
  s."sessionId",
19523
19731
  s."userId",
19524
- s."organizationId"
19732
+ s."organizationId",
19733
+ s."requestId",
19734
+ s."serviceName",
19735
+ s."experimentId"
19525
19736
  FROM ${spanTable} s
19526
19737
  WHERE s."traceId" IS NOT NULL
19527
19738
  AND s."traceId" IN (SELECT "traceId" FROM root_scope)
@@ -21788,6 +21999,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21788
21999
  "trace-query-tenant-scope",
21789
22000
  "feedback",
21790
22001
  "trace-query-context-ids",
22002
+ ...contextFieldFeatures,
21791
22003
  ...timingFeatures,
21792
22004
  ...outcomeFeatures,
21793
22005
  "trace-query-root-span-fields",
@@ -21815,6 +22027,7 @@ var ObservabilityStoragePostgresVNext = class ObservabilityStoragePostgresVNext
21815
22027
  "trace-query-tenant-scope",
21816
22028
  "feedback",
21817
22029
  "trace-query-context-ids",
22030
+ ...contextFieldFeatures,
21818
22031
  ...timingFeatures,
21819
22032
  ...outcomeFeatures,
21820
22033
  "trace-query-root-span-fields",