@calmlens/sdk 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -37
- package/cjs/CalmLensConnection-Bth5fJyp.js +234 -0
- package/cjs/CalmLensUtils-DNdmYeX6.js +699 -0
- package/cjs/{CalmLensError-CKwMKV0S.js → CalmLensVersion-Bs0DreBV.js} +114 -1
- package/cjs/{CalmLensWebhooks-BTPxKvcD.js → CalmLensWebhooks-6thkvr_F.js} +9 -9
- package/cjs/WebhookPayload-vy8HNMaK.d.ts +15834 -0
- package/cjs/app.d.ts +3 -3
- package/cjs/app.js +10 -10
- package/cjs/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
- package/cjs/index.d.ts +3 -3
- package/cjs/index.js +4 -5
- package/cjs/server.d.ts +16 -7
- package/cjs/server.js +35 -21
- package/cjs/testing.d.ts +21 -42
- package/cjs/testing.js +181 -94
- package/esm/CalmLensConnection-DrCOL9h9.js +193 -0
- package/esm/{CalmLensUtils-Cx32j2zt.js → CalmLensUtils-BQo79TA1.js} +316 -2
- package/esm/{CalmLensError-CihGG26K.js → CalmLensVersion-DPnL_Ebv.js} +91 -2
- package/esm/{CalmLensWebhooks-DShmAal4.js → CalmLensWebhooks-CWlD-f_P.js} +2 -2
- package/esm/WebhookPayload-vy8HNMaK.d.ts +15834 -0
- package/esm/app.d.ts +3 -3
- package/esm/app.js +4 -4
- package/esm/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
- package/esm/index.d.ts +3 -3
- package/esm/index.js +1 -3
- package/esm/server.d.ts +16 -7
- package/esm/server.js +24 -10
- package/esm/testing.d.ts +21 -42
- package/esm/testing.js +152 -65
- package/package.json +1 -1
- package/cjs/CalmLensConnection-CYtBP-vu.js +0 -516
- package/cjs/CalmLensUtils-Dg3s4loH.js +0 -367
- package/cjs/WebhookPayload-Cgb32aSH.d.ts +0 -10187
- package/cjs/src-Dsc8Pn6v.js +0 -45
- package/esm/CalmLensConnection-wLNy0OIP.js +0 -475
- package/esm/WebhookPayload-Cgb32aSH.d.ts +0 -10187
- package/esm/src-CJhrILvM.js +0 -22
package/README.md
CHANGED
|
@@ -38,8 +38,8 @@ const client = new CalmLensServerClient({
|
|
|
38
38
|
// Text (comments, captions, bios): waits for the decision
|
|
39
39
|
const comment = await client.text.moderate({ text: "Great photo!", externalId: "comment-42" });
|
|
40
40
|
|
|
41
|
-
if (comment.status === "approved") {
|
|
42
|
-
// publish it; if comment.
|
|
41
|
+
if (comment.moderation.status === "approved") {
|
|
42
|
+
// publish it; if comment.moderation.action is "alert", also queue it for review
|
|
43
43
|
} else {
|
|
44
44
|
// "rejected": don't publish it
|
|
45
45
|
}
|
|
@@ -50,16 +50,19 @@ const image = await client.assets.create({
|
|
|
50
50
|
content: { data: file, type: "image/jpeg" },
|
|
51
51
|
externalId: "post-7",
|
|
52
52
|
});
|
|
53
|
-
console.log(image.status); // "pending"
|
|
53
|
+
console.log(image.processing.status, image.moderation.status); // "processing" "pending"
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
| `status` | `
|
|
57
|
-
| --- | --- | --- |
|
|
58
|
-
| `approved` | `
|
|
59
|
-
| `approved` | `
|
|
60
|
-
| `rejected` | `
|
|
61
|
-
| `pending` |
|
|
62
|
-
| `
|
|
56
|
+
| `processing.status` | `moderation.status` | `moderation.action` | Meaning |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `done` | `approved` | `null` | Safe to show. |
|
|
59
|
+
| `done` | `approved` | `alert` | Show it, but your policy asked to be alerted. |
|
|
60
|
+
| `done` | `rejected` | `hide` or `purge` | Your policy hid or purged it. Don't show it. |
|
|
61
|
+
| `uploading` or `processing` | `pending` | `null` | Still being received or classified. |
|
|
62
|
+
| `failed` | `pending` | `null` | Processing failed; see `assets.retry`. |
|
|
63
|
+
|
|
64
|
+
A reviewer's decision (`moderation.reviewerAction`) takes precedence over the automated one in
|
|
65
|
+
`moderation.status`; `moderation.action` keeps what your policy did.
|
|
63
66
|
|
|
64
67
|
See [How CalmLens works](https://calmlens.com/docs/how-it-works) and [Testing your integration](https://calmlens.com/docs/guides/testing) for test inputs that trigger rejections safely.
|
|
65
68
|
|
|
@@ -146,16 +149,18 @@ Both resolve to the stored `Asset`, a plain object with camelCase keys exactly a
|
|
|
146
149
|
```typescript
|
|
147
150
|
const asset = await client.text.moderate({ text: "Great photo!", externalId: "comment-42" });
|
|
148
151
|
asset.id; // string
|
|
149
|
-
asset.status;
|
|
150
|
-
asset.
|
|
151
|
-
asset.
|
|
152
|
-
asset.report?.score; // number, the overall score
|
|
153
|
-
asset.report?.categories.harassment.status; // "evaluated" | "partial" | "not-evaluated"
|
|
154
|
-
asset.
|
|
155
|
-
asset.
|
|
152
|
+
asset.processing.status; // "uploading" | "processing" | "done" | "failed"; audio and video keep processing in the background
|
|
153
|
+
asset.moderation.status; // "pending" | "approved" | "rejected"
|
|
154
|
+
asset.moderation.action; // "alert" | "hide" | "purge", what your policy did with flagged content; null when nothing was flagged
|
|
155
|
+
asset.moderation.report?.score; // number, the overall score
|
|
156
|
+
asset.moderation.report?.categories.harassment.status; // "evaluated" | "partial" | "not-evaluated"
|
|
157
|
+
asset.content.status; // "temporary" | "stored" | "removed"
|
|
158
|
+
asset.content.removalReason; // "policy" | "retention" | "requested" | "parent-removed" | "disposed" | null
|
|
159
|
+
asset.type; // "plaintext-document" for text
|
|
160
|
+
asset.createdAt; // milliseconds since the Unix epoch
|
|
156
161
|
```
|
|
157
162
|
|
|
158
|
-
Always present: `id`, `projectId`, `name`, `type`, `
|
|
163
|
+
Always present: `id`, `projectId`, `name`, `type`, `createdAt`, `updatedAt`, `processing`, `moderation` and `content` (with their `status` fields). Other fields may be `null`: `moderation.report` until classification finishes (and when processing failed), `moderation.action` when nothing was flagged, the `moderation.reviewer*` fields until a reviewer decides, `externalId`, `description`, `metadata`, `folderId`, `visibility` and `processingOptions` when you did not set them, and `content.format`, `content.sizeInBytes`, `content.image`, `content.previewImage` and `content.extractedText` when they do not apply to the content. Content is `temporary` until processing finishes and then `removed` (reason `retention`) unless you set `processingOptions.keepContentAfterProcessing`. Inside a report, a category's `score` and `likelihood` exist only when its `status` is `evaluated` or `partial` (see [Moderation Reports](#moderation-reports)).
|
|
159
164
|
|
|
160
165
|
##### assets.retrieve(options)
|
|
161
166
|
|
|
@@ -168,8 +173,8 @@ async assets.retrieve(options: { assetId: string }): Promise<Asset>
|
|
|
168
173
|
**Example:**
|
|
169
174
|
```typescript
|
|
170
175
|
const asset = await client.assets.retrieve({ assetId: "asset-uuid-here" });
|
|
171
|
-
console.log(asset.status);
|
|
172
|
-
console.log(asset.report); // Classification results
|
|
176
|
+
console.log(asset.moderation.status);
|
|
177
|
+
console.log(asset.moderation.report); // Classification results
|
|
173
178
|
```
|
|
174
179
|
|
|
175
180
|
##### ensureLens(options)
|
|
@@ -197,7 +202,7 @@ const image = await client.cdn.serve({
|
|
|
197
202
|
});
|
|
198
203
|
```
|
|
199
204
|
|
|
200
|
-
For several lenses, use `ensureLenses()` to
|
|
205
|
+
For several lenses, use `ensureLenses()` to ensure them together: new definitions go in one request, or in consecutive requests of up to 100 each, in order. Each lens keeps the same independent versioning rules.
|
|
201
206
|
|
|
202
207
|
```typescript
|
|
203
208
|
const [normal, uhd] = await client.ensureLenses({ lenses: [normalLens, uhdLens] });
|
|
@@ -233,14 +238,14 @@ const result = await client.assets.list({
|
|
|
233
238
|
|
|
234
239
|
console.log(`Found ${result.total} assets`);
|
|
235
240
|
result.items.forEach(asset => {
|
|
236
|
-
console.log(`${asset.name}: ${asset.status}`);
|
|
241
|
+
console.log(`${asset.name}: ${asset.moderation.status}`);
|
|
237
242
|
});
|
|
238
243
|
```
|
|
239
244
|
|
|
240
245
|
To read every matching asset, iterate instead of paging by hand. Pages are fetched as you go; assets created or deleted meanwhile can shift pages, so one may be skipped or repeated.
|
|
241
246
|
|
|
242
247
|
```typescript
|
|
243
|
-
for await (const asset of client.assets.iterate({ filter: "status==rejected", pageSize: 100 })) {
|
|
248
|
+
for await (const asset of client.assets.iterate({ filter: "moderation.status==rejected", pageSize: 100 })) {
|
|
244
249
|
console.log(asset.id);
|
|
245
250
|
}
|
|
246
251
|
```
|
|
@@ -294,13 +299,26 @@ await client.assets.move({
|
|
|
294
299
|
|
|
295
300
|
##### assets.retry(options)
|
|
296
301
|
|
|
297
|
-
Process an asset
|
|
298
|
-
new upload, so handle the result the same way (webhook or polling). Its content must still be stored:
|
|
302
|
+
Process an asset whose processing failed (`processing.status` `failed`) again. It returns to
|
|
303
|
+
`processing` and is moderated like a new upload, so handle the result the same way (webhook or polling). Its content must still be stored:
|
|
299
304
|
otherwise this throws `asset/content-unavailable`, and you need to upload it again.
|
|
300
305
|
|
|
301
306
|
```typescript
|
|
302
307
|
const asset = await client.assets.retry({ assetId: "asset-uuid" });
|
|
303
|
-
// asset.status === "
|
|
308
|
+
// asset.processing.status === "processing"
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
##### assets.waitForDecision(options)
|
|
312
|
+
|
|
313
|
+
Wait for an asset's moderation decision and get the decided asset: it returns once `moderation.status`
|
|
314
|
+
is `approved` or `rejected`, or `processing.status` is `failed`. The SDK checks the asset every few seconds and rejects with `sdk/timeout` if there's no decision within
|
|
315
|
+
`timeoutMs` (default 60000). Pass `signal` to stop waiting early (`sdk/cancelled`). Use it after
|
|
316
|
+
`assets.create`, or with an asset ID you stored earlier. Webhooks report decisions without polling.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
const queued = await client.assets.create({ name: "photo.jpg", content: { blob, type: "image/jpeg" } });
|
|
320
|
+
const decided = await client.assets.waitForDecision({ assetId: queued.id, timeoutMs: 30_000 });
|
|
321
|
+
if (decided.moderation.status === "rejected") hidePhoto(decided.id);
|
|
304
322
|
```
|
|
305
323
|
|
|
306
324
|
##### folders.list()
|
|
@@ -448,7 +466,7 @@ const analyzed = await client.assets.moderate({
|
|
|
448
466
|
isFinal: true
|
|
449
467
|
}
|
|
450
468
|
});
|
|
451
|
-
console.log(analyzed.status);
|
|
469
|
+
console.log(analyzed.moderation.status);
|
|
452
470
|
```
|
|
453
471
|
|
|
454
472
|
#### Static Methods
|
|
@@ -486,7 +504,7 @@ const client = new CalmLensAppClient({
|
|
|
486
504
|
});
|
|
487
505
|
|
|
488
506
|
const asset = await client.assets.moderate({ name: file.name, content: { data: file, type: file.type } });
|
|
489
|
-
// asset.status is present when the preset shows decisions to uploaders
|
|
507
|
+
// asset.status (the moderation status) is present when the preset shows decisions to uploaders
|
|
490
508
|
```
|
|
491
509
|
|
|
492
510
|
**Options:** `uploadPresetId` (required), `token` (a string, or a function that returns one or a promise of one), plus `baseUrl`, `fetch`, `uploadFetch`, `timeoutMs`, `uploadTimeoutMs`, `maxRetries`, `maxResponseBytes` and `signal` as for `CalmLensServerClient`. Passing `apiKey` throws `sdk/invalid-config`.
|
|
@@ -520,12 +538,11 @@ Assets can be of the following types:
|
|
|
520
538
|
|
|
521
539
|
## Asset Status
|
|
522
540
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
- `
|
|
526
|
-
- `approved`
|
|
527
|
-
- `
|
|
528
|
-
- `error` - Processing failed
|
|
541
|
+
An asset's state is split in three:
|
|
542
|
+
|
|
543
|
+
- `processing.status`: `uploading` (a multipart upload is accepting more parts), `processing`, then `done` or `failed`
|
|
544
|
+
- `moderation.status`: `pending` until moderation finishes, then `approved` or `rejected`. A reviewer's decision (`moderation.reviewerAction`) takes precedence over the automated one. `moderation.action` is what your policy did with flagged content (`alert`, `hide` or `purge`), or `null` when nothing was flagged; an `alert` leaves the asset `approved`.
|
|
545
|
+
- `content.status`: `temporary` (kept only until processing finishes), `stored` or `removed`, with `content.removalReason` `policy` (purged), `retention` (`keepContentAfterProcessing` off), `requested`, `parent-removed` or `disposed`
|
|
529
546
|
|
|
530
547
|
## Moderation Reports
|
|
531
548
|
|
|
@@ -610,7 +627,7 @@ The API keeps a response for 24 hours. Reusing a key for a different request fai
|
|
|
610
627
|
|
|
611
628
|
## Forward compatibility
|
|
612
629
|
|
|
613
|
-
The SDK does not validate responses against its types, so fields and values added to the API later reach your code unchanged. `
|
|
630
|
+
The SDK does not validate responses against its types, so fields and values added to the API later reach your code unchanged. `AssetProcessingStatus`, `AssetModerationStatus`, `AssetModerationAction`, `AssetReviewerAction`, `AssetContentStatus`, `AssetContentRemovalReason`, `AssetType` and `AssetVisibility` include `string` for that reason: handle values you don't know (for example, treat an unknown status as "not ready"). Event types and fact keys are typed for the ones this version knows; keep a default branch when you switch on them.
|
|
614
631
|
|
|
615
632
|
## Testing
|
|
616
633
|
|
|
@@ -629,7 +646,7 @@ const client = new MockCalmLensServerClient({
|
|
|
629
646
|
|
|
630
647
|
`MockCalmLensServerClient` accepts the same options as `CalmLensServerClient` (`apiKey`, `projectId`, `baseUrl`, `cdnBaseUrl`, `timeoutMs`, ...) and validates them the same way, plus `mockOptions`. `cdn.getAssetUrl` uses `cdnBaseUrl` (default `mock://calmlens/v1`).
|
|
631
648
|
|
|
632
|
-
It behaves like the API where tests depend on it: errors carry the API's code, HTTP `status`, a `requestId` and `headers`; `assets.list` applies `ordering`, `folderId` and simple `filter` expressions (comparisons joined by `;` and `,`; anything else throws `sdk/invalid-argument` rather than returning wrong results); `assets.search` matches names, IDs, external IDs, descriptions and tags; multipart parts must arrive in order and the queue mode chosen at the start applies to the final part; a reused `idempotencyKey` replays the first result; and `cdn.serve` returns CDN-like responses with JSON error bodies. `client.enqueueEvent({ type, ... })` adds an event that `events.list` and `events.pages` then return.
|
|
649
|
+
It behaves like the API where tests depend on it: errors carry the API's code, HTTP `status`, a `requestId` and `headers`; `assets.list` applies `ordering`, `folderId` and simple `filter` expressions (comparisons joined by `;` and `,`; anything else throws `sdk/invalid-argument` rather than returning wrong results); `assets.search` matches names, IDs, external IDs, descriptions and tags; multipart parts must arrive in order and the queue mode chosen at the start applies to the final part; `mockOptions.queuedOutcome` (default `pending`) and `mockOptions.analyzedOutcome` (default `approved`) set where queued and waited-for uploads end up (`pending`, `approved`, `rejected` or `failed`); content is removed with reason `retention` once processing is done unless `keepContentAfterProcessing` is set, and `assets.deleteContent` removes it with reason `requested`; a reused `idempotencyKey` replays the first result; and `cdn.serve` returns CDN-like responses with JSON error bodies. `client.enqueueEvent({ type, ... })` adds an event that `events.list` and `events.pages` then return.
|
|
633
650
|
|
|
634
651
|
Every call is recorded in `client.calls` as `{ operation, options }`, where `operation` is the method path (`"text.moderate"`, `"assets.retrieve"`, `"events.list"`, ...) and `options` is the object you passed. `client.callsTo(operation)` filters them. To test failure handling, `client.failNextRequest()` makes the next call throw the same `CalmLensError` the real client throws when the network fails (`sdk/transport-error`); pass `"timeout"` for `sdk/timeout`, or any `Error` to throw it as is, and `{ operation }` to fail only that method. Each queued failure is used once.
|
|
635
652
|
|
|
@@ -648,7 +665,7 @@ To test a webhook handler, build a signed delivery the way CalmLens sends it:
|
|
|
648
665
|
```typescript
|
|
649
666
|
import { createWebhookHeaders, toWebhookEventPayload } from "@calmlens/sdk/testing";
|
|
650
667
|
|
|
651
|
-
const event = client.enqueueEvent({ type: "asset.
|
|
668
|
+
const event = client.enqueueEvent({ type: "asset.approved", assetId: asset.id });
|
|
652
669
|
const body = JSON.stringify(toWebhookEventPayload(event));
|
|
653
670
|
const headers = await createWebhookHeaders({ secret, body, deliveryId: "delivery-1" });
|
|
654
671
|
const response = await app.request("/api/webhooks/calmlens", { method: "POST", body, headers });
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const require_CalmLensVersion = require("./CalmLensVersion-Bs0DreBV.js");
|
|
2
|
+
const require_CalmLensUtils = require("./CalmLensUtils-DNdmYeX6.js");
|
|
3
|
+
//#region src/client/CalmLensConnection.ts
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 33554432;
|
|
7
|
+
/**
|
|
8
|
+
* Bytes as a fetch body. They're sent as they are rather than in a Blob: React Native can't
|
|
9
|
+
* build a Blob from bytes, and every fetch accepts a Uint8Array.
|
|
10
|
+
*/
|
|
11
|
+
const toBodyBytes = (value) => value.buffer instanceof ArrayBuffer ? value : Uint8Array.from(value);
|
|
12
|
+
const toRawUploadBody = (payload) => {
|
|
13
|
+
const data = require_CalmLensUtils.assetToData(payload);
|
|
14
|
+
if (data instanceof Response) {
|
|
15
|
+
if (!data.body) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Response upload data must have a body" });
|
|
16
|
+
return data.body;
|
|
17
|
+
}
|
|
18
|
+
if (data instanceof ArrayBuffer || data instanceof Blob) return data;
|
|
19
|
+
if ("getReader" in data) return data;
|
|
20
|
+
return toBodyBytes(data);
|
|
21
|
+
};
|
|
22
|
+
const setOptionalQueryValue = (params, key, value) => {
|
|
23
|
+
if (value !== void 0 && value !== null) params.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
24
|
+
};
|
|
25
|
+
const assertValidUploadMimeType = (mimeType) => {
|
|
26
|
+
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
27
|
+
message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
|
|
28
|
+
details: { mimeType }
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const isImageMimeType = (mimeType) => mimeType.toLowerCase().startsWith("image/");
|
|
32
|
+
const invalidPayload = (message) => new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
33
|
+
/** Checks upload options before anything is sent. */
|
|
34
|
+
const prepareSubmitAssetOptions = (options) => {
|
|
35
|
+
if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidArgument("Asset options are required");
|
|
36
|
+
if (!("content" in options) && !("url" in options) && !("part" in options)) throw invalidPayload("Either file content, URL, or multipart part is required");
|
|
37
|
+
if (!options.name) throw invalidPayload("Asset name is required");
|
|
38
|
+
if (!("part" in options) && "isFinal" in options) throw invalidPayload("isFinal is only valid for multipart uploads");
|
|
39
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.USE_UPLOAD_PART, { message: "Use uploadPart for multipart upload parts after the first part" });
|
|
40
|
+
if ("content" in options) {
|
|
41
|
+
assertValidUploadMimeType(options.content.type);
|
|
42
|
+
if (!("plainText" in options.content)) {
|
|
43
|
+
const validation = require_CalmLensUtils.validateAssetPayload(options.content);
|
|
44
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if ("part" in options) {
|
|
48
|
+
assertValidUploadMimeType(options.part.type);
|
|
49
|
+
const partSizeInBytes = options.part.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(options.part);
|
|
50
|
+
if (isImageMimeType(options.part.type) && partSizeInBytes !== void 0) {
|
|
51
|
+
if (partSizeInBytes > require_CalmLensUtils.MAX_IMAGE_UPLOAD_BYTES) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE, { message: "Multipart image uploads cannot exceed 20MB." });
|
|
52
|
+
if (options.part.isFinal && partSizeInBytes < require_CalmLensUtils.MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.API_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL, { message: "Multipart image uploads must total at least 10MB." });
|
|
53
|
+
}
|
|
54
|
+
const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
|
|
55
|
+
multipart: true,
|
|
56
|
+
isFinal: options.part.isFinal
|
|
57
|
+
});
|
|
58
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
59
|
+
}
|
|
60
|
+
return options;
|
|
61
|
+
};
|
|
62
|
+
const newIdempotencyKey = () => require_CalmLensUtils.randomUuid();
|
|
63
|
+
/** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
|
|
64
|
+
const isTextPayload = (payload) => "plainText" in payload;
|
|
65
|
+
const hasStringStatus = (value) => !!value && typeof value === "object" && typeof value.status === "string";
|
|
66
|
+
const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && hasStringStatus(data.processing) && hasStringStatus(data.moderation);
|
|
67
|
+
const isUploadedAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id;
|
|
68
|
+
const expectUploadedAsset = (data) => {
|
|
69
|
+
if (!isUploadedAsset(data)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
70
|
+
message: "Expected an uploaded asset response",
|
|
71
|
+
status: 200
|
|
72
|
+
});
|
|
73
|
+
return data;
|
|
74
|
+
};
|
|
75
|
+
const expectAsset = (data) => {
|
|
76
|
+
if (!isAsset(data)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
77
|
+
message: "Expected an asset response",
|
|
78
|
+
status: 200
|
|
79
|
+
});
|
|
80
|
+
return data;
|
|
81
|
+
};
|
|
82
|
+
/** Fills in the default API base URL and drops trailing slashes. */
|
|
83
|
+
const resolveConnectionOptions = (options) => ({
|
|
84
|
+
...options,
|
|
85
|
+
baseUrl: (options.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, "")
|
|
86
|
+
});
|
|
87
|
+
/** One client's transport, base URL and deadlines, and the requests both clients make. */
|
|
88
|
+
var CalmLensConnection = class {
|
|
89
|
+
constructor(options, apiKey) {
|
|
90
|
+
this.options = options;
|
|
91
|
+
const resolveFetch = () => {
|
|
92
|
+
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
93
|
+
if (typeof fetchImpl !== "function") throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.NO_FETCH, { message: "No fetch implementation is available; pass one as the fetch option" });
|
|
94
|
+
return fetchImpl;
|
|
95
|
+
};
|
|
96
|
+
this.transport = new require_CalmLensUtils.CalmLensTransport({
|
|
97
|
+
apiKey,
|
|
98
|
+
fetch: ((input, init) => resolveFetch()(input, init)),
|
|
99
|
+
signal: options.signal,
|
|
100
|
+
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
101
|
+
maxResponseBytes: options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
get timeoutMs() {
|
|
105
|
+
return this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
106
|
+
}
|
|
107
|
+
url(path, query) {
|
|
108
|
+
const search = query ? require_CalmLensUtils.stringifyQuery(query) : "";
|
|
109
|
+
return `${this.options.baseUrl}${path}${search ? `?${search}` : ""}`;
|
|
110
|
+
}
|
|
111
|
+
/** A JSON request whose deadline covers reading the response. */
|
|
112
|
+
json(method, path, { query, body, headers, idempotencyKey } = {}) {
|
|
113
|
+
return this.transport.json({
|
|
114
|
+
method,
|
|
115
|
+
url: this.url(path, query),
|
|
116
|
+
headers: body === void 0 ? headers : {
|
|
117
|
+
...headers,
|
|
118
|
+
"Content-Type": "application/json"
|
|
119
|
+
},
|
|
120
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
121
|
+
idempotencyKey,
|
|
122
|
+
timeoutMs: this.timeoutMs,
|
|
123
|
+
deadline: "body"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
upload(url, payload, headers, idempotencyKey) {
|
|
127
|
+
return this.transport.json({
|
|
128
|
+
method: "POST",
|
|
129
|
+
url,
|
|
130
|
+
headers,
|
|
131
|
+
body: toRawUploadBody(payload),
|
|
132
|
+
idempotencyKey,
|
|
133
|
+
timeoutMs: isTextPayload(payload) ? this.timeoutMs : this.options.uploadTimeoutMs,
|
|
134
|
+
deadline: "body",
|
|
135
|
+
fetch: this.options.uploadFetch
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Uploads file content or a first multipart part to `uploadsPath` (`.../assets`), as raw
|
|
140
|
+
* bytes with the options in the query.
|
|
141
|
+
*/
|
|
142
|
+
submitRawUpload(uploadsPath, options, queue, authHeaders = {}) {
|
|
143
|
+
const query = new URLSearchParams();
|
|
144
|
+
query.set("name", options.name);
|
|
145
|
+
query.set("queue", String(queue));
|
|
146
|
+
setOptionalQueryValue(query, "description", options.description);
|
|
147
|
+
setOptionalQueryValue(query, "processingOptions", options.processingOptions);
|
|
148
|
+
setOptionalQueryValue(query, "visibility", options.visibility);
|
|
149
|
+
setOptionalQueryValue(query, "metadata", options.metadata);
|
|
150
|
+
setOptionalQueryValue(query, "externalId", options.externalId);
|
|
151
|
+
setOptionalQueryValue(query, "externalUrl", options.externalUrl);
|
|
152
|
+
setOptionalQueryValue(query, "folderId", options.folderId);
|
|
153
|
+
setOptionalQueryValue(query, "uploadPresetId", options.uploadPresetId);
|
|
154
|
+
const isMultipart = "part" in options;
|
|
155
|
+
const payload = isMultipart ? options.part : options.content;
|
|
156
|
+
query.set("contentType", payload.type);
|
|
157
|
+
setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(payload));
|
|
158
|
+
if (isMultipart) {
|
|
159
|
+
const part = payload;
|
|
160
|
+
const multipartOptions = options;
|
|
161
|
+
query.set("partNumber", String(part.partNumber ?? 1));
|
|
162
|
+
query.set("isFinal", String(part.isFinal ?? false));
|
|
163
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
|
|
164
|
+
setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
|
|
165
|
+
}
|
|
166
|
+
return this.upload(this.url(`${uploadsPath}?${query}`), payload, {
|
|
167
|
+
...authHeaders,
|
|
168
|
+
"Content-Type": payload.type,
|
|
169
|
+
"X-CalmLens-Upload": isMultipart ? "part" : "content"
|
|
170
|
+
}, options.idempotencyKey ?? newIdempotencyKey());
|
|
171
|
+
}
|
|
172
|
+
/** Uploads the next part of a multipart upload to `partPath` (`.../assets/{id}/part`). */
|
|
173
|
+
uploadPart(partPath, { part, queue, idempotencyKey }, authHeaders = {}) {
|
|
174
|
+
if (!part) throw require_CalmLensVersion.invalidArgument("Part options are required");
|
|
175
|
+
const validation = require_CalmLensUtils.validateAssetPayload(part, {
|
|
176
|
+
multipart: true,
|
|
177
|
+
isFinal: part.isFinal
|
|
178
|
+
});
|
|
179
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
180
|
+
const query = new URLSearchParams({
|
|
181
|
+
partNumber: String(part.partNumber),
|
|
182
|
+
isFinal: String(part.isFinal ?? false)
|
|
183
|
+
});
|
|
184
|
+
if (queue !== void 0) query.set("queue", String(queue));
|
|
185
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(part));
|
|
186
|
+
return this.upload(this.url(`${partPath}?${query}`), part, {
|
|
187
|
+
...authHeaders,
|
|
188
|
+
"X-CalmLens-Upload": "part"
|
|
189
|
+
}, idempotencyKey ?? newIdempotencyKey());
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
//#endregion
|
|
193
|
+
Object.defineProperty(exports, "CalmLensConnection", {
|
|
194
|
+
enumerable: true,
|
|
195
|
+
get: function() {
|
|
196
|
+
return CalmLensConnection;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
Object.defineProperty(exports, "expectAsset", {
|
|
200
|
+
enumerable: true,
|
|
201
|
+
get: function() {
|
|
202
|
+
return expectAsset;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
Object.defineProperty(exports, "expectUploadedAsset", {
|
|
206
|
+
enumerable: true,
|
|
207
|
+
get: function() {
|
|
208
|
+
return expectUploadedAsset;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
Object.defineProperty(exports, "invalidPayload", {
|
|
212
|
+
enumerable: true,
|
|
213
|
+
get: function() {
|
|
214
|
+
return invalidPayload;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
Object.defineProperty(exports, "newIdempotencyKey", {
|
|
218
|
+
enumerable: true,
|
|
219
|
+
get: function() {
|
|
220
|
+
return newIdempotencyKey;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
Object.defineProperty(exports, "prepareSubmitAssetOptions", {
|
|
224
|
+
enumerable: true,
|
|
225
|
+
get: function() {
|
|
226
|
+
return prepareSubmitAssetOptions;
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
Object.defineProperty(exports, "resolveConnectionOptions", {
|
|
230
|
+
enumerable: true,
|
|
231
|
+
get: function() {
|
|
232
|
+
return resolveConnectionOptions;
|
|
233
|
+
}
|
|
234
|
+
});
|