@nohead/sdk 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +29 -15
- package/dist/core.d.ts +16 -0
- package/dist/core.js +12 -3
- package/dist/generated/schemas.gen.d.ts +1 -1
- package/dist/generated/types.gen.d.ts +185 -2
- package/dist/index.d.ts +1 -1
- package/dist/resources/assets.d.ts +6 -2
- package/dist/resources/assets.js +16 -1
- package/dist/types.d.ts +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ 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.2.0
|
|
9
|
+
|
|
10
|
+
- `onRetry` client option: called before each retry with the attempt, the
|
|
11
|
+
delay, and the status or error that failed, for example to report retries.
|
|
12
|
+
- `assets.upload()` with an `idempotencyKey` no longer fails when it
|
|
13
|
+
completes the upload: the key only starts it. Running the same upload again
|
|
14
|
+
resumes it, and returns an asset that is already `ready` without sending the
|
|
15
|
+
bytes again.
|
|
16
|
+
- Types follow the API's current contract. They add types only for
|
|
17
|
+
operations an API key can't call, so no method changed.
|
|
18
|
+
|
|
8
19
|
## 0.1.0
|
|
9
20
|
|
|
10
21
|
The first release.
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ for await (const post of nohead.records.list("posts", {
|
|
|
14
14
|
}
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
> **Status:** 0.x
|
|
17
|
+
> **Status:** 0.x until the Nohead API launches.
|
|
18
18
|
|
|
19
19
|
## Contents
|
|
20
20
|
|
|
@@ -50,16 +50,17 @@ const nohead = new Nohead({
|
|
|
50
50
|
})
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
| Option | Default |
|
|
54
|
-
| ------------ | ---------------------------------------------- |
|
|
55
|
-
| `apiKey` | `NOHEAD_API_KEY` | A project API key (`sk_live_…`). Required.
|
|
56
|
-
| `baseUrl` | `NOHEAD_API_URL`, else `https://api.nohead.io` |
|
|
57
|
-
| `projectId` | the key's project | Looked up once with `GET /v1/me` when omitted.
|
|
58
|
-
| `maxRetries` | `2` | See [retries](#retries-and-idempotency).
|
|
59
|
-
| `timeout` | `60000` | Milliseconds per attempt.
|
|
60
|
-
| `fetch` | `globalThis.fetch` | For proxies, instrumentation and tests.
|
|
61
|
-
| `headers` | `{}` | Added to every request.
|
|
62
|
-
| `warnings` | `true` | Logs deprecation and plan usage warnings, once each.
|
|
53
|
+
| Option | Default | |
|
|
54
|
+
| ------------ | ---------------------------------------------- | ------------------------------------------------------------------ |
|
|
55
|
+
| `apiKey` | `NOHEAD_API_KEY` | A project API key (`sk_live_…`). Required. |
|
|
56
|
+
| `baseUrl` | `NOHEAD_API_URL`, else `https://api.nohead.io` | |
|
|
57
|
+
| `projectId` | the key's project | Looked up once with `GET /v1/me` when omitted. |
|
|
58
|
+
| `maxRetries` | `2` | See [retries](#retries-and-idempotency). |
|
|
59
|
+
| `timeout` | `60000` | Milliseconds per attempt. |
|
|
60
|
+
| `fetch` | `globalThis.fetch` | For proxies, instrumentation and tests. |
|
|
61
|
+
| `headers` | `{}` | Added to every request. |
|
|
62
|
+
| `warnings` | `true` | Logs deprecation and plan usage warnings, once each. |
|
|
63
|
+
| `onRetry` | | Called before each retry. See [retries](#retries-and-idempotency). |
|
|
63
64
|
|
|
64
65
|
API keys belong to a project, so methods like `collections.list()` need no project ID. Collections can be named by ID or slug everywhere.
|
|
65
66
|
|
|
@@ -163,6 +164,17 @@ Failed requests are retried twice by default (`maxRetries`), with exponential ba
|
|
|
163
164
|
- what's retried: connection errors, timeouts, 429, 500, 502, 503, 504, and a 409 for a request that is still running
|
|
164
165
|
- `Retry-After` is honored up to 60 seconds; a longer one throws `RateLimitError` straight away
|
|
165
166
|
|
|
167
|
+
To report retries, pass `onRetry`. It gets the retry's `attempt` (1 for the first), `delay` in seconds, `url`, the failed `status` (none when the connection failed) and the `error`:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
const nohead = new Nohead({
|
|
171
|
+
onRetry: ({ attempt, delay, status }) =>
|
|
172
|
+
console.error(
|
|
173
|
+
`Retry ${attempt} in ${delay.toFixed(1)}s (${status ?? "network"})`
|
|
174
|
+
),
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
166
178
|
Every write gets an `Idempotency-Key` that stays the same across its retries, so a retry after a lost response never writes twice. To make a write safe across your own retries (a job that may run twice), pass a key:
|
|
167
179
|
|
|
168
180
|
```ts
|
|
@@ -223,6 +235,8 @@ const { url } = await nohead.assets.imageUrl(asset.id, {
|
|
|
223
235
|
|
|
224
236
|
**Errors:** `UploadError` if storage refuses the bytes; `ValidationError` if the file fails the checks.
|
|
225
237
|
|
|
238
|
+
**Resuming:** pass an `idempotencyKey` (`upload(file, {}, { idempotencyKey })`) and running the same upload again picks up where it stopped. An asset that is already `ready` comes back without sending the bytes again.
|
|
239
|
+
|
|
226
240
|
**Uploading from a browser:** create the upload on your server with `createUpload`, `PUT` the file from the browser, then `complete` it.
|
|
227
241
|
|
|
228
242
|
## Search
|
|
@@ -336,7 +350,7 @@ NOHEAD_API_URL=http://localhost:3000 NOHEAD_API_KEY=sk_live_… npm run smoke
|
|
|
336
350
|
- the types, in `src/generated/types.gen.ts` (hey-api)
|
|
337
351
|
- the operation table, in `src/generated/operations.gen.ts`
|
|
338
352
|
- The methods in `src/resources` are written by hand.
|
|
339
|
-
- `test/contract.test.ts` calls every public method. It fails when an API-key operation in the contract has no method, or when a request doesn't match its operation.
|
|
353
|
+
- `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.
|
|
340
354
|
|
|
341
355
|
The smoke test (`smoke/smoke.ts`) runs the core flow against a real API, using the built package. Nohead's own CI runs it on every API contract change.
|
|
342
356
|
|
|
@@ -344,9 +358,9 @@ The smoke test (`smoke/smoke.ts`) runs the core flow against a real API, using t
|
|
|
344
358
|
|
|
345
359
|
1. Bump the version in `package.json` and `src/version.ts`.
|
|
346
360
|
2. Add a section for it to `CHANGELOG.md` (`## 1.2.3`), which becomes the release's notes.
|
|
347
|
-
3. Merge to `main`.
|
|
348
|
-
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`.
|
|
349
|
-
5. The tag starts `.github/workflows/release.yml`. It checks the version and its notes, tests, and stages the package on npm with provenance through trusted publishing (no token). Then it creates a draft GitHub release.
|
|
361
|
+
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.
|
|
362
|
+
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`. Nobody else can push `v*` tags: a tag ruleset lets only that workflow's deploy key through.
|
|
363
|
+
5. The tag starts `.github/workflows/release.yml`. 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 checks the version and its notes, tests, and stages the package on npm with provenance through trusted publishing (no token). Then it creates a draft GitHub release.
|
|
350
364
|
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.
|
|
351
365
|
|
|
352
366
|
## License
|
package/dist/core.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { APIPromise, PagePromise } from "./api-promise.ts";
|
|
2
|
+
import { NoheadError } from "./errors.ts";
|
|
2
3
|
import { type OperationId, type Operations } from "./generated/operations.gen.ts";
|
|
3
4
|
import type { ListMeta } from "./generated/types.gen.ts";
|
|
4
5
|
export declare const DEFAULT_BASE_URL = "https://api.nohead.io";
|
|
@@ -19,6 +20,21 @@ export interface ClientOptions {
|
|
|
19
20
|
headers?: Record<string, string>;
|
|
20
21
|
/** Log deprecation and plan usage warnings (once each). Default true. */
|
|
21
22
|
warnings?: boolean;
|
|
23
|
+
/** Called before each retry, for example to report it. */
|
|
24
|
+
onRetry?: (retry: RetryEvent) => void;
|
|
25
|
+
}
|
|
26
|
+
/** A retry about to happen (`onRetry`). */
|
|
27
|
+
export interface RetryEvent {
|
|
28
|
+
/** 1 for the first retry of the request, 2 for the second… */
|
|
29
|
+
attempt: number;
|
|
30
|
+
/** Seconds the client waits before it. */
|
|
31
|
+
delay: number;
|
|
32
|
+
/** The URL being retried. */
|
|
33
|
+
url: string;
|
|
34
|
+
/** The status that failed, or undefined when the connection did. */
|
|
35
|
+
status?: number;
|
|
36
|
+
/** What failed: an `APIError` or a `ConnectionError` (none for storage). */
|
|
37
|
+
error?: NoheadError;
|
|
22
38
|
}
|
|
23
39
|
export interface RequestOptions {
|
|
24
40
|
/** Sent as `Idempotency-Key` on writes. Generated when omitted. */
|
package/dist/core.js
CHANGED
|
@@ -15,6 +15,7 @@ export class Core {
|
|
|
15
15
|
#maxRetries;
|
|
16
16
|
#timeout;
|
|
17
17
|
#warnings;
|
|
18
|
+
#onRetry;
|
|
18
19
|
#projectId;
|
|
19
20
|
constructor(options) {
|
|
20
21
|
const env = environment();
|
|
@@ -31,6 +32,7 @@ export class Core {
|
|
|
31
32
|
this.#maxRetries = options.maxRetries ?? 2;
|
|
32
33
|
this.#timeout = options.timeout ?? 60_000;
|
|
33
34
|
this.#warnings = options.warnings ?? true;
|
|
35
|
+
this.#onRetry = options.onRetry;
|
|
34
36
|
if (options.projectId)
|
|
35
37
|
this.#projectId = Promise.resolve(options.projectId);
|
|
36
38
|
}
|
|
@@ -68,18 +70,21 @@ export class Core {
|
|
|
68
70
|
? (options.maxRetries ?? this.#maxRetries)
|
|
69
71
|
: 0;
|
|
70
72
|
for (let attempt = 0;; attempt++) {
|
|
73
|
+
let failed;
|
|
71
74
|
try {
|
|
72
75
|
const { response } = await this.#attempt(url, init, options, false);
|
|
73
76
|
if (!RETRYABLE_STATUSES.has(response.status) || attempt >= maxRetries) {
|
|
74
77
|
return response;
|
|
75
78
|
}
|
|
79
|
+
failed = { status: response.status };
|
|
76
80
|
}
|
|
77
81
|
catch (error) {
|
|
78
82
|
if (!(error instanceof ConnectionError) || attempt >= maxRetries) {
|
|
79
83
|
throw error;
|
|
80
84
|
}
|
|
85
|
+
failed = { error };
|
|
81
86
|
}
|
|
82
|
-
await
|
|
87
|
+
await this.#retry(url, attempt, backoff(attempt), failed, options.signal);
|
|
83
88
|
}
|
|
84
89
|
}
|
|
85
90
|
async #send(operation, input, options) {
|
|
@@ -122,7 +127,7 @@ export class Core {
|
|
|
122
127
|
if (!(error instanceof ConnectionError) || attempt >= maxRetries) {
|
|
123
128
|
throw error;
|
|
124
129
|
}
|
|
125
|
-
await
|
|
130
|
+
await this.#retry(url.toString(), attempt, backoff(attempt), { error }, options.signal);
|
|
126
131
|
continue;
|
|
127
132
|
}
|
|
128
133
|
const { response, body } = result;
|
|
@@ -134,9 +139,13 @@ export class Core {
|
|
|
134
139
|
const delay = retryDelay(error, attempt, maxRetries);
|
|
135
140
|
if (delay === undefined)
|
|
136
141
|
throw error;
|
|
137
|
-
await
|
|
142
|
+
await this.#retry(url.toString(), attempt, delay, { status: response.status, error }, options.signal);
|
|
138
143
|
}
|
|
139
144
|
}
|
|
145
|
+
#retry(url, attempt, delay, failed, signal) {
|
|
146
|
+
this.#onRetry?.({ attempt: attempt + 1, delay, url, ...failed });
|
|
147
|
+
return sleep(delay, signal);
|
|
148
|
+
}
|
|
140
149
|
async #attempt(url, init, options, readBody) {
|
|
141
150
|
const timeout = options.timeout ?? this.#timeout;
|
|
142
151
|
const timer = AbortSignal.timeout(timeout);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export type { AccessReview, Actor, ApiKey, ApiKeyCreate, ApiKeyList, ApiKeyProperties, ApiKeyScope, ApiKeyWithToken, Asset, AssetList, AssetUpload, AssetUploadCreate, AuditEvent, AuditEventList, Billing, BillingCheckout, BillingPlan, BillingPortal, BillingSession, BillingSubscription, BulkRecordsRequest, BulkRecordsResult, Change, ChangeVia, Collection, CollectionCreate, CollectionList, CollectionUpdate, DownloadUrl, Error, ErrorDetail, ErrorType, FeatureFlags, Field, FieldCreate, FieldDefinition, FieldList, FieldMigration, FieldMigrationCounts, FieldMigrationList, FieldMigrationPreview, FieldMigrationRequest, FieldType, FieldUpdate, Health, ImageUrl, Invitation, InvitationCreate, InvitationList, ListMeta, MeUpdate, Membership, MembershipList, MembershipUpdate, NullableTimestamp, Organization, OrganizationCreate, OrganizationList, OrganizationUpdate, Permission, Principal, Project, ProjectCreate, ProjectList, ProjectUpdate, PublishedOnly, RecordCount, RecordData, RecordDiff, RecordList, RecordRevision, RecordRevisionDetail, RecordRevisionList, RecordRevisionProperties, RecordSchedule, RecordScheduleUpdate, RecordWrite, Redaction, RequestId, RevertPreview, Role, Schema, SchemaChange, SchemaChangeDetail, SchemaChangeList, SchemaChangeProperties, SchemaDefinition, SearchIndex, SearchResults, Timestamp, Usage, UsageMetric, Via, Webhook, WebhookCreate, WebhookDelivery, WebhookDeliveryDetail, WebhookDeliveryList, WebhookDeliveryProperties, WebhookEventPayload, WebhookEventType, WebhookList, WebhookProperties, WebhookUpdate, WebhookWithSecret, } from "./types.gen.ts";
|
|
1
|
+
export type { AccessReview, AccountDeletion, AccountDeletionCode, Actor, ApiKey, ApiKeyCreate, ApiKeyList, ApiKeyProperties, ApiKeyScope, ApiKeyWithToken, Asset, AssetList, AssetUpload, AssetUploadCreate, AuditEvent, AuditEventList, Billing, BillingCheckout, BillingPlan, BillingPortal, BillingSession, BillingSubscription, BulkRecordsRequest, BulkRecordsResult, Change, ChangeVia, Collection, CollectionCreate, CollectionList, CollectionUpdate, DownloadUrl, Error, ErrorDetail, ErrorType, FeatureFlags, Field, FieldCreate, FieldDefinition, FieldList, FieldMigration, FieldMigrationCounts, FieldMigrationList, FieldMigrationPreview, FieldMigrationRequest, FieldType, FieldUpdate, Health, ImageUrl, Invitation, InvitationCreate, InvitationList, ListMeta, MeUpdate, Membership, MembershipList, MembershipUpdate, NullableTimestamp, Organization, OrganizationCreate, OrganizationList, OrganizationUpdate, Permission, Principal, Project, ProjectCreate, ProjectList, ProjectUpdate, PublishedOnly, RecordCount, RecordData, RecordDiff, RecordList, RecordRevision, RecordRevisionDetail, RecordRevisionList, RecordRevisionProperties, RecordSchedule, RecordScheduleUpdate, RecordWrite, Redaction, RequestId, RevertPreview, Role, Schema, SchemaChange, SchemaChangeDetail, SchemaChangeList, SchemaChangeProperties, SchemaDefinition, SearchIndex, SearchResults, Timestamp, Usage, UsageMetric, UsageRetention, UsageRetentionGrace, Via, Webhook, WebhookCreate, WebhookDelivery, WebhookDeliveryDetail, WebhookDeliveryList, WebhookDeliveryProperties, WebhookEventPayload, WebhookEventType, WebhookList, WebhookProperties, WebhookUpdate, WebhookWithSecret, } from "./types.gen.ts";
|
|
@@ -93,7 +93,7 @@ export type Invitation = {
|
|
|
93
93
|
name: string;
|
|
94
94
|
};
|
|
95
95
|
/**
|
|
96
|
-
* What became of the invitation email, or null while it has not been sent (or when sending is off). `sent`: accepted for delivery. `not_sent`: the address had bounced or been marked as spam before,
|
|
96
|
+
* What became of the invitation email, or null while it has not been sent (or when sending is off). `sent`: accepted for delivery. `not_sent`: no email went out, because the address had bounced or been marked as spam before, or had already received several invitation emails that day. `bounced`: the email could not be delivered; check the address. `complained`: the invitee marked it as spam. Either way the invitation still works when the invitee signs in with the address.
|
|
97
97
|
*/
|
|
98
98
|
email_delivery: null | {
|
|
99
99
|
status: 'sent' | 'not_sent' | 'bounced' | 'complained';
|
|
@@ -186,10 +186,15 @@ export type Field = {
|
|
|
186
186
|
api_key: string;
|
|
187
187
|
type: FieldType;
|
|
188
188
|
required: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* A list of distinct values (a repeated value is refused with `duplicate_value`). Not available for boolean, rich_text and json fields.
|
|
191
|
+
*/
|
|
189
192
|
multiple: boolean;
|
|
190
193
|
position: number;
|
|
191
194
|
/**
|
|
192
|
-
* Type-specific options, e.g. `max_length`, `options`, `target_collection_id`,
|
|
195
|
+
* Type-specific options, e.g. `max_length`, `options`, `target_collection_id`,
|
|
196
|
+
* `accepted_types` (asset fields: MIME types or `type*` wildcards that newly
|
|
197
|
+
* written values must have; any file when absent), plus
|
|
193
198
|
* the options for searches with API keys: `searchable` (the search words match the
|
|
194
199
|
* field; default true for text, long_text, rich_text and enum), `filterable` and
|
|
195
200
|
* `sortable` (default false).
|
|
@@ -223,6 +228,9 @@ export type FieldCreate = {
|
|
|
223
228
|
api_key: string;
|
|
224
229
|
type: FieldType;
|
|
225
230
|
required?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* A list of distinct values (a repeated value is refused with `duplicate_value`). Not available for boolean, rich_text and json fields.
|
|
233
|
+
*/
|
|
226
234
|
multiple?: boolean;
|
|
227
235
|
position?: number;
|
|
228
236
|
configuration?: {
|
|
@@ -234,6 +242,9 @@ export type FieldUpdate = {
|
|
|
234
242
|
api_key?: string;
|
|
235
243
|
type?: FieldType;
|
|
236
244
|
required?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
* A list of distinct values (a repeated value is refused with `duplicate_value`). Not available for boolean, rich_text and json fields.
|
|
247
|
+
*/
|
|
237
248
|
multiple?: boolean;
|
|
238
249
|
position?: number;
|
|
239
250
|
configuration?: {
|
|
@@ -319,6 +330,12 @@ export type Actor = {
|
|
|
319
330
|
* The user (`usr_...`) or API key (`key_...`), or null for system changes.
|
|
320
331
|
*/
|
|
321
332
|
id: string | null;
|
|
333
|
+
/**
|
|
334
|
+
* Resolved when read, never stored: a user's current name (or
|
|
335
|
+
* email), "Deleted user" once they deleted their account, an API
|
|
336
|
+
* key's name, or "System".
|
|
337
|
+
*
|
|
338
|
+
*/
|
|
322
339
|
name: string | null;
|
|
323
340
|
};
|
|
324
341
|
export type SchemaChange = SchemaChangeProperties;
|
|
@@ -355,6 +372,9 @@ export type FieldDefinition = {
|
|
|
355
372
|
configuration: {
|
|
356
373
|
[key: string]: unknown;
|
|
357
374
|
};
|
|
375
|
+
/**
|
|
376
|
+
* A list of distinct values (a repeated value is refused with `duplicate_value`). Not available for boolean, rich_text and json fields.
|
|
377
|
+
*/
|
|
358
378
|
multiple: boolean;
|
|
359
379
|
required: boolean;
|
|
360
380
|
};
|
|
@@ -369,6 +389,9 @@ export type FieldMigrationRequest = {
|
|
|
369
389
|
configuration?: {
|
|
370
390
|
[key: string]: unknown;
|
|
371
391
|
};
|
|
392
|
+
/**
|
|
393
|
+
* A list of distinct values (a repeated value is refused with `duplicate_value`). Not available for boolean, rich_text and json fields.
|
|
394
|
+
*/
|
|
372
395
|
multiple?: boolean;
|
|
373
396
|
required?: boolean;
|
|
374
397
|
/**
|
|
@@ -629,6 +652,22 @@ export type FeatureFlags = {
|
|
|
629
652
|
[key: string]: boolean;
|
|
630
653
|
};
|
|
631
654
|
};
|
|
655
|
+
export type AccountDeletionCode = {
|
|
656
|
+
object: 'account_deletion_code';
|
|
657
|
+
/**
|
|
658
|
+
* Where the code was sent.
|
|
659
|
+
*/
|
|
660
|
+
email: string;
|
|
661
|
+
expires_at: string;
|
|
662
|
+
};
|
|
663
|
+
export type AccountDeletion = {
|
|
664
|
+
object: 'account_deletion';
|
|
665
|
+
/**
|
|
666
|
+
* The deleted user, as history still refers to it.
|
|
667
|
+
*/
|
|
668
|
+
user_id: string;
|
|
669
|
+
deleted_at: string;
|
|
670
|
+
};
|
|
632
671
|
export type Principal = {
|
|
633
672
|
object: 'principal';
|
|
634
673
|
type: 'user' | 'api_key';
|
|
@@ -828,6 +867,29 @@ export type Usage = {
|
|
|
828
867
|
end: string;
|
|
829
868
|
};
|
|
830
869
|
metrics: Array<UsageMetric>;
|
|
870
|
+
retention: UsageRetention;
|
|
871
|
+
};
|
|
872
|
+
/**
|
|
873
|
+
* How long the plan keeps record revisions (beyond each record's first and latest 10) and audit events, in days; null keeps everything.
|
|
874
|
+
*/
|
|
875
|
+
export type UsageRetention = {
|
|
876
|
+
revision_days: number | null;
|
|
877
|
+
audit_days: number | null;
|
|
878
|
+
/**
|
|
879
|
+
* After a downgrade, the previous plan's longer retention still applies for 30 days; history past the plan's retention is removed after `ends_at`. Null otherwise.
|
|
880
|
+
*/
|
|
881
|
+
grace: null | UsageRetentionGrace;
|
|
882
|
+
};
|
|
883
|
+
export type UsageRetentionGrace = {
|
|
884
|
+
ends_at: string;
|
|
885
|
+
/**
|
|
886
|
+
* Days of revisions kept until `ends_at`; null keeps everything.
|
|
887
|
+
*/
|
|
888
|
+
revision_days: number | null;
|
|
889
|
+
/**
|
|
890
|
+
* Days of audit events kept until `ends_at`.
|
|
891
|
+
*/
|
|
892
|
+
audit_days: number | null;
|
|
831
893
|
};
|
|
832
894
|
export type UsageMetric = {
|
|
833
895
|
metric: 'api_requests' | 'search_requests' | 'image_transformations' | 'asset_uploads' | 'asset_downloads' | 'webhook_deliveries' | 'record_revisions' | 'projects' | 'collections' | 'records' | 'members' | 'webhooks' | 'asset_storage_bytes' | 'indexed_records';
|
|
@@ -3607,6 +3669,10 @@ export type AssetsListData = {
|
|
|
3607
3669
|
* List soft-deleted items instead of active ones.
|
|
3608
3670
|
*/
|
|
3609
3671
|
deleted?: boolean;
|
|
3672
|
+
/**
|
|
3673
|
+
* Comma-separated MIME types or `type*` wildcards.
|
|
3674
|
+
*/
|
|
3675
|
+
content_type?: string;
|
|
3610
3676
|
};
|
|
3611
3677
|
url: '/v1/projects/{project_id}/assets';
|
|
3612
3678
|
};
|
|
@@ -3680,6 +3746,12 @@ export type AssetsUploadErrors = {
|
|
|
3680
3746
|
* Authentication is missing or invalid.
|
|
3681
3747
|
*/
|
|
3682
3748
|
401: Error;
|
|
3749
|
+
/**
|
|
3750
|
+
* The change would exceed a hard limit of the organization's plan. The
|
|
3751
|
+
* detail names the metric (`field`) with its `usage` and `limit`.
|
|
3752
|
+
*
|
|
3753
|
+
*/
|
|
3754
|
+
402: Error;
|
|
3683
3755
|
/**
|
|
3684
3756
|
* The caller is not allowed to perform this action.
|
|
3685
3757
|
*/
|
|
@@ -6671,6 +6743,66 @@ export type HealthCheckResponses = {
|
|
|
6671
6743
|
200: Health;
|
|
6672
6744
|
};
|
|
6673
6745
|
export type HealthCheckResponse = HealthCheckResponses[keyof HealthCheckResponses];
|
|
6746
|
+
export type MeDeleteData = {
|
|
6747
|
+
body: {
|
|
6748
|
+
/**
|
|
6749
|
+
* The six-digit code emailed by `POST /v1/me/deletion-code`.
|
|
6750
|
+
*/
|
|
6751
|
+
code: string;
|
|
6752
|
+
};
|
|
6753
|
+
headers?: {
|
|
6754
|
+
/**
|
|
6755
|
+
* A unique value (1-255 printable ASCII characters) that makes retrying
|
|
6756
|
+
* this request safe. The first response (below 500) is stored for 24
|
|
6757
|
+
* hours per user or API key; retries with the same key and the same
|
|
6758
|
+
* request get it again with an `Idempotent-Replayed: true` header. The
|
|
6759
|
+
* same key with a different request is refused with `400`; a retry while
|
|
6760
|
+
* the first request is still running gets `409`.
|
|
6761
|
+
*
|
|
6762
|
+
*/
|
|
6763
|
+
'Idempotency-Key'?: string;
|
|
6764
|
+
};
|
|
6765
|
+
path?: never;
|
|
6766
|
+
query?: never;
|
|
6767
|
+
url: '/v1/me';
|
|
6768
|
+
};
|
|
6769
|
+
export type MeDeleteErrors = {
|
|
6770
|
+
/**
|
|
6771
|
+
* The request was malformed.
|
|
6772
|
+
*/
|
|
6773
|
+
400: Error;
|
|
6774
|
+
/**
|
|
6775
|
+
* Authentication is missing or invalid.
|
|
6776
|
+
*/
|
|
6777
|
+
401: Error;
|
|
6778
|
+
/**
|
|
6779
|
+
* The caller is not allowed to perform this action.
|
|
6780
|
+
*/
|
|
6781
|
+
403: Error;
|
|
6782
|
+
/**
|
|
6783
|
+
* The request conflicts with current state.
|
|
6784
|
+
*/
|
|
6785
|
+
409: Error;
|
|
6786
|
+
/**
|
|
6787
|
+
* The request failed validation.
|
|
6788
|
+
*/
|
|
6789
|
+
422: Error;
|
|
6790
|
+
/**
|
|
6791
|
+
* Too many requests for this API key or user (or, with failing
|
|
6792
|
+
* credentials, this IP address). Wait `Retry-After` seconds; retries with
|
|
6793
|
+
* the same `Idempotency-Key` are safe.
|
|
6794
|
+
*
|
|
6795
|
+
*/
|
|
6796
|
+
429: Error;
|
|
6797
|
+
};
|
|
6798
|
+
export type MeDeleteError = MeDeleteErrors[keyof MeDeleteErrors];
|
|
6799
|
+
export type MeDeleteResponses = {
|
|
6800
|
+
/**
|
|
6801
|
+
* The account is deleted, and the session cookie cleared.
|
|
6802
|
+
*/
|
|
6803
|
+
200: AccountDeletion;
|
|
6804
|
+
};
|
|
6805
|
+
export type MeDeleteResponse = MeDeleteResponses[keyof MeDeleteResponses];
|
|
6674
6806
|
export type MeGetData = {
|
|
6675
6807
|
body?: never;
|
|
6676
6808
|
path?: never;
|
|
@@ -6753,6 +6885,57 @@ export type MeUpdateResponses = {
|
|
|
6753
6885
|
200: Principal;
|
|
6754
6886
|
};
|
|
6755
6887
|
export type MeUpdateResponse = MeUpdateResponses[keyof MeUpdateResponses];
|
|
6888
|
+
export type MeDeletionCodeSendData = {
|
|
6889
|
+
body?: never;
|
|
6890
|
+
headers?: {
|
|
6891
|
+
/**
|
|
6892
|
+
* A unique value (1-255 printable ASCII characters) that makes retrying
|
|
6893
|
+
* this request safe. The first response (below 500) is stored for 24
|
|
6894
|
+
* hours per user or API key; retries with the same key and the same
|
|
6895
|
+
* request get it again with an `Idempotent-Replayed: true` header. The
|
|
6896
|
+
* same key with a different request is refused with `400`; a retry while
|
|
6897
|
+
* the first request is still running gets `409`.
|
|
6898
|
+
*
|
|
6899
|
+
*/
|
|
6900
|
+
'Idempotency-Key'?: string;
|
|
6901
|
+
};
|
|
6902
|
+
path?: never;
|
|
6903
|
+
query?: never;
|
|
6904
|
+
url: '/v1/me/deletion-code';
|
|
6905
|
+
};
|
|
6906
|
+
export type MeDeletionCodeSendErrors = {
|
|
6907
|
+
/**
|
|
6908
|
+
* The request was malformed.
|
|
6909
|
+
*/
|
|
6910
|
+
400: Error;
|
|
6911
|
+
/**
|
|
6912
|
+
* Authentication is missing or invalid.
|
|
6913
|
+
*/
|
|
6914
|
+
401: Error;
|
|
6915
|
+
/**
|
|
6916
|
+
* The caller is not allowed to perform this action.
|
|
6917
|
+
*/
|
|
6918
|
+
403: Error;
|
|
6919
|
+
/**
|
|
6920
|
+
* The request conflicts with current state.
|
|
6921
|
+
*/
|
|
6922
|
+
409: Error;
|
|
6923
|
+
/**
|
|
6924
|
+
* Too many requests for this API key or user (or, with failing
|
|
6925
|
+
* credentials, this IP address). Wait `Retry-After` seconds; retries with
|
|
6926
|
+
* the same `Idempotency-Key` are safe.
|
|
6927
|
+
*
|
|
6928
|
+
*/
|
|
6929
|
+
429: Error;
|
|
6930
|
+
};
|
|
6931
|
+
export type MeDeletionCodeSendError = MeDeletionCodeSendErrors[keyof MeDeletionCodeSendErrors];
|
|
6932
|
+
export type MeDeletionCodeSendResponses = {
|
|
6933
|
+
/**
|
|
6934
|
+
* The code is on its way.
|
|
6935
|
+
*/
|
|
6936
|
+
202: AccountDeletionCode;
|
|
6937
|
+
};
|
|
6938
|
+
export type MeDeletionCodeSendResponse = MeDeletionCodeSendResponses[keyof MeDeletionCodeSendResponses];
|
|
6756
6939
|
export type FeatureFlagsListData = {
|
|
6757
6940
|
body?: never;
|
|
6758
6941
|
path?: never;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { Nohead } from "./client.ts";
|
|
2
2
|
export { APIPromise, Page, PagePromise, type APIResponse, } from "./api-promise.ts";
|
|
3
|
-
export type { ClientOptions, ConditionalRequestOptions, RequestOptions, } from "./core.ts";
|
|
3
|
+
export type { ClientOptions, ConditionalRequestOptions, RequestOptions, RetryEvent, } from "./core.ts";
|
|
4
4
|
export { APIError, AbortError, AuthenticationError, AuthorizationError, ConflictError, ConnectionError, InternalServerError, InvalidRequestError, NoheadError, NotFoundError, PlanLimitExceededError, PreconditionFailedError, RateLimitError, ServiceUnavailableError, TimeoutError, UploadError, ValidationError, WebhookVerificationError, } from "./errors.ts";
|
|
5
5
|
export type { Uploadable, UploadParams } from "./resources/assets.ts";
|
|
6
6
|
export type * from "./types.ts";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { APIPromise, PagePromise } from "../api-promise.ts";
|
|
2
2
|
import type { RequestOptions } from "../core.ts";
|
|
3
3
|
import type { Asset, AssetUpload, AssetUploadCreate, DownloadUrl, ImageUrl } from "../generated/types.gen.ts";
|
|
4
|
-
import type {
|
|
4
|
+
import type { AssetListParams, ImageUrlParams } from "../types.ts";
|
|
5
5
|
import { Resource } from "./resource.ts";
|
|
6
6
|
/** What `assets.upload` accepts. A stream needs `byte_size`. */
|
|
7
7
|
export type Uploadable = Blob | ArrayBuffer | Uint8Array | ReadableStream;
|
|
@@ -21,6 +21,10 @@ export declare class Assets extends Resource {
|
|
|
21
21
|
* file). Throws `UploadError` if storage refuses the bytes, and
|
|
22
22
|
* `ValidationError` if the file fails the checks.
|
|
23
23
|
*
|
|
24
|
+
* With an `idempotencyKey`, running the same upload again resumes it: the
|
|
25
|
+
* key starts the upload, and an asset that is already `ready` is returned
|
|
26
|
+
* without sending the bytes again.
|
|
27
|
+
*
|
|
24
28
|
* import { openAsBlob } from "node:fs"
|
|
25
29
|
* const asset = await nohead.assets.upload(await openAsBlob("a.jpg"), { filename: "a.jpg" })
|
|
26
30
|
*/
|
|
@@ -32,7 +36,7 @@ export declare class Assets extends Resource {
|
|
|
32
36
|
createUpload(params: AssetUploadCreate, options?: RequestOptions): APIPromise<AssetUpload>;
|
|
33
37
|
/** Checks an uploaded file and marks the asset `ready`. */
|
|
34
38
|
complete(asset: string, options?: RequestOptions): APIPromise<Asset>;
|
|
35
|
-
list(params?:
|
|
39
|
+
list(params?: AssetListParams, options?: RequestOptions): PagePromise<Asset>;
|
|
36
40
|
get(asset: string, options?: RequestOptions): APIPromise<Asset>;
|
|
37
41
|
/** Soft-deletes the asset; the file is purged after 30 days. */
|
|
38
42
|
delete(asset: string, options?: RequestOptions): APIPromise<Asset>;
|
package/dist/resources/assets.js
CHANGED
|
@@ -8,6 +8,10 @@ export class Assets extends Resource {
|
|
|
8
8
|
* file). Throws `UploadError` if storage refuses the bytes, and
|
|
9
9
|
* `ValidationError` if the file fails the checks.
|
|
10
10
|
*
|
|
11
|
+
* With an `idempotencyKey`, running the same upload again resumes it: the
|
|
12
|
+
* key starts the upload, and an asset that is already `ready` is returned
|
|
13
|
+
* without sending the bytes again.
|
|
14
|
+
*
|
|
11
15
|
* import { openAsBlob } from "node:fs"
|
|
12
16
|
* const asset = await nohead.assets.upload(await openAsBlob("a.jpg"), { filename: "a.jpg" })
|
|
13
17
|
*/
|
|
@@ -25,6 +29,16 @@ export class Assets extends Resource {
|
|
|
25
29
|
((file instanceof Blob && file.type) || "application/octet-stream"),
|
|
26
30
|
byte_size: byteSize,
|
|
27
31
|
}, options);
|
|
32
|
+
// A replayed start returns the first run's asset, which may be done.
|
|
33
|
+
if (options?.idempotencyKey) {
|
|
34
|
+
const current = await this.get(asset.id, {
|
|
35
|
+
...options,
|
|
36
|
+
idempotencyKey: undefined,
|
|
37
|
+
changeNote: undefined,
|
|
38
|
+
});
|
|
39
|
+
if (current.status === "ready")
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
28
42
|
const stream = file instanceof ReadableStream;
|
|
29
43
|
const response = await this.core
|
|
30
44
|
.fetchWithRetries(upload.url, {
|
|
@@ -41,7 +55,8 @@ export class Assets extends Resource {
|
|
|
41
55
|
if (!response.ok) {
|
|
42
56
|
throw new UploadError(`Storage refused the upload of ${asset.filename} (${response.status})`, response.status);
|
|
43
57
|
}
|
|
44
|
-
|
|
58
|
+
// The key belongs to the start: the API refuses one key on two requests.
|
|
59
|
+
return this.complete(asset.id, { ...options, idempotencyKey: undefined });
|
|
45
60
|
}
|
|
46
61
|
/**
|
|
47
62
|
* The first step of an upload: a `pending` asset and a presigned URL to
|
package/dist/types.d.ts
CHANGED
|
@@ -82,6 +82,14 @@ export interface DeletedListParams extends ListParams {
|
|
|
82
82
|
/** List deleted items instead (they can be restored for 30 days). */
|
|
83
83
|
deleted?: boolean;
|
|
84
84
|
}
|
|
85
|
+
export interface AssetListParams extends DeletedListParams {
|
|
86
|
+
/**
|
|
87
|
+
* Only assets of these MIME types or `type/*` wildcards, e.g.
|
|
88
|
+
* `["image/*", "application/pdf"]`, such as an asset field's
|
|
89
|
+
* `accepted_types`.
|
|
90
|
+
*/
|
|
91
|
+
content_type?: string | string[];
|
|
92
|
+
}
|
|
85
93
|
export interface SchemaParams {
|
|
86
94
|
/** A past schema version; the current one otherwise. */
|
|
87
95
|
version?: number;
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.2.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = "0.
|
|
1
|
+
export const VERSION = "0.2.0";
|