busabase-sdk 0.10.1 → 0.10.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/index.js CHANGED
@@ -1,9 +1,34 @@
1
+ import { normalizeBaseUrl } from './chunk-5NYQX65A.js';
2
+ export { normalizeBaseUrl } from './chunk-5NYQX65A.js';
1
3
  import { createORPCClient, ORPCError } from '@orpc/client';
2
4
  import { OpenAPILink } from '@orpc/openapi-client/fetch';
3
5
  import { oc, eventIterator } from '@orpc/contract';
4
6
  import { z } from 'zod';
5
7
 
6
- // src/client.ts
8
+ // src/asset-grep.ts
9
+ var toUnifiedFilesGrepInput = (input) => ({
10
+ pattern: input.pattern,
11
+ flags: input.flags,
12
+ sources: ["files"],
13
+ scope: input.scope ? { files: input.scope } : void 0,
14
+ maxMatches: input.maxMatches,
15
+ contextLines: input.contextLines
16
+ });
17
+ var toFilesOnlyGrepResult = (result) => ({
18
+ matches: result.matches.flatMap((match) => {
19
+ if (match.source !== "files") return [];
20
+ const { source: _source, ...fileMatch } = match;
21
+ return [fileMatch];
22
+ }),
23
+ filesScanned: result.coverage.files.scanned,
24
+ missing: result.coverage.files.missing,
25
+ stale: result.coverage.files.stale,
26
+ unsearchable: result.coverage.files.unsearchable,
27
+ errored: result.coverage.files.errored,
28
+ notReached: result.coverage.files.notReached,
29
+ truncated: result.truncated
30
+ });
31
+ var grepAssets = async (client, input) => toFilesOnlyGrepResult(await client.grep(toUnifiedFilesGrepInput(input)));
7
32
  var i18n = {
8
33
  locales: ["en", "zh-CN", "zh-TW", "ja", "ko", "de", "fr", "es", "pt"]};
9
34
  var LocaleSchema = z.enum(i18n.locales);
@@ -220,6 +245,15 @@ var restoreFieldChangeRequestInputSchema = z.object({
220
245
  message: z.string().optional(),
221
246
  submittedBy: z.string().optional().default("local-editor")
222
247
  });
248
+ var withBaseId = { baseId: z.string().min(1) };
249
+ var fieldChangeRequestInputSchema = z.discriminatedUnion("operation", [
250
+ createFieldChangeRequestInputSchema.extend({ operation: z.literal("create"), ...withBaseId }),
251
+ updateFieldChangeRequestInputSchema.extend({ operation: z.literal("update"), ...withBaseId }),
252
+ deleteFieldChangeRequestInputSchema.extend({ operation: z.literal("delete"), ...withBaseId }),
253
+ convertFieldChangeRequestInputSchema.extend({ operation: z.literal("convert"), ...withBaseId }),
254
+ reorderFieldsChangeRequestInputSchema.extend({ operation: z.literal("reorder"), ...withBaseId }),
255
+ restoreFieldChangeRequestInputSchema.extend({ operation: z.literal("restore"), ...withBaseId })
256
+ ]);
223
257
 
224
258
  // ../../packages/busabase-contract/src/domains/filetree/definition.ts
225
259
  var fileTreeOperations = (type) => [
@@ -521,7 +555,13 @@ var listNodesInputSchema = z.object({
521
555
  parentId: z.string().nullable().optional().describe("Node to start from. Omit or null to start from the space root."),
522
556
  depth: z.coerce.number().int().min(1).max(5).optional().describe(
523
557
  "How many levels beneath the start point to eagerly include (default 2 once either field is set). Capped at 5."
524
- )
558
+ ),
559
+ /**
560
+ * `active` (default) walks the live tree. `archived` returns the flat set of
561
+ * soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
562
+ * archived nodes are shown as a list, not a tree.
563
+ */
564
+ status: z.enum(["active", "archived"]).optional().default("active")
525
565
  }).optional();
