unfazed 0.0.1 → 0.2.0

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../src/operations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Every GraphQL document the workflow layer sends.\n *\n * These are wrapped in `graphql(...)` so graphql-codegen validates them\n * against the vendored schema at codegen time and emits exact result/variable\n * types. A field that stops existing server-side turns into a compile error\n * here rather than a runtime surprise in an agent's script.\n *\n * The one document NOT defined here is the batched file write: it has a\n * variable number of aliased root fields, so it is assembled at runtime in\n * `unfazed.ts`. See the comment there.\n */\nimport { graphql } from \"./generated/gql.js\";\n\n/**\n * Deployment fields the SDK returns everywhere.\n *\n * `id` + `aliasRevision` + `aliasHeld` are the rollback compare-and-swap\n * triple; `previewUrl` is the immutable by-id URL you can verify BEFORE the\n * alias moves; `error` carries the build failure text when status is FAILED.\n */\nexport const DeploymentFieldsFragment = graphql(`\n fragment DeploymentFields on Deployment {\n id\n workspaceID\n kind\n runtime\n alias\n serviceMode\n healthPath\n cronSchedule\n cronPath\n url\n previewUrl\n sshCommand\n aliasHeld\n aliasRevision\n status\n error\n filesCount\n sizeBytes\n size\n spaMode\n gitRepo\n gitCommit\n createdAt\n }\n`);\n\nexport const WhoamiDocument = graphql(`\n query SdkWhoami {\n whoami {\n kind\n userID\n name\n agentID\n agentName\n claimed\n claimedBy\n scopes\n expiresAt\n usage {\n month\n requests\n limit\n }\n resources {\n workspaces\n workspaceLimit\n services\n serviceLimit\n }\n }\n }\n`);\n\nexport const CreateWorkspaceDocument = graphql(`\n mutation SdkCreateWorkspace($name: String!) {\n createWorkspace(input: { name: $name }) {\n workspace {\n id\n name\n ownerId\n projectId\n createdAt\n updatedAt\n }\n }\n }\n`);\n\nexport const ListWorkspacesDocument = graphql(`\n query SdkListWorkspaces {\n workspaces {\n id\n name\n ownerId\n projectId\n createdAt\n updatedAt\n }\n }\n`);\n\n/**\n * Deleting a workspace, with the deployment cascade made explicit.\n *\n * `withDeployments` is not decoration: without it the server REFUSES while the\n * workspace has any deployment history at all — including FAILED records —\n * with `workspace %q still has deployments; delete them first`. A workspace\n * that was ever deployed is therefore undeletable through the bare mutation,\n * so the SDK always sends the flag and lets the caller choose the value.\n */\nexport const DeleteWorkspaceDocument = graphql(`\n mutation SdkDeleteWorkspace($workspaceID: ID!, $withDeployments: Boolean) {\n deleteWorkspace(input: { workspaceID: $workspaceID, withDeployments: $withDeployments }) {\n ok\n }\n }\n`);\n\nexport const WriteTextFileDocument = graphql(`\n mutation SdkWriteTextFile($workspaceID: ID!, $path: String!, $text: String!) {\n writeWorkspaceTextFile(input: { workspaceID: $workspaceID, path: $path, text: $text }) {\n file {\n path\n sizeBytes\n size\n sha256\n updatedAt\n }\n }\n }\n`);\n\nexport const WriteBase64FileDocument = graphql(`\n mutation SdkWriteBase64File($workspaceID: ID!, $path: String!, $contentBase64: String!) {\n writeWorkspaceBase64File(\n input: { workspaceID: $workspaceID, path: $path, contentBase64: $contentBase64 }\n ) {\n file {\n path\n sizeBytes\n size\n sha256\n updatedAt\n }\n }\n }\n`);\n\nexport const ListFilesDocument = graphql(`\n query SdkListFiles($input: ListWorkspaceFilesInput!) {\n workspaceFilesPage(input: $input) {\n items {\n path\n sizeBytes\n size\n sha256\n updatedAt\n }\n total\n limit\n offset\n hasMore\n nextOffset\n }\n }\n`);\n\nexport const ReadFileDocument = graphql(`\n query SdkReadFile($input: ReadWorkspaceFileInput!) {\n readWorkspaceFile(input: $input) {\n text\n offset\n limit\n totalBytes\n truncated\n nextOffset\n file {\n path\n sizeBytes\n size\n sha256\n updatedAt\n }\n }\n }\n`);\n\nexport const DeleteFileDocument = graphql(`\n mutation SdkDeleteFile($workspaceID: ID!, $path: String!) {\n deleteWorkspaceFile(input: { workspaceID: $workspaceID, path: $path }) {\n ok\n }\n }\n`);\n\nexport const CreateDeploymentDocument = graphql(`\n mutation SdkCreateDeployment($input: CreateDeploymentInput!) {\n createDeployment(input: $input) {\n deployment {\n ...DeploymentFields\n }\n }\n }\n`);\n\nexport const DeployWorkspaceDocument = graphql(`\n mutation SdkDeployWorkspace($input: DeployWorkspaceInput!) {\n deployWorkspace(input: $input) {\n deployment {\n ...DeploymentFields\n }\n }\n }\n`);\n\nexport const GetDeploymentDocument = graphql(`\n query SdkGetDeployment($id: ID!) {\n deployment(id: $id) {\n ...DeploymentFields\n }\n }\n`);\n\n/**\n * The poll document, deliberately narrow.\n *\n * Waiting can run for minutes at a few seconds per tick; selecting the full\n * fragment every time would cost the server (and an agent's rate limit) far\n * more than the four fields the state machine actually reads.\n */\nexport const PollDeploymentDocument = graphql(`\n query SdkPollDeployment($id: ID!) {\n deployment(id: $id) {\n id\n status\n error\n aliasHeld\n url\n previewUrl\n }\n }\n`);\n\nexport const GetDeploymentByAliasDocument = graphql(`\n query SdkGetDeploymentByAlias($alias: String!) {\n deploymentByAlias(alias: $alias) {\n ...DeploymentFields\n }\n }\n`);\n\nexport const ListDeploymentsDocument = graphql(`\n query SdkListDeployments($input: ListDeploymentsInput) {\n deploymentsPage(input: $input) {\n items {\n ...DeploymentFields\n }\n total\n limit\n offset\n hasMore\n nextOffset\n }\n }\n`);\n\nexport const ListReleasesDocument = graphql(`\n query SdkListReleases($alias: String!) {\n deploymentReleases(alias: $alias) {\n ...DeploymentFields\n }\n }\n`);\n\nexport const RollbackDocument = graphql(`\n mutation SdkRollback(\n $deploymentID: ID!\n $expectedCurrentDeploymentID: ID!\n $expectedCurrentAliasRevision: String!\n ) {\n rollbackDeployment(\n input: {\n deploymentID: $deploymentID\n expectedCurrentDeploymentID: $expectedCurrentDeploymentID\n expectedCurrentAliasRevision: $expectedCurrentAliasRevision\n }\n ) {\n ...DeploymentFields\n }\n }\n`);\n\nexport const ServiceStatusDocument = graphql(`\n query SdkServiceStatus($deploymentID: ID!) {\n serviceStatus(deploymentID: $deploymentID) {\n deploymentID\n alias\n serviceMode\n running\n health\n vmID\n inFlight\n alwaysOn\n memSizeMiB\n startedAt\n lastUsedAt\n lastStoppedAt\n restartCount\n lastError\n }\n }\n`);\n\nexport const ServiceLogsDocument = graphql(`\n query SdkServiceLogs($deploymentID: ID!, $tailBytes: Int) {\n serviceLogs(deploymentID: $deploymentID, tailBytes: $tailBytes) {\n deploymentID\n stdout\n stderr\n truncated\n stdoutBytes\n stderrBytes\n tailBytes\n }\n }\n`);\n\nexport const DeploymentEventsDocument = graphql(`\n query SdkDeploymentEvents($deploymentID: ID!, $limit: Int) {\n deploymentEvents(deploymentID: $deploymentID, limit: $limit) {\n id\n deploymentID\n type\n rawType\n severity\n message\n vmID\n createdAt\n }\n }\n`);\n\nexport const RestartServiceDocument = graphql(`\n mutation SdkRestartService($deploymentID: ID!) {\n restartService(input: { deploymentID: $deploymentID }) {\n deploymentID\n running\n health\n vmID\n restartCount\n lastError\n }\n }\n`);\n\nexport const StopServiceDocument = graphql(`\n mutation SdkStopService($deploymentID: ID!) {\n stopService(input: { deploymentID: $deploymentID }) {\n deploymentID\n running\n health\n vmID\n restartCount\n lastError\n }\n }\n`);\n\nexport const DeleteDeploymentDocument = graphql(`\n mutation SdkDeleteDeployment($deploymentID: ID!) {\n deleteDeployment(input: { deploymentID: $deploymentID }) {\n ok\n }\n }\n`);\n\n/* -------------------------------------------------------------------------\n * Env & secrets\n *\n * Workspace-scoped values are SERVICE defaults for every app in the source;\n * app-scoped values override them for one alias. Both are snapshotted into a\n * release at deploy time, so a change only reaches a running app after a\n * redeploy — the workflow layer's doc comments repeat this where it matters.\n * ---------------------------------------------------------------------- */\n\nexport const SetWorkspaceEnvDocument = graphql(`\n mutation SdkSetWorkspaceEnv($workspaceID: ID!, $name: String!, $value: String!) {\n setWorkspaceEnv(input: { workspaceID: $workspaceID, name: $name, value: $value }) {\n env {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n }\n`);\n\nexport const SetWorkspaceSecretDocument = graphql(`\n mutation SdkSetWorkspaceSecret($workspaceID: ID!, $name: String!, $value: String!) {\n setWorkspaceSecret(input: { workspaceID: $workspaceID, name: $name, value: $value }) {\n env {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n }\n`);\n\nexport const DeleteWorkspaceEnvDocument = graphql(`\n mutation SdkDeleteWorkspaceEnv($workspaceID: ID!, $name: String!) {\n deleteWorkspaceEnv(input: { workspaceID: $workspaceID, name: $name }) {\n ok\n }\n }\n`);\n\nexport const ListWorkspaceEnvDocument = graphql(`\n query SdkListWorkspaceEnv($workspaceID: ID!) {\n workspaceEnv(workspaceID: $workspaceID) {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n`);\n\nexport const SetAppEnvDocument = graphql(`\n mutation SdkSetAppEnv($workspaceID: ID!, $alias: String!, $name: String!, $value: String!) {\n setAppEnv(input: { workspaceID: $workspaceID, alias: $alias, name: $name, value: $value }) {\n env {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n }\n`);\n\nexport const SetAppSecretDocument = graphql(`\n mutation SdkSetAppSecret($workspaceID: ID!, $alias: String!, $name: String!, $value: String!) {\n setAppSecret(input: { workspaceID: $workspaceID, alias: $alias, name: $name, value: $value }) {\n env {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n }\n`);\n\nexport const RemoveAppEnvDocument = graphql(`\n mutation SdkRemoveAppEnv($workspaceID: ID!, $alias: String!, $name: String!) {\n removeAppEnv(input: { workspaceID: $workspaceID, alias: $alias, name: $name }) {\n ok\n }\n }\n`);\n\nexport const InheritAppEnvDocument = graphql(`\n mutation SdkInheritAppEnv($workspaceID: ID!, $alias: String!, $name: String!) {\n inheritAppEnv(input: { workspaceID: $workspaceID, alias: $alias, name: $name }) {\n ok\n }\n }\n`);\n\nexport const ListAppEnvDocument = graphql(`\n query SdkListAppEnv($workspaceID: ID!, $alias: String!) {\n appEnv(workspaceID: $workspaceID, alias: $alias) {\n name\n value\n secret\n alias\n inherited\n masked\n updatedAt\n }\n }\n`);\n"]}
@@ -0,0 +1,653 @@
1
+ import { UnfazedClient, type ClientOptions, type RequestOptions } from "./client.js";
2
+ import type { CreateDeploymentInput, DeploymentFieldsFragment, ListDeploymentsInput, ServiceMode, ServiceRuntime, SdkCreateWorkspaceMutation } from "./generated/graphql.js";
3
+ import { type WaitOptions } from "./wait.js";
4
+ /** A deployment as the workflow layer returns it. */
5
+ export type Deployment = DeploymentFieldsFragment;
6
+ /**
7
+ * A workspace as {@link Unfazed.createWorkspace} and
8
+ * {@link Unfazed.ensureWorkspace} return it.
9
+ *
10
+ * Derived from the generated mutation type rather than hand-written, so the
11
+ * two documents selecting these fields cannot drift apart without a compile
12
+ * error — which is exactly what makes `ensureWorkspace` able to return either
13
+ * a listed or a freshly created workspace under one type.
14
+ */
15
+ export type Workspace = SdkCreateWorkspaceMutation["createWorkspace"]["workspace"];
16
+ /** Options for {@link Unfazed.deleteWorkspace}. */
17
+ export type DeleteWorkspaceOptions = {
18
+ /**
19
+ * Also delete every deployment snapshotted from the workspace, stopping
20
+ * their service VMs. **Defaults to `true`** — see the method docs for why.
21
+ */
22
+ withDeployments?: boolean;
23
+ /** Resolve `false` instead of throwing when the workspace is not visible. */
24
+ ignoreNotFound?: boolean;
25
+ } & RequestOptions;
26
+ /** Options for {@link Unfazed.deleteDeployment}. */
27
+ export type DeleteDeploymentOptions = {
28
+ /** Resolve `false` instead of throwing when the deployment is not visible. */
29
+ ignoreNotFound?: boolean;
30
+ } & RequestOptions;
31
+ /** A file record returned by any write/list operation. */
32
+ export interface WorkspaceFile {
33
+ path: string;
34
+ sizeBytes: number;
35
+ size: string;
36
+ sha256: string;
37
+ updatedAt: string;
38
+ }
39
+ /** One env var in a workspace- or app-scoped view. Secret values are never returned. */
40
+ export interface EnvVar {
41
+ name: string;
42
+ value: string | null;
43
+ secret: boolean;
44
+ alias: string | null;
45
+ inherited: boolean;
46
+ masked: boolean;
47
+ updatedAt: string;
48
+ }
49
+ /**
50
+ * A file's contents for {@link Unfazed.writeFiles}.
51
+ *
52
+ * A bare string is text — the overwhelmingly common case, and what makes
53
+ * `{ "server.ts": code }` read well. Binary goes through `bytes` or `base64`,
54
+ * which route to `writeWorkspaceBase64File` instead.
55
+ */
56
+ export type FileContent = string | {
57
+ text: string;
58
+ } | {
59
+ base64: string;
60
+ } | {
61
+ bytes: Uint8Array | ArrayBuffer;
62
+ };
63
+ /** Files keyed by workspace-relative path. */
64
+ export type FilesMap = Record<string, FileContent>;
65
+ export interface WriteFilesOptions {
66
+ /**
67
+ * Max files per GraphQL request. Default 20.
68
+ *
69
+ * The batch is one document with N aliased root fields; too many at once
70
+ * makes a single oversized request whose failure is all-or-nothing to
71
+ * diagnose. Twenty keeps a typical app to one or two round trips.
72
+ */
73
+ batchSize?: number;
74
+ /**
75
+ * Max total payload bytes per request. Default 4 MiB. Whichever limit trips
76
+ * first closes the batch, so one large file cannot blow the request up.
77
+ */
78
+ maxBatchBytes?: number;
79
+ signal?: AbortSignal;
80
+ }
81
+ /** Common options for the deploy helpers. */
82
+ export interface DeployOptions {
83
+ workspaceId: string;
84
+ alias?: string;
85
+ /**
86
+ * Block until the deployment reaches a terminal status.
87
+ *
88
+ * `createDeployment` returns BUILDING immediately; the build, VM boot and
89
+ * health probe run in the background. With `wait: true` the call resolves
90
+ * only on READY and throws {@link UnfazedDeployError} on FAILED — which is
91
+ * what makes a one-script deploy possible.
92
+ */
93
+ wait?: boolean;
94
+ /** Backoff/timeout knobs used when `wait` is true. */
95
+ waitOptions?: WaitOptions;
96
+ signal?: AbortSignal;
97
+ }
98
+ export interface DeployServiceOptions extends DeployOptions {
99
+ /** Omit to auto-detect: a root go.mod+main.go is GO, otherwise BUN. */
100
+ runtime?: ServiceRuntime;
101
+ /** Omit for ON_DEMAND. Only set ALWAYS_ON to keep the VM warm. */
102
+ serviceMode?: ServiceMode;
103
+ healthPath?: string;
104
+ /** Standard 5-field cron spec. Prefer ON_DEMAND + cron over ALWAYS_ON. */
105
+ cronSchedule?: string;
106
+ /** Path the cron tick POSTs to. Defaults to /cron when cronSchedule is set. */
107
+ cronPath?: string;
108
+ }
109
+ export interface DeployStaticOptions extends DeployOptions {
110
+ /** Serve index.html for missing paths (single-page apps). */
111
+ spaMode?: boolean;
112
+ }
113
+ export interface DeployBoxOptions extends DeployOptions {
114
+ /** VM size. Omit for platform defaults (2048 MiB / 2 vCPU / 8 GiB disk). */
115
+ resources?: {
116
+ memMiB?: number;
117
+ vcpus?: number;
118
+ diskGiB?: number;
119
+ };
120
+ }
121
+ export interface RollbackOptions {
122
+ /** The alias to roll back. */
123
+ alias: string;
124
+ /**
125
+ * Release to restore. Omit to pick the most recent READY release that is not
126
+ * the current holder — i.e. "undo the last deploy".
127
+ */
128
+ to?: string;
129
+ /**
130
+ * Re-list-and-retry attempts when the compare-and-swap guard is rejected.
131
+ * Default 1. Each retry refreshes the guard from a fresh read; the TARGET
132
+ * release stays pinned, so a retry can never silently roll back to something
133
+ * other than what was chosen.
134
+ *
135
+ * Ignored when {@link expectedCurrentId} is set — see there.
136
+ */
137
+ retries?: number;
138
+ /**
139
+ * The release you believe currently holds the alias. When set, the rollback
140
+ * proceeds only if that is still true.
141
+ *
142
+ * This turns the rollback from "undo whatever is live now" into a checked
143
+ * operation: if the live holder is anything else, you get
144
+ * {@link UnfazedConflictError} BEFORE any mutation is sent, so a rollback
145
+ * script can never act on a world that moved since you inspected it.
146
+ *
147
+ * It also disables the CAS retry, necessarily: a rejected guard means the
148
+ * alias moved, and the alias having moved is precisely the precondition you
149
+ * asked to be enforced. Retrying would refresh the guard to the new holder
150
+ * and roll back the very deploy the precondition existed to protect.
151
+ */
152
+ expectedCurrentId?: string;
153
+ signal?: AbortSignal;
154
+ }
155
+ export interface UnfazedOptions extends ClientOptions {
156
+ /** Use an existing core client instead of constructing one. */
157
+ client?: UnfazedClient;
158
+ }
159
+ /**
160
+ * Layer 2 — the agent workflow layer.
161
+ *
162
+ * Wraps the chains an agent actually performs (create → write → deploy → wait
163
+ * → read logs → roll back) so a code-mode script does in one call what would
164
+ * otherwise be many round trips through a model's context. Everything here is
165
+ * built on {@link UnfazedClient}, exposed as `.client` for anything not
166
+ * wrapped.
167
+ *
168
+ * ```ts
169
+ * const unfazed = new Unfazed(); // reads UNFZ_API_TOKEN
170
+ * const ws = await unfazed.createWorkspace("hello");
171
+ * await unfazed.writeFiles(ws.id, { "server.ts": code });
172
+ * const app = await unfazed.deployService({ workspaceId: ws.id, alias: "hello", wait: true });
173
+ * console.log(app.url);
174
+ * ```
175
+ */
176
+ export declare class Unfazed {
177
+ #private;
178
+ /** The underlying typed GraphQL client. Use it for un-wrapped operations. */
179
+ readonly client: UnfazedClient;
180
+ constructor(options?: UnfazedOptions);
181
+ /** Who this token authenticates as, plus usage and resource limits. */
182
+ whoami(options?: RequestOptions): Promise<{
183
+ kind: import("./index.js").CurrentActorKind;
184
+ userID: string;
185
+ name: string;
186
+ agentID: string | null;
187
+ agentName: string | null;
188
+ claimed: boolean;
189
+ claimedBy: string | null;
190
+ scopes: Array<string>;
191
+ expiresAt: string | null;
192
+ usage: {
193
+ month: string;
194
+ requests: number;
195
+ limit: number;
196
+ } | null;
197
+ resources: {
198
+ workspaces: number;
199
+ workspaceLimit: number;
200
+ services: number;
201
+ serviceLimit: number;
202
+ } | null;
203
+ }>;
204
+ /** Creates a source workspace — the container for files, env and deployments. */
205
+ createWorkspace(name: string, options?: RequestOptions): Promise<{
206
+ id: string;
207
+ name: string;
208
+ ownerId: string;
209
+ projectId: string | null;
210
+ createdAt: string;
211
+ updatedAt: string;
212
+ }>;
213
+ /** Workspaces visible to this token. */
214
+ listWorkspaces(options?: RequestOptions): Promise<{
215
+ id: string;
216
+ name: string;
217
+ ownerId: string;
218
+ projectId: string | null;
219
+ createdAt: string;
220
+ updatedAt: string;
221
+ }[]>;
222
+ /**
223
+ * Deletes a workspace and everything anchored to it — deployments, env, and
224
+ * a BOX's persistent data disk. Not recoverable.
225
+ *
226
+ * **The cascade is opt-out, not opt-in.** The server refuses to delete a
227
+ * workspace that has ANY deployment history — including FAILED records, and
228
+ * including deployments whose alias was long since taken over by something
229
+ * else — unless the mutation carries `withDeployments`. Since almost every
230
+ * workspace an agent creates gets deployed at least once, defaulting to
231
+ * `false` would make this method fail on nearly every real workspace, so it
232
+ * defaults to **`true`**: the behaviour this docstring has always promised.
233
+ *
234
+ * Pass `withDeployments: false` for the strict variant, which refuses rather
235
+ * than cascading and is the safer choice when you are deleting a workspace
236
+ * you did not create:
237
+ *
238
+ * ```ts
239
+ * await unfazed.deleteWorkspace(ws.id); // cascade
240
+ * await unfazed.deleteWorkspace(ws.id, { withDeployments: false }); // refuse if deployed
241
+ * await unfazed.deleteWorkspace(id, { ignoreNotFound: true }); // false, not a throw
242
+ * ```
243
+ *
244
+ * With `ignoreNotFound` a workspace that is absent (or belongs to someone
245
+ * else — the server returns one indistinguishable sentinel for both, on
246
+ * purpose) resolves to `false` instead of throwing, which makes teardown in
247
+ * a `finally` block idempotent.
248
+ */
249
+ deleteWorkspace(workspaceId: string, options?: DeleteWorkspaceOptions): Promise<boolean>;
250
+ /**
251
+ * Returns the workspace named `name`, creating it only if it does not exist.
252
+ *
253
+ * Workspace names are unique per owner, case-insensitively — a second
254
+ * `createWorkspace("Hello")` after a `createWorkspace("hello")` is an error,
255
+ * not a second workspace. That makes the bare create a poor fit for any
256
+ * script that might run twice; this is the re-runnable form.
257
+ *
258
+ * ```ts
259
+ * const ws = await unfazed.ensureWorkspace("greeter"); // safe to re-run
260
+ * ```
261
+ *
262
+ * Costs one list, plus one create only on a miss. The create is still
263
+ * guarded: if a concurrent caller wins the race, the duplicate-name error is
264
+ * caught and the now-existing workspace is returned instead of thrown.
265
+ */
266
+ ensureWorkspace(name: string, options?: RequestOptions): Promise<{
267
+ id: string;
268
+ name: string;
269
+ ownerId: string;
270
+ projectId: string | null;
271
+ createdAt: string;
272
+ updatedAt: string;
273
+ }>;
274
+ /**
275
+ * Writes many files in as few requests as possible.
276
+ *
277
+ * The API is one-file-per-mutation, so writing a 12-file app naively costs 12
278
+ * round trips. This batches them into a single GraphQL document with one
279
+ * ALIASED root field per file (`f0: writeWorkspaceTextFile(...)`,
280
+ * `f1: ...`), which the server resolves in one request.
281
+ *
282
+ * ```ts
283
+ * await unfazed.writeFiles(ws.id, {
284
+ * "server.ts": serverCode,
285
+ * "unfazed.json": JSON.stringify({ entry: "server.ts" }),
286
+ * "public/logo.png": { bytes: pngBytes },
287
+ * });
288
+ * ```
289
+ *
290
+ * Batches are NOT atomic: GraphQL resolves each aliased field independently,
291
+ * so a partial failure leaves earlier writes committed. The thrown
292
+ * {@link UnfazedGraphQLError} carries `data` with whichever aliases
293
+ * succeeded, and `error.path` identifies the ones that did not.
294
+ */
295
+ writeFiles(workspaceId: string, files: FilesMap, options?: WriteFilesOptions): Promise<WorkspaceFile[]>;
296
+ /** Writes one text file. Prefer {@link writeFiles} for more than one. */
297
+ writeFile(workspaceId: string, path: string, text: string, options?: RequestOptions): Promise<WorkspaceFile>;
298
+ /** Writes one binary file. Accepts raw bytes or a base64 string. */
299
+ writeBinaryFile(workspaceId: string, path: string, content: Uint8Array | ArrayBuffer | {
300
+ base64: string;
301
+ }, options?: RequestOptions): Promise<WorkspaceFile>;
302
+ /** Lists workspace files (paginated). */
303
+ listFiles(workspaceId: string, input?: {
304
+ limit?: number;
305
+ offset?: number;
306
+ order?: string;
307
+ }, options?: RequestOptions): Promise<{
308
+ total: number;
309
+ limit: number;
310
+ offset: number;
311
+ hasMore: boolean;
312
+ nextOffset: number | null;
313
+ items: Array<{
314
+ path: string;
315
+ sizeBytes: number;
316
+ size: string;
317
+ sha256: string;
318
+ updatedAt: string;
319
+ }>;
320
+ }>;
321
+ /** Reads a text file, optionally a byte range of it. Null when absent. */
322
+ readFile(workspaceId: string, path: string, input?: {
323
+ offset?: number;
324
+ limit?: number;
325
+ }, options?: RequestOptions): Promise<{
326
+ text: string;
327
+ offset: number;
328
+ limit: number;
329
+ totalBytes: number;
330
+ truncated: boolean;
331
+ nextOffset: number | null;
332
+ file: {
333
+ path: string;
334
+ sizeBytes: number;
335
+ size: string;
336
+ sha256: string;
337
+ updatedAt: string;
338
+ };
339
+ } | null>;
340
+ /**
341
+ * Deletes a workspace file.
342
+ *
343
+ * Returns `false` when there was nothing to delete — an absent path is not
344
+ * an error. See the `ok: false` convention on {@link deleteEnv}.
345
+ */
346
+ deleteFile(workspaceId: string, path: string, options?: RequestOptions): Promise<boolean>;
347
+ /**
348
+ * Deploys the workspace as a SERVICE (an HTTP server).
349
+ *
350
+ * With `wait: true` this resolves only once the build, VM boot and health
351
+ * probe have all passed.
352
+ *
353
+ * Passing the same `alias` again redeploys that alias — a new release that
354
+ * takes over the alias when it goes READY. Omitting `alias` lets the server
355
+ * generate one, which means a different app on every call.
356
+ */
357
+ deployService(options: DeployServiceOptions): Promise<Deployment>;
358
+ /** Deploys the workspace as a STATIC site. */
359
+ deployStatic(options: DeployStaticOptions): Promise<Deployment>;
360
+ /**
361
+ * Deploys a BOX: a persistent always-on Linux VM with a data disk that
362
+ * survives stops and redeploys. A dev machine, not an HTTP service. The
363
+ * result's `sshCommand` is how you get in.
364
+ *
365
+ * ⚠️ **Omitting `alias` creates a NEW BOX every single call.** There is no
366
+ * "the workspace's box" to update: with no alias the server generates a
367
+ * fresh random one, so a script run twice leaves you with two box records,
368
+ * not one redeployed box.
369
+ *
370
+ * The data disk, meanwhile, is keyed by WORKSPACE — one disk, shared by
371
+ * every box in the workspace, and it is the box's real identity. Since only
372
+ * one VM can hold that disk read-write, the second box cannot even start
373
+ * while the first is running (`box data disk for workspace ... is attached
374
+ * to VM ...; stop it first`). The result is a pile of dead box records
375
+ * fighting over one disk.
376
+ *
377
+ * **Always pass an explicit `alias` in anything re-runnable.** Redeploying
378
+ * the same alias is the in-place update you actually want.
379
+ *
380
+ * ```ts
381
+ * await unfazed.deployBox({ workspaceId: ws.id, alias: "dev" }); // re-runnable
382
+ * ```
383
+ */
384
+ deployBox(options: DeployBoxOptions): Promise<Deployment>;
385
+ /** Escape hatch: deploy with a raw CreateDeploymentInput. */
386
+ deploy(input: CreateDeploymentInput, options?: Omit<DeployOptions, "workspaceId">): Promise<Deployment>;
387
+ /**
388
+ * Polls a deployment until it is READY, throwing on FAILED or timeout.
389
+ *
390
+ * BUILDING is the only non-terminal status; READY and FAILED are terminal.
391
+ * The poll query selects six fields rather than the full deployment, because
392
+ * this runs every few seconds for potentially minutes — then the READY
393
+ * deployment is re-read in full so callers get the same shape `deployService`
394
+ * returns without `wait`.
395
+ */
396
+ waitForDeployment(deploymentId: string, waitOptions?: WaitOptions): Promise<Deployment>;
397
+ /** Reads one deployment by id. Null when absent. */
398
+ getDeployment(deploymentId: string, options?: RequestOptions): Promise<Deployment | null>;
399
+ /** Reads the deployment currently serving an alias. Null when absent. */
400
+ getDeploymentByAlias(alias: string, options?: RequestOptions): Promise<Deployment | null>;
401
+ /** Lists deployments (paginated, filterable by kind/alias/workspace). */
402
+ listDeployments(input?: ListDeploymentsInput, options?: RequestOptions): Promise<{
403
+ total: number;
404
+ limit: number;
405
+ offset: number;
406
+ hasMore: boolean;
407
+ nextOffset: number | null;
408
+ items: Array<{
409
+ id: string;
410
+ workspaceID: string;
411
+ kind: import("./index.js").DeploymentKind;
412
+ runtime: ServiceRuntime | null;
413
+ alias: string | null;
414
+ serviceMode: ServiceMode;
415
+ healthPath: string | null;
416
+ cronSchedule: string | null;
417
+ cronPath: string | null;
418
+ url: string;
419
+ previewUrl: string;
420
+ sshCommand: string | null;
421
+ aliasHeld: boolean;
422
+ aliasRevision: string;
423
+ status: import("./index.js").DeploymentStatus;
424
+ error: string | null;
425
+ filesCount: number;
426
+ sizeBytes: number;
427
+ size: string;
428
+ spaMode: boolean;
429
+ gitRepo: string | null;
430
+ gitCommit: string | null;
431
+ createdAt: string;
432
+ }>;
433
+ }>;
434
+ /** Live VM status: health, in-flight requests, restarts, RAM ceiling. */
435
+ getStatus(deploymentId: string, options?: RequestOptions): Promise<{
436
+ deploymentID: string;
437
+ alias: string | null;
438
+ serviceMode: ServiceMode;
439
+ running: boolean;
440
+ health: string;
441
+ vmID: string | null;
442
+ inFlight: number;
443
+ alwaysOn: boolean;
444
+ memSizeMiB: number;
445
+ startedAt: string | null;
446
+ lastUsedAt: string | null;
447
+ lastStoppedAt: string | null;
448
+ restartCount: number;
449
+ lastError: string | null;
450
+ }>;
451
+ /**
452
+ * Tail of the service's stdout/stderr.
453
+ *
454
+ * The point of doing this from a script rather than through tool calls: you
455
+ * can filter locally. `(await unfazed.getLogs(id)).stderr.split("\n")
456
+ * .filter(l => l.includes("ERROR"))` costs a model zero context, where the
457
+ * same tail through an MCP tool spends all of it.
458
+ */
459
+ getLogs(deploymentId: string, input?: {
460
+ tailBytes?: number;
461
+ }, options?: RequestOptions): Promise<{
462
+ deploymentID: string;
463
+ stdout: string;
464
+ stderr: string;
465
+ truncated: boolean;
466
+ stdoutBytes: number;
467
+ stderrBytes: number;
468
+ tailBytes: number;
469
+ }>;
470
+ /** Deployment lifecycle events — build, boot, probe, cron, restarts. */
471
+ getEvents(deploymentId: string, limit?: number, options?: RequestOptions): Promise<{
472
+ id: string;
473
+ deploymentID: string;
474
+ type: import("./index.js").DeploymentEventType;
475
+ rawType: string;
476
+ severity: import("./index.js").DeploymentEventSeverity;
477
+ message: string;
478
+ vmID: string | null;
479
+ createdAt: string;
480
+ }[]>;
481
+ /** Restarts a service's VM. */
482
+ restartService(deploymentId: string, options?: RequestOptions): Promise<{
483
+ deploymentID: string;
484
+ running: boolean;
485
+ health: string;
486
+ vmID: string | null;
487
+ restartCount: number;
488
+ lastError: string | null;
489
+ }>;
490
+ /** Stops a service's VM. An on-demand service cold-starts again on request. */
491
+ stopService(deploymentId: string, options?: RequestOptions): Promise<{
492
+ deploymentID: string;
493
+ running: boolean;
494
+ health: string;
495
+ vmID: string | null;
496
+ restartCount: number;
497
+ lastError: string | null;
498
+ }>;
499
+ /**
500
+ * Deletes a deployment: stops its VM, releases its alias, drops its release.
501
+ *
502
+ * With `ignoreNotFound` a deployment that is absent — or belongs to another
503
+ * tenant, which the server reports identically so ids cannot be probed —
504
+ * resolves to `false` instead of throwing, making cleanup loops idempotent.
505
+ */
506
+ deleteDeployment(deploymentId: string, options?: DeleteDeploymentOptions): Promise<boolean>;
507
+ /**
508
+ * Sets a workspace-default env var. Redeploy the alias to apply it.
509
+ *
510
+ * A name is either plain or secret, never both: this DELETES a workspace
511
+ * secret of the same name. {@link setSecret} displaces a plain var the same
512
+ * way. Last write wins, and the kind flips silently — so re-running a setup
513
+ * script that calls `setSecret("API_KEY", ...)` after one that called
514
+ * `setEnv("API_KEY", ...)` quietly converts it, and vice versa.
515
+ */
516
+ setEnv(workspaceId: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
517
+ /**
518
+ * Sets a workspace-default secret. The value is never readable again.
519
+ *
520
+ * Mutually exclusive with {@link setEnv}: this DELETES a plain workspace var
521
+ * of the same name. See {@link setEnv} for the displacement rule.
522
+ */
523
+ setSecret(workspaceId: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
524
+ /**
525
+ * Sets many workspace env vars in one request, via aliased mutations —
526
+ * the same batching trick as {@link writeFiles}.
527
+ *
528
+ * Pass `{ secret: true }` to route them all to `setWorkspaceSecret`.
529
+ *
530
+ * It inherits {@link writeFiles}' failure semantics too: the batch is NOT
531
+ * atomic. GraphQL resolves each aliased field independently, so a rejected
532
+ * name (over the limit, malformed, whatever) leaves the vars before it
533
+ * already committed. The thrown {@link UnfazedGraphQLError} carries `data`
534
+ * with the aliases that succeeded and `errors[].path` naming those that did
535
+ * not — do not assume "it threw" means "nothing was written".
536
+ *
537
+ * Each name still displaces its opposite kind, exactly as {@link setEnv} and
538
+ * {@link setSecret} do one at a time.
539
+ */
540
+ setEnvMany(workspaceId: string, vars: Record<string, string>, options?: {
541
+ secret?: boolean;
542
+ signal?: AbortSignal;
543
+ }): Promise<EnvVar[]>;
544
+ /**
545
+ * Deletes a workspace-default env var (plain or secret).
546
+ *
547
+ * **The `ok: false` convention.** A `false` here does NOT mean failure — it
548
+ * means "nothing to do, the world is already in the state you asked for".
549
+ * The var did not exist. Every `ok`-returning mutation in this SDK follows
550
+ * the rule ({@link deleteFile}, {@link inheritAppEnv}, {@link removeAppEnv}),
551
+ * so the idempotent-cleanup pattern is simply to ignore the boolean; a real
552
+ * failure throws.
553
+ */
554
+ deleteEnv(workspaceId: string, name: string, options?: RequestOptions): Promise<boolean>;
555
+ /** Workspace-default env vars. Secret values are redacted. */
556
+ listEnv(workspaceId: string, options?: RequestOptions): Promise<EnvVar[]>;
557
+ /**
558
+ * Sets an env var for ONE alias, overriding the workspace default.
559
+ *
560
+ * Displaces both of the other states this name could be in for the app: it
561
+ * deletes an app SECRET of the same name, and it CLEARS a REMOVE mask left
562
+ * by {@link removeAppEnv}. So setting a value is also the way to un-block a
563
+ * masked name, and a name is only ever one of plain / secret / masked.
564
+ */
565
+ setAppEnv(workspaceId: string, alias: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
566
+ /**
567
+ * Sets a secret for ONE alias, overriding the workspace default.
568
+ *
569
+ * Like {@link setAppEnv}, this deletes a plain app var of the same name and
570
+ * clears a REMOVE mask for it.
571
+ */
572
+ setAppSecret(workspaceId: string, alias: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
573
+ /**
574
+ * REMOVE: blocks this name for the app entirely — the app override, the
575
+ * workspace default, and integration enrichment. Not the same as deleting an
576
+ * override; use {@link inheritAppEnv} for that.
577
+ *
578
+ * This WRITES A MASK, and the mask is a real, persistent row. Two things
579
+ * follow that surprise people:
580
+ *
581
+ * - It is written even when nothing was inherited. Masking a name that no
582
+ * workspace default and no integration ever provided is not a no-op; it
583
+ * leaves a tombstone that keeps blocking the name if a default appears
584
+ * later. Clear it with {@link inheritAppEnv}.
585
+ * - It COSTS A SLOT. A workspace gets 128 env entries total — that budget
586
+ * is shared across the workspace defaults AND every app scope combined,
587
+ * not 128 per scope — and masks count toward it just like values do.
588
+ * Masking names in a loop can exhaust the workspace's env budget.
589
+ *
590
+ * Returns `false` when the exact mask was already present (a second call for
591
+ * the same name), per the {@link deleteEnv} `ok: false` convention. It
592
+ * returns `true` when it had something to change — including converting an
593
+ * existing override into a mask.
594
+ */
595
+ removeAppEnv(workspaceId: string, alias: string, name: string, options?: RequestOptions): Promise<boolean>;
596
+ /**
597
+ * INHERIT: clears the app override/mask so workspace and integration values
598
+ * apply again. This is the undo for both {@link setAppEnv} and
599
+ * {@link removeAppEnv}.
600
+ *
601
+ * Returns `false` when there was no override and no mask to clear — nothing
602
+ * to do, not a failure. See {@link deleteEnv}.
603
+ */
604
+ inheritAppEnv(workspaceId: string, alias: string, name: string, options?: RequestOptions): Promise<boolean>;
605
+ /** The effective env view for one alias: overrides, inherited and masked. */
606
+ listAppEnv(workspaceId: string, alias: string, options?: RequestOptions): Promise<EnvVar[]>;
607
+ /**
608
+ * An alias's terminal release history, newest first.
609
+ *
610
+ * Exactly one entry has `aliasHeld: true` — the live release. FAILED entries
611
+ * appear on purpose (they are rejected promotions, so a kept-live previous
612
+ * release is never mistaken for a silent fallback) and cannot be rolled back
613
+ * to. Retention keeps only the most recent few per alias, default 5.
614
+ */
615
+ listReleases(alias: string, options?: RequestOptions): Promise<Deployment[]>;
616
+ /**
617
+ * Rolls an alias back to a prior release, handling the whole compare-and-swap
618
+ * protocol in one call.
619
+ *
620
+ * The server requires the caller to prove it observed the current state: you
621
+ * must pass the live release's id AND its opaque `aliasRevision`, and the
622
+ * swap fails if either changed since you listed — including same-id ABA. So
623
+ * this method does the full dance: list releases, find the `aliasHeld` entry,
624
+ * pick the target, and submit both guard values.
625
+ *
626
+ * On a rejected guard it re-lists and retries (default once). The TARGET is
627
+ * pinned across retries while the guard is refreshed, so a concurrent deploy
628
+ * can never redirect the rollback to a different release than the one chosen.
629
+ *
630
+ * ```ts
631
+ * await unfazed.rollback({ alias: "my-app" }); // undo last deploy
632
+ * await unfazed.rollback({ alias: "my-app", to: relId }); // to a specific release
633
+ * await unfazed.rollback({ alias: "my-app", to: relId, expectedCurrentId: liveId });
634
+ * ```
635
+ *
636
+ * ⚠️ **A bare `rollback({ alias })` is a TOGGLE, not an idempotent step.**
637
+ * It means "restore the most recent READY release that is not the live one",
638
+ * which is a different release each time the alias moves. Run the same
639
+ * script twice and the second run rolls back the rollback — A → B → A → B,
640
+ * forever, with each hop a real alias swap. Nothing errors, because every
641
+ * individual call did exactly what it was asked.
642
+ *
643
+ * In anything re-runnable — a retry loop, a CI job, an agent that might redo
644
+ * a step — pin the outcome instead:
645
+ *
646
+ * - `to:` names the release you want live, so re-running is a no-op-ish
647
+ * convergence rather than a flip (a second run then reports the target
648
+ * already holds the alias instead of moving it);
649
+ * - `expectedCurrentId:` states what you expect to be live now, so a second
650
+ * run fails loudly instead of silently undoing the first.
651
+ */
652
+ rollback(options: RollbackOptions): Promise<Deployment>;
653
+ }