unfazed 0.8.1 → 0.9.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 +44 -0
- package/README.md +90 -2
- package/REFERENCE.md +47 -2
- package/dist/cli/login.d.ts +1 -1
- package/dist/client.d.ts +21 -1
- package/dist/client.js +12 -4
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +49 -0
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/oidc.d.ts +161 -0
- package/dist/oidc.js +391 -0
- package/dist/oidc.js.map +1 -0
- package/dist/reference.generated.js +1 -1
- package/dist/reference.generated.js.map +1 -1
- package/dist/reference.js +3 -3
- package/dist/reference.js.map +1 -1
- package/dist/unfazed.d.ts +13 -2
- package/dist/unfazed.js +45 -4
- package/dist/unfazed.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
Workload identity: an app gets OIDC tokens from the platform with its own
|
|
6
|
+
state token (`POST /oidc/token`, live on unfazed.dev since 2026-10-25). The
|
|
7
|
+
rest of the SDK needs nothing newer than 0.8.1 did.
|
|
8
|
+
|
|
9
|
+
**Added**
|
|
10
|
+
|
|
11
|
+
- `unfazed.oidc.token(audience, { ttlSeconds?, signal? })`: a JWT for
|
|
12
|
+
`audience`, issued to the app the code runs in (issuer
|
|
13
|
+
`https://unfazed.dev/oidc`). Needs only `UNFZ_STATE_TOKEN`, which the
|
|
14
|
+
platform sets in every service and cron VM; without it the call throws
|
|
15
|
+
`not_in_app`. The audience (1 to 256 printable ASCII characters, no
|
|
16
|
+
whitespace) and `ttlSeconds` (60 to 3600) are checked before any request.
|
|
17
|
+
Tokens are cached per (audience, ttl) until 80% of their lifetime (the
|
|
18
|
+
JWT's `exp - iat`), concurrent calls share one request, and 503s (and
|
|
19
|
+
gateway 502/504s), network failures and 429s (after their `Retry-After`,
|
|
20
|
+
up to 10 s) are retried up to 3 times; no other 4xx is. When a refresh
|
|
21
|
+
still fails that way, the previous token comes back while it is valid.
|
|
22
|
+
- `unfazed.oidc.tokenResponse(...)`: the same as `{ token, expiresAt }`.
|
|
23
|
+
- `unfazed.oidc.fetchWithToken(audience, { ttlSeconds?, fetch? })`: a `fetch`
|
|
24
|
+
that sends `Authorization: Bearer <token>` and everything else as given,
|
|
25
|
+
for a client that takes a `fetch` (the OpenAI SDK's `fetch` option).
|
|
26
|
+
- Without an instance: `oidc` (the process's `UnfazedOIDC`, one cache for the
|
|
27
|
+
process) and `oidcToken(audience)`.
|
|
28
|
+
- `UnfazedOIDCError` (`code`, `status`, `description`, `retryable`,
|
|
29
|
+
`retryAfterMs`); `code` is the platform's `error` (`invalid_token`,
|
|
30
|
+
`invalid_audience`, `invalid_request`, `rate_limited`, `oidc_disabled`,
|
|
31
|
+
`temporarily_unavailable`) or the SDK's (`not_in_app`, `unreachable`,
|
|
32
|
+
`invalid_response`, `http_error`).
|
|
33
|
+
- Class `UnfazedOIDC`, constants `STATE_TOKEN_ENV_VAR` and `OIDC_TOKEN_PATH`,
|
|
34
|
+
types `OIDCErrorCode`, `OIDCOptions`, `OIDCToken`, `OIDCTokenOptions`,
|
|
35
|
+
`OIDCTransport`, `FetchWithTokenOptions` and `TokenFetch`.
|
|
36
|
+
|
|
37
|
+
**Changed**
|
|
38
|
+
|
|
39
|
+
- `new Unfazed()` with no API token and no login no longer throws, since an
|
|
40
|
+
app has only its state token and `oidc` needs nothing more. The first
|
|
41
|
+
method that calls the API, or reading `unfazed.client`, throws the same
|
|
42
|
+
`UnfazedAuthError` with the same message. `new UnfazedClient()` still
|
|
43
|
+
throws at once.
|
|
44
|
+
- `unfazed.client` is a getter (read-only, as before).
|
|
45
|
+
- `help("token")` finds `oidc.token`: a method name matches after a `.` too.
|
|
46
|
+
|
|
3
47
|
## 0.8.1
|
|
4
48
|
|
|
5
49
|
Follows the platform's final `waitForDeployment` and timings contract
|
package/README.md
CHANGED
|
@@ -25,6 +25,9 @@ SDK: one script. Three requests. You read the four lines you cared about.
|
|
|
25
25
|
- `npx unfazed workflow inspect runs --app <alias>`: Workflow SDK's CLI and web
|
|
26
26
|
UI read your app's runs through the platform, from any directory
|
|
27
27
|
([details](#inspect-runs-from-your-terminal)).
|
|
28
|
+
- `oidcToken(audience)`: inside an app, a short-lived OIDC token the platform
|
|
29
|
+
issues to it, for services that take one instead of an API key
|
|
30
|
+
([details](#workload-identity-oidc-tokens-for-your-app)).
|
|
28
31
|
- Strict TypeScript, generated from the platform's own GraphQL schema.
|
|
29
32
|
- Bun-first, runs unchanged on Node >= 20 and Deno.
|
|
30
33
|
|
|
@@ -57,6 +60,12 @@ const unfazed = new Unfazed(); // UNFZ_API_TOKEN, else your `npx unfaze
|
|
|
57
60
|
const unfazed = new Unfazed({ token }); // or pass a token explicitly
|
|
58
61
|
```
|
|
59
62
|
|
|
63
|
+
**An app running on unfazed** needs neither for its own OIDC tokens
|
|
64
|
+
(`unfazed.oidc`, `oidcToken`): they use the `UNFZ_STATE_TOKEN` the platform
|
|
65
|
+
sets in each of its VMs ([details](#workload-identity-oidc-tokens-for-your-app)).
|
|
66
|
+
`new Unfazed()` without a credential is made all the same; a method that calls
|
|
67
|
+
the API then throws `UnfazedAuthError`, saying how to sign in.
|
|
68
|
+
|
|
60
69
|
Both endpoint and token are configurable. `UNFZ_API_URL` overrides the endpoint
|
|
61
70
|
(default `https://unfazed.dev/query`, or the stored login's); `baseUrl` is a
|
|
62
71
|
convenience for self-hosted control planes where only the host differs.
|
|
@@ -203,7 +212,8 @@ Errors are typed and specific: `UnfazedAuthError`, `UnfazedHTTPError` (carries
|
|
|
203
212
|
`status`/`body`), `UnfazedGraphQLError` (carries `errors` **and any partial
|
|
204
213
|
`data`**), `UnfazedTimeoutError`, `UnfazedDeployError`, `UnfazedConflictError`,
|
|
205
214
|
`UnfazedNotFoundError`, `UnfazedWriteError` (carries the write's `result` and
|
|
206
|
-
the `failed` paths)
|
|
215
|
+
the `failed` paths), `UnfazedOIDCError` (carries `code`, `status`,
|
|
216
|
+
`description`, `retryable`). All extend `UnfazedError`.
|
|
207
217
|
|
|
208
218
|
### 2. Workflow layer — `Unfazed`
|
|
209
219
|
|
|
@@ -307,6 +317,13 @@ The chains an agent actually performs. Reach `client` from it any time:
|
|
|
307
317
|
| `runConsole(id, { tailBytes? })` | live tail while RUNNING |
|
|
308
318
|
| `runTranscript(id, { tailBytes? })` | the stored transcript once settled |
|
|
309
319
|
|
|
320
|
+
**Workload identity** (the app's own OIDC tokens; no API token needed)
|
|
321
|
+
| Method | Notes |
|
|
322
|
+
| --- | --- |
|
|
323
|
+
| `oidc.token(audience, { ttlSeconds?, signal? })` | a JWT for `audience`, cached until 80% of its lifetime |
|
|
324
|
+
| `oidc.tokenResponse(audience, opts?)` | the same, as `{ token, expiresAt }` |
|
|
325
|
+
| `oidc.fetchWithToken(audience, { ttlSeconds?, fetch? })` | a `fetch` that sends `Authorization: Bearer <token>` |
|
|
326
|
+
|
|
310
327
|
## The two things worth knowing
|
|
311
328
|
|
|
312
329
|
### `writeFiles` is one request and one commit
|
|
@@ -593,6 +610,77 @@ So an agent working inside a run can create workspaces, deploy, and read logs
|
|
|
593
610
|
through this same SDK with zero configuration and no credential ever written
|
|
594
611
|
into the prompt.
|
|
595
612
|
|
|
613
|
+
## Workload identity: OIDC tokens for your app
|
|
614
|
+
|
|
615
|
+
Code running in an app on unfazed (a service or a cron job) can prove which
|
|
616
|
+
app it is. The platform issues it short-lived OIDC tokens (JWTs) for any
|
|
617
|
+
audience, and a service that trusts the issuer `https://unfazed.dev/oidc`
|
|
618
|
+
takes one instead of a long-lived API key kept in the app's env.
|
|
619
|
+
|
|
620
|
+
```ts
|
|
621
|
+
import { oidcToken } from "unfazed";
|
|
622
|
+
|
|
623
|
+
const jwt = await oidcToken("https://api.example.com"); // cached and refreshed for you
|
|
624
|
+
const res = await fetch("https://api.example.com/v1/things", {
|
|
625
|
+
headers: { Authorization: `Bearer ${jwt}` },
|
|
626
|
+
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
A client that takes a `fetch` (here the OpenAI SDK, against an
|
|
630
|
+
OpenAI-compatible endpoint) gets one that puts a fresh token on every request:
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
import OpenAI from "openai";
|
|
634
|
+
import { oidc } from "unfazed";
|
|
635
|
+
|
|
636
|
+
const openai = new OpenAI({
|
|
637
|
+
baseURL: process.env.PRIVATEINFERENCE_BASE_URL,
|
|
638
|
+
apiKey: "unused", // the token replaces it
|
|
639
|
+
fetch: oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),
|
|
640
|
+
});
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
`oidc` is the process's own; an instance has the same methods as
|
|
644
|
+
`unfazed.oidc` (`token`, `tokenResponse` for `{ token, expiresAt }`,
|
|
645
|
+
`fetchWithToken`), asking the platform its client talks to (`baseUrl`,
|
|
646
|
+
`endpoint`, `UNFZ_API_URL`; default `https://unfazed.dev`).
|
|
647
|
+
|
|
648
|
+
- **Credential:** only the app's own `UNFZ_STATE_TOKEN`, which the platform
|
|
649
|
+
sets in each of its VMs. No `UNFZ_API_TOKEN` and no login: `new Unfazed()`
|
|
650
|
+
in an app works for `oidc` alone. Outside an app the call throws
|
|
651
|
+
`UnfazedOIDCError` with `code: "not_in_app"` ("not running inside an unfazed
|
|
652
|
+
app"), before any request.
|
|
653
|
+
- **The token:** issuer `https://unfazed.dev/oidc`; claims include `sub`,
|
|
654
|
+
`aud`, `owner_id`, `project_id`, `app_id`, `alias`, `environment` and
|
|
655
|
+
`deployment_id`. `ttlSeconds` (60 to 3600) asks for a lifetime; without it
|
|
656
|
+
the platform picks. An audience is 1 to 256 printable ASCII characters
|
|
657
|
+
without whitespace, checked before any request (`fetchWithToken` checks it
|
|
658
|
+
when called, so an unset variable fails at startup).
|
|
659
|
+
- **Cache:** per (audience, ttl), reused until 80% of the token's lifetime has
|
|
660
|
+
passed. The lifetime is the JWT's own (`exp - iat`), so a VM clock that is
|
|
661
|
+
off does not shorten or stretch it. Concurrent calls for one pair make one
|
|
662
|
+
request; the cache is the process's (an instance with its own `fetch` keeps
|
|
663
|
+
its own). `{ signal }` stops a call's wait, not the request others share.
|
|
664
|
+
- **Retries:** a 503 (or a gateway's 502/504) and network failures are retried
|
|
665
|
+
up to 3 times (200, 400, 800 ms); a 429 too, after its `Retry-After` (up to
|
|
666
|
+
10 s; a longer one throws at once, with `retryAfterMs`). No other 4xx is
|
|
667
|
+
retried. If a refresh still fails that way while the token it replaces is
|
|
668
|
+
valid, that token comes back.
|
|
669
|
+
|
|
670
|
+
Errors are `UnfazedOIDCError`, with the platform's `error` as `code` and its
|
|
671
|
+
`error_description` as `description`, plus `status` and `retryable`:
|
|
672
|
+
|
|
673
|
+
| `code` | Status | Means |
|
|
674
|
+
| --- | --- | --- |
|
|
675
|
+
| `invalid_token` | 401 | the platform refused `UNFZ_STATE_TOKEN` |
|
|
676
|
+
| `invalid_audience` | 400 | the audience is malformed or not allowed (also refused locally) |
|
|
677
|
+
| `invalid_request` | 400 | a malformed request, `ttlSeconds` out of range among them |
|
|
678
|
+
| `rate_limited` | 429 | too many requests, after the retries |
|
|
679
|
+
| `oidc_disabled` | 404 | the platform issues no OIDC tokens to this app |
|
|
680
|
+
| `temporarily_unavailable` | 503 | after the retries |
|
|
681
|
+
| `not_in_app` | | `UNFZ_STATE_TOKEN` is not set |
|
|
682
|
+
| `unreachable` | | network failures outlasted the retries |
|
|
683
|
+
|
|
596
684
|
## Durable workflows: `unfazed/workflow`
|
|
597
685
|
|
|
598
686
|
[Workflow SDK](https://workflow-sdk.dev) runs durable functions: a
|
|
@@ -956,7 +1044,7 @@ After bumping the `@workflow/world-postgres` devDependency, run
|
|
|
956
1044
|
`REFERENCE.md` is what `help()` returns: after editing it run
|
|
957
1045
|
`bun run embed-reference` (`bun run check` fails while
|
|
958
1046
|
`src/reference.generated.ts` is stale). `test/reference.test.ts` fails when a
|
|
959
|
-
public method of `Unfazed` is missing from it.
|
|
1047
|
+
public method of `Unfazed` (or of `unfazed.oidc`) is missing from it.
|
|
960
1048
|
|
|
961
1049
|
### Regenerating from the schema
|
|
962
1050
|
|
package/REFERENCE.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
Every method of `Unfazed`, with its signature, in one page. In a script:
|
|
4
4
|
`import { Unfazed, help } from "unfazed"; const unfazed = new Unfazed();`
|
|
5
5
|
(run_script injects the credential; elsewhere `UNFZ_API_TOKEN` or
|
|
6
|
-
`npx unfazed login`). `help()` returns this page;
|
|
7
|
-
entry. Methods are async and throw an
|
|
6
|
+
`npx unfazed login`; `oidc` needs neither). `help()` returns this page;
|
|
7
|
+
`help("writeFiles")` one entry. Methods are async and throw an
|
|
8
|
+
`UnfazedError` subclass on failure.
|
|
8
9
|
Every method takes an optional last `options` with `signal` (and, for single
|
|
9
10
|
requests, `timeoutMs`). The names follow the MCP tools: `listCommits` is
|
|
10
11
|
`list_commits`, `waitForEvents` is `wait_for_events`.
|
|
@@ -291,6 +292,50 @@ Answers a BLOCKED run's question.
|
|
|
291
292
|
|
|
292
293
|
### `listRunHarnesses(): Promise<RunHarness[]>`
|
|
293
294
|
|
|
295
|
+
## Workload identity
|
|
296
|
+
|
|
297
|
+
OIDC tokens the platform issues to the app the code runs in (a service or a
|
|
298
|
+
cron job): short-lived JWTs for one audience, from the issuer
|
|
299
|
+
`https://unfazed.dev/oidc`, which a service that trusts it takes instead of
|
|
300
|
+
an API key. Claims include `sub`, `aud`, `owner_id`, `project_id`, `app_id`,
|
|
301
|
+
`alias`, `environment` and `deployment_id`. The only credential is the app's
|
|
302
|
+
`UNFZ_STATE_TOKEN`, set in each of its VMs: no API token, so a bare
|
|
303
|
+
`new Unfazed()` works in an app for this. Without an instance:
|
|
304
|
+
`import { oidc, oidcToken } from "unfazed"` (`oidcToken(audience)` is
|
|
305
|
+
`oidc.token(audience)`).
|
|
306
|
+
|
|
307
|
+
### `oidc.token(audience: string, options?: { ttlSeconds?, signal? }): Promise<string>`
|
|
308
|
+
A JWT for `audience` (1 to 256 printable ASCII characters, no whitespace);
|
|
309
|
+
`ttlSeconds` from 60 to 3600, else the platform's default. Cached per
|
|
310
|
+
(audience, ttl) until 80% of its lifetime; concurrent calls share one
|
|
311
|
+
request. 503s, network failures and 429s (after their `Retry-After`) are
|
|
312
|
+
retried up to 3 times; if a refresh still fails that way, the previous token
|
|
313
|
+
comes back while it is valid. Throws `UnfazedOIDCError` (`code`, `status`,
|
|
314
|
+
`description`, `retryable`, `retryAfterMs`): `not_in_app` (no
|
|
315
|
+
`UNFZ_STATE_TOKEN`), `invalid_audience`, `invalid_request`, `invalid_token`,
|
|
316
|
+
`rate_limited`, `oidc_disabled`, `temporarily_unavailable`, `unreachable`.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const jwt = await unfazed.oidc.token("https://api.example.com", { ttlSeconds: 300 });
|
|
320
|
+
await fetch("https://api.example.com/v1/things", { headers: { Authorization: `Bearer ${jwt}` } });
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### `oidc.tokenResponse(audience: string, options?: { ttlSeconds?, signal? }): Promise<{ token, expiresAt: Date }>`
|
|
324
|
+
`oidc.token` with the token's expiry.
|
|
325
|
+
|
|
326
|
+
### `oidc.fetchWithToken(audience: string, options?: { ttlSeconds?, fetch? }): fetch`
|
|
327
|
+
A `fetch` that sends each request with `Authorization: Bearer <token>`
|
|
328
|
+
(replacing one already there), everything else as given: for a client that
|
|
329
|
+
takes a `fetch`. An invalid audience (an unset variable) throws at once.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
const openai = new OpenAI({
|
|
333
|
+
baseURL: process.env.PRIVATEINFERENCE_BASE_URL,
|
|
334
|
+
apiKey: "unused",
|
|
335
|
+
fetch: unfazed.oidc.fetchWithToken(process.env.PRIVATEINFERENCE_AUDIENCE),
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
294
339
|
## Anything else
|
|
295
340
|
|
|
296
341
|
`unfazed.client` is the typed GraphQL transport over the same credential:
|
package/dist/cli/login.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type StoredCredentials } from "../credentials.js";
|
|
2
2
|
import { UnfazedError } from "../errors.js";
|
|
3
|
-
export declare const USER_AGENT = "unfazed-cli/0.
|
|
3
|
+
export declare const USER_AGENT = "unfazed-cli/0.9.0 (+https://unfazed.dev/auth.md)";
|
|
4
4
|
/** What a login asks for: reading and deploying, like an MCP client. */
|
|
5
5
|
export declare const DEFAULT_SCOPE = "unfz:read unfz:deploy";
|
|
6
6
|
export declare const CLIENT_NAME = "unfazed CLI";
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** SDK version, sent as the User-Agent. Keep in sync with package.json. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.9.0";
|
|
3
3
|
/**
|
|
4
4
|
* Default GraphQL endpoint.
|
|
5
5
|
*
|
|
@@ -153,3 +153,23 @@ export declare class UnfazedClient {
|
|
|
153
153
|
toString(): string;
|
|
154
154
|
}, variables?: unknown, options?: RequestOptions): Promise<TResult>;
|
|
155
155
|
}
|
|
156
|
+
/**
|
|
157
|
+
* The platform origin an endpoint is served from:
|
|
158
|
+
* `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`
|
|
159
|
+
* (a control plane mounted under a prefix) is kept.
|
|
160
|
+
*/
|
|
161
|
+
export declare function platformBaseUrl(endpoint: string): string;
|
|
162
|
+
/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */
|
|
163
|
+
export declare function configuredEndpoint(options: Pick<ClientOptions, "endpoint" | "baseUrl">, env: Record<string, string | undefined>): string | undefined;
|
|
164
|
+
export declare function processEnv(): Record<string, string | undefined>;
|
|
165
|
+
/**
|
|
166
|
+
* Settles with `promise`, or rejects the moment `signal` aborts.
|
|
167
|
+
*
|
|
168
|
+
* A global `fetch` honours the signal for headers and body alike, but an
|
|
169
|
+
* injected {@link FetchLike} — a proxy, an instrumented transport, a test
|
|
170
|
+
* double — need not. Racing the signal makes the timeout an upper bound on the
|
|
171
|
+
* whole request regardless of what the transport does with it. The loser's
|
|
172
|
+
* rejection is swallowed by the already-settled promise rather than surfacing
|
|
173
|
+
* as an unhandled rejection.
|
|
174
|
+
*/
|
|
175
|
+
export declare function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T>;
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { LOGIN_COMMAND, openStoredLogin } from "./credentials.js";
|
|
2
2
|
import { UnfazedAuthError, UnfazedConfigError, UnfazedGraphQLError, UnfazedHTTPError, UnfazedTimeoutError, } from "./errors.js";
|
|
3
3
|
/** SDK version, sent as the User-Agent. Keep in sync with package.json. */
|
|
4
|
-
export const VERSION = "0.
|
|
4
|
+
export const VERSION = "0.9.0";
|
|
5
5
|
/**
|
|
6
6
|
* Default GraphQL endpoint.
|
|
7
7
|
*
|
|
@@ -198,8 +198,16 @@ export class UnfazedClient {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* The platform origin an endpoint is served from:
|
|
203
|
+
* `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`
|
|
204
|
+
* (a control plane mounted under a prefix) is kept.
|
|
205
|
+
*/
|
|
206
|
+
export function platformBaseUrl(endpoint) {
|
|
207
|
+
return trimSlash(endpoint).replace(/\/(?:query|graphql)$/, "");
|
|
208
|
+
}
|
|
201
209
|
/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */
|
|
202
|
-
function configuredEndpoint(options, env) {
|
|
210
|
+
export function configuredEndpoint(options, env) {
|
|
203
211
|
if (options.endpoint)
|
|
204
212
|
return options.endpoint;
|
|
205
213
|
if (options.baseUrl)
|
|
@@ -222,7 +230,7 @@ function noTokenMessage(endpoint, otherLogin) {
|
|
|
222
230
|
return (`unfazed: not signed in. Run \`${LOGIN_COMMAND}\` to sign in with your account, ` +
|
|
223
231
|
`or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`);
|
|
224
232
|
}
|
|
225
|
-
function processEnv() {
|
|
233
|
+
export function processEnv() {
|
|
226
234
|
// Guarded: the SDK must also work in a browser or worker, where there is no
|
|
227
235
|
// `process` at all.
|
|
228
236
|
return globalThis.process?.env ?? {};
|
|
@@ -248,7 +256,7 @@ function abortError() {
|
|
|
248
256
|
* rejection is swallowed by the already-settled promise rather than surfacing
|
|
249
257
|
* as an unhandled rejection.
|
|
250
258
|
*/
|
|
251
|
-
function untilAborted(promise, signal) {
|
|
259
|
+
export function untilAborted(promise, signal) {
|
|
252
260
|
if (!signal)
|
|
253
261
|
return promise;
|
|
254
262
|
if (signal.aborted)
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { LOGIN_COMMAND, openStoredLogin, type StoredLogin } from \"./credentials.js\";\nimport {\n UnfazedAuthError,\n UnfazedConfigError,\n UnfazedGraphQLError,\n UnfazedHTTPError,\n UnfazedTimeoutError,\n type GraphQLResponseError,\n} from \"./errors.js\";\n\n/** SDK version, sent as the User-Agent. Keep in sync with package.json. */\nexport const VERSION = \"0.8.1\";\n\n/**\n * Default GraphQL endpoint.\n *\n * Note this is the FULL endpoint, not an origin: the platform's own documented\n * convention (`UNFZ_API_URL=\"https://unfazed.dev/query\"`, and cmd/showcase-deploy\n * in the unfazed repo) treats the /query URL as the configurable unit, so the\n * SDK matches it rather than inventing a base-URL convention.\n */\nexport const DEFAULT_ENDPOINT = \"https://unfazed.dev/query\";\n\n/** Platform origin, used to derive the endpoint when only `baseUrl` is given. */\nexport const DEFAULT_BASE_URL = \"https://unfazed.dev\";\n\n/** Path of the GraphQL endpoint under an origin. */\nexport const GRAPHQL_PATH = \"/query\";\n\n/**\n * Environment variable holding the bearer token.\n *\n * This is the platform's documented name (docs/agent-deployments.md,\n * internal/agentdocs/reference.md). It accepts either an operator API token or\n * an agent access token from POST /agents/register — /query takes both as\n * plain bearers.\n */\nexport const TOKEN_ENV_VAR = \"UNFZ_API_TOKEN\";\n\n/** Environment variable holding the full GraphQL endpoint URL. */\nexport const ENDPOINT_ENV_VAR = \"UNFZ_API_URL\";\n\n/** Minimal structural type of the `fetch` the transport needs. */\nexport type FetchLike = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\nexport interface ClientOptions {\n /**\n * Bearer token. Falls back to `UNFZ_API_TOKEN`, then to the login\n * `npx unfazed login` stored (see {@link ClientOptions.login}). An agent gets\n * one from https://unfazed.dev/auth.md — the same token the MCP server uses.\n */\n token?: string;\n /**\n * Use the stored login of `npx unfazed login`\n * (`${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json`) when no token\n * is passed and `UNFZ_API_TOKEN` is unset. Its access token is refreshed\n * before it expires and when the platform refuses it. Default `true`; it is\n * only used for the endpoint it was issued by, and is the endpoint when none\n * is configured.\n */\n login?: boolean;\n /**\n * Where `UNFZ_API_TOKEN`, `UNFZ_API_URL` and the stored login's location\n * (`XDG_CONFIG_HOME`, `HOME`) are read from. Default: `process.env`.\n */\n env?: Record<string, string | undefined>;\n /**\n * Full GraphQL endpoint URL. Wins over `baseUrl`. Falls back to\n * `UNFZ_API_URL`, then the stored login's endpoint, then\n * {@link DEFAULT_ENDPOINT}.\n */\n endpoint?: string;\n /**\n * Platform origin; `/query` is appended. Convenience for self-hosted or\n * staging control planes where only the host differs.\n */\n baseUrl?: string;\n /** Extra headers merged into every request. Authorization always wins. */\n headers?: Record<string, string>;\n /**\n * Per-request timeout in ms. Default 60_000. Pass 0 to disable.\n *\n * Bounds the WHOLE request — connect, headers and reading the response body —\n * so a proxy that answers with headers and then stalls still fails on time.\n */\n timeoutMs?: number;\n /** Injection point for tests and instrumented/proxied transports. */\n fetch?: FetchLike;\n /** Overrides the default `unfazed-sdk/<version>` User-Agent. */\n userAgent?: string;\n}\n\n/**\n * A GraphQL document.\n *\n * Structural rather than `string & {...}` on purpose: graphql-codegen emits\n * `TypedDocumentString`, a String SUBCLASS, which is not assignable to the\n * primitive `string`. Requiring only `toString()` plus the optional phantom\n * `__apiType` lets three things through with full inference:\n *\n * - generated `TypedDocumentString` documents (types inferred),\n * - documents assembled at runtime, like the batched writeFiles mutation,\n * - plain template-literal strings a caller wrote by hand.\n */\nexport interface GraphQLDocument<TResult, TVariables> {\n /** Phantom type carrier. Never called; present only to bind the generics. */\n __apiType?: (variables: TVariables) => TResult;\n toString(): string;\n}\n\n/** Result type carried by a typed document. */\nexport type ResultOf<T> = T extends GraphQLDocument<infer R, infer _V> ? R : unknown;\n\n/** Variables type carried by a typed document. */\nexport type VariablesOf<T> = T extends GraphQLDocument<infer _R, infer V> ? V : never;\n\n/** Per-request knobs. */\nexport interface RequestOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n operationName?: string;\n}\n\n/**\n * Layer 1 — a typed, dependency-free GraphQL transport over `fetch`.\n *\n * This is the SDK's entire networking surface. It understands auth, timeouts\n * and the GraphQL error envelope, and nothing about deployments. The workflow\n * layer ({@link import(\"./unfazed.js\").Unfazed}) is built strictly on top of\n * it, so you can always drop down here for any operation the workflow layer\n * does not wrap — the schema has ~660 fields and the workflow layer\n * deliberately covers only the agent-facing ones.\n *\n * ```ts\n * const client = new UnfazedClient(); // UNFZ_API_TOKEN, else `npx unfazed login`\n * const data = await client.query<{ whoami: { name: string } }, never>(\n * `query { whoami { name } }`,\n * );\n * ```\n */\nexport class UnfazedClient {\n /** Resolved GraphQL endpoint URL. */\n readonly endpoint: string;\n\n /** A fixed bearer token, or the stored login handing out refreshed ones. */\n readonly #auth: { token: string } | { login: StoredLogin };\n readonly #headers: Record<string, string>;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n\n constructor(options: ClientOptions = {}) {\n const env = options.env ?? processEnv();\n\n const injected = options.fetch;\n if (injected) {\n this.#fetch = injected;\n } else {\n const globalFetch = (globalThis as { fetch?: unknown }).fetch;\n if (typeof globalFetch !== \"function\") {\n throw new UnfazedConfigError(\n \"unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch } to the client.\",\n );\n }\n this.#fetch = globalFetch.bind(globalThis) as FetchLike;\n }\n\n const ua = options.userAgent ?? `unfazed-sdk/${VERSION}`;\n\n const configured = configuredEndpoint(options, env);\n const token = options.token ?? env[TOKEN_ENV_VAR];\n if (token?.trim()) {\n this.#auth = { token: token.trim() };\n this.endpoint = configured ?? DEFAULT_ENDPOINT;\n } else {\n const stored =\n options.login === false\n ? {}\n : openStoredLogin({\n env,\n ...(configured ? { endpoint: configured } : {}),\n fetch: this.#fetch,\n // The edge refuses some generic agents; always name ourselves.\n userAgent: ua || `unfazed-sdk/${VERSION}`,\n });\n if (!stored.login) {\n throw new UnfazedAuthError(noTokenMessage(configured, stored.other?.endpoint));\n }\n this.#auth = { login: stored.login };\n this.endpoint = stored.login.endpoint;\n }\n\n this.#headers = { ...(options.headers ?? {}) };\n this.#timeoutMs = options.timeoutMs ?? 60_000;\n if (ua) this.#headers[\"User-Agent\"] = ua;\n }\n\n /**\n * Where the bearer token comes from: `\"token\"` (passed or\n * `UNFZ_API_TOKEN`) or `\"login\"` (the stored login of `npx unfazed login`).\n */\n get authSource(): \"token\" | \"login\" {\n return \"login\" in this.#auth ? \"login\" : \"token\";\n }\n\n /** Runs a query document. Identical wire behaviour to {@link mutate}. */\n query<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * Runs a mutation document. Distinct from {@link query} for call-site\n * readability only — GraphQL over HTTP POST makes no distinction.\n */\n mutate<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * The single code path behind {@link query} and {@link mutate}.\n *\n * Throws {@link UnfazedHTTPError} on non-2xx, {@link UnfazedGraphQLError}\n * when the body carries `errors`, {@link UnfazedTimeoutError} on timeout or\n * abort. Resolves to the `data` object on success.\n */\n async request<TResult = unknown>(\n document: string | { toString(): string },\n variables?: unknown,\n options: RequestOptions = {},\n ): Promise<TResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n\n // `document` may be a TypedDocumentString (a String object). JSON.stringify\n // would serialise that fine, but String(...) keeps the wire body a plain\n // primitive regardless of how the caller obtained the document.\n const body = JSON.stringify({\n query: String(document),\n ...(variables === undefined ? {} : { variables }),\n ...(options.operationName ? { operationName: options.operationName } : {}),\n });\n\n let token = await this.#token();\n let { response, text } = await this.#send(body, token, timeoutMs, options.signal);\n // A stored login's access token can be refused before its recorded\n // expiry (the clock, a sign-out elsewhere): refresh once and retry. A\n // fixed token has nothing to refresh.\n if (response.status === 401 && \"login\" in this.#auth) {\n token = await this.#auth.login.refresh(token);\n ({ response, text } = await this.#send(body, token, timeoutMs, options.signal));\n }\n\n if (!response.ok) {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n let parsed: { data?: TResult; errors?: GraphQLResponseError[] };\n try {\n parsed = JSON.parse(text) as typeof parsed;\n } catch {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n // A GraphQL 200 can still be a failure, and can be a PARTIAL failure: the\n // batched writeFiles document resolves each aliased field independently.\n // Attaching `data` to the error is what lets a caller see which writes\n // landed before the failing one.\n if (parsed.errors?.length) {\n throw new UnfazedGraphQLError(parsed.errors, parsed.data);\n }\n if (parsed.data === undefined || parsed.data === null) {\n throw new UnfazedGraphQLError(\n [{ message: \"response contained neither data nor errors\" }],\n undefined,\n );\n }\n return parsed.data;\n }\n\n #token(): Promise<string> {\n return \"login\" in this.#auth ? this.#auth.login.token() : Promise.resolve(this.#auth.token);\n }\n\n async #send(\n body: string,\n token: string,\n timeoutMs: number,\n external: AbortSignal | undefined,\n ): Promise<{ response: Awaited<ReturnType<FetchLike>>; text: string }> {\n const { signal, cleanup, didTimeout } = makeSignal(timeoutMs, external);\n // The deadline covers BOTH phases — headers AND body. A proxy that answers\n // with headers and then stalls the body is a real failure mode, and\n // stopping the clock at the end of `fetch()` would leave `response.text()`\n // hanging forever. `untilAborted` additionally races the signal itself, so\n // an injected transport that ignores `signal` still cannot outlive the\n // timeout.\n try {\n const response = await untilAborted(\n this.#fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n ...this.#headers,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body,\n ...(signal ? { signal } : {}),\n }),\n signal,\n );\n const text = await untilAborted(response.text(), signal);\n return { response, text };\n } catch (cause) {\n if (didTimeout()) {\n throw new UnfazedTimeoutError(`unfazed: request timed out after ${timeoutMs}ms`, { cause });\n }\n if (isAbort(cause)) {\n throw new UnfazedTimeoutError(\"unfazed: request aborted\", { cause });\n }\n throw cause;\n } finally {\n cleanup();\n }\n }\n}\n\n/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */\nfunction configuredEndpoint(options: ClientOptions, env: Record<string, string | undefined>): string | undefined {\n if (options.endpoint) return options.endpoint;\n if (options.baseUrl) return trimSlash(options.baseUrl) + GRAPHQL_PATH;\n const fromEnv = env[ENDPOINT_ENV_VAR];\n // UNFZ_API_URL is documented as the full /query URL and is used verbatim.\n // Tolerate a bare origin anyway, since that is the obvious mistake and\n // guessing right costs nothing.\n if (fromEnv) {\n const trimmed = trimSlash(fromEnv);\n return /\\/query$|\\/graphql$/.test(trimmed) ? trimmed : trimmed + GRAPHQL_PATH;\n }\n return undefined;\n}\n\nfunction noTokenMessage(endpoint: string | undefined, otherLogin: string | undefined): string {\n if (endpoint && otherLogin) {\n return (\n `unfazed: no API token for ${endpoint}: the stored login is for ${otherLogin}. ` +\n `Run \\`${ENDPOINT_ENV_VAR}=${endpoint} ${LOGIN_COMMAND}\\`, or pass { token } / set ${TOKEN_ENV_VAR}.`\n );\n }\n return (\n `unfazed: not signed in. Run \\`${LOGIN_COMMAND}\\` to sign in with your account, ` +\n `or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`\n );\n}\n\nfunction processEnv(): Record<string, string | undefined> {\n // Guarded: the SDK must also work in a browser or worker, where there is no\n // `process` at all.\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};\n}\n\nfunction trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction isAbort(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"AbortError\";\n}\n\nfunction abortError(): Error {\n const e = new Error(\"The operation was aborted.\");\n e.name = \"AbortError\";\n return e;\n}\n\n/**\n * Settles with `promise`, or rejects the moment `signal` aborts.\n *\n * A global `fetch` honours the signal for headers and body alike, but an\n * injected {@link FetchLike} — a proxy, an instrumented transport, a test\n * double — need not. Racing the signal makes the timeout an upper bound on the\n * whole request regardless of what the transport does with it. The loser's\n * rejection is swallowed by the already-settled promise rather than surfacing\n * as an unhandled rejection.\n */\nfunction untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener(\"abort\", onAbort, { once: true });\n const done = () => signal.removeEventListener(\"abort\", onAbort);\n promise.then(\n (value) => {\n done();\n resolve(value);\n },\n (error: unknown) => {\n done();\n reject(error as Error);\n },\n );\n });\n}\n\n/**\n * Combines the request timeout with any caller-supplied signal.\n *\n * `didTimeout` is tracked separately from the abort so a caller's own\n * cancellation is not mislabelled as a timeout in the thrown message.\n */\nfunction makeSignal(\n timeoutMs: number,\n external?: AbortSignal,\n): { signal?: AbortSignal; cleanup: () => void; didTimeout: () => boolean } {\n if (!timeoutMs && !external) return { cleanup: () => {}, didTimeout: () => false };\n if (typeof AbortController !== \"function\") {\n return {\n ...(external ? { signal: external } : {}),\n cleanup: () => {},\n didTimeout: () => false,\n };\n }\n\n const controller = new AbortController();\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs);\n }\n\n const onExternalAbort = () => controller.abort();\n if (external) {\n if (external.aborted) controller.abort();\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", onExternalAbort);\n },\n didTimeout: () => timedOut,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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","sourcesContent":["import { LOGIN_COMMAND, openStoredLogin, type StoredLogin } from \"./credentials.js\";\nimport {\n UnfazedAuthError,\n UnfazedConfigError,\n UnfazedGraphQLError,\n UnfazedHTTPError,\n UnfazedTimeoutError,\n type GraphQLResponseError,\n} from \"./errors.js\";\n\n/** SDK version, sent as the User-Agent. Keep in sync with package.json. */\nexport const VERSION = \"0.9.0\";\n\n/**\n * Default GraphQL endpoint.\n *\n * Note this is the FULL endpoint, not an origin: the platform's own documented\n * convention (`UNFZ_API_URL=\"https://unfazed.dev/query\"`, and cmd/showcase-deploy\n * in the unfazed repo) treats the /query URL as the configurable unit, so the\n * SDK matches it rather than inventing a base-URL convention.\n */\nexport const DEFAULT_ENDPOINT = \"https://unfazed.dev/query\";\n\n/** Platform origin, used to derive the endpoint when only `baseUrl` is given. */\nexport const DEFAULT_BASE_URL = \"https://unfazed.dev\";\n\n/** Path of the GraphQL endpoint under an origin. */\nexport const GRAPHQL_PATH = \"/query\";\n\n/**\n * Environment variable holding the bearer token.\n *\n * This is the platform's documented name (docs/agent-deployments.md,\n * internal/agentdocs/reference.md). It accepts either an operator API token or\n * an agent access token from POST /agents/register — /query takes both as\n * plain bearers.\n */\nexport const TOKEN_ENV_VAR = \"UNFZ_API_TOKEN\";\n\n/** Environment variable holding the full GraphQL endpoint URL. */\nexport const ENDPOINT_ENV_VAR = \"UNFZ_API_URL\";\n\n/** Minimal structural type of the `fetch` the transport needs. */\nexport type FetchLike = (\n input: string,\n init: {\n method: string;\n headers: Record<string, string>;\n body: string;\n signal?: AbortSignal;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n text(): Promise<string>;\n}>;\n\nexport interface ClientOptions {\n /**\n * Bearer token. Falls back to `UNFZ_API_TOKEN`, then to the login\n * `npx unfazed login` stored (see {@link ClientOptions.login}). An agent gets\n * one from https://unfazed.dev/auth.md — the same token the MCP server uses.\n */\n token?: string;\n /**\n * Use the stored login of `npx unfazed login`\n * (`${XDG_CONFIG_HOME:-~/.config}/unfazed/credentials.json`) when no token\n * is passed and `UNFZ_API_TOKEN` is unset. Its access token is refreshed\n * before it expires and when the platform refuses it. Default `true`; it is\n * only used for the endpoint it was issued by, and is the endpoint when none\n * is configured.\n */\n login?: boolean;\n /**\n * Where `UNFZ_API_TOKEN`, `UNFZ_API_URL` and the stored login's location\n * (`XDG_CONFIG_HOME`, `HOME`) are read from. Default: `process.env`.\n */\n env?: Record<string, string | undefined>;\n /**\n * Full GraphQL endpoint URL. Wins over `baseUrl`. Falls back to\n * `UNFZ_API_URL`, then the stored login's endpoint, then\n * {@link DEFAULT_ENDPOINT}.\n */\n endpoint?: string;\n /**\n * Platform origin; `/query` is appended. Convenience for self-hosted or\n * staging control planes where only the host differs.\n */\n baseUrl?: string;\n /** Extra headers merged into every request. Authorization always wins. */\n headers?: Record<string, string>;\n /**\n * Per-request timeout in ms. Default 60_000. Pass 0 to disable.\n *\n * Bounds the WHOLE request — connect, headers and reading the response body —\n * so a proxy that answers with headers and then stalls still fails on time.\n */\n timeoutMs?: number;\n /** Injection point for tests and instrumented/proxied transports. */\n fetch?: FetchLike;\n /** Overrides the default `unfazed-sdk/<version>` User-Agent. */\n userAgent?: string;\n}\n\n/**\n * A GraphQL document.\n *\n * Structural rather than `string & {...}` on purpose: graphql-codegen emits\n * `TypedDocumentString`, a String SUBCLASS, which is not assignable to the\n * primitive `string`. Requiring only `toString()` plus the optional phantom\n * `__apiType` lets three things through with full inference:\n *\n * - generated `TypedDocumentString` documents (types inferred),\n * - documents assembled at runtime, like the batched writeFiles mutation,\n * - plain template-literal strings a caller wrote by hand.\n */\nexport interface GraphQLDocument<TResult, TVariables> {\n /** Phantom type carrier. Never called; present only to bind the generics. */\n __apiType?: (variables: TVariables) => TResult;\n toString(): string;\n}\n\n/** Result type carried by a typed document. */\nexport type ResultOf<T> = T extends GraphQLDocument<infer R, infer _V> ? R : unknown;\n\n/** Variables type carried by a typed document. */\nexport type VariablesOf<T> = T extends GraphQLDocument<infer _R, infer V> ? V : never;\n\n/** Per-request knobs. */\nexport interface RequestOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n operationName?: string;\n}\n\n/**\n * Layer 1 — a typed, dependency-free GraphQL transport over `fetch`.\n *\n * This is the SDK's entire networking surface. It understands auth, timeouts\n * and the GraphQL error envelope, and nothing about deployments. The workflow\n * layer ({@link import(\"./unfazed.js\").Unfazed}) is built strictly on top of\n * it, so you can always drop down here for any operation the workflow layer\n * does not wrap — the schema has ~660 fields and the workflow layer\n * deliberately covers only the agent-facing ones.\n *\n * ```ts\n * const client = new UnfazedClient(); // UNFZ_API_TOKEN, else `npx unfazed login`\n * const data = await client.query<{ whoami: { name: string } }, never>(\n * `query { whoami { name } }`,\n * );\n * ```\n */\nexport class UnfazedClient {\n /** Resolved GraphQL endpoint URL. */\n readonly endpoint: string;\n\n /** A fixed bearer token, or the stored login handing out refreshed ones. */\n readonly #auth: { token: string } | { login: StoredLogin };\n readonly #headers: Record<string, string>;\n readonly #timeoutMs: number;\n readonly #fetch: FetchLike;\n\n constructor(options: ClientOptions = {}) {\n const env = options.env ?? processEnv();\n\n const injected = options.fetch;\n if (injected) {\n this.#fetch = injected;\n } else {\n const globalFetch = (globalThis as { fetch?: unknown }).fetch;\n if (typeof globalFetch !== \"function\") {\n throw new UnfazedConfigError(\n \"unfazed: no global fetch. Use Bun, Node >= 20, or pass { fetch } to the client.\",\n );\n }\n this.#fetch = globalFetch.bind(globalThis) as FetchLike;\n }\n\n const ua = options.userAgent ?? `unfazed-sdk/${VERSION}`;\n\n const configured = configuredEndpoint(options, env);\n const token = options.token ?? env[TOKEN_ENV_VAR];\n if (token?.trim()) {\n this.#auth = { token: token.trim() };\n this.endpoint = configured ?? DEFAULT_ENDPOINT;\n } else {\n const stored =\n options.login === false\n ? {}\n : openStoredLogin({\n env,\n ...(configured ? { endpoint: configured } : {}),\n fetch: this.#fetch,\n // The edge refuses some generic agents; always name ourselves.\n userAgent: ua || `unfazed-sdk/${VERSION}`,\n });\n if (!stored.login) {\n throw new UnfazedAuthError(noTokenMessage(configured, stored.other?.endpoint));\n }\n this.#auth = { login: stored.login };\n this.endpoint = stored.login.endpoint;\n }\n\n this.#headers = { ...(options.headers ?? {}) };\n this.#timeoutMs = options.timeoutMs ?? 60_000;\n if (ua) this.#headers[\"User-Agent\"] = ua;\n }\n\n /**\n * Where the bearer token comes from: `\"token\"` (passed or\n * `UNFZ_API_TOKEN`) or `\"login\"` (the stored login of `npx unfazed login`).\n */\n get authSource(): \"token\" | \"login\" {\n return \"login\" in this.#auth ? \"login\" : \"token\";\n }\n\n /** Runs a query document. Identical wire behaviour to {@link mutate}. */\n query<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * Runs a mutation document. Distinct from {@link query} for call-site\n * readability only — GraphQL over HTTP POST makes no distinction.\n */\n mutate<TResult, TVariables>(\n document: GraphQLDocument<TResult, TVariables>,\n variables?: TVariables,\n options?: RequestOptions,\n ): Promise<TResult> {\n return this.request(document, variables, options);\n }\n\n /**\n * The single code path behind {@link query} and {@link mutate}.\n *\n * Throws {@link UnfazedHTTPError} on non-2xx, {@link UnfazedGraphQLError}\n * when the body carries `errors`, {@link UnfazedTimeoutError} on timeout or\n * abort. Resolves to the `data` object on success.\n */\n async request<TResult = unknown>(\n document: string | { toString(): string },\n variables?: unknown,\n options: RequestOptions = {},\n ): Promise<TResult> {\n const timeoutMs = options.timeoutMs ?? this.#timeoutMs;\n\n // `document` may be a TypedDocumentString (a String object). JSON.stringify\n // would serialise that fine, but String(...) keeps the wire body a plain\n // primitive regardless of how the caller obtained the document.\n const body = JSON.stringify({\n query: String(document),\n ...(variables === undefined ? {} : { variables }),\n ...(options.operationName ? { operationName: options.operationName } : {}),\n });\n\n let token = await this.#token();\n let { response, text } = await this.#send(body, token, timeoutMs, options.signal);\n // A stored login's access token can be refused before its recorded\n // expiry (the clock, a sign-out elsewhere): refresh once and retry. A\n // fixed token has nothing to refresh.\n if (response.status === 401 && \"login\" in this.#auth) {\n token = await this.#auth.login.refresh(token);\n ({ response, text } = await this.#send(body, token, timeoutMs, options.signal));\n }\n\n if (!response.ok) {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n let parsed: { data?: TResult; errors?: GraphQLResponseError[] };\n try {\n parsed = JSON.parse(text) as typeof parsed;\n } catch {\n throw new UnfazedHTTPError(response.status, text, this.endpoint);\n }\n\n // A GraphQL 200 can still be a failure, and can be a PARTIAL failure: the\n // batched writeFiles document resolves each aliased field independently.\n // Attaching `data` to the error is what lets a caller see which writes\n // landed before the failing one.\n if (parsed.errors?.length) {\n throw new UnfazedGraphQLError(parsed.errors, parsed.data);\n }\n if (parsed.data === undefined || parsed.data === null) {\n throw new UnfazedGraphQLError(\n [{ message: \"response contained neither data nor errors\" }],\n undefined,\n );\n }\n return parsed.data;\n }\n\n #token(): Promise<string> {\n return \"login\" in this.#auth ? this.#auth.login.token() : Promise.resolve(this.#auth.token);\n }\n\n async #send(\n body: string,\n token: string,\n timeoutMs: number,\n external: AbortSignal | undefined,\n ): Promise<{ response: Awaited<ReturnType<FetchLike>>; text: string }> {\n const { signal, cleanup, didTimeout } = makeSignal(timeoutMs, external);\n // The deadline covers BOTH phases — headers AND body. A proxy that answers\n // with headers and then stalls the body is a real failure mode, and\n // stopping the clock at the end of `fetch()` would leave `response.text()`\n // hanging forever. `untilAborted` additionally races the signal itself, so\n // an injected transport that ignores `signal` still cannot outlive the\n // timeout.\n try {\n const response = await untilAborted(\n this.#fetch(this.endpoint, {\n method: \"POST\",\n headers: {\n ...this.#headers,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body,\n ...(signal ? { signal } : {}),\n }),\n signal,\n );\n const text = await untilAborted(response.text(), signal);\n return { response, text };\n } catch (cause) {\n if (didTimeout()) {\n throw new UnfazedTimeoutError(`unfazed: request timed out after ${timeoutMs}ms`, { cause });\n }\n if (isAbort(cause)) {\n throw new UnfazedTimeoutError(\"unfazed: request aborted\", { cause });\n }\n throw cause;\n } finally {\n cleanup();\n }\n }\n}\n\n/**\n * The platform origin an endpoint is served from:\n * `https://unfazed.dev/query` → `https://unfazed.dev`. A path before `/query`\n * (a control plane mounted under a prefix) is kept.\n */\nexport function platformBaseUrl(endpoint: string): string {\n return trimSlash(endpoint).replace(/\\/(?:query|graphql)$/, \"\");\n}\n\n/** The endpoint the options or `UNFZ_API_URL` name, or undefined for the default. */\nexport function configuredEndpoint(\n options: Pick<ClientOptions, \"endpoint\" | \"baseUrl\">,\n env: Record<string, string | undefined>,\n): string | undefined {\n if (options.endpoint) return options.endpoint;\n if (options.baseUrl) return trimSlash(options.baseUrl) + GRAPHQL_PATH;\n const fromEnv = env[ENDPOINT_ENV_VAR];\n // UNFZ_API_URL is documented as the full /query URL and is used verbatim.\n // Tolerate a bare origin anyway, since that is the obvious mistake and\n // guessing right costs nothing.\n if (fromEnv) {\n const trimmed = trimSlash(fromEnv);\n return /\\/query$|\\/graphql$/.test(trimmed) ? trimmed : trimmed + GRAPHQL_PATH;\n }\n return undefined;\n}\n\nfunction noTokenMessage(endpoint: string | undefined, otherLogin: string | undefined): string {\n if (endpoint && otherLogin) {\n return (\n `unfazed: no API token for ${endpoint}: the stored login is for ${otherLogin}. ` +\n `Run \\`${ENDPOINT_ENV_VAR}=${endpoint} ${LOGIN_COMMAND}\\`, or pass { token } / set ${TOKEN_ENV_VAR}.`\n );\n }\n return (\n `unfazed: not signed in. Run \\`${LOGIN_COMMAND}\\` to sign in with your account, ` +\n `or pass { token } / set ${TOKEN_ENV_VAR} (an agent token: ${DEFAULT_BASE_URL}/auth.md).`\n );\n}\n\nexport function processEnv(): Record<string, string | undefined> {\n // Guarded: the SDK must also work in a browser or worker, where there is no\n // `process` at all.\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env ?? {};\n}\n\nfunction trimSlash(u: string): string {\n return u.replace(/\\/+$/, \"\");\n}\n\nfunction isAbort(e: unknown): boolean {\n return typeof e === \"object\" && e !== null && (e as { name?: string }).name === \"AbortError\";\n}\n\nfunction abortError(): Error {\n const e = new Error(\"The operation was aborted.\");\n e.name = \"AbortError\";\n return e;\n}\n\n/**\n * Settles with `promise`, or rejects the moment `signal` aborts.\n *\n * A global `fetch` honours the signal for headers and body alike, but an\n * injected {@link FetchLike} — a proxy, an instrumented transport, a test\n * double — need not. Racing the signal makes the timeout an upper bound on the\n * whole request regardless of what the transport does with it. The loser's\n * rejection is swallowed by the already-settled promise rather than surfacing\n * as an unhandled rejection.\n */\nexport function untilAborted<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {\n if (!signal) return promise;\n if (signal.aborted) return Promise.reject(abortError());\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError());\n signal.addEventListener(\"abort\", onAbort, { once: true });\n const done = () => signal.removeEventListener(\"abort\", onAbort);\n promise.then(\n (value) => {\n done();\n resolve(value);\n },\n (error: unknown) => {\n done();\n reject(error as Error);\n },\n );\n });\n}\n\n/**\n * Combines the request timeout with any caller-supplied signal.\n *\n * `didTimeout` is tracked separately from the abort so a caller's own\n * cancellation is not mislabelled as a timeout in the thrown message.\n */\nfunction makeSignal(\n timeoutMs: number,\n external?: AbortSignal,\n): { signal?: AbortSignal; cleanup: () => void; didTimeout: () => boolean } {\n if (!timeoutMs && !external) return { cleanup: () => {}, didTimeout: () => false };\n if (typeof AbortController !== \"function\") {\n return {\n ...(external ? { signal: external } : {}),\n cleanup: () => {},\n didTimeout: () => false,\n };\n }\n\n const controller = new AbortController();\n let timedOut = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n if (timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n controller.abort();\n }, timeoutMs);\n }\n\n const onExternalAbort = () => controller.abort();\n if (external) {\n if (external.aborted) controller.abort();\n else external.addEventListener(\"abort\", onExternalAbort, { once: true });\n }\n\n return {\n signal: controller.signal,\n cleanup: () => {\n if (timer !== undefined) clearTimeout(timer);\n external?.removeEventListener(\"abort\", onExternalAbort);\n },\n didTimeout: () => timedOut,\n };\n}\n"]}
|
package/dist/errors.d.ts
CHANGED
|
@@ -154,6 +154,55 @@ export declare class UnfazedConflictError extends UnfazedError {
|
|
|
154
154
|
/** The requested alias, deployment or release does not exist (or was pruned). */
|
|
155
155
|
export declare class UnfazedNotFoundError extends UnfazedError {
|
|
156
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Why `oidc.token()` gave no token. The first six are the platform's own
|
|
159
|
+
* `error` codes (`POST /oidc/token`); the rest are the SDK's.
|
|
160
|
+
*/
|
|
161
|
+
export type OIDCErrorCode =
|
|
162
|
+
/** 401: the platform refused the app's `UNFZ_STATE_TOKEN`. */
|
|
163
|
+
"invalid_token"
|
|
164
|
+
/** 400, or refused locally: not 1–256 printable ASCII characters without whitespace. */
|
|
165
|
+
| "invalid_audience"
|
|
166
|
+
/** 400, or refused locally: `ttlSeconds` is not a whole number from 60 to 3600. */
|
|
167
|
+
| "invalid_request"
|
|
168
|
+
/** 429, after the retries (or with a `Retry-After` longer than the SDK waits). */
|
|
169
|
+
| "rate_limited"
|
|
170
|
+
/** 404: the platform issues no OIDC tokens to this app. */
|
|
171
|
+
| "oidc_disabled"
|
|
172
|
+
/** 503 (or a gateway's 502/504), after the retries. */
|
|
173
|
+
| "temporarily_unavailable"
|
|
174
|
+
/** `UNFZ_STATE_TOKEN` is not set: not running inside an unfazed app. */
|
|
175
|
+
| "not_in_app"
|
|
176
|
+
/** Network failures (or attempts past `timeoutMs`) outlasted the retries. */
|
|
177
|
+
| "unreachable"
|
|
178
|
+
/** A 200 without a usable `token` and `expires_at`. */
|
|
179
|
+
| "invalid_response"
|
|
180
|
+
/** Any other status, without a code. */
|
|
181
|
+
| "http_error";
|
|
182
|
+
/**
|
|
183
|
+
* `oidc.token()` (or `tokenResponse`, or a `fetchWithToken` fetch) got no
|
|
184
|
+
* token. `code` is the platform's `error` (`invalid_audience`, `rate_limited`,
|
|
185
|
+
* …), passed through as sent, or one of the SDK's own ({@link OIDCErrorCode});
|
|
186
|
+
* `description` is the platform's `error_description`.
|
|
187
|
+
*/
|
|
188
|
+
export declare class UnfazedOIDCError extends UnfazedError {
|
|
189
|
+
readonly code: OIDCErrorCode | (string & {});
|
|
190
|
+
/** HTTP status from the platform; undefined for a refusal made locally. */
|
|
191
|
+
readonly status: number | undefined;
|
|
192
|
+
/** The platform's `error_description`. */
|
|
193
|
+
readonly description: string | undefined;
|
|
194
|
+
/** Asking again later may succeed (`rate_limited`, `temporarily_unavailable`, `unreachable`). */
|
|
195
|
+
readonly retryable: boolean;
|
|
196
|
+
/** The platform's `Retry-After`, in ms, when it sent one. */
|
|
197
|
+
readonly retryAfterMs: number | undefined;
|
|
198
|
+
constructor(code: OIDCErrorCode | (string & {}), message: string, options?: {
|
|
199
|
+
status?: number;
|
|
200
|
+
description?: string;
|
|
201
|
+
retryable?: boolean;
|
|
202
|
+
retryAfterMs?: number;
|
|
203
|
+
cause?: unknown;
|
|
204
|
+
});
|
|
205
|
+
}
|
|
157
206
|
/**
|
|
158
207
|
* What the `unfazed` CLI backend's World (`createWorld()`) throws.
|
|
159
208
|
*
|
package/dist/errors.js
CHANGED
|
@@ -193,6 +193,31 @@ export class UnfazedConflictError extends UnfazedError {
|
|
|
193
193
|
/** The requested alias, deployment or release does not exist (or was pruned). */
|
|
194
194
|
export class UnfazedNotFoundError extends UnfazedError {
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* `oidc.token()` (or `tokenResponse`, or a `fetchWithToken` fetch) got no
|
|
198
|
+
* token. `code` is the platform's `error` (`invalid_audience`, `rate_limited`,
|
|
199
|
+
* …), passed through as sent, or one of the SDK's own ({@link OIDCErrorCode});
|
|
200
|
+
* `description` is the platform's `error_description`.
|
|
201
|
+
*/
|
|
202
|
+
export class UnfazedOIDCError extends UnfazedError {
|
|
203
|
+
code;
|
|
204
|
+
/** HTTP status from the platform; undefined for a refusal made locally. */
|
|
205
|
+
status;
|
|
206
|
+
/** The platform's `error_description`. */
|
|
207
|
+
description;
|
|
208
|
+
/** Asking again later may succeed (`rate_limited`, `temporarily_unavailable`, `unreachable`). */
|
|
209
|
+
retryable;
|
|
210
|
+
/** The platform's `Retry-After`, in ms, when it sent one. */
|
|
211
|
+
retryAfterMs;
|
|
212
|
+
constructor(code, message, options = {}) {
|
|
213
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
214
|
+
this.code = code;
|
|
215
|
+
this.status = options.status;
|
|
216
|
+
this.description = options.description;
|
|
217
|
+
this.retryable = options.retryable ?? false;
|
|
218
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
196
221
|
/**
|
|
197
222
|
* What the `unfazed` CLI backend's World (`createWorld()`) throws.
|
|
198
223
|
*
|