busa-sdk 0.16.2 → 0.17.1
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/airapp.d.ts +75 -7
- package/dist/airapp.js +92 -4
- package/dist/{client-DN7Ol1g1.d.ts → client-DIlpYL9z.d.ts} +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -0
- package/package.json +1 -1
package/dist/airapp.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as BusabaseClient } from './client-
|
|
1
|
+
import { B as BusabaseClient } from './client-DIlpYL9z.js';
|
|
2
2
|
import '@orpc/contract';
|
|
3
3
|
import '@orpc/shared';
|
|
4
4
|
import 'zod';
|
|
@@ -47,6 +47,11 @@ import 'zod';
|
|
|
47
47
|
|
|
48
48
|
type NodeChangeRequestInput = Parameters<BusabaseClient["nodes"]["createChangeRequest"]>[0];
|
|
49
49
|
type NodeOperationInput = NodeChangeRequestInput["operations"][number];
|
|
50
|
+
type FileTreeChangeRequestInput = Parameters<BusabaseClient["fileTrees"]["createChangeRequest"]>[0];
|
|
51
|
+
type FileTreeOperationInput = FileTreeChangeRequestInput["operations"][number];
|
|
52
|
+
type FileTreeCreateOrUpdateOperation = Extract<FileTreeOperationInput, {
|
|
53
|
+
kind: "create" | "update";
|
|
54
|
+
}>;
|
|
50
55
|
/**
|
|
51
56
|
* A Base field, as an app declares it.
|
|
52
57
|
*
|
|
@@ -93,15 +98,20 @@ interface AirAppFolderDeclaration {
|
|
|
93
98
|
/**
|
|
94
99
|
* The app's own AirApp node, when it ships one inside its Folder.
|
|
95
100
|
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
+
* `inspectProvisionedResources`/`provisionDeclaredResources` never create it —
|
|
102
|
+
* an app's Folder and Bases are plain data-schema resources, safe to bring
|
|
103
|
+
* into existence unattended, but an AirApp is a bundle of code the viewer's
|
|
104
|
+
* browser will execute, so bringing it into existence always goes through
|
|
105
|
+
* `publishAirApp`'s separate, always-review-first ChangeRequest instead of
|
|
106
|
+
* riding along on the same `autoMerge: true` request as the data layer.
|
|
107
|
+
* Declaring it here matters for a second reason regardless: without it, an
|
|
108
|
+
* unstamped Folder holding the app's own AirApp would look like it holds an
|
|
109
|
+
* unattributable stranger, and the legacy claim would be refused.
|
|
101
110
|
*/
|
|
102
111
|
interface AirAppNodeDeclaration {
|
|
103
112
|
slug: string;
|
|
104
113
|
name: string;
|
|
114
|
+
description?: string;
|
|
105
115
|
/** Ownership key written into the node's metadata, e.g. `"airapp"`. */
|
|
106
116
|
resourceKey: string;
|
|
107
117
|
}
|
|
@@ -153,6 +163,17 @@ interface AirAppResources {
|
|
|
153
163
|
missing: AirAppBaseDeclaration[];
|
|
154
164
|
/** Owned nodes whose ownership stamp is missing or stale. */
|
|
155
165
|
repairs: AirAppOwnershipRepair[];
|
|
166
|
+
/**
|
|
167
|
+
* The app's own AirApp node (see `AirAppNodeDeclaration`), when `config.airApp`
|
|
168
|
+
* is declared and a matching node exists under the Folder — owned or legacy,
|
|
169
|
+
* stamped or not; a pending stamp repair is reported separately via `repairs`.
|
|
170
|
+
* `null` when not declared, or declared but not found — the latter is what
|
|
171
|
+
* `publishAirApp` treats as "create", not a `missing`-array entry, because
|
|
172
|
+
* unlike a Base it is never auto-created just by finding it absent.
|
|
173
|
+
*/
|
|
174
|
+
airApp: {
|
|
175
|
+
nodeId: string;
|
|
176
|
+
} | null;
|
|
156
177
|
/**
|
|
157
178
|
* Set when the server is too old for `nodes.updateMetadata`, so ownership was
|
|
158
179
|
* established by full structural fingerprint instead of a stamp.
|
|
@@ -229,5 +250,52 @@ declare function inspectProvisionedResources(client: AirAppProvisioningClient, c
|
|
|
229
250
|
* @throws {AirAppSetupError} with a `code` describing which screen to show.
|
|
230
251
|
*/
|
|
231
252
|
declare function provisionDeclaredResources(client: AirAppProvisioningClient, config: AirAppResourceConfig): Promise<AirAppResources>;
|
|
253
|
+
/** The client surface `publishAirApp` needs, on top of provisioning. */
|
|
254
|
+
type AirAppPublishClient = AirAppProvisioningClient & Pick<BusabaseClient, "fileTrees" | "changeRequests">;
|
|
255
|
+
/** One file of the app's built AirApp bundle, as `publishAirApp` receives it. */
|
|
256
|
+
interface AirAppFileInput {
|
|
257
|
+
path: string;
|
|
258
|
+
content: string;
|
|
259
|
+
mimeType?: string;
|
|
260
|
+
}
|
|
261
|
+
type AirAppPublishResult = {
|
|
262
|
+
status: "created";
|
|
263
|
+
changeRequestId: string;
|
|
264
|
+
} | {
|
|
265
|
+
status: "updated";
|
|
266
|
+
changeRequestId: string;
|
|
267
|
+
} | {
|
|
268
|
+
status: "pending";
|
|
269
|
+
changeRequestId: string;
|
|
270
|
+
};
|
|
271
|
+
/**
|
|
272
|
+
* The create-vs-update operation list for one AirApp publish. Pure — no I/O —
|
|
273
|
+
* so the decision is directly testable: a local path already present on the
|
|
274
|
+
* deployed node updates it, anything else is a new file. Never deletes a
|
|
275
|
+
* remote-only path — a file this bundle stopped shipping is left alone rather
|
|
276
|
+
* than assumed stale, the same conservative choice the rest of this module
|
|
277
|
+
* makes for a Base's fields (`additiveFieldsFor` only ever appends).
|
|
278
|
+
*/
|
|
279
|
+
declare function buildAirAppFileOperations(localFiles: AirAppFileInput[], deployedPaths: Iterable<string>): FileTreeCreateOrUpdateOperation[];
|
|
280
|
+
/**
|
|
281
|
+
* Publish the app's own AirApp bundle: create it under the Folder when this
|
|
282
|
+
* Space has never had it, or propose the local files as an update when it
|
|
283
|
+
* already exists. Always a separate, always-review-first ChangeRequest from
|
|
284
|
+
* the data layer's `provisionDeclaredResources` — see the note on
|
|
285
|
+
* `AirAppNodeDeclaration` for why the two must never share a request.
|
|
286
|
+
*
|
|
287
|
+
* Call after `provisionDeclaredResources` has confirmed the Folder exists.
|
|
288
|
+
* Every call proposes the full local file list, even when nothing actually
|
|
289
|
+
* changed — this module has no access to the deployed content hashes
|
|
290
|
+
* (`fileTrees.listFiles` reports paths, not hashes; only a per-file
|
|
291
|
+
* `readFile` does, and fetching one per file to skip a no-op publish is not
|
|
292
|
+
* worth the round trips a normal publish cadence would spend on it). A
|
|
293
|
+
* reviewer sees an empty diff and merges or ignores it; this is a cost in
|
|
294
|
+
* review noise, not correctness.
|
|
295
|
+
*
|
|
296
|
+
* @throws {AirAppSetupError} `SETUP_CONFLICT` when `config` declares no
|
|
297
|
+
* `airApp`; `SETUP_REQUIRED` when the Folder does not exist yet.
|
|
298
|
+
*/
|
|
299
|
+
declare function publishAirApp(client: AirAppPublishClient, config: AirAppResourceConfig, files: AirAppFileInput[]): Promise<AirAppPublishResult>;
|
|
232
300
|
|
|
233
|
-
export { type AirAppBaseDeclaration, type AirAppFieldDeclaration, type AirAppFolderDeclaration, type AirAppNodeDeclaration, type AirAppOwnershipRepair, type AirAppProvisionedBase, type AirAppProvisioningClient, type AirAppResourceConfig, type AirAppResourceOwnership, type AirAppResources, type AirAppSetupCode, AirAppSetupError, buildProvisionOperations, inspectProvisionedResources, isNotFound, provisionDeclaredResources, resolveProvisionedFolder };
|
|
301
|
+
export { type AirAppBaseDeclaration, type AirAppFieldDeclaration, type AirAppFileInput, type AirAppFolderDeclaration, type AirAppNodeDeclaration, type AirAppOwnershipRepair, type AirAppProvisionedBase, type AirAppProvisioningClient, type AirAppPublishClient, type AirAppPublishResult, type AirAppResourceConfig, type AirAppResourceOwnership, type AirAppResources, type AirAppSetupCode, AirAppSetupError, buildAirAppFileOperations, buildProvisionOperations, inspectProvisionedResources, isNotFound, provisionDeclaredResources, publishAirApp, resolveProvisionedFolder };
|
package/dist/airapp.js
CHANGED
|
@@ -17,8 +17,9 @@ var ownsResource = (node, appId, resourceKey, schemaVersion) => node?.metadata?.
|
|
|
17
17
|
var hasResourceIdentity = (node, appId, resourceKey) => node?.metadata?.appId === appId && node?.metadata?.resourceKey === resourceKey;
|
|
18
18
|
var ownsAppRoot = (node, appId, schemaVersion) => hasResourceIdentity(node, appId, "app-root") && node?.metadata?.schemaVersion === schemaVersion;
|
|
19
19
|
var hasEmptyMetadata = (node) => Object.keys(node?.metadata ?? {}).length === 0;
|
|
20
|
+
var isUnclaimed = (node) => node?.metadata?.appId === void 0;
|
|
20
21
|
var matchesDeclaration = (node, declaration, type) => node?.type === type && node?.slug === declaration.slug && node?.name === declaration.name && node?.description === (declaration.description ?? "");
|
|
21
|
-
var matchesLegacyAirApp = (node, config) =>
|
|
22
|
+
var matchesLegacyAirApp = (node, config) => isUnclaimed(node) && node?.type === "airapp" && node?.slug === config.airApp?.slug && node?.name === config.airApp?.name;
|
|
22
23
|
var resourceMetadata = (config, resourceKey) => ({
|
|
23
24
|
appId: config.appId,
|
|
24
25
|
resourceKey,
|
|
@@ -26,7 +27,7 @@ var resourceMetadata = (config, resourceKey) => ({
|
|
|
26
27
|
});
|
|
27
28
|
function resolveProvisionedFolder(folder, config) {
|
|
28
29
|
if (!folder) {
|
|
29
|
-
return { folder: null, bases: [], missing: [...config.bases], repairs: [] };
|
|
30
|
+
return { folder: null, bases: [], missing: [...config.bases], repairs: [], airApp: null };
|
|
30
31
|
}
|
|
31
32
|
if (folder.node?.type !== "folder" || folder.node?.slug !== config.folder.slug) {
|
|
32
33
|
throw setupError(
|
|
@@ -119,7 +120,8 @@ function resolveProvisionedFolder(folder, config) {
|
|
|
119
120
|
folder: { ...config.folder, nodeId: folder.node.id },
|
|
120
121
|
bases,
|
|
121
122
|
missing,
|
|
122
|
-
repairs
|
|
123
|
+
repairs,
|
|
124
|
+
airApp: airAppNode ? { nodeId: airAppNode.id } : null
|
|
123
125
|
};
|
|
124
126
|
}
|
|
125
127
|
function buildProvisionOperations(config, folder, missingBases) {
|
|
@@ -359,5 +361,91 @@ function provisionDeclaredResources(client, config) {
|
|
|
359
361
|
}
|
|
360
362
|
return state.inFlight;
|
|
361
363
|
}
|
|
364
|
+
function buildAirAppFileOperations(localFiles, deployedPaths) {
|
|
365
|
+
const deployed = new Set(deployedPaths);
|
|
366
|
+
return localFiles.map(
|
|
367
|
+
(file) => ({
|
|
368
|
+
kind: deployed.has(file.path) ? "update" : "create",
|
|
369
|
+
path: file.path,
|
|
370
|
+
content: file.content,
|
|
371
|
+
...file.mimeType ? { mimeType: file.mimeType } : {}
|
|
372
|
+
})
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
async function findPendingAirAppCreate(client, slug) {
|
|
376
|
+
let cursor;
|
|
377
|
+
for (let page = 0; page < 10; page += 1) {
|
|
378
|
+
const result = await client.changeRequests.list({
|
|
379
|
+
status: ["in_review"],
|
|
380
|
+
...cursor ? { cursor } : {}
|
|
381
|
+
});
|
|
382
|
+
for (const changeRequest of result.changeRequests) {
|
|
383
|
+
const matches = (changeRequest.operations ?? []).some((operation) => {
|
|
384
|
+
const payload = operation.headCommit?.payload;
|
|
385
|
+
return payload?.kind === "create" && payload?.nodeType === "airapp" && payload?.slug === slug;
|
|
386
|
+
});
|
|
387
|
+
if (matches) return changeRequest.id;
|
|
388
|
+
}
|
|
389
|
+
if (!result.nextCursor) return null;
|
|
390
|
+
cursor = result.nextCursor;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
async function publishAirApp(client, config, files) {
|
|
395
|
+
const airApp = config.airApp;
|
|
396
|
+
if (!airApp) {
|
|
397
|
+
throw setupError("SETUP_CONFLICT", "This app does not declare an airApp to publish");
|
|
398
|
+
}
|
|
399
|
+
const current = await inspectProvisionedResources(client, config);
|
|
400
|
+
if (!current.folder) {
|
|
401
|
+
throw setupError(
|
|
402
|
+
"SETUP_REQUIRED",
|
|
403
|
+
"Provision the Folder and Bases with provisionDeclaredResources before publishing the AirApp"
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
if (!current.airApp) {
|
|
407
|
+
const pendingChangeRequestId = await findPendingAirAppCreate(client, airApp.slug);
|
|
408
|
+
if (pendingChangeRequestId) {
|
|
409
|
+
return { status: "pending", changeRequestId: pendingChangeRequestId };
|
|
410
|
+
}
|
|
411
|
+
const changeRequest2 = await client.fileTrees.create({
|
|
412
|
+
type: "airapp",
|
|
413
|
+
parentNodeId: current.folder.nodeId,
|
|
414
|
+
slug: airApp.slug,
|
|
415
|
+
name: airApp.name,
|
|
416
|
+
description: airApp.description ?? "",
|
|
417
|
+
files,
|
|
418
|
+
mergeMode: "replace",
|
|
419
|
+
// Explicit even though this app's write-permission credential would
|
|
420
|
+
// otherwise auto-merge it: executable AirApp code always gets human
|
|
421
|
+
// review before it runs in a viewer's browser, no exceptions.
|
|
422
|
+
autoMerge: false
|
|
423
|
+
});
|
|
424
|
+
if (changeRequest2.materialized) {
|
|
425
|
+
throw setupError(
|
|
426
|
+
"SCHEMA_INCOMPLETE",
|
|
427
|
+
"AirApp create unexpectedly materialized despite autoMerge: false"
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
return { status: "created", changeRequestId: changeRequest2.id };
|
|
431
|
+
}
|
|
432
|
+
const deployedFiles = await client.fileTrees.listFiles({
|
|
433
|
+
nodeId: current.airApp.nodeId,
|
|
434
|
+
type: "airapp"
|
|
435
|
+
});
|
|
436
|
+
const operations = buildAirAppFileOperations(
|
|
437
|
+
files,
|
|
438
|
+
deployedFiles.map((file) => file.path)
|
|
439
|
+
);
|
|
440
|
+
const changeRequest = await client.fileTrees.createChangeRequest({
|
|
441
|
+
nodeId: current.airApp.nodeId,
|
|
442
|
+
type: "airapp",
|
|
443
|
+
operations,
|
|
444
|
+
message: `Publish ${config.appName} AirApp`,
|
|
445
|
+
submittedBy: config.appId,
|
|
446
|
+
autoMerge: false
|
|
447
|
+
});
|
|
448
|
+
return { status: "updated", changeRequestId: changeRequest.id };
|
|
449
|
+
}
|
|
362
450
|
|
|
363
|
-
export { AirAppSetupError, buildProvisionOperations, inspectProvisionedResources, isNotFound, provisionDeclaredResources, resolveProvisionedFolder };
|
|
451
|
+
export { AirAppSetupError, buildAirAppFileOperations, buildProvisionOperations, inspectProvisionedResources, isNotFound, provisionDeclaredResources, publishAirApp, resolveProvisionedFolder };
|
|
@@ -9182,6 +9182,7 @@ declare const cloudContract: {
|
|
|
9182
9182
|
runLocalNode: _orpc_contract.ContractProcedure<z.ZodObject<{
|
|
9183
9183
|
nodeId: z.ZodString;
|
|
9184
9184
|
files: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
9185
|
+
binaryFiles: z.ZodDefault<z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>>;
|
|
9185
9186
|
engine: z.ZodDefault<z.ZodEnum<{
|
|
9186
9187
|
"local-node": "local-node";
|
|
9187
9188
|
srt: "srt";
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ import * as zod_v4_core from 'zod/v4/core';
|
|
|
3
3
|
import * as zod from 'zod';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import * as _orpc_client from '@orpc/client';
|
|
6
|
-
import { N as NodeOutput, a as NodeType, O as OperationKind, B as BusabaseClient, R as ResolvedConfig, b as BusabaseConfig } from './client-
|
|
7
|
-
export { C as CREATABLE_NODE_TYPES, c as CloudContract, d as CreatableNodeType, D as DEFAULT_BASE_URL, e as cloudContract, f as createBusabaseClient, r as resolveConfig } from './client-
|
|
6
|
+
import { N as NodeOutput, a as NodeType, O as OperationKind, B as BusabaseClient, R as ResolvedConfig, b as BusabaseConfig } from './client-DIlpYL9z.js';
|
|
7
|
+
export { C as CREATABLE_NODE_TYPES, c as CloudContract, d as CreatableNodeType, D as DEFAULT_BASE_URL, e as cloudContract, f as createBusabaseClient, r as resolveConfig } from './client-DIlpYL9z.js';
|
|
8
8
|
import '@orpc/shared';
|
|
9
9
|
|
|
10
10
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1357,6 +1357,14 @@ var airAppRunLocalNodeInputSchema = z.object({
|
|
|
1357
1357
|
/** Text files to mount into the sandbox workdir before installing, keyed by
|
|
1358
1358
|
* path (same shape `RunPanel` already assembles for `NodepodRunner.mount`). */
|
|
1359
1359
|
files: z.record(z.string(), z.string()),
|
|
1360
|
+
/** Binary (asset-backed) files — images, fonts, sample data — keyed by the
|
|
1361
|
+
* same path, base64-encoded because this input crosses a JSON boundary that
|
|
1362
|
+
* `Uint8Array` cannot. Separate from `files` rather than a tagged union so
|
|
1363
|
+
* an older client that sends only `files` keeps working unchanged.
|
|
1364
|
+
*
|
|
1365
|
+
* The in-browser Nodepod engine never uses this field: it hands raw bytes to
|
|
1366
|
+
* `Nodepod.boot({ files })` directly and skips the base64 round trip. */
|
|
1367
|
+
binaryFiles: z.record(z.string(), z.string()).optional().default({}),
|
|
1360
1368
|
/** Server-side execution mode. `"local-node"` spawns a bare host Node.js
|
|
1361
1369
|
* process (previewable, data bridge via reverse proxy, NOT OS-isolated);
|
|
1362
1370
|
* `"srt"` wraps the same commands in the OS sandbox (isolated execution,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "busa-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.1",
|
|
4
4
|
"description": "Typed TypeScript/JavaScript SDK for the Busabase OpenAPI REST API. Talks to a local or remote `busabase server` (or Busabase Cloud). Short-name alias for busabase-sdk.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/busabase/busabase/tree/main/apps/busabase-sdk",
|