526
566
  var isDescendantInputSchema = z.object({
527
567
  nodeId: z.string(),
@@ -850,6 +890,9 @@ var createCommentInputSchema = commentSubjectInputSchema.extend({
850
890
  var listInputSchema = z.object({
851
891
  limit: z.coerce.number().int().min(1).max(100).optional().default(50)
852
892
  }).optional().default({ limit: 50 });
893
+ var listByStatusInputSchema = z.object({
894
+ status: z.enum(["active", "archived"]).optional().default("active")
895
+ });
853
896
  var listChangeRequestsPagedInputSchema = z.object({
854
897
  limit: z.coerce.number().int().min(1).max(100).optional().default(50),
855
898
  /** Opaque base64 cursor (`createdAt|id`) for keyset pagination. */
@@ -1032,74 +1075,75 @@ var createFileTreeChangeRequestInputSchema = z.object({
1032
1075
  submittedBy: z.string().optional().default("local-producer"),
1033
1076
  operations: z.array(fileTreeFileOperationInputSchema).min(1)
1034
1077
  });
1035
- var makeFileTreeContract = (routeBase, tag) => {
1036
- const label = tag.endsWith("s") ? tag.slice(0, -1) : tag;
1037
- const basePath = `/${routeBase}`;
1038
- return {
1039
- list: oc.route({
1040
- method: "GET",
1041
- path: basePath,
1042
- tags: [tag],
1043
- summary: `List ${label} nodes`,
1044
- successDescription: `${label} nodes with their Asset-backed file trees.`
1045
- }).output(z.array(fileTreeNodeSchema)),
1046
- create: oc.route({
1047
- method: "POST",
1048
- path: basePath,
1049
- tags: [tag],
1050
- summary: `Create ${label} node`,
1051
- successDescription: `Review-first by default: a pending ChangeRequest proposing the ${label} node (\`materialized: false\`). Returns the materialized ${label} node instead (\`materialized: true\`) when \`autoMerge: true\` is passed.`
1052
- }).input(createFileTreeInputSchema).output(
1053
- z.union([
1054
- fileTreeNodeSchema.extend({ materialized: z.literal(true) }),
1055
- changeRequestSchema.extend({ materialized: z.literal(false) })
1056
- ])
1057
- ),
1058
- get: oc.route({
1059
- method: "GET",
1060
- path: `${basePath}/{nodeId}`,
1061
- tags: [tag],
1062
- summary: `Get ${label} node`,
1063
- successDescription: `${label} node detail and file tree.`
1064
- }).input(z.object({ nodeId: z.string() })).output(fileTreeNodeSchema),
1065
- listFiles: oc.route({
1066
- method: "GET",
1067
- path: `${basePath}/{nodeId}/files`,
1068
- tags: [tag],
1069
- summary: `List ${label} files`,
1070
- successDescription: `Asset-backed files mounted under the ${label} node.`
1071
- }).input(z.object({ nodeId: z.string() })).output(z.array(fileTreeFileSchema)),
1072
- readFile: oc.route({
1073
- method: "GET",
1074
- path: `${basePath}/{nodeId}/files/{+filePath}`,
1075
- tags: [tag],
1076
- summary: `Read ${label} file`,
1077
- successDescription: `${label} file content and content hash.`
1078
- }).input(z.object({ nodeId: z.string(), filePath: z.string() })).output(
1079
- z.object({
1080
- nodeId: z.string(),
1081
- path: z.string(),
1082
- encoding: z.enum(["utf8", "url"]),
1083
- content: z.string(),
1084
- mimeType: z.string(),
1085
- assetId: z.string(),
1086
- displayName: z.string().nullable(),
1087
- assetUrl: z.string().nullable(),
1088
- contentHash: z.string()
1089
- })
1090
- ),
1091
- createChangeRequest: oc.route({
1092
- method: "POST",
1093
- path: `${basePath}/{nodeId}/change-requests`,
1094
- tags: [tag, "Change Requests"],
1095
- summary: `Create ${label} change request`,
1096
- successDescription: `Created file-tree change request for a ${label} node.`
1097
- }).input(createFileTreeChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
1098
- };
1078
+ var FILE_TREE_NODE_TYPES = ["skill", "drive", "airapp"];
1079
+ var fileTreeNodeTypeSchema = z.enum(FILE_TREE_NODE_TYPES);
1080
+ var fileTreeRefSchema = z.object({
1081
+ nodeId: z.string(),
1082
+ type: fileTreeNodeTypeSchema.optional()
1083
+ });
1084
+ var fileTreeContract = {
1085
+ list: oc.route({
1086
+ method: "GET",
1087
+ path: "/file-trees",
1088
+ tags: ["File Trees"],
1089
+ summary: "List file-tree nodes",
1090
+ successDescription: "Skill, Drive, and AirApp nodes with their Asset-backed file trees. Pass `type` to narrow to one kind."
1091
+ }).input(z.object({ type: fileTreeNodeTypeSchema.optional() })).output(z.array(fileTreeNodeSchema)),
1092
+ create: oc.route({
1093
+ method: "POST",
1094
+ path: "/file-trees",
1095
+ tags: ["File Trees"],
1096
+ summary: "Create file-tree node",
1097
+ successDescription: "Review-first by default: a pending ChangeRequest proposing the node (`materialized: false`). Returns the materialized node instead (`materialized: true`) when `autoMerge: true` is passed."
1098
+ }).input(createFileTreeInputSchema.extend({ type: fileTreeNodeTypeSchema })).output(
1099
+ z.union([
1100
+ fileTreeNodeSchema.extend({ materialized: z.literal(true) }),
1101
+ changeRequestSchema.extend({ materialized: z.literal(false) })
1102
+ ])
1103
+ ),
1104
+ get: oc.route({
1105
+ method: "GET",
1106
+ path: "/file-trees/{nodeId}",
1107
+ tags: ["File Trees"],
1108
+ summary: "Get file-tree node",
1109
+ successDescription: "File-tree node detail and its file list."
1110
+ }).input(fileTreeRefSchema).output(fileTreeNodeSchema),
1111
+ listFiles: oc.route({
1112
+ method: "GET",
1113
+ path: "/file-trees/{nodeId}/files",
1114
+ tags: ["File Trees"],
1115
+ summary: "List file-tree files",
1116
+ successDescription: "Asset-backed files mounted under the node."
1117
+ }).input(fileTreeRefSchema).output(z.array(fileTreeFileSchema)),
1118
+ readFile: oc.route({
1119
+ method: "GET",
1120
+ path: "/file-trees/{nodeId}/files/{+filePath}",
1121
+ tags: ["File Trees"],
1122
+ summary: "Read file-tree file",
1123
+ successDescription: "File content and content hash."
1124
+ }).input(fileTreeRefSchema.extend({ filePath: z.string() })).output(
1125
+ z.object({
1126
+ nodeId: z.string(),
1127
+ path: z.string(),
1128
+ encoding: z.enum(["utf8", "url"]),
1129
+ content: z.string(),
1130
+ mimeType: z.string(),
1131
+ assetId: z.string(),
1132
+ displayName: z.string().nullable(),
1133
+ assetUrl: z.string().nullable(),
1134
+ contentHash: z.string()
1135
+ })
1136
+ ),
1137
+ createChangeRequest: oc.route({
1138
+ method: "POST",
1139
+ path: "/file-trees/{nodeId}/change-requests",
1140
+ tags: ["File Trees", "Change Requests"],
1141
+ summary: "Create file-tree change request",
1142
+ successDescription: "Created file-tree change request for the node."
1143
+ }).input(createFileTreeChangeRequestInputSchema.extend(fileTreeRefSchema.shape)).output(changeRequestSchema)
1099
1144
  };
1100
1145
 
1101
1146
  // ../../packages/busabase-contract/src/domains/airapp/contract.ts
1102
- var airappContract = makeFileTreeContract("airapps", "AirApps");
1103
1147
  var airAppRunLocalNodeInputSchema = z.object({
1104
1148
  nodeId: z.string(),
1105
1149
  /** Text files to mount into the sandbox workdir before installing, keyed by
@@ -1258,7 +1302,7 @@ var GREP_DEFAULT_MAX_MATCHES = 100;
1258
1302
  var GREP_HARD_MAX_MATCHES = 1e3;
1259
1303
  var GREP_DEFAULT_CONTEXT_LINES = 0;
1260
1304
  var GREP_MAX_CONTEXT_LINES = 10;
1261
- var GrepInputSchema = z.object({
1305
+ z.object({
1262
1306
  pattern: z.string().min(1),
1263
1307
  /** JS RegExp flags, e.g. `"i"` for case-insensitive. `g`/`y` are ignored (grep always scans every match per line). */
1264
1308
  flags: z.string().optional().default(""),
@@ -1279,7 +1323,7 @@ var GrepMatchVOSchema = z.object({
1279
1323
  before: z.array(z.string()),
1280
1324
  after: z.array(z.string())
1281
1325
  });
1282
- var GrepResultVOSchema = z.object({
1326
+ z.object({
1283
1327
  matches: z.array(GrepMatchVOSchema),
1284
1328
  filesScanned: z.number().int().nonnegative(),
1285
1329
  /** Asset ids in scope with no text yet (contentKind text-or-writable-binary, no row). */
@@ -1413,13 +1457,6 @@ var assetsContract = {
1413
1457
  summary: "Request a presigned upload URL for large text",
1414
1458
  successDescription: "Presigned (or dev) upload URL for a temporary text object; PUT the bytes there, then call putText with the returned storageKey to bind, verify, and content-address it."
1415
1459
  }).input(CreateTextUploadUrlInputSchema).output(CreateTextUploadUrlVOSchema),
1416
- grep: oc.route({
1417
- method: "POST",
1418
- path: "/assets/grep",
1419
- tags: ["Assets"],
1420
- summary: "Search every text-bearing asset in scope",
1421
- successDescription: "Streaming regex/literal matches with real file + line + column numbers and context, across every asset with text \u2014 any size, no 256KB cap. Honest coverage: missing/stale/unsearchable/errored name assets that were not (fully or successfully) searched, notReached counts present assets the scan never got to, and truncated flags a capped response."
1422
- }).input(GrepInputSchema).output(GrepResultVOSchema),
1423
1460
  readTextLines: oc.route({
1424
1461
  method: "GET",
1425
1462
  path: "/assets/{assetId}/text/lines",
@@ -1531,6 +1568,24 @@ var restoreViewInputSchema = z.object({
1531
1568
  message: z.string().optional().default("Restore view"),
1532
1569
  submittedBy: z.string().optional().default("local-producer")
1533
1570
  });
1571
+ var viewChangeRequestInputSchema = z.discriminatedUnion("operation", [
1572
+ createViewInputSchema.extend({
1573
+ operation: z.literal("create"),
1574
+ baseId: z.string().min(1).describe("Base the new view belongs to.")
1575
+ }),
1576
+ updateViewInputSchema.extend({
1577
+ operation: z.literal("update"),
1578
+ viewId: z.string().min(1)
1579
+ }),
1580
+ deleteViewInputSchema.extend({
1581
+ operation: z.literal("delete"),
1582
+ viewId: z.string().min(1)
1583
+ }),
1584
+ restoreViewInputSchema.extend({
1585
+ operation: z.literal("restore"),
1586
+ viewId: z.string().min(1)
1587
+ })
1588
+ ]);
1534
1589
 
1535
1590
  // ../../packages/busabase-contract/src/domains/base/contract/record-schemas.ts
1536
1591
  var recordSchema = z.object({
@@ -1564,20 +1619,33 @@ var listRecordsInputSchema = z.object({
1564
1619
  baseId: z.string().optional(),
1565
1620
  /** Opaque base64 cursor for keyset pagination (createdAt-keyed, or sort-keyed when `sort` is set). */
1566
1621
  cursor: z.string().optional(),
1622
+ /**
1623
+ * `active` (default) is the live table; `archived` is the Base's trash — the
1624
+ * same keyset pagination either way, which is why these are one endpoint
1625
+ * rather than a `/records/archived` twin.
1626
+ */
1627
+ status: z.enum(["active", "archived"]).optional().default("active"),
1567
1628
  /** View filters for server-side push-down (superset; client still narrows). */
1568
1629
  filters: z.array(listRecordsFilterSchema).optional(),
1569
1630
  /** View sort for server-side push-down (number/date fields only). */
1570
1631
  sort: listRecordsSortSchema.optional()
1571
- }).optional().default({ limit: 50 });
1632
+ }).optional().default({ limit: 50, status: "active" });
1572
1633
  var listRecordsResponseSchema = z.object({
1573
1634
  records: z.array(recordSchema),
1574
1635
  nextCursor: z.string().nullable()
1575
1636
  });
1576
- var listArchivedRecordsPagedInputSchema = z.object({
1577
- baseId: z.string(),
1578
- limit: z.coerce.number().int().min(1).max(100).optional().default(50),
1579
- /** Opaque base64 cursor (createdAt-keyed) for keyset pagination. */
1580
- cursor: z.string().optional()
1637
+ var listRecordsPageInputSchema = z.object({
1638
+ baseId: z.string().min(1),
1639
+ viewId: z.string().min(1).optional(),
1640
+ page: z.coerce.number().int().min(1).optional().default(1),
1641
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(50)
1642
+ });
1643
+ var listRecordsPageResponseSchema = z.object({
1644
+ records: z.array(recordSchema),
1645
+ total: z.number().int().nonnegative(),
1646
+ totalPages: z.number().int().nonnegative(),
1647
+ page: z.number().int().min(1),
1648
+ pageSize: z.number().int().min(1).max(100)
1581
1649
  });
1582
1650
  var countRecordsInputSchema = z.object({
1583
1651
  baseId: z.string().optional()
@@ -1625,14 +1693,30 @@ var recordFieldFilterInputSchema = z.object({
1625
1693
  limit: z.coerce.number().int().min(1).max(100).optional().default(50)
1626
1694
  });
1627
1695
  var recordFieldGetInputSchema = z.object({
1628
- baseId: z.string(),
1629
- fieldSlug: z.string().min(1),
1630
- valueText: z.string().min(1)
1696
+ baseId: z.string().describe("Field selector: Base id. Requires fieldSlug and valueText."),
1697
+ fieldSlug: z.string().min(1).describe("Field selector: exact field slug. Requires baseId and valueText."),
1698
+ valueText: z.string().min(1).describe("Field selector: exact text value. Requires baseId and fieldSlug.")
1631
1699
  });
1700
+ var recordGetInputSchema = z.union([
1701
+ z.object({
1702
+ recordId: z.string().min(1).describe("Record id selector. Use alone; do not combine with field selector fields.")
1703
+ }).strict(),
1704
+ recordFieldGetInputSchema.strict()
1705
+ ]);
1632
1706
  var restoreRecordInputSchema = z.object({
1633
1707
  message: z.string().optional(),
1634
1708
  submittedBy: z.string().optional().default("local-editor")
1635
1709
  });
1710
+ var withRecordId = { recordId: z.string().min(1) };
1711
+ var recordChangeRequestInputSchema = z.discriminatedUnion("operation", [
1712
+ reviseOperationInputSchema.extend({
1713
+ operation: z.literal("update"),
1714
+ autoMerge: z.boolean().optional(),
1715
+ ...withRecordId
1716
+ }),
1717
+ createDeleteChangeRequestInputSchema.extend({ operation: z.literal("delete"), ...withRecordId }),
1718
+ restoreRecordInputSchema.extend({ operation: z.literal("restore"), ...withRecordId })
1719
+ ]);
1636
1720
  var recordLinkSchema = z.object({
1637
1721
  id: z.string(),
1638
1722
  baseId: z.string(),
@@ -1652,22 +1736,15 @@ var baseContract = {
1652
1736
  path: "/bases",
1653
1737
  tags: ["Bases"],
1654
1738
  summary: "List Bases",
1655
- successDescription: "Flat list of developer-facing Bases."
1656
- }).output(z.array(baseSchema)),
1657
- listArchived: oc.route({
1658
- method: "GET",
1659
- path: "/bases/archived",
1660
- tags: ["Bases"],
1661
- summary: "List archived bases",
1662
- successDescription: "Bases that have been archived."
1663
- }).output(z.array(baseSchema)),
1739
+ successDescription: "Developer-facing Bases. `status=archived` returns the archived ones instead of the active ones."
1740
+ }).input(listByStatusInputSchema).output(z.array(baseSchema)),
1664
1741
  get: oc.route({
1665
1742
  method: "GET",
1666
1743
  path: "/bases/{baseId}",
1667
1744
  tags: ["Bases"],
1668
1745
  summary: "Get Base",
1669
- successDescription: "Single Base by id or slug."
1670
- }).input(z.object({ baseId: z.string() })).output(baseSchema.nullable()),
1746
+ successDescription: "Single Base by id or slug, or 404 when it does not exist or is not visible."
1747
+ }).input(z.object({ baseId: z.string() })).output(baseSchema),
1671
1748
  listDeletedFields: oc.route({
1672
1749
  method: "GET",
1673
1750
  path: "/bases/{baseId}/fields/deleted",
@@ -1679,30 +1756,9 @@ var baseContract = {
1679
1756
  method: "GET",
1680
1757
  path: "/bases/{baseId}/views",
1681
1758
  tags: ["Views"],
1682
- summary: "List active views for a Base",
1683
- successDescription: "Saved table views for a Base."
1684
- }).input(z.object({ baseId: z.string() })).output(z.array(viewSchema)),
1685
- listArchivedViews: oc.route({
1686
- method: "GET",
1687
- path: "/bases/{baseId}/views/archived",
1688
- tags: ["Views"],
1689
- summary: "List archived views for a Base",
1690
- successDescription: "Views that have been archived (soft-deleted) from a Base."
1691
- }).input(z.object({ baseId: z.string() })).output(z.array(viewSchema)),
1692
- listArchivedRecords: oc.route({
1693
- method: "GET",
1694
- path: "/bases/{baseId}/records/archived",
1695
- tags: ["Records"],
1696
- summary: "List archived records for a Base",
1697
- successDescription: "Records that have been archived (soft-deleted) from a Base."
1698
- }).input(z.object({ baseId: z.string() })).output(z.array(recordSchema)),
1699
- listArchivedRecordsPaged: oc.route({
1700
- method: "GET",
1701
- path: "/bases/{baseId}/records/archived/paged",
1702
- tags: ["Records"],
1703
- summary: "List archived records for a Base with keyset pagination",
1704
- successDescription: "A page of archived (soft-deleted) records plus an opaque nextCursor (null at the end)."
1705
- }).input(listArchivedRecordsPagedInputSchema).output(listRecordsResponseSchema),
1759
+ summary: "List views for a Base",
1760
+ successDescription: "Saved table views for a Base. `status=archived` returns the soft-deleted ones instead."
1761
+ }).input(listByStatusInputSchema.extend({ baseId: z.string() })).output(z.array(viewSchema)),
1706
1762
  create: oc.route({
1707
1763
  method: "POST",
1708
1764
  path: "/bases",
@@ -1741,34 +1797,13 @@ var baseContract = {
1741
1797
  summary: "Create Base field",
1742
1798
  successDescription: "Created Base field."
1743
1799
  }).input(createBaseFieldInputSchema.extend({ baseId: z.string() })).output(baseSchema),
1744
- createFieldChangeRequest: oc.route({
1800
+ fieldChangeRequest: oc.route({
1745
1801
  method: "POST",
1746
1802
  path: "/bases/{baseId}/fields/change-requests",
1747
1803
  tags: ["Bases", "Change Requests"],
1748
- summary: "Create Add Field change request",
1749
- successDescription: "Created change request that proposes a new field."
1750
- }).input(createFieldChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1751
- createViewChangeRequest: oc.route({
1752
- method: "POST",
1753
- path: "/bases/{baseId}/views/change-requests",
1754
- tags: ["Views", "Change Requests"],
1755
- summary: "Create View change request",
1756
- successDescription: "Created change request that proposes a new View."
1757
- }).input(createViewInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1758
- deleteFieldChangeRequest: oc.route({
1759
- method: "DELETE",
1760
- path: "/bases/{baseId}/fields/change-requests",
1761
- tags: ["Bases", "Change Requests"],
1762
- summary: "Create Delete Field change request",
1763
- successDescription: "Created change request that soft-deletes a field."
1764
- }).input(deleteFieldChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1765
- updateFieldChangeRequest: oc.route({
1766
- method: "PATCH",
1767
- path: "/bases/{baseId}/fields/change-requests",
1768
- tags: ["Bases", "Change Requests"],
1769
- summary: "Create Update Field change request",
1770
- successDescription: "Created change request that updates field metadata (name, required, options)."
1771
- }).input(updateFieldChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1804
+ summary: "Create field change request",
1805
+ successDescription: "Created change request proposing a field change. `operation` selects what to propose: `create`, `update`, `delete`, `convert` (change type), `reorder`, or `restore` (undo a soft delete)."
1806
+ }).input(fieldChangeRequestInputSchema).output(changeRequestSchema),
1772
1807
  previewFieldConversion: oc.route({
1773
1808
  method: "POST",
1774
1809
  path: "/bases/{baseId}/fields/convert/preview",
@@ -1776,20 +1811,6 @@ var baseContract = {
1776
1811
  summary: "Preview field type conversion",
1777
1812
  successDescription: "Dry-run statistics for converting a field to a different type."
1778
1813
  }).input(previewFieldConversionInputSchema.extend({ baseId: z.string() })).output(previewFieldConversionOutputSchema),
1779
- convertFieldChangeRequest: oc.route({
1780
- method: "POST",
1781
- path: "/bases/{baseId}/fields/convert/change-requests",
1782
- tags: ["Bases", "Change Requests"],
1783
- summary: "Create Convert Field change request",
1784
- successDescription: "Created change request that converts a field to a different type."
1785
- }).input(convertFieldChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1786
- reorderFieldsChangeRequest: oc.route({
1787
- method: "POST",
1788
- path: "/bases/{baseId}/fields/reorder/change-requests",
1789
- tags: ["Bases", "Change Requests"],
1790
- summary: "Reorder fields",
1791
- successDescription: "Created change request that reorders fields."
1792
- }).input(reorderFieldsChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1793
1814
  archiveChangeRequest: oc.route({
1794
1815
  method: "POST",
1795
1816
  path: "/bases/{baseId}/archive/change-requests",
@@ -1803,30 +1824,27 @@ var baseContract = {
1803
1824
  tags: ["Bases", "Change Requests"],
1804
1825
  summary: "Restore base",
1805
1826
  successDescription: "Created change request that restores an archived base."
1806
- }).input(restoreBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1807
- restoreFieldChangeRequest: oc.route({
1808
- method: "POST",
1809
- path: "/bases/{baseId}/fields/restore/change-requests",
1810
- tags: ["Bases", "Change Requests"],
1811
- summary: "Restore deleted field",
1812
- successDescription: "Created change request that restores a soft-deleted field."
1813
- }).input(restoreFieldChangeRequestInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema)
1827
+ }).input(restoreBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema)
1814
1828
  };
1815
1829
  var recordContract = {
1830
+ // One listing for records: always keyset-paginated, `baseId` always honoured,
1831
+ // `status` picking live rows or the trash. The old unpaginated `/records`
1832
+ // silently dropped `baseId` (its schema had no such field), so a caller
1833
+ // scoping to one Base quietly got the whole space back.
1816
1834
  list: oc.route({
1817
1835
  method: "GET",
1818
1836
  path: "/records",
1819
1837
  tags: ["Records"],
1820
1838
  summary: "List records",
1821
- successDescription: "Canonical records created from merged change requests."
1822
- }).input(listInputSchema).output(z.array(recordSchema)),
1823
- listPaged: oc.route({
1839
+ successDescription: "A page of canonical records plus an opaque nextCursor (null at the end). `status=archived` lists the Base's trash instead of its live rows."
1840
+ }).input(listRecordsInputSchema).output(listRecordsResponseSchema),
1841
+ listPage: oc.route({
1824
1842
  method: "GET",
1825
- path: "/records/paged",
1843
+ path: "/records/page",
1826
1844
  tags: ["Records"],
1827
- summary: "List records with keyset pagination",
1828
- successDescription: "A page of canonical records plus an opaque nextCursor (null at the end)."
1829
- }).input(listRecordsInputSchema).output(listRecordsResponseSchema),
1845
+ summary: "List a numbered record page",
1846
+ successDescription: "A random-access page of active records. When viewId is supplied, the saved view is authoritatively filtered and sorted before total and page slicing are calculated."
1847
+ }).input(listRecordsPageInputSchema).output(listRecordsPageResponseSchema),
1830
1848
  count: oc.route({
1831
1849
  method: "GET",
1832
1850
  path: "/records/count",
@@ -1836,11 +1854,15 @@ var recordContract = {
1836
1854
  }).input(countRecordsInputSchema).output(countRecordsResponseSchema),
1837
1855
  get: oc.route({
1838
1856
  method: "GET",
1839
- path: "/records/{recordId}",
1857
+ path: "/records/get",
1840
1858
  tags: ["Records"],
1841
1859
  summary: "Get record",
1842
- successDescription: "Canonical record detail."
1843
- }).input(z.object({ recordId: z.string() })).output(recordSchema),
1860
+ description: "Provide exactly one selector: recordId alone, or the complete baseId + fieldSlug + valueText tuple. Other combinations return 400.",
1861
+ successDescription: "One canonical record selected by id or exact field value."
1862
+ }).errors({
1863
+ BAD_REQUEST: { status: 400, message: "Exactly one record selector is required" },
1864
+ NOT_FOUND: { status: 404, message: "Record not found" }
1865
+ }).input(recordGetInputSchema).output(recordSchema),
1844
1866
  search: oc.route({
1845
1867
  method: "GET",
1846
1868
  path: "/records/search",
@@ -1848,37 +1870,18 @@ var recordContract = {
1848
1870
  summary: "Filter records by field text",
1849
1871
  successDescription: "Canonical records matching a field text filter."
1850
1872
  }).input(recordFieldFilterInputSchema).output(z.array(recordSchema)),
1851
- getByField: oc.route({
1852
- method: "GET",
1853
- path: "/records/by-field",
1854
- tags: ["Records"],
1855
- summary: "Get record by field value",
1856
- successDescription: "Single canonical record whose field value exactly matches, or null when none does \u2014 a scoped point lookup (e.g. by a unique slug or path field), not a list."
1857
- }).input(recordFieldGetInputSchema).output(recordSchema.nullable()),
1858
- updateChangeRequest: oc.route({
1859
- method: "PUT",
1873
+ changeRequest: oc.route({
1874
+ method: "POST",
1860
1875
  path: "/records/{recordId}/change-requests",
1861
1876
  tags: ["Records", "Change Requests"],
1862
- summary: "Create record update change request",
1863
- successDescription: "Permission-aware by default: updates the record immediately when the actor has write access, otherwise returns a pending ChangeRequest. Pass `autoMerge: false` to force review."
1864
- }).input(
1865
- reviseOperationInputSchema.extend({
1866
- recordId: z.string(),
1867
- autoMerge: z.boolean().optional()
1868
- })
1869
- ).output(
1877
+ summary: "Create record change request",
1878
+ successDescription: "Creates a record change request selected by `operation`. Updates auto-merge when the actor has write access unless `autoMerge: false`; delete and restore remain review-first."
1879
+ }).input(recordChangeRequestInputSchema).output(
1870
1880
  z.union([
1871
1881
  recordSchema.extend({ materialized: z.literal(true) }),
1872
1882
  changeRequestSchema.extend({ materialized: z.literal(false) })
1873
1883
  ])
1874
1884
  ),
1875
- deleteChangeRequest: oc.route({
1876
- method: "DELETE",
1877
- path: "/records/{recordId}/change-requests",
1878
- tags: ["Records", "Change Requests"],
1879
- summary: "Create record delete change request",
1880
- successDescription: "Created change request that proposes archiving or deleting a record."
1881
- }).input(createDeleteChangeRequestInputSchema.extend({ recordId: z.string() })).output(changeRequestSchema),
1882
1885
  listChangeRequests: oc.route({
1883
1886
  method: "GET",
1884
1887
  path: "/records/{recordId}/change-requests",
@@ -1886,13 +1889,6 @@ var recordContract = {
1886
1889
  summary: "List record change request history",
1887
1890
  successDescription: "Change requests and operations connected to the canonical record."
1888
1891
  }).input(z.object({ recordId: z.string() })).output(z.array(changeRequestSchema)),
1889
- restoreChangeRequest: oc.route({
1890
- method: "POST",
1891
- path: "/records/{recordId}/restore/change-requests",
1892
- tags: ["Records", "Change Requests"],
1893
- summary: "Create record restore change request",
1894
- successDescription: "Created change request that restores an archived record."
1895
- }).input(restoreRecordInputSchema.extend({ recordId: z.string() })).output(changeRequestSchema),
1896
1892
  listLinks: oc.route({
1897
1893
  method: "GET",
1898
1894
  path: "/records/{recordId}/links",
@@ -1902,27 +1898,13 @@ var recordContract = {
1902
1898
  }).input(z.object({ recordId: z.string() })).output(z.array(recordLinkSchema))
1903
1899
  };
1904
1900
  var viewContract = {
1905
- updateChangeRequest: oc.route({
1906
- method: "PUT",
1907
- path: "/views/{viewId}/change-requests",
1908
- tags: ["Views", "Change Requests"],
1909
- summary: "Create View update change request",
1910
- successDescription: "Created change request that proposes updating a View."
1911
- }).input(updateViewInputSchema.extend({ viewId: z.string() })).output(changeRequestSchema),
1912
- deleteChangeRequest: oc.route({
1913
- method: "DELETE",
1914
- path: "/views/{viewId}/change-requests",
1915
- tags: ["Views", "Change Requests"],
1916
- summary: "Create View delete change request",
1917
- successDescription: "Created change request that proposes archiving a View."
1918
- }).input(deleteViewInputSchema.extend({ viewId: z.string() })).output(changeRequestSchema),
1919
- restoreChangeRequest: oc.route({
1901
+ changeRequest: oc.route({
1920
1902
  method: "POST",
1921
- path: "/views/{viewId}/restore/change-requests",
1903
+ path: "/views/change-requests",
1922
1904
  tags: ["Views", "Change Requests"],
1923
- summary: "Create View restore change request",
1924
- successDescription: "Created change request that restores an archived View."
1925
- }).input(restoreViewInputSchema.extend({ viewId: z.string() })).output(changeRequestSchema)
1905
+ summary: "Create view change request",
1906
+ successDescription: "Created change request proposing a view change. `operation` selects what to propose: `create` (addressed by `baseId`), or `update` / `delete` / `restore` (addressed by `viewId`)."
1907
+ }).input(viewChangeRequestInputSchema).output(changeRequestSchema)
1926
1908
  };
1927
1909
  var ReadDocLinesInputSchema = z.object({
1928
1910
  nodeId: z.string(),
@@ -2009,9 +1991,6 @@ var docContract = {
2009
1991
  successDescription: "Created a change request that proposes a new Doc body."
2010
1992
  }).input(createDocChangeRequestInputSchema.extend({ nodeId: z.string() })).output(changeRequestSchema)
2011
1993
  };
2012
-
2013
- // ../../packages/busabase-contract/src/domains/drive/contract.ts
2014
- var driveContract = makeFileTreeContract("drives", "Drives");
2015
1994
  var DumpTableSchema = z.enum([
2016
1995
  "nodes",
2017
1996
  /** Node-level access grants (permissions). Not secret — restore must keep them
@@ -2466,9 +2445,6 @@ var installContract = {
2466
2445
  successDescription: "Created counts plus the number of change requests left for review. Structure (folders, Bases, fields, views) is created immediately \u2014 a pending Base has no id to attach a view or record to; content (records, docs, skills, AirApps) lands as change requests unless `autoMerge` is set."
2467
2446
  }).input(InstallFromGithubDTOSchema).output(InstallResultVOSchema)
2468
2447
  };
2469
-
2470
- // ../../packages/busabase-contract/src/domains/skill/contract.ts
2471
- var skillContract = makeFileTreeContract("skills", "Skills");
2472
2448
  var VaultItemKeySchema = z.string().trim().min(1).max(128).regex(/^[A-Z_][A-Z0-9_]*$/, "Use uppercase letters, numbers, and underscores");
2473
2449
  var VaultItemValueSchema = z.string().max(8192);
2474
2450
  var VaultItemKindSchema = z.enum(["secret", "variable"]);
@@ -2762,7 +2738,7 @@ var UnifiedGrepScopeSchema = z.object({
2762
2738
  });
2763
2739
  var UnifiedGrepInputSchema = z.object({
2764
2740
  pattern: z.string().min(1),
2765
- /** JS RegExp flags, e.g. `"i"` for case-insensitive — same language as `assets.grep`. */
2741
+ /** JS RegExp flags, e.g. `"i"` for case-insensitive. */
2766
2742
  flags: z.string().optional().default(""),
2767
2743
  /** Which sources to scan. Omitted = all three (`files`, `docs`, `records`). */
2768
2744
  sources: z.array(GrepSourceSchema).optional(),
@@ -2871,11 +2847,9 @@ var busabaseContractRoutes = {
2871
2847
  summary: "Search Busabase",
2872
2848
  successDescription: "Paginated search results across records, change requests, Bases, File nodes, and Assets."
2873
2849
  }).input(searchInputSchema).output(searchResponseSchema),
2874
- // Unified Grep (P2a files+docs, P2b records) — top-level, cross-source
2875
- // superset of `assets.grep`. See apps/busabase/content/spec/unified-grep.md.
2876
- // Composes `logic/grep.ts`; `assets.grep` (files-only specialist) is
2877
- // unchanged and stays the dedicated endpoint for its fuller
2878
- // missing/stale/unsearchable reporting.
2850
+ // Unified Grep (P2a files+docs, P2b records) — the single public pattern
2851
+ // search endpoint. Files-only callers use `sources: ["files"]` and retain
2852
+ // the full missing/stale/unsearchable coverage block.
2879
2853
  grep: oc.route({
2880
2854
  method: "POST",
2881
2855
  path: "/grep",
@@ -2905,13 +2879,6 @@ var busabaseContractRoutes = {
2905
2879
  summary: "Check whether a node is a descendant of another",
2906
2880
  successDescription: "Server-authoritative parentId-chain walk from nodeId up to potentialAncestorId. Used to gate cross-branch drag-and-drop drops in the sidebar, since the full tree is no longer guaranteed to be loaded client-side (depth-bounded lazy load) \u2014 a purely local walk could wrongly allow dropping a folder into its own unloaded descendant."
2907
2881
  }).input(isDescendantInputSchema).output(isDescendantOutputSchema),
2908
- listArchived: oc.route({
2909
- method: "GET",
2910
- path: "/nodes/archived",
2911
- tags: ["Nodes"],
2912
- summary: "List archived nodes",
2913
- successDescription: "Soft-archived folders, docs, and skills (for the Trash view)."
2914
- }).output(z.array(nodeSchema)),
2915
2882
  createChangeRequest: oc.route({
2916
2883
  method: "POST",
2917
2884
  path: "/nodes/change-requests",
@@ -3093,9 +3060,10 @@ var busabaseContractRoutes = {
3093
3060
  subscribe: oc.output(eventIterator(liveEventSchema))
3094
3061
  },
3095
3062
  bases: baseContract,
3096
- skills: skillContract,
3097
- drives: driveContract,
3098
- airapps: { ...airappContract, ...airappRuntimeContract },
3063
+ // Skills, Drives, and AirApps share one transport surface — they differ only
3064
+ // in seed files and entry file, which is a server-side config concern.
3065
+ fileTrees: fileTreeContract,
3066
+ airapps: airappRuntimeContract,
3099
3067
  files: fileContract,
3100
3068
  docs: docContract,
3101
3069
  folders: folderContract,
@@ -3106,18 +3074,13 @@ var busabaseContractRoutes = {
3106
3074
  dump: dumpContract,
3107
3075
  install: installContract,
3108
3076
  changeRequests: {
3077
+ // Always keyset-paginated — the unpaginated twin returned a bare array that
3078
+ // silently truncated at `limit` with no way to ask for the next page.
3109
3079
  list: oc.route({
3110
3080
  method: "GET",
3111
3081
  path: "/change-requests",
3112
3082
  tags: ["Change Requests"],
3113
3083
  summary: "List change requests",
3114
- successDescription: "Change requests waiting for review or ready to merge."
3115
- }).input(listInputSchema).output(z.array(changeRequestSchema)),
3116
- listPaged: oc.route({
3117
- method: "GET",
3118
- path: "/change-requests/paged",
3119
- tags: ["Change Requests"],
3120
- summary: "List change requests with keyset pagination",
3121
3084
  successDescription: "A page of change requests plus an opaque nextCursor (null at the end). Filter with `status` and/or `mine`."
3122
3085
  }).input(listChangeRequestsPagedInputSchema).output(listChangeRequestsResponseSchema),
3123
3086
  counts: oc.route({
@@ -3460,9 +3423,6 @@ var env = (key) => {
3460
3423
  const value = process.env[key];
3461
3424
  return value && value.length > 0 ? value : void 0;
3462
3425
  };
3463
- function normalizeBaseUrl(raw) {
3464
- return raw.replace(/\/+$/, "").replace(/\/api\/v1$/, "");
3465
- }
3466
3426
  function resolveConfig(config = {}) {
3467
3427
  return {
3468
3428
  baseUrl: normalizeBaseUrl(config.baseUrl ?? env("BUSABASE_BASE_URL") ?? DEFAULT_BASE_URL),
@@ -3510,6 +3470,17 @@ function createBusabaseClient(config = {}) {
3510
3470
  return createORPCClient(link);
3511
3471
  }
3512
3472
 
3473
+ // src/record-get.ts
3474
+ var isNotFound = (error) => typeof error === "object" && error !== null && ("status" in error && error.status === 404 || "code" in error && error.code === "NOT_FOUND");
3475
+ var getRecordByField = async (client, input) => {
3476
+ try {
3477
+ return await client.records.get(input);
3478
+ } catch (error) {
3479
+ if (isNotFound(error)) return null;
3480
+ throw error;
3481
+ }
3482
+ };
3483
+
3513
3484
  // src/index.ts
3514
3485
  var Busabase = class {
3515
3486
  /** The underlying fully-typed oRPC client. Use it for anything not surfaced here. */
@@ -3526,7 +3497,13 @@ var Busabase = class {
3526
3497
  return this.client.bases;
3527
3498
  }
3528
3499
  get records() {
3529
- return this.client.records;
3500
+ const getByField = (input) => getRecordByField(this.client, input);
3501
+ return new Proxy(this.client.records, {
3502
+ get(target, property, receiver) {
3503
+ if (property === "getByField") return getByField;
3504
+ return Reflect.get(target, property, receiver);
3505
+ }
3506
+ });
3530
3507
  }
3531
3508
  get views() {
3532
3509
  return this.client.views;
@@ -3550,13 +3527,17 @@ var Busabase = class {
3550
3527
  return this.client.agent;
3551
3528
  }
3552
3529
  get assets() {
3553
- return this.client.assets;
3554
- }
3555
- get skills() {
3556
- return this.client.skills;
3530
+ const filesOnlyGrep = (input) => grepAssets(this.client, input);
3531
+ return new Proxy(this.client.assets, {
3532
+ get(target, property, receiver) {
3533
+ if (property === "grep") return filesOnlyGrep;
3534
+ return Reflect.get(target, property, receiver);
3535
+ }
3536
+ });
3557
3537
  }
3558
- get drives() {
3559
- return this.client.drives;
3538
+ /** Skills, Drives, and AirApps — one surface, discriminated by `type`. */
3539
+ get fileTrees() {
3540
+ return this.client.fileTrees;
3560
3541
  }
3561
3542
  get files() {
3562
3543
  return this.client.files;
@@ -3585,10 +3566,8 @@ var Busabase = class {
3585
3566
  * source (Drive/Skill files, Doc bodies, and Base records — records read
3586
3567
  * the canonical `headCommit.fields`, never the truncated search
3587
3568
  * projection), with a shared `maxMatches`/deadline budget and per-source
3588
- * honest coverage. Use this when the answer could live anywhere; use
3589
- * `client.assets.grep` directly instead when you specifically only care
3590
- * about files and want its fuller `missing`/`stale`/`unsearchable`
3591
- * file-only reporting.
3569
+ * honest coverage. `bb.assets.grep` remains available as a files-only SDK
3570
+ * convenience and delegates here with `sources: ["files"]`.
3592
3571
  */
3593
3572
  grep(input) {
3594
3573
  return this.client.grep(input);
@@ -3638,4 +3617,4 @@ var Busabase = class {
3638
3617
  }
3639
3618
  };
3640
3619
 
3641
- export { Busabase, CREATABLE_NODE_TYPES, DEFAULT_BASE_URL, cloudContract, createBusabaseClient, normalizeBaseUrl, resolveConfig };
3620
+ export { Busabase, CREATABLE_NODE_TYPES, DEFAULT_BASE_URL, cloudContract, createBusabaseClient, getRecordByField, grepAssets, resolveConfig, toFilesOnlyGrepResult, toUnifiedFilesGrepInput };