@calmlens/sdk 0.10.1 → 0.12.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 +60 -16
- package/cjs/CalmLensConnection-CYtBP-vu.js +516 -0
- package/cjs/CalmLensError-CKwMKV0S.js +362 -0
- package/cjs/CalmLensUtils-Dg3s4loH.js +367 -0
- package/cjs/CalmLensWebhooks-BTPxKvcD.js +201 -0
- package/cjs/{CalmLensTypes-DjYFaTyh.d.ts → WebhookPayload-Cgb32aSH.d.ts} +5423 -3844
- package/cjs/app.d.ts +43 -0
- package/cjs/app.js +91 -0
- package/cjs/index-BgmWj3KL.d.ts +350 -0
- package/cjs/index.d.ts +3 -382
- package/cjs/index.js +5 -816
- package/cjs/server.d.ts +202 -0
- package/cjs/server.js +371 -0
- package/cjs/src-Dsc8Pn6v.js +45 -0
- package/cjs/testing.d.ts +54 -18
- package/cjs/testing.js +191 -62
- package/esm/CalmLensConnection-wLNy0OIP.js +475 -0
- package/esm/CalmLensError-CihGG26K.js +321 -0
- package/esm/CalmLensUtils-Cx32j2zt.js +260 -0
- package/esm/CalmLensWebhooks-DShmAal4.js +148 -0
- package/esm/{CalmLensTypes-DjYFaTyh.d.ts → WebhookPayload-Cgb32aSH.d.ts} +5423 -3844
- package/esm/app.d.ts +43 -0
- package/esm/app.js +87 -0
- package/esm/index-BgmWj3KL.d.ts +350 -0
- package/esm/index.d.ts +3 -382
- package/esm/index.js +3 -807
- package/esm/server.d.ts +202 -0
- package/esm/server.js +361 -0
- package/esm/src-CJhrILvM.js +22 -0
- package/esm/testing.d.ts +54 -18
- package/esm/testing.js +142 -13
- package/package.json +23 -1
- package/cjs/CalmLensWebhooks-DJnaLpRy.js +0 -741
- package/esm/CalmLensWebhooks-DLUozliX.js +0 -556
package/README.md
CHANGED
|
@@ -10,9 +10,16 @@ CalmLens provides content moderation and classification for text, images, docume
|
|
|
10
10
|
npm install @calmlens/sdk
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The package has two clients, each with its own import:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
| Import | Client | Runs | Authenticates with |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| `@calmlens/sdk/server` | `CalmLensServerClient` | On your server: Node.js 22 or later, Bun, Deno, Cloudflare Workers | A project API key |
|
|
18
|
+
| `@calmlens/sdk/app` | `CalmLensAppClient` | In browsers and React Native apps | An [upload preset](#browser-and-app-uploads-with-upload-presets), with an optional upload token from your server |
|
|
19
|
+
|
|
20
|
+
`@calmlens/sdk` itself exports the shared types, `CalmLensError`, the error codes and `SDK_VERSION`. The package ships ES modules and CommonJS with type declarations and loads no other packages at runtime: importing only `verifyWebhookSignature` adds about 5 KB (minified) to a bundle, `CalmLensAppClient` about 19 KB and `CalmLensServerClient` about 28 KB.
|
|
21
|
+
|
|
22
|
+
API keys are secret, so `CalmLensServerClient` refuses to start in a web page or app. Upload from browsers and apps with `CalmLensAppClient`, and serve private content to them with [signed URLs](#assetscreatesignedurloptions).
|
|
16
23
|
|
|
17
24
|
Every public SDK operation accepts one options object. This keeps call sites stable as optional fields are added.
|
|
18
25
|
|
|
@@ -21,9 +28,9 @@ Every public SDK operation accepts one options object. This keeps call sites sta
|
|
|
21
28
|
Create an API key under **Developer → API Keys** in the CalmLens portal. The same page shows your **Project ID**. Keep both on your server.
|
|
22
29
|
|
|
23
30
|
```typescript
|
|
24
|
-
import {
|
|
31
|
+
import { CalmLensServerClient } from "@calmlens/sdk/server";
|
|
25
32
|
|
|
26
|
-
const client = new
|
|
33
|
+
const client = new CalmLensServerClient({
|
|
27
34
|
apiKey: process.env.CALMLENS_API_KEY!,
|
|
28
35
|
projectId: process.env.CALMLENS_PROJECT_ID!,
|
|
29
36
|
});
|
|
@@ -58,14 +65,14 @@ See [How CalmLens works](https://calmlens.com/docs/how-it-works) and [Testing yo
|
|
|
58
65
|
|
|
59
66
|
## API Reference
|
|
60
67
|
|
|
61
|
-
###
|
|
68
|
+
### CalmLensServerClient
|
|
62
69
|
|
|
63
|
-
The
|
|
70
|
+
The client for your server, with every API method. Import it from `@calmlens/sdk/server`.
|
|
64
71
|
|
|
65
72
|
#### Constructor
|
|
66
73
|
|
|
67
74
|
```typescript
|
|
68
|
-
new
|
|
75
|
+
new CalmLensServerClient(options: CalmLensServerClientOptions)
|
|
69
76
|
```
|
|
70
77
|
|
|
71
78
|
**Options:**
|
|
@@ -80,7 +87,7 @@ new CalmLensClient(options: CalmLensClientOptions)
|
|
|
80
87
|
- `maxRetries` (integer, optional): Retries for failed requests that are safe to repeat (defaults to `2`, `0` disables them). See [Retries and idempotency](#retries-and-idempotency)
|
|
81
88
|
- `maxResponseBytes` (positive integer, optional): Largest JSON response the client reads (defaults to 32 MiB)
|
|
82
89
|
- `signal` (AbortSignal, optional): Cancels every request made by this client
|
|
83
|
-
- `dangerouslyAllowBrowser` (boolean, optional): Lets the client run in a web page. Only for trusted internal tools: anyone who can open the page can read the API key
|
|
90
|
+
- `dangerouslyAllowBrowser` (boolean, optional): Lets the client run in a web page or app. Only for trusted internal tools: anyone who can open the page can read the API key
|
|
84
91
|
|
|
85
92
|
Options are validated before anything else is set up: a missing `apiKey` or `projectId`, a `timeoutMs` that is not a positive integer, or a `baseUrl`/`cdnBaseUrl` that is not an absolute `http(s)` URL throws a `CalmLensError` with the code `sdk/invalid-config`. The client keeps its API key out of `console.log`, `util.inspect` and `JSON.stringify`.
|
|
86
93
|
|
|
@@ -101,6 +108,7 @@ async assets.create(options: SubmitAssetOptions): Promise<Asset>
|
|
|
101
108
|
- `part` (optional): First part of a sequential multipart upload
|
|
102
109
|
- `description` (string, optional): Description of the asset
|
|
103
110
|
- `processingOptions.keepContentAfterProcessing` (boolean, optional): Retain content after processing
|
|
111
|
+
- `processingOptions.features` (object, optional): Optional analyses to run, each turned on with `enabled: true`: `ocr`, and in beta `personDetection` and `faceDetection` (with `ageEstimation`). Person and face results are returned in `asset.report.features`. See [People and faces](https://calmlens.com/docs/guides/people-and-faces).
|
|
104
112
|
- `visibility` (string, optional): "public" or "private"
|
|
105
113
|
|
|
106
114
|
**Example:**
|
|
@@ -195,7 +203,7 @@ For several lenses, use `ensureLenses()` to perform all ensures in one request.
|
|
|
195
203
|
const [normal, uhd] = await client.ensureLenses({ lenses: [normalLens, uhdLens] });
|
|
196
204
|
```
|
|
197
205
|
|
|
198
|
-
The CDN uses named lenses with an `@` prefix in URLs, such as `/content/@picmagic-normal`. The SDK adds it automatically when you supply `lens`. Use `transform` for one-off direct transforms, for example `client.cdn.serve({ assetIdentifier: assetId, transform: "imageTransform[format=png,width=1024]"
|
|
206
|
+
The CDN uses named lenses with an `@` prefix in URLs, such as `/content/@picmagic-normal`. The SDK adds it automatically when you supply `lens`. Use `transform` for one-off direct transforms, either as structured steps, for example `client.cdn.serve({ assetIdentifier: assetId, transform: { type: "imageTransform", options: { format: "png", width: 1024 } } })`, or as a selector string such as `"imageTransform[format=png,width=1024]"`. Pass an array of steps to chain transforms. `assets.createSignedUrl` accepts the same forms. Supply either `lens` or `transform`, not both. The resolved lens version is part of CalmLens's CDN cache and ETag identity, so changing a lens with the next version invalidates prior derived output.
|
|
199
207
|
|
|
200
208
|
##### assets.list(options)
|
|
201
209
|
|
|
@@ -463,6 +471,42 @@ static async verifyWebhookSignature(options: {
|
|
|
463
471
|
|
|
464
472
|
It returns `true` only when the `x-calmlens-signature` header contains a `v1=` HMAC-SHA256 of `"<timestamp>.<raw body>"` (one match in a comma-separated list is enough) and the timestamp is within `toleranceSeconds` of now. Passing parsed JSON as `payload` throws `sdk/invalid-argument`: re-serializing it can change the signed bytes.
|
|
465
473
|
|
|
474
|
+
## Browser and app uploads with upload presets
|
|
475
|
+
|
|
476
|
+
`CalmLensAppClient` uploads from a browser or app through an upload preset, without an API key. Create presets in the portal under Developer → Upload presets.
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
import { CalmLensAppClient } from "@calmlens/sdk/app";
|
|
480
|
+
|
|
481
|
+
// In the browser: no API key
|
|
482
|
+
const client = new CalmLensAppClient({
|
|
483
|
+
uploadPresetId: "your-upload-preset-id",
|
|
484
|
+
// Only for presets that require a token: called once per new upload
|
|
485
|
+
token: () => fetch("/api/upload-token", { method: "POST" }).then((r) => r.json()).then((b) => b.token),
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
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
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**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`.
|
|
493
|
+
|
|
494
|
+
The client has `assets.create`, `assets.moderate`, `assets.uploadPart` and `assets.retrieve`, for its own uploads, and each returns an `UploadedAsset`. It fetches a new token for each upload and reuses that token for the upload's parts and status checks.
|
|
495
|
+
|
|
496
|
+
The same client works in React Native and Expo apps. Mobile apps send no `Origin` header, so use a preset that requires a token and leave its allowed sites empty. Pass the picked file's bytes, for example `content: { buffer: await new File(uri).arrayBuffer(), type }` with `expo-file-system`. See the [Calmsnap example](https://github.com/CalmLens/CalmLens/tree/main/examples/mobile-uploads-expo).
|
|
497
|
+
|
|
498
|
+
On your server, sign a single-use token for each upload with `CalmLensServerClient`. Signing is local and makes no request:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
const { token, expiresAt } = await client.uploadTokens.sign({
|
|
502
|
+
uploadPresetId: "your-upload-preset-id",
|
|
503
|
+
externalId: "post-42", // optional
|
|
504
|
+
expiresInSeconds: 600, // optional, up to 3600
|
|
505
|
+
});
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
Server uploads can also pass `uploadPresetId` to `assets.create` or `assets.moderate` to apply a preset's folder, visibility, file types and size limit. See [Upload presets](https://calmlens.com/docs/guides/upload-presets).
|
|
509
|
+
|
|
466
510
|
## Asset Types
|
|
467
511
|
|
|
468
512
|
Assets can be of the following types:
|
|
@@ -570,27 +614,27 @@ The SDK does not validate responses against its types, so fields and values adde
|
|
|
570
614
|
|
|
571
615
|
## Testing
|
|
572
616
|
|
|
573
|
-
`@calmlens/sdk/testing` exports
|
|
617
|
+
`@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.
|
|
574
618
|
|
|
575
|
-
The
|
|
619
|
+
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.
|
|
576
620
|
|
|
577
621
|
```typescript
|
|
578
|
-
import {
|
|
622
|
+
import { MockCalmLensServerClient, createMockApiKey } from "@calmlens/sdk/testing";
|
|
579
623
|
|
|
580
|
-
const client = new
|
|
624
|
+
const client = new MockCalmLensServerClient({
|
|
581
625
|
apiKey: createMockApiKey(),
|
|
582
626
|
projectId: crypto.randomUUID(),
|
|
583
627
|
});
|
|
584
628
|
```
|
|
585
629
|
|
|
586
|
-
`
|
|
630
|
+
`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`).
|
|
587
631
|
|
|
588
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.
|
|
589
633
|
|
|
590
634
|
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.
|
|
591
635
|
|
|
592
636
|
```typescript
|
|
593
|
-
const client = new
|
|
637
|
+
const client = new MockCalmLensServerClient({ apiKey: createMockApiKey(), projectId, timeoutMs: 5_000 });
|
|
594
638
|
|
|
595
639
|
client.failNextRequest("timeout", { operation: "text.moderate" });
|
|
596
640
|
await expect(postComment(client, "hello")).rejects.toMatchObject({ code: "sdk/timeout" });
|
|
@@ -648,7 +692,7 @@ The result is typed as `WebhookEventPayload`; narrow it on `type` to get that ev
|
|
|
648
692
|
**Fetch API** (Next.js route handlers, Remix, Cloudflare Workers, Bun, Deno):
|
|
649
693
|
|
|
650
694
|
```typescript
|
|
651
|
-
import { CalmLensError, parseWebhookEvent } from "@calmlens/sdk";
|
|
695
|
+
import { CalmLensError, parseWebhookEvent } from "@calmlens/sdk/server";
|
|
652
696
|
|
|
653
697
|
export async function POST(request: Request) {
|
|
654
698
|
let event;
|
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
const require_CalmLensError = require("./CalmLensError-CKwMKV0S.js");
|
|
2
|
+
const require_src = require("./src-Dsc8Pn6v.js");
|
|
3
|
+
const require_CalmLensUtils = require("./CalmLensUtils-Dg3s4loH.js");
|
|
4
|
+
//#region src/client/CalmLensTransport.ts
|
|
5
|
+
/** Retry delays grow from this base, doubling per attempt, with jitter. */
|
|
6
|
+
const RETRY_BASE_DELAY_MS = 500;
|
|
7
|
+
const RETRY_MAX_DELAY_MS = 8e3;
|
|
8
|
+
/** A `Retry-After` longer than this is returned to the caller instead of waited out. */
|
|
9
|
+
const RETRY_AFTER_MAX_MS = 6e4;
|
|
10
|
+
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
|
|
11
|
+
429,
|
|
12
|
+
502,
|
|
13
|
+
503,
|
|
14
|
+
504
|
|
15
|
+
]);
|
|
16
|
+
const timeoutReason = () => {
|
|
17
|
+
const reason = /* @__PURE__ */ new Error("The operation timed out.");
|
|
18
|
+
reason.name = "TimeoutError";
|
|
19
|
+
return reason;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Links the callers' signals and a per-attempt deadline. `AbortSignal.any` holds its sources
|
|
23
|
+
* weakly, so a long-lived caller signal doesn't collect a listener per streamed response;
|
|
24
|
+
* runtimes without it get explicit listeners that `release` removes.
|
|
25
|
+
*/
|
|
26
|
+
const createAttemptSignal = (signals, timeoutMs) => {
|
|
27
|
+
const callers = signals.filter((signal) => signal !== void 0);
|
|
28
|
+
const deadline = new AbortController();
|
|
29
|
+
let timedOut = false;
|
|
30
|
+
let signal;
|
|
31
|
+
let onAbort;
|
|
32
|
+
if (typeof AbortSignal.any === "function") signal = AbortSignal.any([...callers, deadline.signal]);
|
|
33
|
+
else {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
signal = controller.signal;
|
|
36
|
+
onAbort = (event) => controller.abort(event.target.reason);
|
|
37
|
+
for (const source of [...callers, deadline.signal]) if (source.aborted) controller.abort(source.reason);
|
|
38
|
+
else source.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
}
|
|
40
|
+
let timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
|
|
41
|
+
timedOut = true;
|
|
42
|
+
deadline.abort(timeoutReason());
|
|
43
|
+
}, timeoutMs);
|
|
44
|
+
return {
|
|
45
|
+
signal,
|
|
46
|
+
isTimedOut: () => timedOut,
|
|
47
|
+
isCancelled: () => callers.some((caller) => caller.aborted),
|
|
48
|
+
clearDeadline: () => {
|
|
49
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
50
|
+
timer = void 0;
|
|
51
|
+
},
|
|
52
|
+
release: () => {
|
|
53
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
54
|
+
timer = void 0;
|
|
55
|
+
if (onAbort) for (const caller of callers) caller.removeEventListener("abort", onAbort);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* A redirect the SDK must not follow: it would send the API key to another origin. Browsers report
|
|
61
|
+
* a manual redirect as an opaque response; other runtimes return the 3xx with its Location.
|
|
62
|
+
*/
|
|
63
|
+
const isRedirect = (response) => response.type === "opaqueredirect" || response.status >= 300 && response.status < 400 && response.headers.has("location");
|
|
64
|
+
/** Turns anything thrown while sending or reading a request into a `CalmLensError`. */
|
|
65
|
+
const toFailure = (cause, attempt) => {
|
|
66
|
+
if (require_CalmLensError.CalmLensError.isCalmLensError(cause)) return cause;
|
|
67
|
+
if (attempt.isCancelled()) return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.CANCELLED, {
|
|
68
|
+
message: "Request cancelled",
|
|
69
|
+
cause
|
|
70
|
+
});
|
|
71
|
+
if (attempt.isTimedOut()) return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TIMEOUT, {
|
|
72
|
+
message: "Request timed out",
|
|
73
|
+
cause
|
|
74
|
+
});
|
|
75
|
+
return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TRANSPORT_ERROR, {
|
|
76
|
+
message: "Request failed",
|
|
77
|
+
cause
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
81
|
+
if (signal?.aborted) {
|
|
82
|
+
reject(new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const onAbort = () => {
|
|
86
|
+
clearTimeout(timer);
|
|
87
|
+
reject(new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
|
|
88
|
+
};
|
|
89
|
+
const timer = setTimeout(() => {
|
|
90
|
+
signal?.removeEventListener("abort", onAbort);
|
|
91
|
+
resolve();
|
|
92
|
+
}, ms);
|
|
93
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
});
|
|
95
|
+
/** Exponential backoff with jitter: half the delay is fixed, the other half random. */
|
|
96
|
+
const getRetryDelayMs = (attempt, random = Math.random) => {
|
|
97
|
+
const delay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);
|
|
98
|
+
return Math.round(delay / 2 + random() * (delay / 2));
|
|
99
|
+
};
|
|
100
|
+
/** Parses `Retry-After` as seconds or an HTTP date, in seconds from `now`. */
|
|
101
|
+
const parseRetryAfter = (value, now = Date.now()) => {
|
|
102
|
+
if (!value) return void 0;
|
|
103
|
+
const trimmed = value.trim();
|
|
104
|
+
if (/^\d+$/.test(trimmed)) return Number(trimmed);
|
|
105
|
+
const date = Date.parse(trimmed);
|
|
106
|
+
if (Number.isNaN(date)) return void 0;
|
|
107
|
+
return Math.max(0, Math.ceil((date - now) / 1e3));
|
|
108
|
+
};
|
|
109
|
+
/** Bodies that can be sent again unchanged. Streams are consumed by the first attempt. */
|
|
110
|
+
const isReplayableBody = (body) => body === void 0 || body === null || typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof URLSearchParams;
|
|
111
|
+
const isApiErrorPayload = (value) => !!value && typeof value === "object" && typeof value.code === "string";
|
|
112
|
+
/** Reads at most `limit` bytes of a body, failing with `sdk/response-too-large` beyond that. */
|
|
113
|
+
const readBody = async (response, limit) => {
|
|
114
|
+
const declared = Number(response.headers.get("content-length"));
|
|
115
|
+
const tooLarge = () => new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.RESPONSE_TOO_LARGE, {
|
|
116
|
+
message: `Response body exceeds ${limit} bytes`,
|
|
117
|
+
status: response.status,
|
|
118
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
119
|
+
headers: response.headers
|
|
120
|
+
});
|
|
121
|
+
if (Number.isFinite(declared) && declared > limit) {
|
|
122
|
+
await response.body?.cancel().catch(() => {});
|
|
123
|
+
throw tooLarge();
|
|
124
|
+
}
|
|
125
|
+
if (!response.body) return /* @__PURE__ */ new Uint8Array(0);
|
|
126
|
+
const reader = response.body.getReader();
|
|
127
|
+
const chunks = [];
|
|
128
|
+
let total = 0;
|
|
129
|
+
for (;;) {
|
|
130
|
+
const { done, value } = await reader.read();
|
|
131
|
+
if (done) break;
|
|
132
|
+
total += value.byteLength;
|
|
133
|
+
if (total > limit) {
|
|
134
|
+
await reader.cancel().catch(() => {});
|
|
135
|
+
throw tooLarge();
|
|
136
|
+
}
|
|
137
|
+
chunks.push(value);
|
|
138
|
+
}
|
|
139
|
+
const bytes = new Uint8Array(total);
|
|
140
|
+
let offset = 0;
|
|
141
|
+
for (const chunk of chunks) {
|
|
142
|
+
bytes.set(chunk, offset);
|
|
143
|
+
offset += chunk.byteLength;
|
|
144
|
+
}
|
|
145
|
+
return bytes;
|
|
146
|
+
};
|
|
147
|
+
const responseInfo = (response) => ({
|
|
148
|
+
status: response.status,
|
|
149
|
+
requestId: response.headers.get("x-request-id") ?? void 0,
|
|
150
|
+
headers: response.headers,
|
|
151
|
+
retryAfter: parseRetryAfter(response.headers.get("retry-after"))
|
|
152
|
+
});
|
|
153
|
+
/** The `CalmLensError` for a non-2xx response whose body has been read. */
|
|
154
|
+
const toHttpError = (response, bytes) => {
|
|
155
|
+
let data;
|
|
156
|
+
try {
|
|
157
|
+
data = JSON.parse(new TextDecoder().decode(bytes));
|
|
158
|
+
} catch {}
|
|
159
|
+
if (isApiErrorPayload(data)) {
|
|
160
|
+
const { code, message, details, traceId } = data;
|
|
161
|
+
return new require_CalmLensError.CalmLensError(code, {
|
|
162
|
+
message: message ?? code,
|
|
163
|
+
...responseInfo(response),
|
|
164
|
+
details: details || traceId ? {
|
|
165
|
+
...details,
|
|
166
|
+
...traceId ? { traceId } : {}
|
|
167
|
+
} : void 0
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.HTTP_ERROR, {
|
|
171
|
+
message: `HTTP ${response.status}`,
|
|
172
|
+
...responseInfo(response)
|
|
173
|
+
});
|
|
174
|
+
};
|
|
175
|
+
/** Sends requests with the client's credentials, deadlines, retries and response limits. */
|
|
176
|
+
var CalmLensTransport = class {
|
|
177
|
+
constructor(settings) {
|
|
178
|
+
this.settings = settings;
|
|
179
|
+
}
|
|
180
|
+
headersFor(request) {
|
|
181
|
+
const headers = new Headers(request.init?.headers);
|
|
182
|
+
for (const [name, value] of Object.entries(request.headers ?? {})) headers.set(name, value);
|
|
183
|
+
if (!headers.has("accept") && request.deadline === "body") headers.set("accept", "application/json");
|
|
184
|
+
if (this.settings.apiKey) headers.set("x-api-key", this.settings.apiKey);
|
|
185
|
+
if (!require_src.isBrowserRuntime()) headers.set("user-agent", require_src.getUserAgent());
|
|
186
|
+
if (request.idempotencyKey) headers.set("idempotency-key", request.idempotencyKey);
|
|
187
|
+
return Object.fromEntries(headers.entries());
|
|
188
|
+
}
|
|
189
|
+
canRetry(request) {
|
|
190
|
+
return this.settings.maxRetries > 0 && (request.method === "GET" || request.method === "HEAD" || !!request.idempotencyKey) && isReplayableBody(request.body);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Sends a request and returns the response, retrying transport failures, 429, 502–504 and
|
|
194
|
+
* `idempotency/request-in-progress` when the request is safe to repeat. Non-2xx responses
|
|
195
|
+
* throw unless `allowHttpErrors` is set. Call `release` once the body has been read.
|
|
196
|
+
*/
|
|
197
|
+
async send(request, { allowHttpErrors = false } = {}) {
|
|
198
|
+
const fetchImpl = request.fetch ?? this.settings.fetch;
|
|
199
|
+
const retryable = this.canRetry(request);
|
|
200
|
+
const headers = this.headersFor(request);
|
|
201
|
+
for (let attemptNumber = 0;; attemptNumber++) {
|
|
202
|
+
const canRetryAgain = retryable && attemptNumber < this.settings.maxRetries;
|
|
203
|
+
const attempt = createAttemptSignal([this.settings.signal, request.init?.signal ?? void 0], request.timeoutMs);
|
|
204
|
+
let response;
|
|
205
|
+
try {
|
|
206
|
+
if (attempt.signal.aborted) throw attempt.signal.reason;
|
|
207
|
+
response = await fetchImpl(request.url, {
|
|
208
|
+
...request.init,
|
|
209
|
+
method: request.method,
|
|
210
|
+
headers,
|
|
211
|
+
body: request.body,
|
|
212
|
+
redirect: "manual",
|
|
213
|
+
signal: attempt.signal,
|
|
214
|
+
duplex: "half"
|
|
215
|
+
});
|
|
216
|
+
if (isRedirect(response)) {
|
|
217
|
+
await response.body?.cancel().catch(() => {});
|
|
218
|
+
throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TRANSPORT_ERROR, { message: `Unexpected redirect (HTTP ${response.status})` });
|
|
219
|
+
}
|
|
220
|
+
} catch (cause) {
|
|
221
|
+
attempt.release();
|
|
222
|
+
const failure = toFailure(cause, attempt);
|
|
223
|
+
if (canRetryAgain && failure.code === require_CalmLensError.SDK_ERROR_CODES.TRANSPORT_ERROR) {
|
|
224
|
+
await sleep(getRetryDelayMs(attemptNumber), this.settings.signal);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
throw failure;
|
|
228
|
+
}
|
|
229
|
+
if (request.deadline === "headers") attempt.clearDeadline();
|
|
230
|
+
if (response.ok) return {
|
|
231
|
+
response,
|
|
232
|
+
attempt
|
|
233
|
+
};
|
|
234
|
+
if (allowHttpErrors) {
|
|
235
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
|
|
236
|
+
const delayMs = retryAfter !== void 0 ? retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
|
|
237
|
+
if (!canRetryAgain || !RETRYABLE_STATUSES.has(response.status) || delayMs > RETRY_AFTER_MAX_MS) return {
|
|
238
|
+
response,
|
|
239
|
+
attempt
|
|
240
|
+
};
|
|
241
|
+
attempt.release();
|
|
242
|
+
await response.body?.cancel().catch(() => {});
|
|
243
|
+
await sleep(delayMs, this.settings.signal);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
let error;
|
|
247
|
+
try {
|
|
248
|
+
error = toHttpError(response, await readBody(response, this.settings.maxResponseBytes));
|
|
249
|
+
} catch (cause) {
|
|
250
|
+
error = toFailure(cause, attempt);
|
|
251
|
+
} finally {
|
|
252
|
+
attempt.release();
|
|
253
|
+
}
|
|
254
|
+
const retryDelayMs = error.retryAfter !== void 0 ? error.retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
|
|
255
|
+
if (canRetryAgain && retryDelayMs <= RETRY_AFTER_MAX_MS && (error.status !== void 0 && RETRYABLE_STATUSES.has(error.status) || error.code === require_CalmLensError.API_ERROR_CODES.IDEMPOTENT_REQUEST_IN_PROGRESS)) {
|
|
256
|
+
await sleep(retryDelayMs, this.settings.signal);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Sends a request and parses its JSON response body. */
|
|
263
|
+
async json(request) {
|
|
264
|
+
const { response, attempt } = await this.send(request);
|
|
265
|
+
try {
|
|
266
|
+
const bytes = await readBody(response, this.settings.maxResponseBytes);
|
|
267
|
+
if (bytes.byteLength === 0 && (response.status === 204 || request.method === "DELETE")) return;
|
|
268
|
+
try {
|
|
269
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
270
|
+
} catch (cause) {
|
|
271
|
+
throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
272
|
+
message: `Invalid JSON response (HTTP ${response.status})`,
|
|
273
|
+
...responseInfo(response),
|
|
274
|
+
cause
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
} catch (cause) {
|
|
278
|
+
throw toFailure(cause, attempt);
|
|
279
|
+
} finally {
|
|
280
|
+
attempt.release();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
//#region src/client/CalmLensConnection.ts
|
|
286
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
287
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
288
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 33554432;
|
|
289
|
+
/**
|
|
290
|
+
* Bytes as a fetch body. They're sent as they are rather than in a Blob: React Native can't
|
|
291
|
+
* build a Blob from bytes, and every fetch accepts a Uint8Array.
|
|
292
|
+
*/
|
|
293
|
+
const toBodyBytes = (value) => value.buffer instanceof ArrayBuffer ? value : Uint8Array.from(value);
|
|
294
|
+
const toRawUploadBody = (payload) => {
|
|
295
|
+
const data = require_CalmLensUtils.assetToData(payload);
|
|
296
|
+
if (data instanceof Response) {
|
|
297
|
+
if (!data.body) throw new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Response upload data must have a body" });
|
|
298
|
+
return data.body;
|
|
299
|
+
}
|
|
300
|
+
if (data instanceof ArrayBuffer || data instanceof Blob) return data;
|
|
301
|
+
if ("getReader" in data) return data;
|
|
302
|
+
return toBodyBytes(data);
|
|
303
|
+
};
|
|
304
|
+
const setOptionalQueryValue = (params, key, value) => {
|
|
305
|
+
if (value !== void 0 && value !== null) params.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
306
|
+
};
|
|
307
|
+
const assertValidUploadMimeType = (mimeType) => {
|
|
308
|
+
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
309
|
+
message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
|
|
310
|
+
details: { mimeType }
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
const isImageMimeType = (mimeType) => mimeType.toLowerCase().startsWith("image/");
|
|
314
|
+
const invalidPayload = (message) => new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
315
|
+
/** Checks upload options before anything is sent. */
|
|
316
|
+
const prepareSubmitAssetOptions = (options) => {
|
|
317
|
+
if (!options || typeof options !== "object") throw require_CalmLensError.invalidArgument("Asset options are required");
|
|
318
|
+
if (!("content" in options) && !("url" in options) && !("part" in options)) throw invalidPayload("Either file content, URL, or multipart part is required");
|
|
319
|
+
if (!options.name) throw invalidPayload("Asset name is required");
|
|
320
|
+
if (!("part" in options) && "isFinal" in options) throw invalidPayload("isFinal is only valid for multipart uploads");
|
|
321
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.USE_UPLOAD_PART, { message: "Use uploadPart for multipart upload parts after the first part" });
|
|
322
|
+
if ("content" in options) {
|
|
323
|
+
assertValidUploadMimeType(options.content.type);
|
|
324
|
+
if (!("plainText" in options.content)) {
|
|
325
|
+
const validation = require_CalmLensUtils.validateAssetPayload(options.content);
|
|
326
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if ("part" in options) {
|
|
330
|
+
assertValidUploadMimeType(options.part.type);
|
|
331
|
+
const partSizeInBytes = options.part.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(options.part);
|
|
332
|
+
if (isImageMimeType(options.part.type) && partSizeInBytes !== void 0) {
|
|
333
|
+
if (partSizeInBytes > require_CalmLensUtils.MAX_IMAGE_UPLOAD_BYTES) throw new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE, { message: "Multipart image uploads cannot exceed 20MB." });
|
|
334
|
+
if (options.part.isFinal && partSizeInBytes < require_CalmLensUtils.MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw new require_CalmLensError.CalmLensError(require_CalmLensError.API_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL, { message: "Multipart image uploads must total at least 10MB." });
|
|
335
|
+
}
|
|
336
|
+
const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
|
|
337
|
+
multipart: true,
|
|
338
|
+
isFinal: options.part.isFinal
|
|
339
|
+
});
|
|
340
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
341
|
+
}
|
|
342
|
+
return options;
|
|
343
|
+
};
|
|
344
|
+
const newIdempotencyKey = () => require_CalmLensUtils.randomUuid();
|
|
345
|
+
/** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
|
|
346
|
+
const isTextPayload = (payload) => "plainText" in payload;
|
|
347
|
+
const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
|
|
348
|
+
const isUploadedAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id;
|
|
349
|
+
const expectUploadedAsset = (data) => {
|
|
350
|
+
if (!isUploadedAsset(data)) throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
351
|
+
message: "Expected an uploaded asset response",
|
|
352
|
+
status: 200
|
|
353
|
+
});
|
|
354
|
+
return data;
|
|
355
|
+
};
|
|
356
|
+
const expectAsset = (data) => {
|
|
357
|
+
if (!isAsset(data)) throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
358
|
+
message: "Expected an asset response",
|
|
359
|
+
status: 200
|
|
360
|
+
});
|
|
361
|
+
return data;
|
|
362
|
+
};
|
|
363
|
+
/** Fills in the default API base URL and drops trailing slashes. */
|
|
364
|
+
const resolveConnectionOptions = (options) => ({
|
|
365
|
+
...options,
|
|
366
|
+
baseUrl: (options.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, "")
|
|
367
|
+
});
|
|
368
|
+
/** One client's transport, base URL and deadlines, and the requests both clients make. */
|
|
369
|
+
var CalmLensConnection = class {
|
|
370
|
+
constructor(options, apiKey) {
|
|
371
|
+
this.options = options;
|
|
372
|
+
const resolveFetch = () => {
|
|
373
|
+
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
374
|
+
if (typeof fetchImpl !== "function") throw new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.NO_FETCH, { message: "No fetch implementation is available; pass one as the fetch option" });
|
|
375
|
+
return fetchImpl;
|
|
376
|
+
};
|
|
377
|
+
this.transport = new CalmLensTransport({
|
|
378
|
+
apiKey,
|
|
379
|
+
fetch: ((input, init) => resolveFetch()(input, init)),
|
|
380
|
+
signal: options.signal,
|
|
381
|
+
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
382
|
+
maxResponseBytes: options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
get timeoutMs() {
|
|
386
|
+
return this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
387
|
+
}
|
|
388
|
+
url(path, query) {
|
|
389
|
+
const search = query ? require_CalmLensUtils.stringifyQuery(query) : "";
|
|
390
|
+
return `${this.options.baseUrl}${path}${search ? `?${search}` : ""}`;
|
|
391
|
+
}
|
|
392
|
+
/** A JSON request whose deadline covers reading the response. */
|
|
393
|
+
json(method, path, { query, body, headers, idempotencyKey } = {}) {
|
|
394
|
+
return this.transport.json({
|
|
395
|
+
method,
|
|
396
|
+
url: this.url(path, query),
|
|
397
|
+
headers: body === void 0 ? headers : {
|
|
398
|
+
...headers,
|
|
399
|
+
"Content-Type": "application/json"
|
|
400
|
+
},
|
|
401
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
402
|
+
idempotencyKey,
|
|
403
|
+
timeoutMs: this.timeoutMs,
|
|
404
|
+
deadline: "body"
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
upload(url, payload, headers, idempotencyKey) {
|
|
408
|
+
return this.transport.json({
|
|
409
|
+
method: "POST",
|
|
410
|
+
url,
|
|
411
|
+
headers,
|
|
412
|
+
body: toRawUploadBody(payload),
|
|
413
|
+
idempotencyKey,
|
|
414
|
+
timeoutMs: isTextPayload(payload) ? this.timeoutMs : this.options.uploadTimeoutMs,
|
|
415
|
+
deadline: "body",
|
|
416
|
+
fetch: this.options.uploadFetch
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Uploads file content or a first multipart part to `uploadsPath` (`.../assets`), as raw
|
|
421
|
+
* bytes with the options in the query.
|
|
422
|
+
*/
|
|
423
|
+
submitRawUpload(uploadsPath, options, queue, authHeaders = {}) {
|
|
424
|
+
const query = new URLSearchParams();
|
|
425
|
+
query.set("name", options.name);
|
|
426
|
+
query.set("queue", String(queue));
|
|
427
|
+
setOptionalQueryValue(query, "description", options.description);
|
|
428
|
+
setOptionalQueryValue(query, "processingOptions", options.processingOptions);
|
|
429
|
+
setOptionalQueryValue(query, "visibility", options.visibility);
|
|
430
|
+
setOptionalQueryValue(query, "metadata", options.metadata);
|
|
431
|
+
setOptionalQueryValue(query, "externalId", options.externalId);
|
|
432
|
+
setOptionalQueryValue(query, "externalUrl", options.externalUrl);
|
|
433
|
+
setOptionalQueryValue(query, "folderId", options.folderId);
|
|
434
|
+
setOptionalQueryValue(query, "uploadPresetId", options.uploadPresetId);
|
|
435
|
+
const isMultipart = "part" in options;
|
|
436
|
+
const payload = isMultipart ? options.part : options.content;
|
|
437
|
+
query.set("contentType", payload.type);
|
|
438
|
+
setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(payload));
|
|
439
|
+
if (isMultipart) {
|
|
440
|
+
const part = payload;
|
|
441
|
+
const multipartOptions = options;
|
|
442
|
+
query.set("partNumber", String(part.partNumber ?? 1));
|
|
443
|
+
query.set("isFinal", String(part.isFinal ?? false));
|
|
444
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
|
|
445
|
+
setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
|
|
446
|
+
}
|
|
447
|
+
const path = isMultipart ? "multipart-upload" : "upload";
|
|
448
|
+
return this.upload(this.url(`${uploadsPath}/${path}?${query}`), payload, {
|
|
449
|
+
...authHeaders,
|
|
450
|
+
"Content-Type": payload.type,
|
|
451
|
+
"X-CalmLens-Upload": isMultipart ? "part" : "content"
|
|
452
|
+
}, options.idempotencyKey ?? newIdempotencyKey());
|
|
453
|
+
}
|
|
454
|
+
/** Uploads the next part of a multipart upload to `partPath` (`.../assets/{id}/part`). */
|
|
455
|
+
uploadPart(partPath, { part, queue, idempotencyKey }, authHeaders = {}) {
|
|
456
|
+
if (!part) throw require_CalmLensError.invalidArgument("Part options are required");
|
|
457
|
+
const validation = require_CalmLensUtils.validateAssetPayload(part, {
|
|
458
|
+
multipart: true,
|
|
459
|
+
isFinal: part.isFinal
|
|
460
|
+
});
|
|
461
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
462
|
+
const query = new URLSearchParams({
|
|
463
|
+
partNumber: String(part.partNumber),
|
|
464
|
+
isFinal: String(part.isFinal ?? false)
|
|
465
|
+
});
|
|
466
|
+
if (queue !== void 0) query.set("queue", String(queue));
|
|
467
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(part));
|
|
468
|
+
return this.upload(this.url(`${partPath}?${query}`), part, {
|
|
469
|
+
...authHeaders,
|
|
470
|
+
"X-CalmLens-Upload": "part"
|
|
471
|
+
}, idempotencyKey ?? newIdempotencyKey());
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
//#endregion
|
|
475
|
+
Object.defineProperty(exports, "CalmLensConnection", {
|
|
476
|
+
enumerable: true,
|
|
477
|
+
get: function() {
|
|
478
|
+
return CalmLensConnection;
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
Object.defineProperty(exports, "expectAsset", {
|
|
482
|
+
enumerable: true,
|
|
483
|
+
get: function() {
|
|
484
|
+
return expectAsset;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
Object.defineProperty(exports, "expectUploadedAsset", {
|
|
488
|
+
enumerable: true,
|
|
489
|
+
get: function() {
|
|
490
|
+
return expectUploadedAsset;
|
|
491
|
+
}
|
|
492
|
+
});
|
|
493
|
+
Object.defineProperty(exports, "invalidPayload", {
|
|
494
|
+
enumerable: true,
|
|
495
|
+
get: function() {
|
|
496
|
+
return invalidPayload;
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
Object.defineProperty(exports, "newIdempotencyKey", {
|
|
500
|
+
enumerable: true,
|
|
501
|
+
get: function() {
|
|
502
|
+
return newIdempotencyKey;
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
Object.defineProperty(exports, "prepareSubmitAssetOptions", {
|
|
506
|
+
enumerable: true,
|
|
507
|
+
get: function() {
|
|
508
|
+
return prepareSubmitAssetOptions;
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
Object.defineProperty(exports, "resolveConnectionOptions", {
|
|
512
|
+
enumerable: true,
|
|
513
|
+
get: function() {
|
|
514
|
+
return resolveConnectionOptions;
|
|
515
|
+
}
|
|
516
|
+
});
|