busabase-sdk 0.10.3 → 0.11.2

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 (4) hide show
  1. package/README.md +11 -1
  2. package/dist/index.d.ts +18768 -18496
  3. package/dist/index.js +262 -124
  4. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -254,6 +254,10 @@ var fieldChangeRequestInputSchema = z.discriminatedUnion("operation", [
254
254
  reorderFieldsChangeRequestInputSchema.extend({ operation: z.literal("reorder"), ...withBaseId }),
255
255
  restoreFieldChangeRequestInputSchema.extend({ operation: z.literal("restore"), ...withBaseId })
256
256
  ]);
257
+ var baseLifecycleChangeRequestInputSchema = z.discriminatedUnion("operation", [
258
+ archiveBaseInputSchema.extend({ operation: z.literal("archive"), ...withBaseId }),
259
+ restoreBaseInputSchema.extend({ operation: z.literal("restore"), ...withBaseId })
260
+ ]);
257
261
 
258
262
  // ../../packages/busabase-contract/src/domains/filetree/definition.ts
259
263
  var fileTreeOperations = (type) => [
@@ -561,7 +565,33 @@ var listNodesInputSchema = z.object({
561
565
  * soft-archived nodes for the Trash view — no `parentId`/`depth` walk, since
562
566
  * archived nodes are shown as a list, not a tree.
563
567
  */
564
- status: z.enum(["active", "archived"]).optional().default("active")
568
+ status: z.enum(["active", "archived"]).optional().default("active"),
569
+ /**
570
+ * Narrow to specific node types and return a FLAT list of lightweight node
571
+ * summaries (`children: []`) instead of walking the tree. This is what
572
+ * replaced the four retired narrow listings (`GET /docs`, `/files`,
573
+ * `/folders`, `/file-trees`); file-trees are selected with their real
574
+ * discriminators `skill` / `drive` / `airapp`, since there is no synthetic
575
+ * "file-tree" node type.
576
+ *
577
+ * Omitting `types` leaves every existing caller on exactly today's
578
+ * behaviour (full tree, or a `parentId`/`depth`-bounded walk, or the
579
+ * archived flat list) — the two modes never interfere.
580
+ *
581
+ * NOTE — no `projection` parameter, deliberately. The consolidation roadmap
582
+ * sketched `?projection=summary`, but it also rules out adding
583
+ * `projection=detail` in this batch (the retired detail lists were the
584
+ * N+1 payloads this change exists to remove). That would leave a parameter
585
+ * with exactly one legal value, which is noise in OpenAPI/MCP/CLI rather
586
+ * than a decision a caller gets to make. Detail is `GET /nodes/{nodeId}`.
587
+ *
588
+ * A GET query param that occurs exactly once (`?types=doc`) arrives as a
589
+ * bare string, not a 1-element array — only a REPEATED occurrence
590
+ * (`?types=doc&types=file`) becomes an array. Accept both and normalize.
591
+ */
592
+ types: z.union([z.array(z.enum(NODE_TYPES)), z.enum(NODE_TYPES)]).transform((value) => Array.isArray(value) ? value : [value]).optional().describe(
593
+ "Return a flat list of lightweight summaries for these node types instead of the tree. Read one node's full detail with GET /nodes/{nodeId}."
594
+ )
565
595
  }).optional();
566
596
  var isDescendantInputSchema = z.object({
567
597
  nodeId: z.string(),
@@ -729,11 +759,19 @@ var liveEventSchema = z.object({
729
759
  // via the audit funnel, but nothing needs reviewing). Consumed by
730
760
  // `use-live-sync.ts` to pop a desktop Notification, and by
731
761
  // busabase-cloud's host hook to persist an inbox notification row.
732
- "change_request.pending_review"
762
+ "change_request.pending_review",
763
+ // A node's metadata was written directly, outside the change-request flow
764
+ // (`PATCH /api/v1/nodes/{nodeId}/metadata` — agents, the SDK, an MCP tool,
765
+ // and every rich-node editor's own Save). Carries the touched node in
766
+ // `nodeIds` so open dashboards refetch the node tree instead of showing a
767
+ // stale whiteboard/workflow/HTML document until the next reload.
768
+ "node.metadata_updated"
733
769
  ]),
734
770
  spaceId: z.string(),
735
771
  actorId: z.string(),
736
- changeRequestId: z.string(),
772
+ // Null for events that aren't about a change request at all
773
+ // (`node.metadata_updated`), which is every direct, auto-audited write.
774
+ changeRequestId: z.string().nullable(),
737
775
  baseId: z.string().nullable(),
738
776
  nodeIds: z.array(z.string()),
739
777
  recordIds: z.array(z.string()),
@@ -904,6 +942,19 @@ var listChangeRequestsResponseSchema = z.object({
904
942
  changeRequests: z.array(changeRequestSchema),
905
943
  nextCursor: z.string().nullable()
906
944
  });
945
+ var listChangeRequestsPageInputSchema = z.object({
946
+ page: z.coerce.number().int().min(1).optional().default(1),
947
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(50),
948
+ status: z.array(changeRequestStatusSchema).optional(),
949
+ mine: z.boolean().optional()
950
+ }).optional().default({ page: 1, pageSize: 50 });
951
+ var listChangeRequestsPageResponseSchema = z.object({
952
+ changeRequests: z.array(changeRequestSchema),
953
+ total: z.number().int().nonnegative(),
954
+ totalPages: z.number().int().nonnegative(),
955
+ page: z.number().int().min(1),
956
+ pageSize: z.number().int().min(1).max(100)
957
+ });
907
958
  var changeRequestCountsSchema = z.object({
908
959
  review: z.number().int().nonnegative(),
909
960
  changes: z.number().int().nonnegative(),
@@ -1082,13 +1133,6 @@ var fileTreeRefSchema = z.object({
1082
1133
  type: fileTreeNodeTypeSchema.optional()
1083
1134
  });
1084
1135
  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
1136
  create: oc.route({
1093
1137
  method: "POST",
1094
1138
  path: "/file-trees",
@@ -1101,13 +1145,6 @@ var fileTreeContract = {
1101
1145
  changeRequestSchema.extend({ materialized: z.literal(false) })
1102
1146
  ])
1103
1147
  ),
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
1148
  listFiles: oc.route({
1112
1149
  method: "GET",
1113
1150
  path: "/file-trees/{nodeId}/files",
@@ -1543,6 +1580,9 @@ var viewSchema = z.object({
1543
1580
  createdAt: z.string(),
1544
1581
  updatedAt: z.string()
1545
1582
  });
1583
+ var autoMergeSchema = z.boolean().optional().describe(
1584
+ "Whether to approve and merge this view change immediately. Omitted defaults to merging immediately if the actor has write access on the Base's node, otherwise falling back to a pending Change Request; pass explicit false to force review even with write access."
1585
+ );
1546
1586
  var createViewInputSchema = z.object({
1547
1587
  config: viewConfigSchema.optional().default({ filters: [], sorts: [] }),
1548
1588
  description: z.string().optional().default(""),
@@ -1550,7 +1590,8 @@ var createViewInputSchema = z.object({
1550
1590
  name: z.string().min(1),
1551
1591
  type: viewTypeSchema.optional().default("table"),
1552
1592
  slug: z.string().min(1).regex(/^[a-z0-9-]+$/).optional(),
1553
- submittedBy: z.string().optional().default("local-producer")
1593
+ submittedBy: z.string().optional().default("local-producer"),
1594
+ autoMerge: autoMergeSchema
1554
1595
  });
1555
1596
  var updateViewInputSchema = z.object({
1556
1597
  config: viewConfigSchema.optional(),
@@ -1558,15 +1599,18 @@ var updateViewInputSchema = z.object({
1558
1599
  message: z.string().optional().default("Update view"),
1559
1600
  name: z.string().min(1).optional(),
1560
1601
  type: viewTypeSchema.optional(),
1561
- submittedBy: z.string().optional().default("local-producer")
1602
+ submittedBy: z.string().optional().default("local-producer"),
1603
+ autoMerge: autoMergeSchema
1562
1604
  });
1563
1605
  var deleteViewInputSchema = z.object({
1564
1606
  message: z.string().optional().default("Delete view"),
1565
- submittedBy: z.string().optional().default("local-producer")
1607
+ submittedBy: z.string().optional().default("local-producer"),
1608
+ autoMerge: autoMergeSchema
1566
1609
  });
1567
1610
  var restoreViewInputSchema = z.object({
1568
1611
  message: z.string().optional().default("Restore view"),
1569
- submittedBy: z.string().optional().default("local-producer")
1612
+ submittedBy: z.string().optional().default("local-producer"),
1613
+ autoMerge: autoMergeSchema
1570
1614
  });
1571
1615
  var viewChangeRequestInputSchema = z.discriminatedUnion("operation", [
1572
1616
  createViewInputSchema.extend({
@@ -1811,20 +1855,13 @@ var baseContract = {
1811
1855
  summary: "Preview field type conversion",
1812
1856
  successDescription: "Dry-run statistics for converting a field to a different type."
1813
1857
  }).input(previewFieldConversionInputSchema.extend({ baseId: z.string() })).output(previewFieldConversionOutputSchema),
1814
- archiveChangeRequest: oc.route({
1858
+ lifecycleChangeRequest: oc.route({
1815
1859
  method: "POST",
1816
- path: "/bases/{baseId}/archive/change-requests",
1860
+ path: "/bases/{baseId}/lifecycle/change-requests",
1817
1861
  tags: ["Bases", "Change Requests"],
1818
- summary: "Archive base",
1819
- successDescription: "Created change request that archives a base."
1820
- }).input(archiveBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1821
- restoreChangeRequest: oc.route({
1822
- method: "POST",
1823
- path: "/bases/{baseId}/restore/change-requests",
1824
- tags: ["Bases", "Change Requests"],
1825
- summary: "Restore base",
1826
- successDescription: "Created change request that restores an archived base."
1827
- }).input(restoreBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema)
1862
+ summary: "Create Base lifecycle change request",
1863
+ successDescription: "Created change request that moves a Base between its lifecycle states. `operation` selects the direction: `archive` (soft-delete a live Base) or `restore` (bring an archived Base back)."
1864
+ }).input(baseLifecycleChangeRequestInputSchema).output(changeRequestSchema)
1828
1865
  };
1829
1866
  var recordContract = {
1830
1867
  // One listing for records: always keyset-paginated, `baseId` always honoured,
@@ -1903,8 +1940,13 @@ var viewContract = {
1903
1940
  path: "/views/change-requests",
1904
1941
  tags: ["Views", "Change Requests"],
1905
1942
  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)
1943
+ successDescription: "Proposes a view change. `operation` selects what to propose: `create` (addressed by `baseId`), or `update` / `delete` / `restore` (addressed by `viewId`). Review-first when the actor lacks write access or passes `autoMerge: false` \u2014 a pending ChangeRequest (`materialized: false`). Otherwise the change is approved and merged in the same call and the materialized View comes back instead (`materialized: true`)."
1944
+ }).input(viewChangeRequestInputSchema).output(
1945
+ z.union([
1946
+ viewSchema.extend({ materialized: z.literal(true) }),
1947
+ changeRequestSchema.extend({ materialized: z.literal(false) })
1948
+ ])
1949
+ )
1908
1950
  };
1909
1951
  var ReadDocLinesInputSchema = z.object({
1910
1952
  nodeId: z.string(),
@@ -1943,13 +1985,6 @@ var createDocChangeRequestInputSchema = z.object({
1943
1985
  submittedBy: z.string().optional().default("local-producer")
1944
1986
  });
1945
1987
  var docContract = {
1946
- list: oc.route({
1947
- method: "GET",
1948
- path: "/docs",
1949
- tags: ["Docs"],
1950
- summary: "List Doc nodes",
1951
- successDescription: "Doc nodes with their storage-backed bodies."
1952
- }).output(z.array(docSchema)),
1953
1988
  create: oc.route({
1954
1989
  method: "POST",
1955
1990
  path: "/docs",
@@ -1962,19 +1997,12 @@ var docContract = {
1962
1997
  changeRequestSchema.extend({ materialized: z.literal(false) })
1963
1998
  ])
1964
1999
  ),
1965
- get: oc.route({
1966
- method: "GET",
1967
- path: "/docs/{nodeId}",
1968
- tags: ["Docs"],
1969
- summary: "Get Doc node",
1970
- successDescription: "Doc node detail and body."
1971
- }).input(z.object({ nodeId: z.string() })).output(docSchema),
1972
2000
  readLines: oc.route({
1973
2001
  method: "GET",
1974
2002
  path: "/docs/{nodeId}/lines",
1975
2003
  tags: ["Docs"],
1976
2004
  summary: "Read an exact line range from a Doc body",
1977
- successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) sliced from the Doc's full body \u2014 Docs are KB-scale, so the whole body is read in memory; no byte-range/checkpoint machinery like assets.readTextLines uses for potentially multi-GB files. The Doc-domain follow-up to a Unified Grep match with `source: \"docs\"`, so an agent can read just the lines around a match instead of `get`'s entire body."
2005
+ successDescription: "Lines [startLine, endLine] (range capped at 2000 lines / ~2MB response) sliced from the Doc's full body \u2014 Docs are KB-scale, so the whole body is read in memory; no byte-range/checkpoint machinery like assets.readTextLines uses for potentially multi-GB files. The Doc-domain follow-up to a Unified Grep match with `source: \"docs\"`, so an agent can read just the lines around a match instead of `nodes.get`'s entire body."
1978
2006
  }).input(ReadDocLinesInputSchema).output(ReadLinesVOSchema),
1979
2007
  updateBody: oc.route({
1980
2008
  method: "PUT",
@@ -2140,13 +2168,6 @@ var createFileNodeInputSchema = z.object({
2140
2168
  autoMerge: z.boolean().optional()
2141
2169
  });
2142
2170
  var fileContract = {
2143
- list: oc.route({
2144
- method: "GET",
2145
- path: "/files",
2146
- tags: ["Files"],
2147
- summary: "List File nodes",
2148
- successDescription: "Workspace File nodes with their backing Asset metadata."
2149
- }).output(z.array(FileNodeVOSchema)),
2150
2171
  create: oc.route({
2151
2172
  method: "POST",
2152
2173
  path: "/files",
@@ -2158,34 +2179,7 @@ var fileContract = {
2158
2179
  FileNodeVOSchema.extend({ materialized: z.literal(true) }),
2159
2180
  changeRequestSchema.extend({ materialized: z.literal(false) })
2160
2181
  ])
2161
- ),
2162
- get: oc.route({
2163
- method: "GET",
2164
- path: "/files/{nodeId}",
2165
- tags: ["Files"],
2166
- summary: "Get File node",
2167
- successDescription: "File node detail and backing Asset metadata."
2168
- }).input(z.object({ nodeId: z.string() })).output(FileNodeVOSchema)
2169
- };
2170
- var folderSchema = z.object({
2171
- node: nodeSchema,
2172
- children: z.array(nodeSchema)
2173
- });
2174
- var folderContract = {
2175
- list: oc.route({
2176
- method: "GET",
2177
- path: "/folders",
2178
- tags: ["Folders"],
2179
- summary: "List Folder nodes",
2180
- successDescription: "Folder nodes with their direct children."
2181
- }).output(z.array(folderSchema)),
2182
- get: oc.route({
2183
- method: "GET",
2184
- path: "/folders/{nodeId}",
2185
- tags: ["Folders"],
2186
- summary: "Get Folder node",
2187
- successDescription: "Folder node and its direct children."
2188
- }).input(z.object({ nodeId: z.string() })).output(folderSchema)
2182
+ )
2189
2183
  };
2190
2184
  var FormFieldBindingSchema = z.object({
2191
2185
  inputName: z.string().min(1),
@@ -2818,16 +2812,86 @@ var UnifiedGrepResultVOSchema = z.object({
2818
2812
  /** True when any source truncated, or any source has `notReached > 0`. */
2819
2813
  truncated: z.boolean()
2820
2814
  });
2815
+ var folderSchema = z.object({
2816
+ node: nodeSchema,
2817
+ children: z.array(nodeSchema)
2818
+ });
2819
+
2820
+ // ../../packages/busabase-contract/src/contract/node-detail-schemas.ts
2821
+ var genericNodeDetailSchema = (type) => z.object({
2822
+ type: z.literal(type),
2823
+ node: nodeSchema
2824
+ });
2825
+ var NODE_DETAIL_VARIANTS = {
2826
+ folder: folderSchema.extend({ type: z.literal("folder") }),
2827
+ doc: docSchema.extend({ type: z.literal("doc") }),
2828
+ file: FileNodeVOSchema.extend({ type: z.literal("file") }),
2829
+ // Skills, Drives, and AirApps are one server-side shape (`fileTreeNodeSchema`)
2830
+ // but three real node types — there is no synthetic "file-tree" node type, so
2831
+ // each gets its own discriminated variant rather than a shared alias.
2832
+ skill: fileTreeNodeSchema.extend({ type: z.literal("skill") }),
2833
+ drive: fileTreeNodeSchema.extend({ type: z.literal("drive") }),
2834
+ airapp: fileTreeNodeSchema.extend({ type: z.literal("airapp") }),
2835
+ base: genericNodeDetailSchema("base"),
2836
+ form: genericNodeDetailSchema("form"),
2837
+ whiteboard: genericNodeDetailSchema("whiteboard"),
2838
+ workflow: genericNodeDetailSchema("workflow"),
2839
+ html: genericNodeDetailSchema("html")
2840
+ };
2841
+ var NodeDetailVOSchema = z.discriminatedUnion("type", [
2842
+ NODE_DETAIL_VARIANTS.folder,
2843
+ NODE_DETAIL_VARIANTS.doc,
2844
+ NODE_DETAIL_VARIANTS.file,
2845
+ NODE_DETAIL_VARIANTS.skill,
2846
+ NODE_DETAIL_VARIANTS.drive,
2847
+ NODE_DETAIL_VARIANTS.airapp,
2848
+ NODE_DETAIL_VARIANTS.base,
2849
+ NODE_DETAIL_VARIANTS.form,
2850
+ NODE_DETAIL_VARIANTS.whiteboard,
2851
+ NODE_DETAIL_VARIANTS.workflow,
2852
+ NODE_DETAIL_VARIANTS.html
2853
+ ]);
2854
+ var getNodeInputSchema = z.object({
2855
+ nodeId: z.string().describe("Node id, or a slug that is unique within its type."),
2856
+ type: z.enum(NODE_TYPES).optional().describe(
2857
+ "Optional disambiguation hint, only needed when `nodeId` is a slug that exists under more than one node type."
2858
+ )
2859
+ });
2821
2860
 
2822
2861
  // ../../packages/busabase-contract/src/contract/busabase.ts
2823
- var changeRequestBatchResultSchema = z.object({
2862
+ var changeRequestBatchFailureSchema = z.object({
2863
+ changeRequestId: z.string(),
2864
+ ok: z.literal(false),
2865
+ error: z.string(),
2866
+ code: z.string().optional(),
2867
+ data: z.unknown().optional()
2868
+ });
2869
+ var changeRequestReviewBatchResultSchema = z.object({
2824
2870
  results: z.array(
2825
- z.object({
2826
- changeRequestId: z.string(),
2827
- ok: z.boolean(),
2828
- status: z.string().optional(),
2829
- error: z.string().optional()
2830
- })
2871
+ z.discriminatedUnion("ok", [
2872
+ z.object({
2873
+ changeRequestId: z.string(),
2874
+ ok: z.literal(true),
2875
+ status: z.string(),
2876
+ changeRequest: changeRequestSchema
2877
+ }),
2878
+ changeRequestBatchFailureSchema
2879
+ ])
2880
+ )
2881
+ });
2882
+ var changeRequestMergeBatchResultSchema = z.object({
2883
+ results: z.array(
2884
+ z.discriminatedUnion("ok", [
2885
+ z.object({
2886
+ changeRequestId: z.string(),
2887
+ ok: z.literal(true),
2888
+ status: z.string(),
2889
+ changeRequest: changeRequestSchema,
2890
+ record: recordSchema.nullable(),
2891
+ view: viewSchema.nullable()
2892
+ }),
2893
+ changeRequestBatchFailureSchema
2894
+ ])
2831
2895
  )
2832
2896
  });
2833
2897
  var busabaseContractRoutes = {
@@ -2862,8 +2926,8 @@ var busabaseContractRoutes = {
2862
2926
  method: "GET",
2863
2927
  path: "/nodes",
2864
2928
  tags: ["Nodes"],
2865
- summary: "List node tree",
2866
- successDescription: "Workspace node tree including folders, Bases, files, and agents. With no `parentId`/`depth`, returns the FULL tree (legacy behavior, still what every non-sidebar caller gets). Passing `parentId` and/or `depth` switches to a depth-bounded fetch: `parentId` omitted/null starts from the space root and returns it wrapped exactly like the legacy call (just depth-limited); an explicit `parentId` returns that node's children directly, ready to merge into its `NodeVO.children` for a sidebar's lazy per-folder expand. See `NodeVO.hasChildren` for how a depth boundary is surfaced."
2929
+ summary: "List nodes (workspace tree, or a flat summary list by type)",
2930
+ successDescription: "Workspace node tree including folders, Bases, files, and agents. With no `parentId`/`depth`, returns the FULL tree (legacy behavior, still what every non-sidebar caller gets). Passing `parentId` and/or `depth` switches to a depth-bounded fetch: `parentId` omitted/null starts from the space root and returns it wrapped exactly like the legacy call (just depth-limited); an explicit `parentId` returns that node's children directly, ready to merge into its `NodeVO.children` for a sidebar's lazy per-folder expand. See `NodeVO.hasChildren` for how a depth boundary is surfaced. Passing `types` instead returns a FLAT, ACL-filtered list of lightweight summaries (`children: []`) for just those node types \u2014 this is what replaced `GET /docs`, `/files`, `/folders`, and `/file-trees`, and it deliberately hydrates nothing heavy (no Doc bodies, backing Assets, folder children, or file inventories). Open one item with `GET /nodes/{nodeId}`."
2867
2931
  }).input(listNodesInputSchema).output(z.array(nodeSchema)),
2868
2932
  searchByName: oc.route({
2869
2933
  method: "GET",
@@ -2933,6 +2997,21 @@ var busabaseContractRoutes = {
2933
2997
  summary: "List the current actor's favorited nodes",
2934
2998
  successDescription: "The acting user's favorited nodes, newest-favorited first, filtered through the same archived/deleted/visibility rules as the main tree \u2014 a favorited node that's later archived, purged, or (cloud) hidden from this actor silently drops out rather than erroring."
2935
2999
  }).output(z.array(nodeSchema)),
3000
+ // Registered LAST among the `/nodes/...` GETs on purpose. `GET /nodes/search`
3001
+ // and `GET /nodes/favorites` are literal paths that now share a prefix with
3002
+ // this template. The oRPC OpenAPI matcher is a rou3 radix trie, which
3003
+ // prefers a static segment over a param segment independently of insertion
3004
+ // order — but keeping the literals declared first means the source order
3005
+ // matches the resolution order, so nobody has to know that to read this
3006
+ // file. `tests/openapi-node-routes.test.ts` proves the literals still win
3007
+ // against a real handler rather than resolving as `nodeId: "search"`.
3008
+ get: oc.route({
3009
+ method: "GET",
3010
+ path: "/nodes/{nodeId}",
3011
+ tags: ["Nodes"],
3012
+ summary: "Get one node's typed detail",
3013
+ successDescription: "The node's full detail, discriminated by its `type`. One entry point for every node type, so a caller holding an id never has to discover the type first: `folder` carries its direct `children`, `doc` its storage-backed `body`, `file` its backing `asset`, and `skill`/`drive`/`airapp` their Asset-backed `files`. Types with no richer detail yet (`base`, `form`, `whiteboard`, `workflow`, `html`) return just `node`. `nodeId` accepts an id or a slug; pass `type` when a slug exists under more than one type. Archived nodes are not returned (404), matching the typed gets this replaced."
3014
+ }).input(getNodeInputSchema).output(NodeDetailVOSchema),
2936
3015
  principals: {
2937
3016
  list: oc.route({
2938
3017
  method: "GET",
@@ -3066,7 +3145,9 @@ var busabaseContractRoutes = {
3066
3145
  airapps: airappRuntimeContract,
3067
3146
  files: fileContract,
3068
3147
  docs: docContract,
3069
- folders: folderContract,
3148
+ // No `folders` key: the Folder domain's only two operations were `GET /folders`
3149
+ // and `GET /folders/{nodeId}`, both now served by the unified Node surface
3150
+ // (`nodes.list({ types: ["folder"] })` / `nodes.get`).
3070
3151
  forms: formContract,
3071
3152
  assets: assetsContract,
3072
3153
  vault: vaultContract,
@@ -3083,6 +3164,16 @@ var busabaseContractRoutes = {
3083
3164
  summary: "List change requests",
3084
3165
  successDescription: "A page of change requests plus an opaque nextCursor (null at the end). Filter with `status` and/or `mine`."
3085
3166
  }).input(listChangeRequestsPagedInputSchema).output(listChangeRequestsResponseSchema),
3167
+ // Numbered paging alongside the cursor listing, mirroring records.listPage.
3168
+ // Keyset is right for "keep scrolling"; a reviewer working a 2,000-item tab
3169
+ // needs to jump to page 30 and to see how many pages there are at all.
3170
+ listPage: oc.route({
3171
+ method: "GET",
3172
+ path: "/change-requests/page",
3173
+ tags: ["Change Requests"],
3174
+ summary: "List a numbered change request page",
3175
+ successDescription: "A random-access page of change requests plus the total across the whole filter. Same `status` / `mine` filters as the cursor listing."
3176
+ }).input(listChangeRequestsPageInputSchema).output(listChangeRequestsPageResponseSchema),
3086
3177
  counts: oc.route({
3087
3178
  method: "GET",
3088
3179
  path: "/change-requests/counts",
@@ -3098,23 +3189,16 @@ var busabaseContractRoutes = {
3098
3189
  successDescription: "Change Request detail."
3099
3190
  }).input(z.object({ changeRequestId: z.string() })).output(changeRequestSchema),
3100
3191
  review: oc.route({
3101
- method: "POST",
3102
- path: "/change-requests/{changeRequestId}/reviews",
3103
- tags: ["Change Requests"],
3104
- summary: "Review change request",
3105
- successDescription: "Reviewed change request."
3106
- }).input(reviewChangeRequestInputSchema.extend({ changeRequestId: z.string() })).output(changeRequestSchema),
3107
- reviewMany: oc.route({
3108
3192
  method: "POST",
3109
3193
  path: "/change-requests/reviews",
3110
3194
  tags: ["Change Requests"],
3111
- summary: "Review many change requests",
3195
+ summary: "Review change requests",
3112
3196
  successDescription: "Per-change-request review results (failures isolated \u2014 one bad id does not abort the rest)."
3113
3197
  }).input(
3114
3198
  reviewChangeRequestInputSchema.extend({
3115
3199
  changeRequestIds: z.array(z.string()).min(1).max(100)
3116
3200
  })
3117
- ).output(changeRequestBatchResultSchema),
3201
+ ).output(changeRequestReviewBatchResultSchema),
3118
3202
  close: oc.route({
3119
3203
  method: "POST",
3120
3204
  path: "/change-requests/{changeRequestId}/close",
@@ -3123,25 +3207,12 @@ var busabaseContractRoutes = {
3123
3207
  successDescription: "Closed change request (terminal \u2014 distinct from request changes)."
3124
3208
  }).input(z.object({ changeRequestId: z.string(), reason: z.string().optional() })).output(changeRequestSchema),
3125
3209
  merge: oc.route({
3126
- method: "POST",
3127
- path: "/change-requests/{changeRequestId}/merge",
3128
- tags: ["Change Requests"],
3129
- summary: "Merge change request into Base",
3130
- successDescription: "Merged change request and canonical record."
3131
- }).input(z.object({ changeRequestId: z.string() })).output(
3132
- z.object({
3133
- changeRequest: changeRequestSchema,
3134
- record: recordSchema.nullable(),
3135
- view: viewSchema.nullable()
3136
- })
3137
- ),
3138
- mergeMany: oc.route({
3139
3210
  method: "POST",
3140
3211
  path: "/change-requests/merge",
3141
3212
  tags: ["Change Requests"],
3142
- summary: "Merge many change requests",
3213
+ summary: "Merge change requests",
3143
3214
  successDescription: "Per-change-request merge results (each merged in its own transaction; failures isolated)."
3144
- }).input(z.object({ changeRequestIds: z.array(z.string()).min(1).max(100) })).output(changeRequestBatchResultSchema)
3215
+ }).input(z.object({ changeRequestIds: z.array(z.string()).min(1).max(100) })).output(changeRequestMergeBatchResultSchema)
3145
3216
  },
3146
3217
  operations: {
3147
3218
  revise: oc.route({
@@ -3482,6 +3553,10 @@ var getRecordByField = async (client, input) => {
3482
3553
  };
3483
3554
 
3484
3555
  // src/index.ts
3556
+ var batchItemError = (result) => Object.assign(new Error(result?.error ?? "Change request action returned no result"), {
3557
+ ...result?.code ? { code: result.code } : {},
3558
+ ...result?.data === void 0 ? {} : { data: result.data }
3559
+ });
3485
3560
  var Busabase = class {
3486
3561
  /** The underlying fully-typed oRPC client. Use it for anything not surfaced here. */
3487
3562
  client;
@@ -3509,11 +3584,55 @@ var Busabase = class {
3509
3584
  return this.client.views;
3510
3585
  }
3511
3586
  get changeRequests() {
3512
- return this.client.changeRequests;
3587
+ const review = async (input) => {
3588
+ if ("changeRequestIds" in input) return this.client.changeRequests.review(input);
3589
+ const { changeRequestId, ...reviewInput } = input;
3590
+ const { results } = await this.client.changeRequests.review({
3591
+ ...reviewInput,
3592
+ changeRequestIds: [changeRequestId]
3593
+ });
3594
+ const result = results[0];
3595
+ if (!result?.ok) throw batchItemError(result);
3596
+ return result.changeRequest;
3597
+ };
3598
+ const merge = async (input) => {
3599
+ if ("changeRequestIds" in input) return this.client.changeRequests.merge(input);
3600
+ const { results } = await this.client.changeRequests.merge({
3601
+ changeRequestIds: [input.changeRequestId]
3602
+ });
3603
+ const result = results[0];
3604
+ if (!result?.ok) throw batchItemError(result);
3605
+ return {
3606
+ changeRequest: result.changeRequest,
3607
+ record: result.record,
3608
+ view: result.view
3609
+ };
3610
+ };
3611
+ return new Proxy(this.client.changeRequests, {
3612
+ get(target, property, receiver) {
3613
+ if (property === "review") return review;
3614
+ if (property === "merge") return merge;
3615
+ return Reflect.get(target, property, receiver);
3616
+ }
3617
+ });
3513
3618
  }
3514
3619
  get operations() {
3515
3620
  return this.client.operations;
3516
3621
  }
3622
+ /**
3623
+ * The workspace node surface, and the single entry point for reading ONE node
3624
+ * of any type: `bb.nodes.get({ nodeId })` returns a `NodeDetailVO`
3625
+ * discriminated by `type` (`folder` carries `children`, `doc` a `body`, `file`
3626
+ * its `asset`, `skill`/`drive`/`airapp` their `files`). It replaced the four
3627
+ * typed gets (`docs`/`files`/`folders`/`fileTrees`), so a caller holding an id
3628
+ * no longer has to know the node's type before it can read it.
3629
+ *
3630
+ * `bb.nodes.list({ types })` is the matching list: a flat array of lightweight
3631
+ * summaries for just those types. Without `types` it still returns the full
3632
+ * workspace tree.
3633
+ *
3634
+ * There is no `bb.folders` any more — folders are `type: "folder"` here.
3635
+ */
3517
3636
  get nodes() {
3518
3637
  return this.client.nodes;
3519
3638
  }
@@ -3535,19 +3654,38 @@ var Busabase = class {
3535
3654
  }
3536
3655
  });
3537
3656
  }
3538
- /** Skills, Drives, and AirApps — one surface, discriminated by `type`. */
3657
+ /**
3658
+ * Skills, Drives, and AirApps — one surface, discriminated by `type`.
3659
+ *
3660
+ * Creation and per-file reads/writes live here. Listing them and reading one
3661
+ * node's detail moved to the unified Node surface:
3662
+ * `bb.nodes.list({ types: ["skill", "drive", "airapp"] })` and
3663
+ * `bb.nodes.get({ nodeId, type })`.
3664
+ */
3539
3665
  get fileTrees() {
3540
3666
  return this.client.fileTrees;
3541
3667
  }
3668
+ /**
3669
+ * File nodes. `create` only — list with `bb.nodes.list({ types: ["file"] })`
3670
+ * and read one (backing Asset included) with `bb.nodes.get({ nodeId })`.
3671
+ */
3542
3672
  get files() {
3543
3673
  return this.client.files;
3544
3674
  }
3675
+ /**
3676
+ * Docs. Create / read a line range / update the body / open a Change Request.
3677
+ * List with `bb.nodes.list({ types: ["doc"] })` and read one (body included)
3678
+ * with `bb.nodes.get({ nodeId })`.
3679
+ *
3680
+ * There is deliberately no `bb.docs.list()` shim. The retired `GET /docs`
3681
+ * returned every Doc *with its body*; the one-call replacement returns
3682
+ * lightweight summaries, and the only way to keep the old shape would be a
3683
+ * detail request per Doc. An SDK convenience that quietly turns one call into
3684
+ * N is worse than a compile error that points at `bb.nodes`.
3685
+ */
3545
3686
  get docs() {
3546
3687
  return this.client.docs;
3547
3688
  }
3548
- get folders() {
3549
- return this.client.folders;
3550
- }
3551
3689
  get agentTasks() {
3552
3690
  return this.client.agentTasks;
3553
3691
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.10.3",
3
+ "version": "0.11.2",
4
4
  "description": "Typed TypeScript/JavaScript SDK for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server` (or Busabase Cloud).",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-sdk",
@@ -49,9 +49,9 @@
49
49
  "tsx": "^4.20.5",
50
50
  "typescript": "^5.9.3",
51
51
  "vitest": "^2.1.8",
52
- "busabase-contract": "0.10.3",
53
52
  "open-domains": "0.0.2",
54
- "openlib": "0.1.1"
53
+ "openlib": "0.1.1",
54
+ "busabase-contract": "0.11.2"
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=24.18.0"