@calmlens/sdk 0.13.0 → 0.15.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.
Files changed (30) hide show
  1. package/README.md +44 -40
  2. package/cjs/{CalmLensConnection-DzRDCMOP.js → CalmLensConnection-DoemramF.js} +4 -3
  3. package/cjs/{CalmLensUtils-vX5H_vMn.js → CalmLensUtils-Cv1Ylp9-.js} +10 -10
  4. package/cjs/{CalmLensVersion-DjhsJ1gj.js → CalmLensVersion-CuxKdijk.js} +67 -2
  5. package/cjs/{CalmLensWebhooks-hVquX5L1.js → CalmLensWebhooks-qrL-Zdu3.js} +2 -2
  6. package/cjs/{WebhookPayload-B7CgSmI5.d.ts → WebhookPayload-vy8HNMaK.d.ts} +4355 -449
  7. package/cjs/app.d.ts +3 -3
  8. package/cjs/app.js +3 -3
  9. package/cjs/{index-BywTQbRq.d.ts → index-Dz340UQ9.d.ts} +27 -1
  10. package/cjs/index.d.ts +3 -3
  11. package/cjs/index.js +1 -1
  12. package/cjs/server.d.ts +9 -8
  13. package/cjs/server.js +10 -9
  14. package/cjs/testing.d.ts +19 -45
  15. package/cjs/testing.js +99 -30
  16. package/esm/{CalmLensConnection-D8VsTdsA.js → CalmLensConnection-CQLoUoBd.js} +4 -3
  17. package/esm/{CalmLensUtils-B4i3Z2He.js → CalmLensUtils-B3S--hBG.js} +10 -10
  18. package/esm/{CalmLensVersion-DK4BMEST.js → CalmLensVersion-N71PMJAz.js} +67 -2
  19. package/esm/{CalmLensWebhooks-DyCoEvpf.js → CalmLensWebhooks-BxMSx1Vm.js} +2 -2
  20. package/esm/{WebhookPayload-B7CgSmI5.d.ts → WebhookPayload-vy8HNMaK.d.ts} +4355 -449
  21. package/esm/app.d.ts +3 -3
  22. package/esm/app.js +3 -3
  23. package/esm/{index-BywTQbRq.d.ts → index-Dz340UQ9.d.ts} +27 -1
  24. package/esm/index.d.ts +3 -3
  25. package/esm/index.js +1 -1
  26. package/esm/server.d.ts +9 -8
  27. package/esm/server.js +10 -9
  28. package/esm/testing.d.ts +19 -45
  29. package/esm/testing.js +99 -30
  30. package/package.json +1 -1
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.report?.flagged is true, also queue it for review
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` | `report.flagged` | Meaning |
57
- | --- | --- | --- |
58
- | `approved` | `false` | Safe to show. |
59
- | `approved` | `true` | Show it, but your policy asked to be alerted. |
60
- | `rejected` | `true` | Your policy hid or purged it. Don't show it. |
61
- | `pending` | — | Still being classified. |
62
- | `error` | — | Processing failed. |
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; // "approved" | "rejected" | "error"; "pending" for audio and video, which always run in the background
150
- asset.type; // "plaintext-document" for text
151
- asset.report?.flagged; // boolean
152
- asset.report?.score; // number, the overall score
153
- asset.report?.categories.harassment.status; // "evaluated" | "partial" | "not-evaluated"
154
- asset.moderationAction; // the policy action applied, if any
155
- asset.createdAt; // milliseconds since the Unix epoch
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`, `status`, `createdAt` and `updatedAt`. Every other field may be `null` or missing: `report` is absent when processing failed (`status: "error"`) or has not finished, `moderationAction` when no policy action applied, `externalId`, `description`, `metadata`, `folderId`, `visibility` and `processingOptions` when you did not set them, and `sizeInBytes`, `fileFormat`, `imageData`, `documentData`, `previewImage` and `extractedText` when they do not apply to the content. Inside a report, a category's `score` and `likelihood` exist only when its `status` is `evaluated` or `partial` (see [Moderation Reports](#moderation-reports)).
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)
@@ -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,26 +299,26 @@ await client.assets.move({
294
299
 
295
300
  ##### assets.retry(options)
296
301
 
297
- Process an asset that failed (status `error`) again. It returns to `pending` and is moderated like a
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 === "pending"
308
+ // asset.processing.status === "processing"
304
309
  ```
305
310
 
306
311
  ##### assets.waitForDecision(options)
307
312
 
308
- Wait for an asset's moderation decision and get the decided asset: `approved`, `rejected` or `error`.
309
- The SDK checks the asset every few seconds and rejects with `sdk/timeout` if there's no decision within
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
310
315
  `timeoutMs` (default 60000). Pass `signal` to stop waiting early (`sdk/cancelled`). Use it after
311
316
  `assets.create`, or with an asset ID you stored earlier. Webhooks report decisions without polling.
312
317
 
313
318
  ```typescript
314
319
  const queued = await client.assets.create({ name: "photo.jpg", content: { blob, type: "image/jpeg" } });
315
320
  const decided = await client.assets.waitForDecision({ assetId: queued.id, timeoutMs: 30_000 });
316
- if (decided.status === "rejected") hidePhoto(decided.id);
321
+ if (decided.moderation.status === "rejected") hidePhoto(decided.id);
317
322
  ```
318
323
 
319
324
  ##### folders.list()
@@ -461,7 +466,7 @@ const analyzed = await client.assets.moderate({
461
466
  isFinal: true
462
467
  }
463
468
  });
464
- console.log(analyzed.status);
469
+ console.log(analyzed.moderation.status);
465
470
  ```
466
471
 
467
472
  #### Static Methods
@@ -499,7 +504,7 @@ const client = new CalmLensAppClient({
499
504
  });
500
505
 
501
506
  const asset = await client.assets.moderate({ name: file.name, content: { data: file, type: file.type } });
502
- // 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
503
508
  ```
504
509
 
505
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`.
@@ -533,12 +538,11 @@ Assets can be of the following types:
533
538
 
534
539
  ## Asset Status
535
540
 
536
- Assets progress through these statuses:
537
- - `uploading` - Multipart upload is accepting more parts
538
- - `pending` - Asset is being processed
539
- - `approved` - Processing completed and delivery is allowed; an `alert` action can still leave `report.flagged` set to `true`
540
- - `rejected` - A hide, purge, child-asset, or manual rejection decision was applied
541
- - `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`
542
546
 
543
547
  ## Moderation Reports
544
548
 
@@ -623,11 +627,11 @@ The API keeps a response for 24 hours. Reusing a key for a different request fai
623
627
 
624
628
  ## Forward compatibility
625
629
 
626
- The SDK does not validate responses against its types, so fields and values added to the API later reach your code unchanged. `AssetStatus`, `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.
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.
627
631
 
628
632
  ## Testing
629
633
 
630
- `@calmlens/sdk/testing` exports `MockCalmLensServerClient`, an in-memory stand-in for `CalmLensServerClient`,, in-memory storage implementations, mock API key helpers, webhook delivery builders and `generateMediaTestCode`, for testing without network requests.
634
+ `@calmlens/sdk/testing` exports `MockCalmLensServerClient`, an in-memory stand-in for `CalmLensServerClient`,, in-memory storage implementations, mock API key helpers, webhook delivery builders and `generateMediaTestCode`, for testing without network requests. `generateMediaTestCode(signal, testInputKey)` returns the spoken or on-screen test phrase for your project's secret test input key, shown under **Assets → Try test inputs** in the portal; a phrase with another project's key is moderated normally.
631
635
 
632
636
  The `@calmlens/sdk`, `/server` and `/app` entry points do not load or export testing helpers, to keep production bundles small. Keep `/testing` imports in test files.
633
637
 
@@ -642,7 +646,7 @@ const client = new MockCalmLensServerClient({
642
646
 
643
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`).
644
648
 
645
- 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.
646
650
 
647
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.
648
652
 
@@ -661,7 +665,7 @@ To test a webhook handler, build a signed delivery the way CalmLens sends it:
661
665
  ```typescript
662
666
  import { createWebhookHeaders, toWebhookEventPayload } from "@calmlens/sdk/testing";
663
667
 
664
- const event = client.enqueueEvent({ type: "asset.classified", assetId: asset.id });
668
+ const event = client.enqueueEvent({ type: "asset.approved", assetId: asset.id });
665
669
  const body = JSON.stringify(toWebhookEventPayload(event));
666
670
  const headers = await createWebhookHeaders({ secret, body, deliveryId: "delivery-1" });
667
671
  const response = await app.request("/api/webhooks/calmlens", { method: "POST", body, headers });
@@ -1,5 +1,5 @@
1
- const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
2
- const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.js");
1
+ const require_CalmLensVersion = require("./CalmLensVersion-CuxKdijk.js");
2
+ const require_CalmLensUtils = require("./CalmLensUtils-Cv1Ylp9-.js");
3
3
  //#region src/client/CalmLensConnection.ts
4
4
  const DEFAULT_TIMEOUT_MS = 12e4;
5
5
  const DEFAULT_MAX_RETRIES = 2;
@@ -62,7 +62,8 @@ const prepareSubmitAssetOptions = (options) => {
62
62
  const newIdempotencyKey = () => require_CalmLensUtils.randomUuid();
63
63
  /** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
64
64
  const isTextPayload = (payload) => "plainText" in payload;
65
- const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
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);
66
67
  const isUploadedAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id;
67
68
  const expectUploadedAsset = (data) => {
68
69
  if (!isUploadedAsset(data)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
@@ -1,4 +1,4 @@
1
- const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
1
+ const require_CalmLensVersion = require("./CalmLensVersion-CuxKdijk.js");
2
2
  const MAX_IMAGE_UPLOAD_BYTES = /* @__PURE__ */ (() => 20971520)();
3
3
  const MIN_MULTIPART_IMAGE_UPLOAD_BYTES = /* @__PURE__ */ (() => 10485760)();
4
4
  const REJECTED_UPLOAD_MIME_TYPE_SET = /* @__PURE__ */ new Set([
@@ -537,12 +537,11 @@ const stringifyQuery = (query) => {
537
537
  for (const [key, value] of Object.entries(query)) appendQueryValue(parts, key, value);
538
538
  return parts.join("&");
539
539
  };
540
- /** Statuses that are a moderation decision; `assets.waitForDecision` returns at the first. */
541
- const DECIDED_ASSET_STATUSES = [
542
- "approved",
543
- "rejected",
544
- "error"
545
- ];
540
+ /**
541
+ * Whether an asset has a moderation decision (`moderation.status` `approved` or `rejected`) or
542
+ * failed processing; `assets.waitForDecision` returns at the first such state.
543
+ */
544
+ const isAssetDecided = (asset) => asset.moderation.status === "approved" || asset.moderation.status === "rejected" || asset.processing.status === "failed";
546
545
  const DECISION_POLL_FIRST_DELAY_MS = 500;
547
546
  const DECISION_POLL_MAX_DELAY_MS = 5e3;
548
547
  const DEFAULT_DECISION_TIMEOUT_MS = 6e4;
@@ -557,13 +556,14 @@ const pollForDecision = async (retrieve, { timeoutMs = DEFAULT_DECISION_TIMEOUT_
557
556
  for (;;) {
558
557
  if (signal?.aborted) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" });
559
558
  const asset = await retrieve();
560
- if (DECIDED_ASSET_STATUSES.includes(asset.status)) return asset;
559
+ if (isAssetDecided(asset)) return asset;
561
560
  const remaining = deadline - now();
562
561
  if (remaining <= 0) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
563
- message: `Asset ${asset.id} had no moderation decision after ${timeoutMs}ms (status ${asset.status})`,
562
+ message: `Asset ${asset.id} had no moderation decision after ${timeoutMs}ms (processing ${asset.processing.status}, moderation ${asset.moderation.status})`,
564
563
  details: {
565
564
  assetId: asset.id,
566
- status: asset.status
565
+ processingStatus: asset.processing.status,
566
+ moderationStatus: asset.moderation.status
567
567
  }
568
568
  });
569
569
  await sleep(Math.min(delay, remaining), signal);
@@ -39,6 +39,8 @@ const API_ERROR_CODES = {
39
39
  UPLOAD_PRESET_NOT_FOUND: "upload-preset/not-found",
40
40
  UPLOAD_PRESET_DISABLED: "upload-preset/disabled",
41
41
  UPLOAD_PRESET_TOKEN_REQUIRED: "upload-preset/token-required",
42
+ REMOTE_URL_NOT_ALLOWED: "upload/remote-url-not-allowed",
43
+ WEBSITE_RATE_LIMIT_EXCEEDED: "upload/website-rate-limit-exceeded",
42
44
  UPLOAD_PRESET_CONTENT_TYPE_NOT_ALLOWED: "upload-preset/content-type-not-allowed",
43
45
  UPLOAD_PRESET_TOO_LARGE: "upload-preset/too-large",
44
46
  UPLOAD_PRESET_DAILY_LIMIT_REACHED: "upload-preset/daily-limit-reached",
@@ -46,7 +48,18 @@ const API_ERROR_CODES = {
46
48
  UPLOAD_PRESET_FIELD_NOT_ALLOWED: "upload-preset/field-not-allowed",
47
49
  UPLOAD_TOKEN_INVALID: "upload-token/invalid",
48
50
  UPLOAD_TOKEN_EXPIRED: "upload-token/expired",
49
- UPLOAD_TOKEN_USED: "upload-token/used"
51
+ UPLOAD_TOKEN_USED: "upload-token/used",
52
+ INVALID_REMOTE_URL: "asset/invalid-remote-url",
53
+ REMOTE_URL_UNAVAILABLE: "asset/remote-url-unavailable",
54
+ APPEAL_NOT_FOUND: "appeal/not-found",
55
+ APPEAL_JUDGMENT_NOT_FOUND: "appeal/judgment-not-found",
56
+ APPEAL_ASSET_NOT_FOUND: "appeal/asset-not-found",
57
+ APPEAL_PERMISSION_DENIED: "appeal/permission-denied",
58
+ APPEAL_ALREADY_REVIEWED: "appeal/already-reviewed",
59
+ APPEAL_DECISION_CHANGED: "appeal/decision-changed",
60
+ APPEAL_INVALID_ACTION: "appeal/invalid-action",
61
+ APPEAL_SAFETY_HELD: "appeal/safety-held",
62
+ APPEAL_CONTENT_UNAVAILABLE: "appeal/content-unavailable"
50
63
  };
51
64
  /** The HTTP status and default message the API returns with each documented error code. */
52
65
  const API_ERROR_DEFINITIONS = {
@@ -202,6 +215,14 @@ const API_ERROR_DEFINITIONS = {
202
215
  status: 401,
203
216
  message: "This upload preset only accepts uploads with an upload token. Sign one on your server with uploadTokens.sign and pass it to the client."
204
217
  },
218
+ "upload/remote-url-not-allowed": {
219
+ status: 403,
220
+ message: "This project doesn't accept URLs from this domain. Change the allowed or blocked domains under Project settings."
221
+ },
222
+ "upload/website-rate-limit-exceeded": {
223
+ status: 429,
224
+ message: "This project has submitted too many websites in the last minute. Wait for the Retry-After period, or lower the pace of submissions."
225
+ },
205
226
  "upload-preset/content-type-not-allowed": {
206
227
  status: 415,
207
228
  message: "This upload preset doesn't accept this file type."
@@ -233,6 +254,50 @@ const API_ERROR_DEFINITIONS = {
233
254
  "upload-token/used": {
234
255
  status: 409,
235
256
  message: "The upload token has already been used. Each token uploads one asset."
257
+ },
258
+ "asset/invalid-remote-url": {
259
+ status: 400,
260
+ message: "Remote URLs must use HTTP or HTTPS and cannot target private networks, localhost names or URL credentials."
261
+ },
262
+ "asset/remote-url-unavailable": {
263
+ status: 422,
264
+ message: "The URL responded with an error status. Check that it is publicly reachable."
265
+ },
266
+ "appeal/not-found": {
267
+ status: 404,
268
+ message: "The appeal does not exist in this project."
269
+ },
270
+ "appeal/judgment-not-found": {
271
+ status: 404,
272
+ message: "The judgment event is unavailable, belongs to another project or cannot supply an asset decision. Supply explicit context instead."
273
+ },
274
+ "appeal/asset-not-found": {
275
+ status: 404,
276
+ message: "The linked asset does not exist in this project."
277
+ },
278
+ "appeal/permission-denied": {
279
+ status: 403,
280
+ message: "You cannot review this project's appeals."
281
+ },
282
+ "appeal/already-reviewed": {
283
+ status: 409,
284
+ message: "A reviewer already resolved this appeal."
285
+ },
286
+ "appeal/decision-changed": {
287
+ status: 409,
288
+ message: "The linked asset's decision changed after the appealed decision. Submit a new appeal for the current decision."
289
+ },
290
+ "appeal/invalid-action": {
291
+ status: 400,
292
+ message: "The decision's action is not valid for this appeal."
293
+ },
294
+ "appeal/safety-held": {
295
+ status: 409,
296
+ message: "A safety hold prevents this appeal from being submitted or applied."
297
+ },
298
+ "appeal/content-unavailable": {
299
+ status: 409,
300
+ message: "The appeal's content is unavailable, or approval would require restoring removed content."
236
301
  }
237
302
  };
238
303
  //#endregion
@@ -325,7 +390,7 @@ const invalidConfig = (message) => new CalmLensError(SDK_ERROR_CODES.INVALID_CON
325
390
  //#endregion
326
391
  //#region src/client/CalmLensVersion.ts
327
392
  /** This SDK's version, from its package.json. */
328
- const SDK_VERSION = "0.13.0";
393
+ const SDK_VERSION = "0.15.0";
329
394
  const runtimeName = () => {
330
395
  const runtime = globalThis;
331
396
  if (isReactNativeRuntime()) return "react-native";
@@ -1,5 +1,5 @@
1
- const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
2
- const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.js");
1
+ const require_CalmLensVersion = require("./CalmLensVersion-CuxKdijk.js");
2
+ const require_CalmLensUtils = require("./CalmLensUtils-Cv1Ylp9-.js");
3
3
  //#region ../shared/src/JsonUtils.ts
4
4
  const stringifyJsonCanonical = (value) => JSON.stringify(value, (_key, nestedValue) => {
5
5
  if (!nestedValue || typeof nestedValue !== "object" || Array.isArray(nestedValue)) return nestedValue;