@nohead/sdk 0.7.0 → 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 +96 -0
- package/README.md +58 -26
- package/dist/client.d.ts +9 -1
- package/dist/client.js +14 -2
- package/dist/core.d.ts +22 -5
- package/dist/core.js +158 -45
- package/dist/errors.d.ts +13 -1
- package/dist/errors.js +74 -16
- package/dist/generated/operations.gen.d.ts +0 -8
- package/dist/generated/operations.gen.js +0 -1
- package/dist/generated/schemas.gen.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +23 -59
- package/dist/images.js +8 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -0
- package/dist/resources/assets.d.ts +6 -4
- package/dist/resources/assets.js +23 -18
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/webhooks.d.ts +2 -1
- package/dist/webhooks.js +6 -5
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,102 @@ Changes to `@nohead/sdk` that you can notice. Versions follow
|
|
|
5
5
|
releases; a change that could break your code is a major one. Each release's
|
|
6
6
|
section is its GitHub release's notes.
|
|
7
7
|
|
|
8
|
+
## 0.9.0
|
|
9
|
+
|
|
10
|
+
- **Breaking:** the client no longer reads `NOHEAD_API_URL`: a stray value
|
|
11
|
+
sent requests, with the API key, to another host. The base URL is
|
|
12
|
+
`baseUrl`, else `https://api.nohead.io`; pass `baseUrl` to use another
|
|
13
|
+
API, as in `new Nohead({ baseUrl: process.env.NOHEAD_API_URL })`.
|
|
14
|
+
|
|
15
|
+
- An `APIError`'s `message` names the status, the error type and the
|
|
16
|
+
request ID along with the API's message, as in the Python and Ruby SDKs:
|
|
17
|
+
`422 validation_error: Title is required (request req_…)`, where it was
|
|
18
|
+
only `Title is required`. The API's own text is `apiMessage`.
|
|
19
|
+
|
|
20
|
+
- `unwrapWebhook` and `webhooks.unwrap` throw a `WebhookVerificationError`
|
|
21
|
+
for an empty secret (`""` or `"whsec_"`, as from an unset environment
|
|
22
|
+
variable), not the platform's `DOMException`.
|
|
23
|
+
|
|
24
|
+
- `DEFAULT_BASE_URL` is exported: the API's address (`https://api.nohead.io`)
|
|
25
|
+
that a client uses when `baseUrl` isn't set.
|
|
26
|
+
|
|
27
|
+
- `timeout` is now the longest an attempt may go without progress: to get
|
|
28
|
+
the response, then between chunks of its body. A large response that keeps
|
|
29
|
+
arriving no longer times out; one that stalls still does. An upload's bytes
|
|
30
|
+
get as long as sending them at 64 KiB/s takes, if that is longer.
|
|
31
|
+
|
|
32
|
+
- `withOptions({ timeout, maxRetries, headers, onRetry })` copies a client
|
|
33
|
+
with other settings; the copy shares the key's project lookup. `headers`
|
|
34
|
+
are added to the client's.
|
|
35
|
+
|
|
36
|
+
- Redirects are no longer followed, which could send the API key to another
|
|
37
|
+
host or a write as a GET: a `3xx` throws an `APIError` with `type`
|
|
38
|
+
`redirect` and the message
|
|
39
|
+
`301 redirect: The API answered with a redirect to <Location>; check the base URL`.
|
|
40
|
+
A redirect from storage fails an upload with `UploadError`.
|
|
41
|
+
|
|
42
|
+
- Headers are matched by name in any case, and each is sent once. A call's
|
|
43
|
+
`headers` replace the client's, which replace the SDK's own; the headers
|
|
44
|
+
the request options set (`Idempotency-Key`, `If-Match`,
|
|
45
|
+
`Nohead-Change-Note`, `Content-Type`) always win. A client header
|
|
46
|
+
`authorization` was sent along with the SDK's `Authorization`.
|
|
47
|
+
|
|
48
|
+
- Empty options count as unset: `apiKey`, `baseUrl` and `projectId` of `""`
|
|
49
|
+
fall back to `NOHEAD_API_KEY` and the defaults, an `idempotencyKey` of
|
|
50
|
+
`""` gets a generated key, an upload's `filename` and `content_type` of
|
|
51
|
+
`""` their defaults, and `imageUrl`'s empty options are left out.
|
|
52
|
+
|
|
53
|
+
- A `baseUrl` that isn't an `http` or `https` URL throws a `NoheadError`
|
|
54
|
+
when the client is made, not on its first request.
|
|
55
|
+
|
|
56
|
+
- A path value of `.` or `..` (`records.get("..")`) throws a `NoheadError`,
|
|
57
|
+
`Invalid record_id: ".."`, before anything is sent: URLs resolve them.
|
|
58
|
+
|
|
59
|
+
- `ifMatch` that isn't a revision number, or an object with one, throws a
|
|
60
|
+
`TypeError` before anything is sent, where an object without one sent
|
|
61
|
+
`If-Match: "undefined"`.
|
|
62
|
+
|
|
63
|
+
- `NaN` and `Infinity` in a body throw a `TypeError`, where they were sent as
|
|
64
|
+
`null`.
|
|
65
|
+
|
|
66
|
+
- A list whose response isn't the API's list (a proxy's page, say) throws a
|
|
67
|
+
`NoheadError`, `The API's response could not be read (HTTP 200)`, not a
|
|
68
|
+
`TypeError` or a page without items.
|
|
69
|
+
|
|
70
|
+
- `Retry-After` is read as a number of seconds (`120`, `1.5`) or an HTTP
|
|
71
|
+
date, in all three of its forms, in GMT. Anything else (`1e3`, `0x10`,
|
|
72
|
+
`-1`, an ISO 8601 time) is ignored and the usual backoff applies.
|
|
73
|
+
|
|
74
|
+
- An error's details that aren't objects are kept, as
|
|
75
|
+
`{ code: "unknown", message: <the value as text> }`, rather than dropped.
|
|
76
|
+
|
|
77
|
+
- `assets.upload` sends a stream with `Content-Length: byte_size`, which the
|
|
78
|
+
upload URL is signed for; storage could refuse it without one. Given a string,
|
|
79
|
+
it throws a `TypeError`: pass `await fs.openAsBlob(path)` for a file. Its
|
|
80
|
+
`UploadError` messages name the filename you passed (or `upload`).
|
|
81
|
+
|
|
82
|
+
- `unwrapWebhook` and `webhooks.unwrap` take an unset `secret` (`undefined`,
|
|
83
|
+
as from a missing environment variable) and throw `WebhookVerificationError`
|
|
84
|
+
`The webhook secret is empty`, not a `TypeError`.
|
|
85
|
+
|
|
86
|
+
- `imageUrl` throws a `TypeError` for an unknown `fit`
|
|
87
|
+
(`fit must be one of contain, cover, fill`), where it wrote `rs:undefined`
|
|
88
|
+
into the URL.
|
|
89
|
+
|
|
90
|
+
## 0.8.0
|
|
91
|
+
|
|
92
|
+
- `Asset` has `file_url`, the file as uploaded, which downloads under its
|
|
93
|
+
filename, and for video and audio `media_url`, which plays it inline (in
|
|
94
|
+
`<video>` and `<audio>`, with range requests). Both are open, never expire
|
|
95
|
+
and are cached on a CDN, like `image_url`.
|
|
96
|
+
|
|
97
|
+
- `assets.downloadUrl` is removed, with the API's
|
|
98
|
+
`GET /v1/assets/{asset_id}/download-url` and its 2-minute links: use the
|
|
99
|
+
asset's `file_url`.
|
|
100
|
+
|
|
101
|
+
- An upload larger than the plan allows (10 MB a file on Free, 100 MB on paid
|
|
102
|
+
plans) is refused with a `PlanLimitExceededError` when it starts.
|
|
103
|
+
|
|
8
104
|
## 0.7.0
|
|
9
105
|
|
|
10
106
|
- `assets.list({ q, kind })`: `q` finds assets whose filename or alt text
|
package/README.md
CHANGED
|
@@ -40,28 +40,39 @@ for await (const post of nohead.records.list("posts", {
|
|
|
40
40
|
npm install @nohead/sdk
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
The package is ESM; Node 22+ can also `require()` it. It runs on Node 22, 24 and 26, Bun, Deno, Cloudflare Workers and other runtimes with `fetch
|
|
43
|
+
The package is ESM; Node 22+ can also `require()` it. It runs on Node 22, 24 and 26, Bun, Deno, Cloudflare Workers and other runtimes with `fetch`, Web Crypto and ES2024. Use it on servers: API keys are secrets.
|
|
44
|
+
|
|
45
|
+
Until 1.0, any release may break your code: pin a version, and read the changelog's **Breaking** entries before upgrading.
|
|
44
46
|
|
|
45
47
|
## Configuration
|
|
46
48
|
|
|
47
49
|
```ts
|
|
48
50
|
const nohead = new Nohead({
|
|
49
|
-
apiKey: process.env.NOHEAD_API_KEY, // default
|
|
50
|
-
|
|
51
|
+
apiKey: process.env.NOHEAD_API_KEY, // the default
|
|
52
|
+
timeout: 10_000,
|
|
53
|
+
maxRetries: 3,
|
|
51
54
|
})
|
|
52
55
|
```
|
|
53
56
|
|
|
54
|
-
| Option | Default
|
|
55
|
-
| ------------ |
|
|
56
|
-
| `apiKey` | `NOHEAD_API_KEY`
|
|
57
|
-
| `baseUrl` | `
|
|
58
|
-
| `projectId` | the key's project
|
|
59
|
-
| `maxRetries` | `2`
|
|
60
|
-
| `timeout` | `60000`
|
|
61
|
-
| `fetch` | `globalThis.fetch`
|
|
62
|
-
| `headers` | `{}`
|
|
63
|
-
| `warnings` | `true`
|
|
64
|
-
| `onRetry` |
|
|
57
|
+
| Option | Default | |
|
|
58
|
+
| ------------ | ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
59
|
+
| `apiKey` | `NOHEAD_API_KEY` | A project API key (`nohead_sk_…`, or `nohead_pk_…` when published-only). Required. |
|
|
60
|
+
| `baseUrl` | `https://api.nohead.io` | For a proxy, or a mock server in tests. |
|
|
61
|
+
| `projectId` | the key's project | Looked up once with `GET /v1/me` when omitted. |
|
|
62
|
+
| `maxRetries` | `2` | See [retries](#retries-and-idempotency). |
|
|
63
|
+
| `timeout` | `60000` | Milliseconds an attempt may go without progress. See [retries](#retries-and-idempotency). |
|
|
64
|
+
| `fetch` | `globalThis.fetch` | For proxies, instrumentation and tests. |
|
|
65
|
+
| `headers` | `{}` | Added to every request. |
|
|
66
|
+
| `warnings` | `true` | Logs deprecation and plan usage warnings, once each. |
|
|
67
|
+
| `onRetry` | | Called before each retry. See [retries](#retries-and-idempotency). |
|
|
68
|
+
|
|
69
|
+
An empty option counts as unset, as an empty environment variable does: `apiKey: ""` falls back to `NOHEAD_API_KEY`, and `baseUrl: ""` to `https://api.nohead.io`. The base URL comes only from `baseUrl`, never from the environment. A `baseUrl` that isn't an `http` or `https` URL throws.
|
|
70
|
+
|
|
71
|
+
`withOptions` copies a client with another `timeout`, `maxRetries` or `onRetry`, or more `headers` (added to the client's). The copy shares the key's project, so it doesn't look it up again:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
const patient = nohead.withOptions({ timeout: 120_000, maxRetries: 5 })
|
|
75
|
+
```
|
|
65
76
|
|
|
66
77
|
API keys belong to a project, so methods like `collections.list()` need no project ID. Collections can be named by ID or slug everywhere.
|
|
67
78
|
|
|
@@ -190,6 +201,10 @@ Every error is a `NoheadError`. API errors are `APIError`s with `status`, `type`
|
|
|
190
201
|
| `InternalServerError` | 500 and other 5xx |
|
|
191
202
|
| `ServiceUnavailableError` | 503 |
|
|
192
203
|
|
|
204
|
+
An API error's `message` names the status, the type and the request ID along with the API's message, so logging it is enough to look the request up: `422 validation_error: Title is required (request req_…)`. The API's own text is `apiMessage`, to show a person.
|
|
205
|
+
|
|
206
|
+
The client follows no redirects: a `3xx` is an `APIError` (`type` `redirect`) that names where it pointed, which usually means the base URL is wrong.
|
|
207
|
+
|
|
193
208
|
Other errors: `ConnectionError`, `TimeoutError` (a `ConnectionError`), `AbortError` (your `signal` fired), `UploadError` and `WebhookVerificationError`.
|
|
194
209
|
|
|
195
210
|
```ts
|
|
@@ -241,6 +256,10 @@ Every method takes request options as its last argument:
|
|
|
241
256
|
|
|
242
257
|
Writes that change records or the schema also take `changeNote`, a reason kept with the revision, schema change or migration they make. Record and draft writes take `ifMatch` (see [concurrency](#concurrency)).
|
|
243
258
|
|
|
259
|
+
Headers are matched by name in any case, and each is sent once: a call's `headers` replace the client's, which replace the SDK's own (`Authorization`, `User-Agent`…). The headers the request options set (`Idempotency-Key`, `If-Match`, `Nohead-Change-Note`, and `Content-Type` for a body) always win.
|
|
260
|
+
|
|
261
|
+
**Timeouts:** `timeout` is the longest an attempt may go without progress: to connect and get the response, then between chunks of its body. A response that keeps arriving never times out; one that stalls for `timeout` throws `TimeoutError`, and is retried. fetch shows no progress while it sends an upload's bytes, so storage gets as long as sending them at 64 KiB/s takes, when that is longer than `timeout`.
|
|
262
|
+
|
|
244
263
|
## Concurrency
|
|
245
264
|
|
|
246
265
|
Pass the revision you read to make sure nobody changed the record since:
|
|
@@ -302,11 +321,11 @@ Thumbnails: an image's `preview_url` fits in 640×640, as WebP.
|
|
|
302
321
|
2. Sends the bytes straight to storage.
|
|
303
322
|
3. Completes the upload, which checks the file, and returns the `ready` asset.
|
|
304
323
|
|
|
305
|
-
|
|
324
|
+
A string is neither a path nor the content: it throws `TypeError` (use `await openAsBlob(path)` from `node:fs`). A stream is sent with `Content-Length: byte_size`, which the upload URL is signed for.
|
|
306
325
|
|
|
307
|
-
**
|
|
326
|
+
**Errors:** `UploadError` whenever the bytes don't reach storage: `Storage refused the upload of cover.jpg (403)` when it refuses them, with its `status`, or `Could not upload cover.jpg: …` when it can't be reached or times out, with that error as the `cause`. `ValidationError` if the file fails the checks.
|
|
308
327
|
|
|
309
|
-
**
|
|
328
|
+
**Resuming:** pass an `idempotencyKey` (`upload(file, {}, { idempotencyKey })`) and running the same upload again resumes it: an asset that is already `ready` comes back without sending the bytes again. Once the upload's URL has expired (its `upload.expires_at`), it throws `UploadError`: start over with a new key.
|
|
310
329
|
|
|
311
330
|
**Renaming and alt text:** `update` changes an asset's `filename` and `alt` text, the default wherever the asset is used (`expand` returns it with the asset). The file itself never changes.
|
|
312
331
|
|
|
@@ -372,22 +391,26 @@ Verify a webhook request, then use its event:
|
|
|
372
391
|
|
|
373
392
|
```ts
|
|
374
393
|
// e.g. a Next.js route handler
|
|
394
|
+
import { revalidateTag } from "next/cache"
|
|
395
|
+
|
|
375
396
|
export async function POST(request: Request) {
|
|
376
397
|
const event = await nohead.webhooks.unwrap(
|
|
377
398
|
await request.text(),
|
|
378
399
|
request.headers,
|
|
379
400
|
{
|
|
380
|
-
secret: process.env.NOHEAD_WEBHOOK_SECRET
|
|
401
|
+
secret: process.env.NOHEAD_WEBHOOK_SECRET,
|
|
381
402
|
}
|
|
382
403
|
)
|
|
383
404
|
if (event.type === "record.published" && event.data.record) {
|
|
384
|
-
revalidateTag(event.data.record.collection)
|
|
405
|
+
revalidateTag(event.data.record.collection, "max")
|
|
385
406
|
}
|
|
386
407
|
return new Response(null, { status: 204 })
|
|
387
408
|
}
|
|
388
409
|
```
|
|
389
410
|
|
|
390
|
-
|
|
411
|
+
With `"max"`, Next.js serves the old version to the next request while it fetches the new one; pass `{ expire: 0 }` instead to drop it at once.
|
|
412
|
+
|
|
413
|
+
Event types are those of the activity log (`record.published`, `record.restored`, `field.updated`, `asset.purged`, …), and a webhook subscribes to types or wildcards (`record.*`, `*`). Narrowing by `type` types `event.data`, and `unwrap<Post>` types the records and drafts in it. `unwrap` checks the signature and the timestamp (Standard Webhooks), and throws `WebhookVerificationError` if either is off, or if the secret is empty or unset (`The webhook secret is empty`). After `rotateSecret`, requests carry a signature for the old secret and the new one for 24 hours, so switch the receiver's secret within that day (or end it sooner with `webhooks.secrets.retire`). Pass the raw body: parsing and re-serializing JSON changes the bytes.
|
|
391
414
|
|
|
392
415
|
A receiver that never calls the API can use `unwrapWebhook` from `@nohead/sdk/webhooks` without a client. Events can arrive more than once, so deduplicate by the `webhook-id` header.
|
|
393
416
|
|
|
@@ -414,7 +437,7 @@ response.headers.get("RateLimit-Remaining")
|
|
|
414
437
|
| `collections.searchIndex` | `get`, `rebuild` |
|
|
415
438
|
| `fields` | `list`, `get`, `create`, `update`, `delete`, `restore`, `order`, `reorder`, `removeAlias` |
|
|
416
439
|
| `migrations` | `create`, `list`, `get`, `cancel` |
|
|
417
|
-
| `assets` | `upload`, `createUpload`, `complete`, `list`, `get`, `update`, `delete`, `restore`, `purge`, `usage
|
|
440
|
+
| `assets` | `upload`, `createUpload`, `complete`, `list`, `get`, `update`, `delete`, `restore`, `purge`, `usage` |
|
|
418
441
|
| `webhooks` | `list`, `get`, `create`, `update`, `delete`, `restore`, `rotateSecret`, `test`, `unwrap` |
|
|
419
442
|
| `webhooks.secrets` | `list`, `retire` |
|
|
420
443
|
| `webhooks.deliveries` | `list`, `get`, `retry` |
|
|
@@ -430,28 +453,37 @@ The SDK covers every operation an API key can call. Organizations, projects, mem
|
|
|
430
453
|
```bash
|
|
431
454
|
npm install # also builds dist/
|
|
432
455
|
npm test # unit and contract tests
|
|
433
|
-
npm run lint && npm run typecheck && npm run format:check
|
|
456
|
+
npm run lint && npm run typecheck && npm run typecheck:smoke && npm run format:check
|
|
434
457
|
npm run generate # after updating openapi.json
|
|
435
458
|
npm run samples # after changing test/calls.ts (the docs' code samples)
|
|
436
459
|
NOHEAD_API_URL=http://localhost:3000 NOHEAD_API_KEY=nohead_sk_… npm run smoke
|
|
437
460
|
```
|
|
438
461
|
|
|
462
|
+
Before pushing, also check that the generated code and the samples are current, as CI does:
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
npm run generate && npm run samples && git diff --exit-code src/generated samples.json
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
The unit tests' mock client (`mockClient` in `test/helpers.ts`) checks every request to the API, and every reply to one, against `openapi.json`, and fails the test on a mismatch; build replies from the contract's examples (`exampleOf`), and mark one that breaks the contract on purpose with `raw()`.
|
|
469
|
+
|
|
439
470
|
**How the code is organized:**
|
|
440
471
|
|
|
441
|
-
- `openapi.json` is the API's published contract. `npm run generate` derives
|
|
472
|
+
- `openapi.json` is the API's published contract. `npm run generate` derives three things from it:
|
|
442
473
|
- the types, in `src/generated/types.gen.ts` (hey-api)
|
|
443
474
|
- the operation table, in `src/generated/operations.gen.ts`
|
|
475
|
+
- the schema types the SDK exports, in `src/generated/schemas.gen.ts`
|
|
444
476
|
- The methods in `src/resources` are written by hand.
|
|
445
477
|
- `test/contract.test.ts` calls every public method. It fails when an API-key operation in the contract has no method, when a query parameter is never sent (no method takes it, or no call passes it), or when a request doesn't match its operation (method, path, query parameters and JSON body, against their schemas). Each call gets an example of its operation's response, built from the contract's schema, and must return it.
|
|
446
478
|
|
|
447
|
-
The smoke test (`smoke/smoke.ts`) runs the core flow against a real API, using the built package. Nohead
|
|
479
|
+
The smoke test (`smoke/smoke.ts`) runs the core flow against a real API, using the built package. The Nohead API repository runs it in its end-to-end tests and before every release.
|
|
448
480
|
|
|
449
481
|
## Releasing
|
|
450
482
|
|
|
451
|
-
1. Bump the version in `package.json` and `src/version.ts
|
|
483
|
+
1. Bump the version in `package.json` and `src/version.ts`, then `npm install --package-lock-only`, so `package-lock.json` records it.
|
|
452
484
|
2. Add a section for it to `CHANGELOG.md` (`## 1.2.3`), which becomes the release's notes.
|
|
453
485
|
3. Merge to `main`. Its ruleset requires the **CI passed** check, so the commit goes through a pull request or a branch whose CI passed, and force pushes are refused.
|
|
454
|
-
4. Run the **SDK release** workflow in the Nohead API repository. It runs this commit's smoke test against the API and pushes the tag `v1.2.3`.
|
|
486
|
+
4. Run the **SDK release** workflow in the Nohead API repository. It runs this commit's smoke test against the API and pushes the tag `v1.2.3`. A tag ruleset lets only deploy keys push `v*` tags.
|
|
455
487
|
5. The tag starts `.github/workflows/release.yml`. A build job checks the tag is on `main` and matches the version and its notes, and packs the package, with no publishing credential. Its publishing job runs in the `release` environment, which only `v*` tags can use, and the registry's trusted publisher accepts only that environment. It stages that package on npm with provenance through trusted publishing (no token), then creates a draft GitHub release.
|
|
456
488
|
6. Approve the staged version with 2FA, on npmjs.com, or with `npm stage list @nohead/sdk` and `npm stage approve <stage-id>`. Only then is it installable. Publish the draft GitHub release.
|
|
457
489
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PagePromise } from "./api-promise.ts";
|
|
2
|
-
import { type ClientOptions, type RequestOptions } from "./core.ts";
|
|
2
|
+
import { type ClientOptions, type CopyOptions, type RequestOptions } from "./core.ts";
|
|
3
3
|
import type * as G from "./generated/types.gen.ts";
|
|
4
4
|
import { Assets } from "./resources/assets.ts";
|
|
5
5
|
import { Drafts } from "./resources/drafts.ts";
|
|
@@ -31,6 +31,14 @@ export declare class Nohead {
|
|
|
31
31
|
readonly me: Me;
|
|
32
32
|
readonly health: Health;
|
|
33
33
|
constructor(options?: ClientOptions);
|
|
34
|
+
/**
|
|
35
|
+
* A copy of the client with another `timeout`, `maxRetries` or `onRetry`,
|
|
36
|
+
* or more `headers` (added to this client's). It shares the key's project,
|
|
37
|
+
* so it doesn't look it up again.
|
|
38
|
+
*
|
|
39
|
+
* await nohead.withOptions({ timeout: 5_000 }).records.get(id)
|
|
40
|
+
*/
|
|
41
|
+
withOptions(options: CopyOptions): Nohead;
|
|
34
42
|
/**
|
|
35
43
|
* Full-text search across the key's project (or some of its
|
|
36
44
|
* collections), most relevant first, through the first 1,000 hits.
|
package/dist/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Core } from "./core.js";
|
|
1
|
+
import { Core, } from "./core.js";
|
|
2
2
|
import { Assets } from "./resources/assets.js";
|
|
3
3
|
import { Drafts } from "./resources/drafts.js";
|
|
4
4
|
import { AuditEvents, FeatureFlags, Health, Me } from "./resources/other.js";
|
|
@@ -28,7 +28,7 @@ export class Nohead {
|
|
|
28
28
|
health;
|
|
29
29
|
#core;
|
|
30
30
|
constructor(options = {}) {
|
|
31
|
-
const core = new Core(options);
|
|
31
|
+
const core = options[COPY_OF] ?? new Core(options);
|
|
32
32
|
this.#core = core;
|
|
33
33
|
this.records = new Records(core);
|
|
34
34
|
this.revisions = new RevisionsById(core);
|
|
@@ -43,6 +43,16 @@ export class Nohead {
|
|
|
43
43
|
this.me = new Me(core);
|
|
44
44
|
this.health = new Health(core);
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* A copy of the client with another `timeout`, `maxRetries` or `onRetry`,
|
|
48
|
+
* or more `headers` (added to this client's). It shares the key's project,
|
|
49
|
+
* so it doesn't look it up again.
|
|
50
|
+
*
|
|
51
|
+
* await nohead.withOptions({ timeout: 5_000 }).records.get(id)
|
|
52
|
+
*/
|
|
53
|
+
withOptions(options) {
|
|
54
|
+
return new Nohead({ [COPY_OF]: this.#core.withOptions(options) });
|
|
55
|
+
}
|
|
46
56
|
/**
|
|
47
57
|
* Full-text search across the key's project (or some of its
|
|
48
58
|
* collections), most relevant first, through the first 1,000 hits.
|
|
@@ -61,3 +71,5 @@ export class Nohead {
|
|
|
61
71
|
}, options);
|
|
62
72
|
}
|
|
63
73
|
}
|
|
74
|
+
// The core a copy is made with (`withOptions`), out of ClientOptions' sight.
|
|
75
|
+
const COPY_OF = Symbol("copyOf");
|
package/dist/core.d.ts
CHANGED
|
@@ -9,13 +9,19 @@ export interface ClientOptions {
|
|
|
9
9
|
* Defaults to `NOHEAD_API_KEY`.
|
|
10
10
|
*/
|
|
11
11
|
apiKey?: string;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* The API's address. Defaults to `https://api.nohead.io`; the environment
|
|
14
|
+
* isn't read for it, so the key only goes where the code says.
|
|
15
|
+
*/
|
|
13
16
|
baseUrl?: string;
|
|
14
17
|
/** The key's project. Looked up once with `GET /v1/me` when omitted. */
|
|
15
18
|
projectId?: string;
|
|
16
19
|
/** Retries of failed requests (see README, "Retries"). Default 2. */
|
|
17
20
|
maxRetries?: number;
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Milliseconds an attempt may go without progress (connecting, waiting for
|
|
23
|
+
* the response, then between chunks of its body). Default 60 000.
|
|
24
|
+
*/
|
|
19
25
|
timeout?: number;
|
|
20
26
|
/** A `fetch` implementation, for proxies, instrumentation or tests. */
|
|
21
27
|
fetch?: typeof fetch;
|
|
@@ -26,6 +32,8 @@ export interface ClientOptions {
|
|
|
26
32
|
/** Called before each retry, for example to report it. */
|
|
27
33
|
onRetry?: (retry: RetryEvent) => void;
|
|
28
34
|
}
|
|
35
|
+
/** What `withOptions` changes in a client's copy. */
|
|
36
|
+
export type CopyOptions = Pick<ClientOptions, "timeout" | "maxRetries" | "headers" | "onRetry">;
|
|
29
37
|
/** A retry about to happen (`onRetry`). */
|
|
30
38
|
export interface RetryEvent {
|
|
31
39
|
/** 1 for the first retry of the request, 2 for the second… */
|
|
@@ -43,7 +51,7 @@ export interface RequestOptions {
|
|
|
43
51
|
/** Sent as `Idempotency-Key` on writes. Generated when omitted. */
|
|
44
52
|
idempotencyKey?: string;
|
|
45
53
|
signal?: AbortSignal;
|
|
46
|
-
/** Milliseconds
|
|
54
|
+
/** Milliseconds without progress, overriding the client's `timeout`. */
|
|
47
55
|
timeout?: number;
|
|
48
56
|
/** Retries, overriding the client's. */
|
|
49
57
|
maxRetries?: number;
|
|
@@ -92,15 +100,24 @@ export interface RequestInput {
|
|
|
92
100
|
}
|
|
93
101
|
export declare class Core {
|
|
94
102
|
#private;
|
|
95
|
-
constructor(options: ClientOptions
|
|
103
|
+
constructor(options: ClientOptions, project?: {
|
|
104
|
+
id?: Promise<string>;
|
|
105
|
+
});
|
|
106
|
+
/** A copy with other settings, sharing the project lookup. */
|
|
107
|
+
withOptions(options: CopyOptions): Core;
|
|
96
108
|
/** The API key's project, from `GET /v1/me` the first time. */
|
|
97
109
|
projectId(): Promise<string>;
|
|
98
110
|
request<Op extends OperationId>(operation: Op, input?: RequestInput, options?: OptionsOf<Op>): APIPromise<Operations[Op]["response"]>;
|
|
99
111
|
/** A list operation as pages of `Item`. */
|
|
100
112
|
paginate<Item, Meta extends ListMeta = ListMeta>(operation: OperationId, input: RequestInput, options?: RequestOptions): PagePromise<Item, Meta>;
|
|
101
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* `fetch` with this client's retries, for an upload's presigned URL. fetch
|
|
115
|
+
* shows no upload progress, so each attempt may take as long as sending
|
|
116
|
+
* `byteSize` at 64 KiB/s, if that is longer than the timeout.
|
|
117
|
+
*/
|
|
102
118
|
fetchWithRetries(url: string, init: RequestInit, options: RequestOptions & {
|
|
103
119
|
retry: boolean;
|
|
120
|
+
byteSize: number;
|
|
104
121
|
}): Promise<Response>;
|
|
105
122
|
}
|
|
106
123
|
/**
|