unfazed 0.6.1 → 0.8.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/CHANGELOG.md +88 -0
- package/README.md +201 -52
- package/REFERENCE.md +284 -0
- package/dist/cli/login.d.ts +1 -1
- 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 +64 -0
- package/dist/errors.js +95 -0
- package/dist/errors.js.map +1 -1
- package/dist/generated/gql.d.ts +78 -2
- package/dist/generated/gql.js +21 -2
- package/dist/generated/gql.js.map +1 -1
- package/dist/generated/graphql.d.ts +505 -6
- package/dist/generated/graphql.js +439 -2
- package/dist/generated/graphql.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/operations.d.ts +116 -5
- package/dist/operations.js +379 -7
- package/dist/operations.js.map +1 -1
- package/dist/reference.d.ts +18 -0
- package/dist/reference.generated.d.ts +2 -0
- package/dist/reference.generated.js +5 -0
- package/dist/reference.generated.js.map +1 -0
- package/dist/reference.js +38 -0
- package/dist/reference.js.map +1 -0
- package/dist/unfazed.d.ts +544 -67
- package/dist/unfazed.js +687 -63
- package/dist/unfazed.js.map +1 -1
- package/dist/wait.d.ts +7 -0
- package/dist/wait.js.map +1 -1
- package/dist/workflow/nitro.js +5 -3
- package/dist/workflow/nitro.js.map +1 -1
- package/package.json +8 -4
- package/schema/SOURCE.json +4 -4
- package/schema/schema.graphqls +1628 -30
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
Fixes from a hands-on Code Mode test (2026-10-22), and the MCP tools the SDK
|
|
6
|
+
lacked. Needs the platform change that adds `writeWorkspaceFiles`,
|
|
7
|
+
`waitForDeployment`, the error and event queries and the new
|
|
8
|
+
`restoreAppVersion` fields; on an older platform `writeFiles` and the deploy
|
|
9
|
+
wait fall back to their 0.7 behaviour.
|
|
10
|
+
|
|
11
|
+
**Breaking**
|
|
12
|
+
|
|
13
|
+
- `writeFiles` returns a `WriteFilesResult` (`{ files, written, failed,
|
|
14
|
+
totalBytes, commit, commits, landed? }`) instead of `WorkspaceFile[]`, and a
|
|
15
|
+
file that fails throws `UnfazedWriteError` (its `result` and `failed`)
|
|
16
|
+
instead of `UnfazedGraphQLError`; `allowPartial: true` returns the result
|
|
17
|
+
instead. Its defaults are now 100 files and 32 MiB per request (were 20 and
|
|
18
|
+
4 MiB).
|
|
19
|
+
- `getEvents` returns `DeploymentEvent[]` whose `type` is the event's own
|
|
20
|
+
dotted name (`"deployment.build_finished"`, `"service.vm_started"`), as MCP
|
|
21
|
+
shows it. The GraphQL enum moved to `typeEnum`; `rawType` stays, deprecated.
|
|
22
|
+
|
|
23
|
+
**Changed**
|
|
24
|
+
|
|
25
|
+
- `writeFiles` is ONE request and ONE commit on an app with platform git (the
|
|
26
|
+
`writeWorkspaceFiles` mutation MCP's `write_workspace_files` uses), with
|
|
27
|
+
`commitMessage`, and answers the commit: `sha`, `committed`, `waiting`,
|
|
28
|
+
`waitingCount`, `deploys`, `conflicts`, `writeSHA`, `paused`. A map over one
|
|
29
|
+
request's limits is several requests, one commit each. `writeFile` and
|
|
30
|
+
`writeBinaryFile` are one-file batches and answer the file with its
|
|
31
|
+
`commit`.
|
|
32
|
+
- `deployService/deployStatic/deployBox/deploy({ wait: true })` and
|
|
33
|
+
`waitForDeployment` wait server-side (MCP `wait_for_deployment`): READY comes
|
|
34
|
+
back one round trip after it happens, with the full deployment, no poll
|
|
35
|
+
interval and no re-read. The 9.0 s vs 6.8 s gap in the report was the old
|
|
36
|
+
1 s × 1.5 poll backoff (READY seen at the 8.1 s poll) plus the full re-read.
|
|
37
|
+
The polling fallback starts at 0.5 s and caps at 3 s.
|
|
38
|
+
- A deploy with `wait: true` carries `timings`: `platform` (`totalMs`,
|
|
39
|
+
`buildMs`, `promoteMs`, every measured phase as numbers) and `client`
|
|
40
|
+
(`createMs`, `waitMs`, `totalMs`, `waitRequests`, `waitMode`).
|
|
41
|
+
`timings: false` skips the extra read.
|
|
42
|
+
- The platform holds at most 16 long waits of one account at once (and one
|
|
43
|
+
per GraphQL request). A deploy wait refused past that polls instead;
|
|
44
|
+
`waitForEvents` throws an error `isTooManyWaits` recognizes.
|
|
45
|
+
|
|
46
|
+
**Added**
|
|
47
|
+
|
|
48
|
+
- `help(topic?)` and `REFERENCE`: every method with its signature, in one
|
|
49
|
+
page (REFERENCE.md, shipped in the package).
|
|
50
|
+
- `waitForCommit(workspaceId)` and `writeFiles(…, { waitForCommit })`; the
|
|
51
|
+
saved / committed / deployed states are documented.
|
|
52
|
+
- History: `listCommits`, `getCommit`, `restoreVersion` (with `wait: true` it
|
|
53
|
+
follows the import the restore queues to its READY deployments).
|
|
54
|
+
- Errors: `listErrors`, `getErrorGroup`, `resolveErrorGroup`,
|
|
55
|
+
`muteErrorGroup`, `reopenErrorGroup`.
|
|
56
|
+
- Events: `listEventTypes`, `pollEvents`, `waitForEvents`.
|
|
57
|
+
- `getDeploymentTimings(id)` and `parseDeploymentTimings(events)`.
|
|
58
|
+
|
|
59
|
+
## 0.7.0
|
|
60
|
+
|
|
61
|
+
`deleteApp`, and `deleteWorkspace` names every app it takes offline
|
|
62
|
+
(`confirmAliases`, `liveApps`).
|
|
63
|
+
|
|
64
|
+
## 0.6.1
|
|
65
|
+
|
|
66
|
+
`npx unfazed login`, and the SDK uses the stored login.
|
|
67
|
+
|
|
68
|
+
## 0.6.0
|
|
69
|
+
|
|
70
|
+
`unfazed/workflow`: the unfazed World for Workflow SDK; `--backend unfazed`
|
|
71
|
+
for Workflow SDK's CLI.
|
|
72
|
+
|
|
73
|
+
## 0.5.0
|
|
74
|
+
|
|
75
|
+
Runs: access mode, app alias, integrations on a run, `listRunHarnesses`.
|
|
76
|
+
|
|
77
|
+
## 0.4.0
|
|
78
|
+
|
|
79
|
+
Runs: `verify` commands and the harness result.
|
|
80
|
+
|
|
81
|
+
## 0.3.0
|
|
82
|
+
|
|
83
|
+
Agent runs.
|
|
84
|
+
|
|
85
|
+
## 0.2.0
|
|
86
|
+
|
|
87
|
+
`ensureWorkspace`, `ignoreNotFound`; `deleteWorkspace` fixed; a harder
|
|
88
|
+
`rollback`.
|
package/README.md
CHANGED
|
@@ -119,8 +119,8 @@ const unfazed = new Unfazed();
|
|
|
119
119
|
|
|
120
120
|
const ws = await unfazed.createWorkspace("greeter");
|
|
121
121
|
|
|
122
|
-
// One request, not three —
|
|
123
|
-
await unfazed.writeFiles(ws.id, {
|
|
122
|
+
// One request, not three — and ONE commit on an app with platform git.
|
|
123
|
+
const written = await unfazed.writeFiles(ws.id, {
|
|
124
124
|
"server.ts": `
|
|
125
125
|
export default {
|
|
126
126
|
fetch(req: Request) {
|
|
@@ -131,7 +131,8 @@ await unfazed.writeFiles(ws.id, {
|
|
|
131
131
|
`,
|
|
132
132
|
"unfazed.json": JSON.stringify({ entry: "server.ts" }),
|
|
133
133
|
"README.md": "# greeter",
|
|
134
|
-
});
|
|
134
|
+
}, { commitMessage: "First version" });
|
|
135
|
+
console.log(written.commit?.sha ?? "no platform git");
|
|
135
136
|
|
|
136
137
|
try {
|
|
137
138
|
// Blocks until the build, VM boot and health probe all pass.
|
|
@@ -141,6 +142,8 @@ try {
|
|
|
141
142
|
wait: true,
|
|
142
143
|
});
|
|
143
144
|
console.log(`live: ${app.url}`);
|
|
145
|
+
// What the platform measured, and what this script waited.
|
|
146
|
+
console.log(app.timings?.platform?.totalMs, app.timings?.client.totalMs);
|
|
144
147
|
|
|
145
148
|
// Verify it for real before trusting the alias.
|
|
146
149
|
const res = await fetch(`${app.url}?name=agent`);
|
|
@@ -164,6 +167,17 @@ try {
|
|
|
164
167
|
}
|
|
165
168
|
```
|
|
166
169
|
|
|
170
|
+
## Every signature, from a script: `help()`
|
|
171
|
+
|
|
172
|
+
`help()` returns [REFERENCE.md](REFERENCE.md): every method with its
|
|
173
|
+
signature, result and an example, in one page. `help("writeFiles")` is one
|
|
174
|
+
entry, `help("Events")` one section. A Code Mode script (MCP `run_script`) can
|
|
175
|
+
`export default () => help("restoreVersion")` to read it without leaving the
|
|
176
|
+
tool. The platform serves the reference of the version it vendors at
|
|
177
|
+
https://unfazed.dev/sdk.md; a run_script result's `sdk_version` (or
|
|
178
|
+
`VERSION` in the script) names the version a script actually ran, which lags
|
|
179
|
+
for a while after a release (it ships with the platform's worker releases).
|
|
180
|
+
|
|
167
181
|
## Two layers
|
|
168
182
|
|
|
169
183
|
### 1. Typed GraphQL core — `UnfazedClient`
|
|
@@ -188,7 +202,8 @@ const data = await client.query<{ workspaces: { id: string }[] }, never>(
|
|
|
188
202
|
Errors are typed and specific: `UnfazedAuthError`, `UnfazedHTTPError` (carries
|
|
189
203
|
`status`/`body`), `UnfazedGraphQLError` (carries `errors` **and any partial
|
|
190
204
|
`data`**), `UnfazedTimeoutError`, `UnfazedDeployError`, `UnfazedConflictError`,
|
|
191
|
-
`UnfazedNotFoundError
|
|
205
|
+
`UnfazedNotFoundError`, `UnfazedWriteError` (carries the write's `result` and
|
|
206
|
+
the `failed` paths). All extend `UnfazedError`.
|
|
192
207
|
|
|
193
208
|
### 2. Workflow layer — `Unfazed`
|
|
194
209
|
|
|
@@ -206,14 +221,17 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
206
221
|
| `createWorkspace(name)` | names are unique per owner, case-insensitively |
|
|
207
222
|
| `ensureWorkspace(name)` | get-or-create; **use this in re-runnable scripts** |
|
|
208
223
|
| `listWorkspaces()` | |
|
|
209
|
-
| `
|
|
224
|
+
| `liveApps(id)` | the apps the workspace serves now: what a cascading delete takes offline |
|
|
225
|
+
| `deleteApp({ workspaceId, alias }, opts?)` | deletes ONE app; the workspace and its other apps keep serving |
|
|
226
|
+
| `deleteWorkspace(id, opts?)` | with deployments only via `{ withDeployments: true, confirmAliases }` naming every live app; also deletes a BOX's data disk |
|
|
210
227
|
|
|
211
228
|
**Files**
|
|
212
229
|
| Method | Notes |
|
|
213
230
|
| --- | --- |
|
|
214
|
-
| `writeFiles(wsId, files, opts?)` | **
|
|
215
|
-
| `writeFile(wsId, path, text)` | |
|
|
216
|
-
| `writeBinaryFile(wsId, path, bytes)` | base64 under the hood |
|
|
231
|
+
| `writeFiles(wsId, files, opts?)` | **one request, one commit** (MCP `write_workspace_files`); `commitMessage`; answers the `commit` |
|
|
232
|
+
| `writeFile(wsId, path, text, opts?)` | a one-file `writeFiles`; answers the file and its `commit` |
|
|
233
|
+
| `writeBinaryFile(wsId, path, bytes, opts?)` | base64 under the hood |
|
|
234
|
+
| `waitForCommit(wsId)` | resolves once no write waits for its commit (see "Saved, committed, deployed") |
|
|
217
235
|
| `listFiles(wsId, page?)` | |
|
|
218
236
|
| `readFile(wsId, path, range?)` | |
|
|
219
237
|
| `deleteFile(wsId, path)` | |
|
|
@@ -221,11 +239,11 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
221
239
|
**Deploy**
|
|
222
240
|
| Method | Notes |
|
|
223
241
|
| --- | --- |
|
|
224
|
-
| `deployService(opts)` | `wait: true`
|
|
242
|
+
| `deployService(opts)` | `wait: true` waits server-side to READY; the result carries `timings` |
|
|
225
243
|
| `deployStatic(opts)` | `spaMode` for single-page apps |
|
|
226
244
|
| `deployBox(opts)` | persistent Linux VM; result carries `sshCommand` |
|
|
227
245
|
| `deploy(input, opts?)` | raw `CreateDeploymentInput` escape hatch |
|
|
228
|
-
| `waitForDeployment(id, opts?)` |
|
|
246
|
+
| `waitForDeployment(id, opts?)` | the server-side wait (MCP `wait_for_deployment`); polls on older platforms |
|
|
229
247
|
| `getDeployment(id)` / `getDeploymentByAlias(alias)` | |
|
|
230
248
|
| `listDeployments(filter?)` | |
|
|
231
249
|
|
|
@@ -234,7 +252,8 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
234
252
|
| --- | --- |
|
|
235
253
|
| `getLogs(id, { tailBytes })` | stdout + stderr tail |
|
|
236
254
|
| `getStatus(id)` | health, in-flight, restarts, `memSizeMiB` |
|
|
237
|
-
| `getEvents(id, limit?)` |
|
|
255
|
+
| `getEvents(id, limit?)` | lifecycle events; `type` is the dotted name MCP shows (`deployment.build_finished`), `typeEnum` the GraphQL enum |
|
|
256
|
+
| `getDeploymentTimings(id)` | the platform's measured phases, as numbers |
|
|
238
257
|
| `restartService(id)` / `stopService(id)` | |
|
|
239
258
|
| `deleteDeployment(id, opts?)` | `ignoreNotFound` for idempotent cleanup |
|
|
240
259
|
|
|
@@ -253,6 +272,27 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
253
272
|
| `listReleases(alias)` | newest first; one entry has `aliasHeld: true` |
|
|
254
273
|
| `rollback({ alias, to?, expectedCurrentId?, retries? })` | **whole compare-and-swap in one call** |
|
|
255
274
|
|
|
275
|
+
**History (platform git)**
|
|
276
|
+
| Method | Notes |
|
|
277
|
+
| --- | --- |
|
|
278
|
+
| `listCommits(wsId, { limit?, before? })` | newest first; `deployed` says what each alias runs (MCP `list_commits`) |
|
|
279
|
+
| `getCommit(wsId, sha)` | the changed paths and a unified diff (MCP `get_commit`) |
|
|
280
|
+
| `restoreVersion({ workspaceId, sha, wait? })` | one new commit with that commit's files; `wait: true` follows its deploy to READY (MCP `restore_version`) |
|
|
281
|
+
|
|
282
|
+
**App errors**
|
|
283
|
+
| Method | Notes |
|
|
284
|
+
| --- | --- |
|
|
285
|
+
| `listErrors({ workspaceId, alias, status? })` | a service app's error groups (MCP `list_errors`) |
|
|
286
|
+
| `getErrorGroup({ workspaceId, alias, groupId })` | the latest occurrence whole, and where it was seen |
|
|
287
|
+
| `resolveErrorGroup` / `muteErrorGroup` / `reopenErrorGroup` | same ref; only on your human's say-so |
|
|
288
|
+
|
|
289
|
+
**Events**
|
|
290
|
+
| Method | Notes |
|
|
291
|
+
| --- | --- |
|
|
292
|
+
| `listEventTypes()` | every type with its filters and payload |
|
|
293
|
+
| `pollEvents({ names?, arguments?, cursor? })` | MCP `poll_events`; the same cursors |
|
|
294
|
+
| `waitForEvents({ …, timeoutMs? })` | blocks server-side until an event arrives (MCP `wait_for_events`) |
|
|
295
|
+
|
|
256
296
|
**Agent runs**
|
|
257
297
|
| Method | Notes |
|
|
258
298
|
| --- | --- |
|
|
@@ -269,25 +309,53 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
269
309
|
|
|
270
310
|
## The two things worth knowing
|
|
271
311
|
|
|
272
|
-
### `writeFiles`
|
|
312
|
+
### `writeFiles` is one request and one commit
|
|
273
313
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
mutation SdkWriteFilesBatch($workspaceID: ID!, $p0: String!, $c0: String!, ...) {
|
|
279
|
-
f0: writeWorkspaceTextFile(input: { workspaceID: $workspaceID, path: $p0, text: $c0 }) { file { ... } }
|
|
280
|
-
f1: writeWorkspaceBase64File(input: { workspaceID: $workspaceID, path: $p1, contentBase64: $c1 }) { file { ... } }
|
|
281
|
-
}
|
|
282
|
-
```
|
|
314
|
+
`writeFiles` sends every file in one `writeWorkspaceFiles` mutation, the one
|
|
315
|
+
MCP's `write_workspace_files` uses. Text and binary mix freely. On an app with
|
|
316
|
+
platform git the whole call is ONE commit, made before any file is written,
|
|
317
|
+
with your `commitMessage`; the answer carries it:
|
|
283
318
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
319
|
+
```ts
|
|
320
|
+
const out = await unfazed.writeFiles(ws.id, { "server.ts": code, "package.json": pkg }, { commitMessage: "Add the API" });
|
|
321
|
+
out.commit; // { sha, branch, message, committed, waiting, waitingCount, deploys, conflicts, writeSHA, paused }
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
A call is **not transactional**, exactly as MCP's: files are written in order,
|
|
325
|
+
each atomically, and one failing does not undo the others. Then `writeFiles`
|
|
326
|
+
throws `UnfazedWriteError`, whose `result` lists what landed and `failed` what
|
|
327
|
+
did not — retry only those, or pass `allowPartial: true` to get the result
|
|
328
|
+
instead. A request carries up to 100 files and 32 MiB (the platform's limits);
|
|
329
|
+
a bigger map is split, one commit per request (`commits`). On a platform older
|
|
330
|
+
than the batch mutation the SDK falls back to one aliased mutation per file in
|
|
331
|
+
one request (one commit each, `commit: null`).
|
|
332
|
+
|
|
333
|
+
### Saved, committed, deployed
|
|
334
|
+
|
|
335
|
+
- **Saved** — `writeFiles` returned: the app's files have it; the next deploy
|
|
336
|
+
uses it.
|
|
337
|
+
- **Committed** — `commit.committed && !commit.waiting`: it is in the app's
|
|
338
|
+
history (`listCommits`). When the git server is slow the commit comes back
|
|
339
|
+
`waiting` (`sha: null`): saved, landing in order, nothing to retry.
|
|
340
|
+
`writeFiles(…, { waitForCommit: true })` or `waitForCommit(wsId)` resolves
|
|
341
|
+
once it has landed, with the branch head.
|
|
342
|
+
- **Deployed** — a deploy of those files went READY: `deployService({ wait:
|
|
343
|
+
true })`. A write deploys by itself only when `commit.deploys` is true (the
|
|
344
|
+
branch head is imported like a push).
|
|
345
|
+
|
|
346
|
+
### Waiting is server-side, and you get both clocks
|
|
347
|
+
|
|
348
|
+
`deployService({ wait: true })` and `waitForDeployment` wait on the platform
|
|
349
|
+
(MCP `wait_for_deployment`): each request blocks until the deployment leaves
|
|
350
|
+
BUILDING — woken by the deploy pipeline — or 45 s pass, and answers with the
|
|
351
|
+
full deployment. READY reaches the script one round trip after it happens.
|
|
352
|
+
Before 0.8.0 the SDK polled at 1 s × 1.5 backoff and re-read the deployment in
|
|
353
|
+
full: a deploy READY after 6.8 s was seen at about 8.1 s and returned at 9.0 s.
|
|
354
|
+
|
|
355
|
+
The result carries `timings`: `platform` is what the platform measured
|
|
356
|
+
(`totalMs` from creation to READY, `buildMs`, `promoteMs`, and every phase in
|
|
357
|
+
`phases`), `client` what the script waited (`createMs`, `waitMs`, `totalMs`,
|
|
358
|
+
`waitRequests`, `waitMode`). `timings: false` skips the one extra read.
|
|
291
359
|
|
|
292
360
|
### `rollback` does the compare-and-swap for you
|
|
293
361
|
|
|
@@ -361,25 +429,78 @@ what the first one did:
|
|
|
361
429
|
| `deployBox({ workspaceId })` | `deployBox({ workspaceId, alias })` | no alias ⇒ a brand-new box each time |
|
|
362
430
|
| `rollback({ alias })` | `rollback({ alias, to })` or `expectedCurrentId` | bare rollback is a toggle |
|
|
363
431
|
| `deleteWorkspace(id)` throwing | `deleteWorkspace(id, { ignoreNotFound: true })` | already-deleted is not a failure |
|
|
432
|
+
| `deleteApp({ workspaceId, alias })` throwing | `deleteApp({ workspaceId, alias }, { ignoreNotFound: true })` | same; resolves `null` |
|
|
364
433
|
| `deleteDeployment(id)` throwing | `deleteDeployment(id, { ignoreNotFound: true })` | same |
|
|
365
434
|
|
|
366
|
-
`ignoreNotFound` matches the server's
|
|
367
|
-
found
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
435
|
+
`ignoreNotFound` matches the server's exact refusals — `workspace was not
|
|
436
|
+
found`, `deployment was not found`, and for `deleteApp` `app "<alias>" was not
|
|
437
|
+
found in workspace "…"` naming that alias — and nothing else. A message that
|
|
438
|
+
merely *contains* "not found" still throws, so a real failure can't be mistaken
|
|
439
|
+
for a successful teardown. (The two sentinels also cover "belongs to another
|
|
440
|
+
tenant": the server reports the two identically so ids can't be probed.)
|
|
441
|
+
|
|
442
|
+
### Deleting names what goes
|
|
443
|
+
|
|
444
|
+
A workspace is a source, and one source can serve several apps: each
|
|
445
|
+
`unfazed.json` `apps[]` entry, or several aliases deployed into it. A delete is
|
|
446
|
+
never wider than what you name:
|
|
447
|
+
|
|
448
|
+
- **`deleteApp({ workspaceId, alias })` deletes one app**: every deployment of
|
|
449
|
+
the alias in that workspace (its VM stops, its URL and custom domains stop
|
|
450
|
+
serving), its App State, its app-scoped env and secrets, and its integration
|
|
451
|
+
bindings. The workspace, its files and defaults, its other apps and a box's
|
|
452
|
+
data disk stay. The result says what went (`deployments`, `domains`,
|
|
453
|
+
`envEntries`) and what still serves (`otherApps`). In a git-linked workspace
|
|
454
|
+
pushes and syncs skip the alias while `unfazed.json` `apps[]` still declares
|
|
455
|
+
it (`skippedOnSync: true`); remove it from `apps[]`, or bring it back with
|
|
456
|
+
the GraphQL `syncRepo` mutation's `restoreApps`. A delete that stops
|
|
457
|
+
part-way throws saying so; deleting again finishes it.
|
|
458
|
+
- **`deleteWorkspace(id, { withDeployments: true, confirmAliases })` deletes the
|
|
459
|
+
workspace with every app it serves**, so `confirmAliases` must name exactly
|
|
460
|
+
those apps, in any order. A missing or extra alias, or an app that went live
|
|
461
|
+
after the check, makes the server delete nothing and refuse with
|
|
462
|
+
`UnfazedUnconfirmedAppsError` (still an `UnfazedGraphQLError`), whose message
|
|
463
|
+
is the server's: it lists every live app with its custom domains. A
|
|
464
|
+
workspace whose deploys all failed serves no app: `confirmAliases: []`.
|
|
465
|
+
- **`deleteWorkspace(id)`** deletes only a workspace with no deployment records
|
|
466
|
+
left; otherwise the server refuses with `still has deployments; delete them
|
|
467
|
+
first`.
|
|
468
|
+
|
|
469
|
+
The SDK never fills `confirmAliases` in for you. `liveApps(id)` is there to look
|
|
470
|
+
the list up deliberately — read it, then name what you mean to take down:
|
|
378
471
|
|
|
379
472
|
```ts
|
|
380
|
-
await unfazed.
|
|
381
|
-
|
|
382
|
-
|
|
473
|
+
await unfazed.liveApps(ws.id);
|
|
474
|
+
// [{ alias: "api", deploymentID: "…", domains: ["api.example.com"] },
|
|
475
|
+
// { alias: "web", deploymentID: "…", domains: [] }]
|
|
476
|
+
|
|
477
|
+
// Either one app — "web" keeps serving:
|
|
478
|
+
await unfazed.deleteApp({ workspaceId: ws.id, alias: "api" });
|
|
479
|
+
|
|
480
|
+
// or the workspace with every app it serves, each named:
|
|
481
|
+
await unfazed.deleteWorkspace(ws.id, { withDeployments: true, confirmAliases: ["api", "web"] });
|
|
482
|
+
|
|
483
|
+
// Naming fewer (or more) deletes nothing and throws:
|
|
484
|
+
await unfazed.deleteWorkspace(ws.id, { withDeployments: true, confirmAliases: ["web"] });
|
|
485
|
+
// UnfazedUnconfirmedAppsError: unfazed: nothing was deleted: workspace "shop" (…) serves 2 apps:
|
|
486
|
+
// api (api.example.com); web. Deleting it takes every one of them offline. To delete one app,
|
|
487
|
+
// use deleteApp (MCP delete_app). To delete the workspace with all of them, name exactly these
|
|
488
|
+
// aliases in confirmAliases.
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Deleted is kept for 30 days.** A delete takes effect at once: the app,
|
|
492
|
+
release or workspace disappears from your views, its name and alias are free
|
|
493
|
+
for anyone, and it stops serving. The platform keeps what you deleted — its
|
|
494
|
+
files, releases with their env and secrets, App State, box disk, integration
|
|
495
|
+
resources and custom domains — for 30 days. Within that time support (the
|
|
496
|
+
platform operator) can restore it exactly as it was; you cannot restore it
|
|
497
|
+
yourself, so ask support. After 30 days it is erased for good. This holds for
|
|
498
|
+
`deleteApp`, `deleteWorkspace` and `deleteDeployment` alike.
|
|
499
|
+
|
|
500
|
+
**Upgrading from 0.6.** `deleteWorkspace(id)` used to send `withDeployments:
|
|
501
|
+
true` with no apps named; the server now refuses that whenever the workspace
|
|
502
|
+
serves an app. From 0.7 the bare call is the strict delete, and a cascade takes
|
|
503
|
+
`{ withDeployments: true, confirmAliases }`. To remove one app, use `deleteApp`.
|
|
383
504
|
|
|
384
505
|
## Env & secrets: three states, not two
|
|
385
506
|
|
|
@@ -501,25 +622,38 @@ to the app as well.
|
|
|
501
622
|
|
|
502
623
|
### With Nitro (the recipe)
|
|
503
624
|
|
|
625
|
+
The app deploys in Nitro's own layout, as the platform's "Deploying a Nitro
|
|
626
|
+
app" describes, with no re-export file:
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
// server.ts — the app itself; Nitro takes a root server.ts as its server entry
|
|
630
|
+
import { Hono } from "hono";
|
|
631
|
+
|
|
632
|
+
const app = new Hono();
|
|
633
|
+
// … routes that start() workflows
|
|
634
|
+
export default app;
|
|
635
|
+
```
|
|
636
|
+
|
|
504
637
|
```ts
|
|
505
638
|
// nitro.config.ts
|
|
506
639
|
import { defineConfig } from "nitro";
|
|
507
640
|
|
|
508
641
|
export default defineConfig({
|
|
509
642
|
modules: ["workflow/nitro", "unfazed/workflow/nitro"],
|
|
510
|
-
routes: { "/**": "./src/app.ts" }, // a Hono app; not named server.ts (Nitro claims that name)
|
|
511
643
|
});
|
|
512
644
|
```
|
|
513
645
|
|
|
514
646
|
```json
|
|
515
|
-
// unfazed.json —
|
|
516
|
-
{ "kind": "SERVICE", "
|
|
647
|
+
// unfazed.json — no "entry"
|
|
648
|
+
{ "kind": "SERVICE", "build": "nitro build" }
|
|
517
649
|
```
|
|
518
650
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
651
|
+
With no `entry` in unfazed.json and a `standard` Nitro build, unfazed serves
|
|
652
|
+
`.output/server/index.mjs`; the build log says `using Nitro output
|
|
653
|
+
.output/server/index.mjs as the service entry`. Do not set `entry` or add an
|
|
654
|
+
`unfazed-entry.ts` re-export: an explicit entry replaces the Nitro output.
|
|
655
|
+
Install and build commands find the app's `node_modules/.bin` CLIs on PATH, so
|
|
656
|
+
`"build": "bun run build"` over a package.json script works as well.
|
|
523
657
|
|
|
524
658
|
Then bind a Postgres or Neon integration to the app and deploy. In a
|
|
525
659
|
production build the module:
|
|
@@ -536,14 +670,16 @@ production build the module:
|
|
|
536
670
|
- mounts the **inspection route** the unfazed dashboard and MCP read runs
|
|
537
671
|
through (below), with the build's workflow list and Workflow SDK version;
|
|
538
672
|
- sets `noExternals: true` and builds Nitro's `standard` output (`export
|
|
539
|
-
default { fetch }
|
|
673
|
+
default { fetch }`, recorded as `"preset": "standard"` in
|
|
674
|
+
`.output/nitro.json`), which is what unfazed serves, whenever no preset was
|
|
540
675
|
chosen, whatever Nitro detected on the build machine: `bun` under Bun (as on
|
|
541
676
|
the unfazed build VM), `node-server` under Node, `deno-server` under Deno.
|
|
542
677
|
Their own outputs start a server of their own (under Bun with a 10 s idle
|
|
543
678
|
timeout that cuts every quiet workflow stream). A preset chosen explicitly
|
|
544
679
|
(`preset` in nitro.config.ts or .nitrorc, `NITRO_PRESET`, `SERVER_PRESET`)
|
|
545
680
|
other than `standard` **fails the build** with a message saying so; leave it
|
|
546
|
-
out, or set `preset: "standard"`.
|
|
681
|
+
out, or set `preset: "standard"`. A Nitro app without this module sets both
|
|
682
|
+
itself: `defineConfig({ preset: "standard", noExternals: true })`.
|
|
547
683
|
- serves public assets only when `serveStatic: "inline"` embeds them in the
|
|
548
684
|
bundle (a service VM has nothing next to it to read from disk). That is how
|
|
549
685
|
one service ships an API and its UI (`publicAssets: [{ dir: "dist" }]`
|
|
@@ -817,6 +953,11 @@ After bumping the `@workflow/world-postgres` devDependency, run
|
|
|
817
953
|
`bun run embed-migrations`: `bun run check` fails while
|
|
818
954
|
`src/workflow/migrations.generated.ts` is stale.
|
|
819
955
|
|
|
956
|
+
`REFERENCE.md` is what `help()` returns: after editing it run
|
|
957
|
+
`bun run embed-reference` (`bun run check` fails while
|
|
958
|
+
`src/reference.generated.ts` is stale). `test/reference.test.ts` fails when a
|
|
959
|
+
public method of `Unfazed` is missing from it.
|
|
960
|
+
|
|
820
961
|
### Regenerating from the schema
|
|
821
962
|
|
|
822
963
|
`schema/schema.graphqls` is a **vendored copy** of `graph/schema.graphqls` from
|
|
@@ -855,6 +996,14 @@ bun run check # typecheck + tests + build + dist smoke
|
|
|
855
996
|
npm publish # or: bun publish --access public
|
|
856
997
|
```
|
|
857
998
|
|
|
999
|
+
Then bring the release to Code Mode: MCP `run_script` runs a copy of the SDK
|
|
1000
|
+
vendored in the unfazed repo, not npm's. There, `hack/update-script-sdk.sh
|
|
1001
|
+
<version>` vendors the published tarball (checked against npm's integrity),
|
|
1002
|
+
and a worker release ships it, since scripts run on workers (unfazed
|
|
1003
|
+
`docs/code-mode.md`, "Shipping a new SDK"). Its `script-sdk` check turns red
|
|
1004
|
+
within a day of a publish that was not vendored. A release that needs new
|
|
1005
|
+
platform fields goes out after the platform change that adds them.
|
|
1006
|
+
|
|
858
1007
|
## License
|
|
859
1008
|
|
|
860
1009
|
MIT — see [LICENSE](./LICENSE).
|