busabase-sdk 0.11.0 → 0.11.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 (3) hide show
  1. package/dist/index.d.ts +1468 -1419
  2. package/dist/index.js +138 -30
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -36,6 +36,9 @@ var LocaleSchema = z.enum(i18n.locales);
36
36
  // ../../packages/openlib/i18n/i-string.ts
37
37
  var iStringRecordSchema = z.partialRecord(LocaleSchema, z.string());
38
38
  z.union([z.string(), iStringRecordSchema]).describe("i18n string");
39
+ var autoMergeNotAccepted = (reason) => z.literal(false, { error: `\`autoMerge: true\` is not accepted here: ${reason}` }).optional().describe(`Only \`false\` (or omitted) is accepted. ${reason}`);
40
+
41
+ // ../../packages/busabase-contract/src/domains/base/contract/base-schemas.ts
39
42
  var fieldNameSchema = z.union([
40
43
  z.string().min(1),
41
44
  iStringRecordSchema.refine(
@@ -191,14 +194,21 @@ var createBaseFieldInputSchema = z.object({
191
194
  required: z.boolean().optional().default(false),
192
195
  options: fieldOptionsSchema.optional().default({})
193
196
  });
197
+ var fieldAutoMergeSchema = z.boolean().optional().describe(
198
+ "Whether to approve and merge this field 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. Not accepted by the delete and convert operations, which always require review."
199
+ );
194
200
  var createFieldChangeRequestInputSchema = createBaseFieldInputSchema.extend({
195
201
  message: z.string().optional().default("Add field"),
196
- submittedBy: z.string().optional().default("local-editor")
202
+ submittedBy: z.string().optional().default("local-editor"),
203
+ autoMerge: fieldAutoMergeSchema
197
204
  });
198
205
  var deleteFieldChangeRequestInputSchema = z.object({
199
206
  fieldId: z.string().min(1),
200
207
  message: z.string().optional(),
201
- submittedBy: z.string().optional().default("local-editor")
208
+ submittedBy: z.string().optional().default("local-editor"),
209
+ autoMerge: autoMergeNotAccepted(
210
+ "deleting a field soft-deletes its stored values with it, so it always requires review. Omit the flag."
211
+ )
202
212
  });
203
213
  var updateFieldChangeRequestInputSchema = z.object({
204
214
  fieldId: z.string().min(1),
@@ -208,7 +218,8 @@ var updateFieldChangeRequestInputSchema = z.object({
208
218
  options: fieldOptionsSchema.optional()
209
219
  }),
210
220
  message: z.string().optional(),
211
- submittedBy: z.string().optional().default("local-editor")
221
+ submittedBy: z.string().optional().default("local-editor"),
222
+ autoMerge: fieldAutoMergeSchema
212
223
  });
213
224
  var previewFieldConversionInputSchema = z.object({
214
225
  fieldId: z.string().min(1),
@@ -225,25 +236,39 @@ var convertFieldChangeRequestInputSchema = z.object({
225
236
  newType: fieldTypeSchema,
226
237
  selectChoiceMode: z.enum(["auto_create", "null_on_missing"]).default("null_on_missing"),
227
238
  message: z.string().optional(),
228
- submittedBy: z.string().optional().default("local-editor")
239
+ submittedBy: z.string().optional().default("local-editor"),
240
+ autoMerge: autoMergeNotAccepted(
241
+ "converting a field's type can drop values, so it always requires review. Run previewFieldConversion first to see what would change, then omit the flag."
242
+ )
229
243
  });
230
244
  var reorderFieldsChangeRequestInputSchema = z.object({
231
245
  fieldIds: z.array(z.string()).min(1),
232
246
  message: z.string().optional(),
233
- submittedBy: z.string().optional().default("local-editor")
247
+ submittedBy: z.string().optional().default("local-editor"),
248
+ autoMerge: fieldAutoMergeSchema
234
249
  });
235
250
  var archiveBaseInputSchema = z.object({
236
251
  message: z.string().optional(),
237
- submittedBy: z.string().optional().default("local-editor")
252
+ submittedBy: z.string().optional().default("local-editor"),
253
+ autoMerge: autoMergeNotAccepted(
254
+ "archiving a Base removes it and every record in it from every listing at once, so it always requires review. Omit the flag."
255
+ )
238
256
  });
239
257
  var restoreBaseInputSchema = z.object({
240
258
  message: z.string().optional(),
241
- submittedBy: z.string().optional().default("local-editor")
259
+ submittedBy: z.string().optional().default("local-editor"),
260
+ // Restoring an archived Base is the undo of a destructive act — nothing is at
261
+ // risk, so it takes the same permission-aware default as everything else.
262
+ // `archiveBaseInputSchema` above deliberately has no `autoMerge`: archiving
263
+ // takes a whole Base and every record in it out of every listing at once,
264
+ // which is strictly larger than the record `delete` that is already review-only.
265
+ autoMerge: z.boolean().optional()
242
266
  });
243
267
  var restoreFieldChangeRequestInputSchema = z.object({
244
268
  fieldId: z.string().min(1),
245
269
  message: z.string().optional(),
246
- submittedBy: z.string().optional().default("local-editor")
270
+ submittedBy: z.string().optional().default("local-editor"),
271
+ autoMerge: fieldAutoMergeSchema
247
272
  });
248
273
  var withBaseId = { baseId: z.string().min(1) };
249
274
  var fieldChangeRequestInputSchema = z.discriminatedUnion("operation", [
@@ -254,6 +279,10 @@ var fieldChangeRequestInputSchema = z.discriminatedUnion("operation", [
254
279
  reorderFieldsChangeRequestInputSchema.extend({ operation: z.literal("reorder"), ...withBaseId }),
255
280
  restoreFieldChangeRequestInputSchema.extend({ operation: z.literal("restore"), ...withBaseId })
256
281
  ]);
282
+ var baseLifecycleChangeRequestInputSchema = z.discriminatedUnion("operation", [
283
+ archiveBaseInputSchema.extend({ operation: z.literal("archive"), ...withBaseId }),
284
+ restoreBaseInputSchema.extend({ operation: z.literal("restore"), ...withBaseId })
285
+ ]);
257
286
 
258
287
  // ../../packages/busabase-contract/src/domains/filetree/definition.ts
259
288
  var fileTreeOperations = (type) => [
@@ -896,7 +925,10 @@ var createDeleteChangeRequestInputSchema = z.object({
896
925
  submittedBy: z.string().optional().default("local-producer"),
897
926
  // Only "archive" is supported — hard delete after retention was never
898
927
  // implemented, so the API no longer accepts it (breaking change).
899
- deleteMode: z.enum(["archive"]).optional().default("archive")
928
+ deleteMode: z.enum(["archive"]).optional().default("archive"),
929
+ autoMerge: autoMergeNotAccepted(
930
+ "archiving a record removes user content from every listing, so it always requires review. Omit the flag."
931
+ )
900
932
  });
901
933
  var reviseOperationInputSchema = z.object({
902
934
  fields: z.record(z.string(), z.unknown()).describe(
@@ -938,6 +970,19 @@ var listChangeRequestsResponseSchema = z.object({
938
970
  changeRequests: z.array(changeRequestSchema),
939
971
  nextCursor: z.string().nullable()
940
972
  });
973
+ var listChangeRequestsPageInputSchema = z.object({
974
+ page: z.coerce.number().int().min(1).optional().default(1),
975
+ pageSize: z.coerce.number().int().min(1).max(100).optional().default(50),
976
+ status: z.array(changeRequestStatusSchema).optional(),
977
+ mine: z.boolean().optional()
978
+ }).optional().default({ page: 1, pageSize: 50 });
979
+ var listChangeRequestsPageResponseSchema = z.object({
980
+ changeRequests: z.array(changeRequestSchema),
981
+ total: z.number().int().nonnegative(),
982
+ totalPages: z.number().int().nonnegative(),
983
+ page: z.number().int().min(1),
984
+ pageSize: z.number().int().min(1).max(100)
985
+ });
941
986
  var changeRequestCountsSchema = z.object({
942
987
  review: z.number().int().nonnegative(),
943
988
  changes: z.number().int().nonnegative(),
@@ -1107,7 +1152,16 @@ var createFileTreeChangeRequestInputSchema = z.object({
1107
1152
  'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Rewrite README.md quickstart for the new auth flow".'
1108
1153
  ),
1109
1154
  submittedBy: z.string().optional().default("local-producer"),
1110
- operations: z.array(fileTreeFileOperationInputSchema).min(1)
1155
+ operations: z.array(fileTreeFileOperationInputSchema).min(1),
1156
+ // Honoured only when EVERY operation in the batch is non-destructive
1157
+ // (create / update / metadata_update). A batch containing a `delete` stays
1158
+ // review-first no matter what this says, because deleting a mounted file
1159
+ // destroys content — the same line that keeps record `delete` review-only.
1160
+ // Server-side enforcement lives in the filetree handler, not here: the check
1161
+ // is over the operations array, which a per-field schema cannot express.
1162
+ autoMerge: z.boolean().optional().describe(
1163
+ "Whether to approve and merge these file changes immediately. Omitted defaults to merging immediately if the actor has write access on the node, otherwise falling back to a pending Change Request; pass explicit false to force review even with write access. IGNORED when any operation is a delete \u2014 those batches always require review."
1164
+ )
1111
1165
  });
1112
1166
  var FILE_TREE_NODE_TYPES = ["skill", "drive", "airapp"];
1113
1167
  var fileTreeNodeTypeSchema = z.enum(FILE_TREE_NODE_TYPES);
@@ -1391,7 +1445,10 @@ var EditAssetContentInputSchema = z.object({
1391
1445
  message: z.string().optional().default("Edit file content").describe(
1392
1446
  'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Fix typo in setup instructions".'
1393
1447
  ),
1394
- submittedBy: z.string().optional().default("agent")
1448
+ submittedBy: z.string().optional().default("agent"),
1449
+ autoMerge: autoMergeNotAccepted(
1450
+ "editContent rewrites the real mounted file bytes, so it always requires review. Omit the flag."
1451
+ )
1395
1452
  });
1396
1453
  var AssetDownloadInputSchema = z.object({ assetId: z.string() });
1397
1454
  var AssetDownloadVOSchema = z.object({
@@ -1674,9 +1731,42 @@ var listRecordsPageResponseSchema = z.object({
1674
1731
  page: z.number().int().min(1),
1675
1732
  pageSize: z.number().int().min(1).max(100)
1676
1733
  });
1677
- var countRecordsInputSchema = z.object({
1678
- baseId: z.string().optional()
1679
- }).optional().default({});
1734
+ var countRecordsShapeSchema = z.object({
1735
+ baseId: z.string().optional(),
1736
+ /**
1737
+ * Count only the rows a saved View would display (the View's filters
1738
+ * applied; its sort is ignored — a count doesn't need an order). A View
1739
+ * belongs to exactly one Base, so this requires `baseId`.
1740
+ */
1741
+ viewId: z.string().optional(),
1742
+ /**
1743
+ * Ad-hoc filter conditions — same shape `records.list`'s `filters` uses —
1744
+ * for composing a condition set without a saved View (e.g. an AirApp
1745
+ * summary tile like "main-branch PRs"). Combined with the View's own
1746
+ * filters (AND) when `viewId` is also given. Requires `baseId`: a field
1747
+ * slug is only unambiguous within one Base, and proving a filter exact
1748
+ * (see `countRecords`) requires that Base's real field definitions —
1749
+ * never the caller-supplied `fieldType` hint, which elsewhere is only a
1750
+ * pushdown hint and isn't trustworthy enough for an exact count.
1751
+ */
1752
+ filters: z.array(listRecordsFilterSchema).optional()
1753
+ }).superRefine((value, ctx) => {
1754
+ if (value.viewId && !value.baseId) {
1755
+ ctx.addIssue({
1756
+ code: "custom",
1757
+ path: ["baseId"],
1758
+ message: "baseId is required when viewId is given"
1759
+ });
1760
+ }
1761
+ if (value.filters?.length && !value.baseId) {
1762
+ ctx.addIssue({
1763
+ code: "custom",
1764
+ path: ["baseId"],
1765
+ message: "baseId is required when filters is given"
1766
+ });
1767
+ }
1768
+ });
1769
+ var countRecordsInputSchema = countRecordsShapeSchema.optional().default({});
1680
1770
  var countRecordsResponseSchema = z.object({
1681
1771
  /** Total active records in the space (optionally scoped to a base). */
1682
1772
  total: z.number().int().nonnegative()
@@ -1711,7 +1801,13 @@ var createBulkChangeRequestInputSchema = z.object({
1711
1801
  submittedBy: z.string().optional().default("local-producer"),
1712
1802
  idempotencyKey: z.string().optional().describe(
1713
1803
  "Optional client-supplied key that dedupes retries. Scoped per base + submitter: calling this endpoint again with the SAME idempotencyKey returns the bulk change request created by the first call instead of creating a duplicate. Omit for normal one-shot calls; only set it when you might retry."
1714
- )
1804
+ ),
1805
+ // Same permission-aware tri-state as the single-record endpoint above. N record
1806
+ // CREATES are purely additive, so there is nothing here the review gate is
1807
+ // protecting — and until now the published skill doc told agents to send N
1808
+ // separate single-record calls precisely because this one could not merge,
1809
+ // which is slower and produces N change requests instead of one.
1810
+ autoMerge: z.boolean().optional()
1715
1811
  });
1716
1812
  var recordFieldFilterInputSchema = z.object({
1717
1813
  baseId: z.string().optional(),
@@ -1732,7 +1828,10 @@ var recordGetInputSchema = z.union([
1732
1828
  ]);
1733
1829
  var restoreRecordInputSchema = z.object({
1734
1830
  message: z.string().optional(),
1735
- submittedBy: z.string().optional().default("local-editor")
1831
+ submittedBy: z.string().optional().default("local-editor"),
1832
+ autoMerge: autoMergeNotAccepted(
1833
+ "restoring a record brings archived content back into every listing, so it always requires review. Omit the flag."
1834
+ )
1736
1835
  });
1737
1836
  var withRecordId = { recordId: z.string().min(1) };
1738
1837
  var recordChangeRequestInputSchema = z.discriminatedUnion("operation", [
@@ -1838,20 +1937,13 @@ var baseContract = {
1838
1937
  summary: "Preview field type conversion",
1839
1938
  successDescription: "Dry-run statistics for converting a field to a different type."
1840
1939
  }).input(previewFieldConversionInputSchema.extend({ baseId: z.string() })).output(previewFieldConversionOutputSchema),
1841
- archiveChangeRequest: oc.route({
1842
- method: "POST",
1843
- path: "/bases/{baseId}/archive/change-requests",
1844
- tags: ["Bases", "Change Requests"],
1845
- summary: "Archive base",
1846
- successDescription: "Created change request that archives a base."
1847
- }).input(archiveBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema),
1848
- restoreChangeRequest: oc.route({
1940
+ lifecycleChangeRequest: oc.route({
1849
1941
  method: "POST",
1850
- path: "/bases/{baseId}/restore/change-requests",
1942
+ path: "/bases/{baseId}/lifecycle/change-requests",
1851
1943
  tags: ["Bases", "Change Requests"],
1852
- summary: "Restore base",
1853
- successDescription: "Created change request that restores an archived base."
1854
- }).input(restoreBaseInputSchema.extend({ baseId: z.string() })).output(changeRequestSchema)
1944
+ summary: "Create Base lifecycle change request",
1945
+ 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)."
1946
+ }).input(baseLifecycleChangeRequestInputSchema).output(changeRequestSchema)
1855
1947
  };
1856
1948
  var recordContract = {
1857
1949
  // One listing for records: always keyset-paginated, `baseId` always honoured,
@@ -1877,7 +1969,8 @@ var recordContract = {
1877
1969
  path: "/records/count",
1878
1970
  tags: ["Records"],
1879
1971
  summary: "Count records",
1880
- successDescription: "Total active records in the space, optionally scoped to a base."
1972
+ description: "A real SQL COUNT \u2014 always the exact total, never a partial or capped number, so it's safe to render as a canonical figure (e.g. a dashboard summary tile). Plain `baseId` scoping is always cheap. Adding `viewId` and/or `filters` is exact too \u2014 provably-exact conditions (e.g. text equals/contains, not_empty/is_empty, checkbox is_true/is_false) stay a cheap SQL COUNT; everything else falls back to evaluating every matching row server-side, which is exact but not free on a large Base. Both `viewId` and `filters` require `baseId`.",
1973
+ successDescription: "Total active records matching the scope: the whole space, one Base, a saved View, an ad-hoc filter set, or a combination."
1881
1974
  }).input(countRecordsInputSchema).output(countRecordsResponseSchema),
1882
1975
  get: oc.route({
1883
1976
  method: "GET",
@@ -1972,7 +2065,12 @@ var createDocChangeRequestInputSchema = z.object({
1972
2065
  message: z.string().optional().default("Update doc").describe(
1973
2066
  'Explanation shown to the human reviewer. Write a conventional-commit style subject \u2014 imperative verb + what + why, e.g. "Add rollback steps to the deploy runbook".'
1974
2067
  ),
1975
- submittedBy: z.string().optional().default("local-producer")
2068
+ submittedBy: z.string().optional().default("local-producer"),
2069
+ // A Doc body update is the Doc-domain twin of a record `update`, which has taken
2070
+ // the permission-aware default since #5712 — and this node type already has a
2071
+ // direct-write bypass (`PUT /docs/{nodeId}/body`), so review-first here was never
2072
+ // an actual guarantee, just a slower path to the same place.
2073
+ autoMerge: z.boolean().optional()
1976
2074
  });
1977
2075
  var docContract = {
1978
2076
  create: oc.route({
@@ -3154,6 +3252,16 @@ var busabaseContractRoutes = {
3154
3252
  summary: "List change requests",
3155
3253
  successDescription: "A page of change requests plus an opaque nextCursor (null at the end). Filter with `status` and/or `mine`."
3156
3254
  }).input(listChangeRequestsPagedInputSchema).output(listChangeRequestsResponseSchema),
3255
+ // Numbered paging alongside the cursor listing, mirroring records.listPage.
3256
+ // Keyset is right for "keep scrolling"; a reviewer working a 2,000-item tab
3257
+ // needs to jump to page 30 and to see how many pages there are at all.
3258
+ listPage: oc.route({
3259
+ method: "GET",
3260
+ path: "/change-requests/page",
3261
+ tags: ["Change Requests"],
3262
+ summary: "List a numbered change request page",
3263
+ successDescription: "A random-access page of change requests plus the total across the whole filter. Same `status` / `mine` filters as the cursor listing."
3264
+ }).input(listChangeRequestsPageInputSchema).output(listChangeRequestsPageResponseSchema),
3157
3265
  counts: oc.route({
3158
3266
  method: "GET",
3159
3267
  path: "/change-requests/counts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "busabase-sdk",
3
- "version": "0.11.0",
3
+ "version": "0.11.3",
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.11.0",
53
- "openlib": "0.1.1",
54
- "open-domains": "0.0.2"
52
+ "busabase-contract": "0.11.3",
53
+ "open-domains": "0.0.2",
54
+ "openlib": "0.1.1"
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=24.18.0"