@calmlens/sdk 0.11.0 → 0.13.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 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
- Requires Node.js 22 or later, or another server runtime with `fetch` and Web Crypto, such as Bun, Deno or Cloudflare Workers. 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, and the whole client about 25 KB.
13
+ The package has two clients, each with its own import:
14
14
 
15
- Use it on your server. API keys are secret, so `CalmLensClient` refuses to start in a web page; serve private content to browsers with [signed URLs](#assetscreatesignedurloptions).
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 { CalmLensClient } from "@calmlens/sdk";
31
+ import { CalmLensServerClient } from "@calmlens/sdk/server";
25
32
 
26
- const client = new CalmLensClient({
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
- ### CalmLensClient
68
+ ### CalmLensServerClient
62
69
 
63
- The main client class for interacting with the CalmLens API.
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 CalmLensClient(options: CalmLensClientOptions)
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:**
@@ -189,7 +197,7 @@ const image = await client.cdn.serve({
189
197
  });
190
198
  ```
191
199
 
192
- For several lenses, use `ensureLenses()` to perform all ensures in one request. Each lens keeps the same independent versioning rules.
200
+ 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.
193
201
 
194
202
  ```typescript
195
203
  const [normal, uhd] = await client.ensureLenses({ lenses: [normalLens, uhdLens] });
@@ -295,6 +303,19 @@ const asset = await client.assets.retry({ assetId: "asset-uuid" });
295
303
  // asset.status === "pending"
296
304
  ```
297
305
 
306
+ ##### assets.waitForDecision(options)
307
+
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
310
+ `timeoutMs` (default 60000). Pass `signal` to stop waiting early (`sdk/cancelled`). Use it after
311
+ `assets.create`, or with an asset ID you stored earlier. Webhooks report decisions without polling.
312
+
313
+ ```typescript
314
+ const queued = await client.assets.create({ name: "photo.jpg", content: { blob, type: "image/jpeg" } });
315
+ const decided = await client.assets.waitForDecision({ assetId: queued.id, timeoutMs: 30_000 });
316
+ if (decided.status === "rejected") hidePhoto(decided.id);
317
+ ```
318
+
298
319
  ##### folders.list()
299
320
 
300
321
  List all folders in the current project.
@@ -463,6 +484,42 @@ static async verifyWebhookSignature(options: {
463
484
 
464
485
  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
486
 
487
+ ## Browser and app uploads with upload presets
488
+
489
+ `CalmLensAppClient` uploads from a browser or app through an upload preset, without an API key. Create presets in the portal under Developer → Upload presets.
490
+
491
+ ```ts
492
+ import { CalmLensAppClient } from "@calmlens/sdk/app";
493
+
494
+ // In the browser: no API key
495
+ const client = new CalmLensAppClient({
496
+ uploadPresetId: "your-upload-preset-id",
497
+ // Only for presets that require a token: called once per new upload
498
+ token: () => fetch("/api/upload-token", { method: "POST" }).then((r) => r.json()).then((b) => b.token),
499
+ });
500
+
501
+ 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
503
+ ```
504
+
505
+ **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`.
506
+
507
+ 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.
508
+
509
+ 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).
510
+
511
+ On your server, sign a single-use token for each upload with `CalmLensServerClient`. Signing is local and makes no request:
512
+
513
+ ```ts
514
+ const { token, expiresAt } = await client.uploadTokens.sign({
515
+ uploadPresetId: "your-upload-preset-id",
516
+ externalId: "post-42", // optional
517
+ expiresInSeconds: 600, // optional, up to 3600
518
+ });
519
+ ```
520
+
521
+ 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).
522
+
466
523
  ## Asset Types
467
524
 
468
525
  Assets can be of the following types:
@@ -570,27 +627,27 @@ The SDK does not validate responses against its types, so fields and values adde
570
627
 
571
628
  ## Testing
572
629
 
573
- `@calmlens/sdk/testing` exports a matching object-parameter `MockCalmLensClient`, in-memory storage implementations, mock API key helpers, webhook delivery builders and `generateMediaTestCode`, for testing without network requests.
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.
574
631
 
575
- The main `@calmlens/sdk` entry point does not load or export testing helpers, to keep production bundles small. Keep `/testing` imports in test files.
632
+ 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
633
 
577
634
  ```typescript
578
- import { MockCalmLensClient, createMockApiKey } from "@calmlens/sdk/testing";
635
+ import { MockCalmLensServerClient, createMockApiKey } from "@calmlens/sdk/testing";
579
636
 
580
- const client = new MockCalmLensClient({
637
+ const client = new MockCalmLensServerClient({
581
638
  apiKey: createMockApiKey(),
582
639
  projectId: crypto.randomUUID(),
583
640
  });
584
641
  ```
585
642
 
586
- `MockCalmLensClient` accepts the same options as `CalmLensClient` (`apiKey`, `projectId`, `baseUrl`, `cdnBaseUrl`, `timeoutMs`, ...) and validates them the same way, plus `mockOptions`. `cdn.getAssetUrl` uses `cdnBaseUrl` (default `mock://calmlens/v1`).
643
+ `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
644
 
588
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.
589
646
 
590
647
  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
648
 
592
649
  ```typescript
593
- const client = new MockCalmLensClient({ apiKey: createMockApiKey(), projectId, timeoutMs: 5_000 });
650
+ const client = new MockCalmLensServerClient({ apiKey: createMockApiKey(), projectId, timeoutMs: 5_000 });
594
651
 
595
652
  client.failNextRequest("timeout", { operation: "text.moderate" });
596
653
  await expect(postComment(client, "hello")).rejects.toMatchObject({ code: "sdk/timeout" });
@@ -648,7 +705,7 @@ The result is typed as `WebhookEventPayload`; narrow it on `type` to get that ev
648
705
  **Fetch API** (Next.js route handlers, Remix, Cloudflare Workers, Bun, Deno):
649
706
 
650
707
  ```typescript
651
- import { CalmLensError, parseWebhookEvent } from "@calmlens/sdk";
708
+ import { CalmLensError, parseWebhookEvent } from "@calmlens/sdk/server";
652
709
 
653
710
  export async function POST(request: Request) {
654
711
  let event;
@@ -0,0 +1,233 @@
1
+ const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
2
+ const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.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 isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
66
+ const isUploadedAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id;
67
+ const expectUploadedAsset = (data) => {
68
+ if (!isUploadedAsset(data)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
69
+ message: "Expected an uploaded asset response",
70
+ status: 200
71
+ });
72
+ return data;
73
+ };
74
+ const expectAsset = (data) => {
75
+ if (!isAsset(data)) throw new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.INVALID_RESPONSE, {
76
+ message: "Expected an asset response",
77
+ status: 200
78
+ });
79
+ return data;
80
+ };
81
+ /** Fills in the default API base URL and drops trailing slashes. */
82
+ const resolveConnectionOptions = (options) => ({
83
+ ...options,
84
+ baseUrl: (options.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, "")
85
+ });
86
+ /** One client's transport, base URL and deadlines, and the requests both clients make. */
87
+ var CalmLensConnection = class {
88
+ constructor(options, apiKey) {
89
+ this.options = options;
90
+ const resolveFetch = () => {
91
+ const fetchImpl = this.options.fetch ?? globalThis.fetch;
92
+ 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" });
93
+ return fetchImpl;
94
+ };
95
+ this.transport = new require_CalmLensUtils.CalmLensTransport({
96
+ apiKey,
97
+ fetch: ((input, init) => resolveFetch()(input, init)),
98
+ signal: options.signal,
99
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
100
+ maxResponseBytes: options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
101
+ });
102
+ }
103
+ get timeoutMs() {
104
+ return this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
105
+ }
106
+ url(path, query) {
107
+ const search = query ? require_CalmLensUtils.stringifyQuery(query) : "";
108
+ return `${this.options.baseUrl}${path}${search ? `?${search}` : ""}`;
109
+ }
110
+ /** A JSON request whose deadline covers reading the response. */
111
+ json(method, path, { query, body, headers, idempotencyKey } = {}) {
112
+ return this.transport.json({
113
+ method,
114
+ url: this.url(path, query),
115
+ headers: body === void 0 ? headers : {
116
+ ...headers,
117
+ "Content-Type": "application/json"
118
+ },
119
+ body: body === void 0 ? void 0 : JSON.stringify(body),
120
+ idempotencyKey,
121
+ timeoutMs: this.timeoutMs,
122
+ deadline: "body"
123
+ });
124
+ }
125
+ upload(url, payload, headers, idempotencyKey) {
126
+ return this.transport.json({
127
+ method: "POST",
128
+ url,
129
+ headers,
130
+ body: toRawUploadBody(payload),
131
+ idempotencyKey,
132
+ timeoutMs: isTextPayload(payload) ? this.timeoutMs : this.options.uploadTimeoutMs,
133
+ deadline: "body",
134
+ fetch: this.options.uploadFetch
135
+ });
136
+ }
137
+ /**
138
+ * Uploads file content or a first multipart part to `uploadsPath` (`.../assets`), as raw
139
+ * bytes with the options in the query.
140
+ */
141
+ submitRawUpload(uploadsPath, options, queue, authHeaders = {}) {
142
+ const query = new URLSearchParams();
143
+ query.set("name", options.name);
144
+ query.set("queue", String(queue));
145
+ setOptionalQueryValue(query, "description", options.description);
146
+ setOptionalQueryValue(query, "processingOptions", options.processingOptions);
147
+ setOptionalQueryValue(query, "visibility", options.visibility);
148
+ setOptionalQueryValue(query, "metadata", options.metadata);
149
+ setOptionalQueryValue(query, "externalId", options.externalId);
150
+ setOptionalQueryValue(query, "externalUrl", options.externalUrl);
151
+ setOptionalQueryValue(query, "folderId", options.folderId);
152
+ setOptionalQueryValue(query, "uploadPresetId", options.uploadPresetId);
153
+ const isMultipart = "part" in options;
154
+ const payload = isMultipart ? options.part : options.content;
155
+ query.set("contentType", payload.type);
156
+ setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(payload));
157
+ if (isMultipart) {
158
+ const part = payload;
159
+ const multipartOptions = options;
160
+ query.set("partNumber", String(part.partNumber ?? 1));
161
+ query.set("isFinal", String(part.isFinal ?? false));
162
+ setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
163
+ setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
164
+ }
165
+ return this.upload(this.url(`${uploadsPath}?${query}`), payload, {
166
+ ...authHeaders,
167
+ "Content-Type": payload.type,
168
+ "X-CalmLens-Upload": isMultipart ? "part" : "content"
169
+ }, options.idempotencyKey ?? newIdempotencyKey());
170
+ }
171
+ /** Uploads the next part of a multipart upload to `partPath` (`.../assets/{id}/part`). */
172
+ uploadPart(partPath, { part, queue, idempotencyKey }, authHeaders = {}) {
173
+ if (!part) throw require_CalmLensVersion.invalidArgument("Part options are required");
174
+ const validation = require_CalmLensUtils.validateAssetPayload(part, {
175
+ multipart: true,
176
+ isFinal: part.isFinal
177
+ });
178
+ if (!validation.ok) throw invalidPayload(validation.error);
179
+ const query = new URLSearchParams({
180
+ partNumber: String(part.partNumber),
181
+ isFinal: String(part.isFinal ?? false)
182
+ });
183
+ if (queue !== void 0) query.set("queue", String(queue));
184
+ setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(part));
185
+ return this.upload(this.url(`${partPath}?${query}`), part, {
186
+ ...authHeaders,
187
+ "X-CalmLens-Upload": "part"
188
+ }, idempotencyKey ?? newIdempotencyKey());
189
+ }
190
+ };
191
+ //#endregion
192
+ Object.defineProperty(exports, "CalmLensConnection", {
193
+ enumerable: true,
194
+ get: function() {
195
+ return CalmLensConnection;
196
+ }
197
+ });
198
+ Object.defineProperty(exports, "expectAsset", {
199
+ enumerable: true,
200
+ get: function() {
201
+ return expectAsset;
202
+ }
203
+ });
204
+ Object.defineProperty(exports, "expectUploadedAsset", {
205
+ enumerable: true,
206
+ get: function() {
207
+ return expectUploadedAsset;
208
+ }
209
+ });
210
+ Object.defineProperty(exports, "invalidPayload", {
211
+ enumerable: true,
212
+ get: function() {
213
+ return invalidPayload;
214
+ }
215
+ });
216
+ Object.defineProperty(exports, "newIdempotencyKey", {
217
+ enumerable: true,
218
+ get: function() {
219
+ return newIdempotencyKey;
220
+ }
221
+ });
222
+ Object.defineProperty(exports, "prepareSubmitAssetOptions", {
223
+ enumerable: true,
224
+ get: function() {
225
+ return prepareSubmitAssetOptions;
226
+ }
227
+ });
228
+ Object.defineProperty(exports, "resolveConnectionOptions", {
229
+ enumerable: true,
230
+ get: function() {
231
+ return resolveConnectionOptions;
232
+ }
233
+ });