unfazed 0.1.0 → 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.
- package/README.md +92 -6
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +23 -0
- package/dist/errors.js +40 -0
- package/dist/errors.js.map +1 -1
- package/dist/generated/gql.d.ts +1 -1
- package/dist/generated/gql.js +1 -1
- package/dist/generated/gql.js.map +1 -1
- package/dist/generated/graphql.d.ts +1 -0
- package/dist/generated/graphql.js +4 -2
- package/dist/generated/graphql.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +10 -0
- package/dist/operations.js +11 -2
- package/dist/operations.js.map +1 -1
- package/dist/unfazed.d.ts +219 -11
- package/dist/unfazed.js +246 -18
- package/dist/unfazed.js.map +1 -1
- package/package.json +1 -1
package/dist/unfazed.d.ts
CHANGED
|
@@ -1,8 +1,33 @@
|
|
|
1
1
|
import { UnfazedClient, type ClientOptions, type RequestOptions } from "./client.js";
|
|
2
|
-
import type { CreateDeploymentInput, DeploymentFieldsFragment, ListDeploymentsInput, ServiceMode, ServiceRuntime } from "./generated/graphql.js";
|
|
2
|
+
import type { CreateDeploymentInput, DeploymentFieldsFragment, ListDeploymentsInput, ServiceMode, ServiceRuntime, SdkCreateWorkspaceMutation } from "./generated/graphql.js";
|
|
3
3
|
import { type WaitOptions } from "./wait.js";
|
|
4
4
|
/** A deployment as the workflow layer returns it. */
|
|
5
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;
|
|
6
31
|
/** A file record returned by any write/list operation. */
|
|
7
32
|
export interface WorkspaceFile {
|
|
8
33
|
path: string;
|
|
@@ -106,8 +131,25 @@ export interface RollbackOptions {
|
|
|
106
131
|
* Default 1. Each retry refreshes the guard from a fresh read; the TARGET
|
|
107
132
|
* release stays pinned, so a retry can never silently roll back to something
|
|
108
133
|
* other than what was chosen.
|
|
134
|
+
*
|
|
135
|
+
* Ignored when {@link expectedCurrentId} is set — see there.
|
|
109
136
|
*/
|
|
110
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;
|
|
111
153
|
signal?: AbortSignal;
|
|
112
154
|
}
|
|
113
155
|
export interface UnfazedOptions extends ClientOptions {
|
|
@@ -180,8 +222,55 @@ export declare class Unfazed {
|
|
|
180
222
|
/**
|
|
181
223
|
* Deletes a workspace and everything anchored to it — deployments, env, and
|
|
182
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.
|
|
183
248
|
*/
|
|
184
|
-
deleteWorkspace(workspaceId: string, options?:
|
|
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
|
+
}>;
|
|
185
274
|
/**
|
|
186
275
|
* Writes many files in as few requests as possible.
|
|
187
276
|
*
|
|
@@ -248,13 +337,22 @@ export declare class Unfazed {
|
|
|
248
337
|
updatedAt: string;
|
|
249
338
|
};
|
|
250
339
|
} | null>;
|
|
251
|
-
/**
|
|
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
|
+
*/
|
|
252
346
|
deleteFile(workspaceId: string, path: string, options?: RequestOptions): Promise<boolean>;
|
|
253
347
|
/**
|
|
254
348
|
* Deploys the workspace as a SERVICE (an HTTP server).
|
|
255
349
|
*
|
|
256
350
|
* With `wait: true` this resolves only once the build, VM boot and health
|
|
257
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.
|
|
258
356
|
*/
|
|
259
357
|
deployService(options: DeployServiceOptions): Promise<Deployment>;
|
|
260
358
|
/** Deploys the workspace as a STATIC site. */
|
|
@@ -263,6 +361,25 @@ export declare class Unfazed {
|
|
|
263
361
|
* Deploys a BOX: a persistent always-on Linux VM with a data disk that
|
|
264
362
|
* survives stops and redeploys. A dev machine, not an HTTP service. The
|
|
265
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
|
+
* ```
|
|
266
383
|
*/
|
|
267
384
|
deployBox(options: DeployBoxOptions): Promise<Deployment>;
|
|
268
385
|
/** Escape hatch: deploy with a raw CreateDeploymentInput. */
|
|
@@ -379,37 +496,111 @@ export declare class Unfazed {
|
|
|
379
496
|
restartCount: number;
|
|
380
497
|
lastError: string | null;
|
|
381
498
|
}>;
|
|
382
|
-
/**
|
|
383
|
-
|
|
384
|
-
|
|
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
|
+
*/
|
|
385
516
|
setEnv(workspaceId: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
|
|
386
|
-
/**
|
|
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
|
+
*/
|
|
387
523
|
setSecret(workspaceId: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
|
|
388
524
|
/**
|
|
389
525
|
* Sets many workspace env vars in one request, via aliased mutations —
|
|
390
526
|
* the same batching trick as {@link writeFiles}.
|
|
391
527
|
*
|
|
392
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.
|
|
393
539
|
*/
|
|
394
540
|
setEnvMany(workspaceId: string, vars: Record<string, string>, options?: {
|
|
395
541
|
secret?: boolean;
|
|
396
542
|
signal?: AbortSignal;
|
|
397
543
|
}): Promise<EnvVar[]>;
|
|
398
|
-
/**
|
|
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
|
+
*/
|
|
399
554
|
deleteEnv(workspaceId: string, name: string, options?: RequestOptions): Promise<boolean>;
|
|
400
555
|
/** Workspace-default env vars. Secret values are redacted. */
|
|
401
556
|
listEnv(workspaceId: string, options?: RequestOptions): Promise<EnvVar[]>;
|
|
402
|
-
/**
|
|
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
|
+
*/
|
|
403
565
|
setAppEnv(workspaceId: string, alias: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
|
|
404
|
-
/**
|
|
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
|
+
*/
|
|
405
572
|
setAppSecret(workspaceId: string, alias: string, name: string, value: string, options?: RequestOptions): Promise<EnvVar>;
|
|
406
573
|
/**
|
|
407
574
|
* REMOVE: blocks this name for the app entirely — the app override, the
|
|
408
575
|
* workspace default, and integration enrichment. Not the same as deleting an
|
|
409
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.
|
|
410
594
|
*/
|
|
411
595
|
removeAppEnv(workspaceId: string, alias: string, name: string, options?: RequestOptions): Promise<boolean>;
|
|
412
|
-
/**
|
|
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
|
+
*/
|
|
413
604
|
inheritAppEnv(workspaceId: string, alias: string, name: string, options?: RequestOptions): Promise<boolean>;
|
|
414
605
|
/** The effective env view for one alias: overrides, inherited and masked. */
|
|
415
606
|
listAppEnv(workspaceId: string, alias: string, options?: RequestOptions): Promise<EnvVar[]>;
|
|
@@ -439,7 +630,24 @@ export declare class Unfazed {
|
|
|
439
630
|
* ```ts
|
|
440
631
|
* await unfazed.rollback({ alias: "my-app" }); // undo last deploy
|
|
441
632
|
* await unfazed.rollback({ alias: "my-app", to: relId }); // to a specific release
|
|
633
|
+
* await unfazed.rollback({ alias: "my-app", to: relId, expectedCurrentId: liveId });
|
|
442
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.
|
|
443
651
|
*/
|
|
444
652
|
rollback(options: RollbackOptions): Promise<Deployment>;
|
|
445
653
|
}
|