@calmlens/sdk 0.11.0 → 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/cjs/index.js CHANGED
@@ -1,817 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_CalmLensWebhooks = require("./CalmLensWebhooks-CH2PaPs0.js");
3
- //#endregion
4
- //#region src/client/CalmLensVersion.ts
5
- /** This SDK's version, from its package.json. */
6
- const SDK_VERSION = "0.11.0";
7
- const runtimeName = () => {
8
- const runtime = globalThis;
9
- if (runtime.Bun?.version) return `bun/${runtime.Bun.version}`;
10
- if (runtime.Deno?.version?.deno) return `deno/${runtime.Deno.version.deno}`;
11
- if (runtime.navigator?.userAgent === "Cloudflare-Workers") return "workerd";
12
- if (runtime.process?.versions?.node) return `node/${runtime.process.versions.node}`;
13
- return "unknown";
14
- };
15
- /** The `User-Agent` the SDK sends from server runtimes, e.g. `calmlens-js/0.10.0 (node/22.12.0)`. */
16
- const getUserAgent = () => `calmlens-js/${SDK_VERSION} (${runtimeName()})`;
17
- /** True in a web page, where API keys would be visible to every visitor. */
18
- const isBrowserRuntime = () => typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
19
- //#endregion
20
- //#region src/client/CalmLensTransport.ts
21
- /** Retry delays grow from this base, doubling per attempt, with jitter. */
22
- const RETRY_BASE_DELAY_MS = 500;
23
- const RETRY_MAX_DELAY_MS = 8e3;
24
- /** A `Retry-After` longer than this is returned to the caller instead of waited out. */
25
- const RETRY_AFTER_MAX_MS = 6e4;
26
- const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
27
- 429,
28
- 502,
29
- 503,
30
- 504
31
- ]);
32
- const timeoutReason = () => {
33
- const reason = /* @__PURE__ */ new Error("The operation timed out.");
34
- reason.name = "TimeoutError";
35
- return reason;
36
- };
37
- /**
38
- * Links the callers' signals and a per-attempt deadline. `AbortSignal.any` holds its sources
39
- * weakly, so a long-lived caller signal doesn't collect a listener per streamed response;
40
- * runtimes without it get explicit listeners that `release` removes.
41
- */
42
- const createAttemptSignal = (signals, timeoutMs) => {
43
- const callers = signals.filter((signal) => signal !== void 0);
44
- const deadline = new AbortController();
45
- let timedOut = false;
46
- let signal;
47
- let onAbort;
48
- if (typeof AbortSignal.any === "function") signal = AbortSignal.any([...callers, deadline.signal]);
49
- else {
50
- const controller = new AbortController();
51
- signal = controller.signal;
52
- onAbort = (event) => controller.abort(event.target.reason);
53
- for (const source of [...callers, deadline.signal]) if (source.aborted) controller.abort(source.reason);
54
- else source.addEventListener("abort", onAbort, { once: true });
55
- }
56
- let timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
57
- timedOut = true;
58
- deadline.abort(timeoutReason());
59
- }, timeoutMs);
60
- return {
61
- signal,
62
- isTimedOut: () => timedOut,
63
- isCancelled: () => callers.some((caller) => caller.aborted),
64
- clearDeadline: () => {
65
- if (timer !== void 0) clearTimeout(timer);
66
- timer = void 0;
67
- },
68
- release: () => {
69
- if (timer !== void 0) clearTimeout(timer);
70
- timer = void 0;
71
- if (onAbort) for (const caller of callers) caller.removeEventListener("abort", onAbort);
72
- }
73
- };
74
- };
75
- /**
76
- * A redirect the SDK must not follow: it would send the API key to another origin. Browsers report
77
- * a manual redirect as an opaque response; other runtimes return the 3xx with its Location.
78
- */
79
- const isRedirect = (response) => response.type === "opaqueredirect" || response.status >= 300 && response.status < 400 && response.headers.has("location");
80
- /** Turns anything thrown while sending or reading a request into a `CalmLensError`. */
81
- const toFailure = (cause, attempt) => {
82
- if (require_CalmLensWebhooks.CalmLensError.isCalmLensError(cause)) return cause;
83
- if (attempt.isCancelled()) return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, {
84
- message: "Request cancelled",
85
- cause
86
- });
87
- if (attempt.isTimedOut()) return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
88
- message: "Request timed out",
89
- cause
90
- });
91
- return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
92
- message: "Request failed",
93
- cause
94
- });
95
- };
96
- const sleep = (ms, signal) => new Promise((resolve, reject) => {
97
- if (signal?.aborted) {
98
- reject(new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
99
- return;
100
- }
101
- const onAbort = () => {
102
- clearTimeout(timer);
103
- reject(new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
104
- };
105
- const timer = setTimeout(() => {
106
- signal?.removeEventListener("abort", onAbort);
107
- resolve();
108
- }, ms);
109
- signal?.addEventListener("abort", onAbort, { once: true });
110
- });
111
- /** Exponential backoff with jitter: half the delay is fixed, the other half random. */
112
- const getRetryDelayMs = (attempt, random = Math.random) => {
113
- const delay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);
114
- return Math.round(delay / 2 + random() * (delay / 2));
115
- };
116
- /** Parses `Retry-After` as seconds or an HTTP date, in seconds from `now`. */
117
- const parseRetryAfter = (value, now = Date.now()) => {
118
- if (!value) return void 0;
119
- const trimmed = value.trim();
120
- if (/^\d+$/.test(trimmed)) return Number(trimmed);
121
- const date = Date.parse(trimmed);
122
- if (Number.isNaN(date)) return void 0;
123
- return Math.max(0, Math.ceil((date - now) / 1e3));
124
- };
125
- /** Bodies that can be sent again unchanged. Streams are consumed by the first attempt. */
126
- const isReplayableBody = (body) => body === void 0 || body === null || typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof URLSearchParams;
127
- const isApiErrorPayload = (value) => !!value && typeof value === "object" && typeof value.code === "string";
128
- /** Reads at most `limit` bytes of a body, failing with `sdk/response-too-large` beyond that. */
129
- const readBody = async (response, limit) => {
130
- const declared = Number(response.headers.get("content-length"));
131
- const tooLarge = () => new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.RESPONSE_TOO_LARGE, {
132
- message: `Response body exceeds ${limit} bytes`,
133
- status: response.status,
134
- requestId: response.headers.get("x-request-id") ?? void 0,
135
- headers: response.headers
136
- });
137
- if (Number.isFinite(declared) && declared > limit) {
138
- await response.body?.cancel().catch(() => {});
139
- throw tooLarge();
140
- }
141
- if (!response.body) return /* @__PURE__ */ new Uint8Array(0);
142
- const reader = response.body.getReader();
143
- const chunks = [];
144
- let total = 0;
145
- for (;;) {
146
- const { done, value } = await reader.read();
147
- if (done) break;
148
- total += value.byteLength;
149
- if (total > limit) {
150
- await reader.cancel().catch(() => {});
151
- throw tooLarge();
152
- }
153
- chunks.push(value);
154
- }
155
- const bytes = new Uint8Array(total);
156
- let offset = 0;
157
- for (const chunk of chunks) {
158
- bytes.set(chunk, offset);
159
- offset += chunk.byteLength;
160
- }
161
- return bytes;
162
- };
163
- const responseInfo = (response) => ({
164
- status: response.status,
165
- requestId: response.headers.get("x-request-id") ?? void 0,
166
- headers: response.headers,
167
- retryAfter: parseRetryAfter(response.headers.get("retry-after"))
168
- });
169
- /** The `CalmLensError` for a non-2xx response whose body has been read. */
170
- const toHttpError = (response, bytes) => {
171
- let data;
172
- try {
173
- data = JSON.parse(new TextDecoder().decode(bytes));
174
- } catch {}
175
- if (isApiErrorPayload(data)) {
176
- const { code, message, details, traceId } = data;
177
- return new require_CalmLensWebhooks.CalmLensError(code, {
178
- message: message ?? code,
179
- ...responseInfo(response),
180
- details: details || traceId ? {
181
- ...details,
182
- ...traceId ? { traceId } : {}
183
- } : void 0
184
- });
185
- }
186
- return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.HTTP_ERROR, {
187
- message: `HTTP ${response.status}`,
188
- ...responseInfo(response)
189
- });
190
- };
191
- /** Sends requests with the client's credentials, deadlines, retries and response limits. */
192
- var CalmLensTransport = class {
193
- constructor(settings) {
194
- this.settings = settings;
195
- }
196
- headersFor(request) {
197
- const headers = new Headers(request.init?.headers);
198
- for (const [name, value] of Object.entries(request.headers ?? {})) headers.set(name, value);
199
- if (!headers.has("accept") && request.deadline === "body") headers.set("accept", "application/json");
200
- headers.set("x-api-key", this.settings.apiKey);
201
- if (!isBrowserRuntime()) headers.set("user-agent", getUserAgent());
202
- if (request.idempotencyKey) headers.set("idempotency-key", request.idempotencyKey);
203
- return Object.fromEntries(headers.entries());
204
- }
205
- canRetry(request) {
206
- return this.settings.maxRetries > 0 && (request.method === "GET" || request.method === "HEAD" || !!request.idempotencyKey) && isReplayableBody(request.body);
207
- }
208
- /**
209
- * Sends a request and returns the response, retrying transport failures, 429, 502–504 and
210
- * `idempotency/request-in-progress` when the request is safe to repeat. Non-2xx responses
211
- * throw unless `allowHttpErrors` is set. Call `release` once the body has been read.
212
- */
213
- async send(request, { allowHttpErrors = false } = {}) {
214
- const fetchImpl = request.fetch ?? this.settings.fetch;
215
- const retryable = this.canRetry(request);
216
- const headers = this.headersFor(request);
217
- for (let attemptNumber = 0;; attemptNumber++) {
218
- const canRetryAgain = retryable && attemptNumber < this.settings.maxRetries;
219
- const attempt = createAttemptSignal([this.settings.signal, request.init?.signal ?? void 0], request.timeoutMs);
220
- let response;
221
- try {
222
- attempt.signal.throwIfAborted();
223
- response = await fetchImpl(request.url, {
224
- ...request.init,
225
- method: request.method,
226
- headers,
227
- body: request.body,
228
- redirect: "manual",
229
- signal: attempt.signal,
230
- duplex: "half"
231
- });
232
- if (isRedirect(response)) {
233
- await response.body?.cancel().catch(() => {});
234
- throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, { message: `Unexpected redirect (HTTP ${response.status})` });
235
- }
236
- } catch (cause) {
237
- attempt.release();
238
- const failure = toFailure(cause, attempt);
239
- if (canRetryAgain && failure.code === require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR) {
240
- await sleep(getRetryDelayMs(attemptNumber), this.settings.signal);
241
- continue;
242
- }
243
- throw failure;
244
- }
245
- if (request.deadline === "headers") attempt.clearDeadline();
246
- if (response.ok) return {
247
- response,
248
- attempt
249
- };
250
- if (allowHttpErrors) {
251
- const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
252
- const delayMs = retryAfter !== void 0 ? retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
253
- if (!canRetryAgain || !RETRYABLE_STATUSES.has(response.status) || delayMs > RETRY_AFTER_MAX_MS) return {
254
- response,
255
- attempt
256
- };
257
- attempt.release();
258
- await response.body?.cancel().catch(() => {});
259
- await sleep(delayMs, this.settings.signal);
260
- continue;
261
- }
262
- let error;
263
- try {
264
- error = toHttpError(response, await readBody(response, this.settings.maxResponseBytes));
265
- } catch (cause) {
266
- error = toFailure(cause, attempt);
267
- } finally {
268
- attempt.release();
269
- }
270
- const retryDelayMs = error.retryAfter !== void 0 ? error.retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
271
- if (canRetryAgain && retryDelayMs <= RETRY_AFTER_MAX_MS && (error.status !== void 0 && RETRYABLE_STATUSES.has(error.status) || error.code === require_CalmLensWebhooks.API_ERROR_CODES.IDEMPOTENT_REQUEST_IN_PROGRESS)) {
272
- await sleep(retryDelayMs, this.settings.signal);
273
- continue;
274
- }
275
- throw error;
276
- }
277
- }
278
- /** Sends a request and parses its JSON response body. */
279
- async json(request) {
280
- const { response, attempt } = await this.send(request);
281
- try {
282
- const bytes = await readBody(response, this.settings.maxResponseBytes);
283
- if (bytes.byteLength === 0 && (response.status === 204 || request.method === "DELETE")) return;
284
- try {
285
- return JSON.parse(new TextDecoder().decode(bytes));
286
- } catch (cause) {
287
- throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, {
288
- message: `Invalid JSON response (HTTP ${response.status})`,
289
- ...responseInfo(response),
290
- cause
291
- });
292
- }
293
- } catch (cause) {
294
- throw toFailure(cause, attempt);
295
- } finally {
296
- attempt.release();
297
- }
298
- }
299
- };
300
- //#endregion
301
- //#region src/client/CalmLensClient.ts
302
- const DEFAULT_TIMEOUT_MS = 12e4;
303
- const DEFAULT_MAX_RETRIES = 2;
304
- const DEFAULT_MAX_RESPONSE_BYTES = 33554432;
305
- const toBlobPartBytes = (value) => {
306
- if (value instanceof ArrayBuffer) return new Uint8Array(value);
307
- return Uint8Array.from(value);
308
- };
309
- const normalizePayloadForRequest = (payload) => {
310
- if ("base64" in payload && payload.base64) return {
311
- ...payload,
312
- base64: require_CalmLensWebhooks.getRawBase64(payload.base64)
313
- };
314
- if ("uint8Array" in payload && payload.uint8Array) {
315
- const { uint8Array, ...rest } = payload;
316
- return {
317
- ...rest,
318
- data: new Blob([toBlobPartBytes(uint8Array)])
319
- };
320
- }
321
- if ("buffer" in payload && payload.buffer) {
322
- const { buffer, ...rest } = payload;
323
- return {
324
- ...rest,
325
- data: new Blob([toBlobPartBytes(buffer)])
326
- };
327
- }
328
- return payload;
329
- };
330
- const toRawUploadBody = (payload) => {
331
- const data = require_CalmLensWebhooks.assetToData(normalizePayloadForRequest(payload));
332
- if (data instanceof Response) {
333
- if (!data.body) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Response upload data must have a body" });
334
- return data.body;
335
- }
336
- if (data instanceof ArrayBuffer || data instanceof Blob) return data;
337
- if ("getReader" in data) return data;
338
- return new Blob([toBlobPartBytes(data)]);
339
- };
340
- const setOptionalQueryValue = (params, key, value) => {
341
- if (value !== void 0 && value !== null) params.set(key, typeof value === "string" ? value : JSON.stringify(value));
342
- };
343
- const assertValidUploadMimeType = (mimeType) => {
344
- if (require_CalmLensWebhooks.isRejectedUploadMimeType(mimeType)) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
345
- message: require_CalmLensWebhooks.getRejectedUploadMimeTypeError(mimeType),
346
- details: { mimeType }
347
- });
348
- };
349
- const isImageMimeType = (mimeType) => mimeType.toLowerCase().startsWith("image/");
350
- const invalidPayload = (message) => new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
351
- const prepareSubmitAssetOptions = (options) => {
352
- if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
353
- if (!("content" in options) && !("url" in options) && !("part" in options)) throw invalidPayload("Either file content, URL, or multipart part is required");
354
- if (!options.name) throw invalidPayload("Asset name is required");
355
- if (!("part" in options) && "isFinal" in options) throw invalidPayload("isFinal is only valid for multipart uploads");
356
- if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.USE_UPLOAD_PART, { message: "Use uploadPart for multipart upload parts after the first part" });
357
- if ("content" in options) {
358
- assertValidUploadMimeType(options.content.type);
359
- if (!("plainText" in options.content)) {
360
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
361
- if (!validation.ok) throw invalidPayload(validation.error);
362
- }
363
- }
364
- if ("part" in options) {
365
- assertValidUploadMimeType(options.part.type);
366
- const partSizeInBytes = options.part.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
367
- if (isImageMimeType(options.part.type) && partSizeInBytes !== void 0) {
368
- if (partSizeInBytes > require_CalmLensWebhooks.MAX_IMAGE_UPLOAD_BYTES) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE, { message: "Multipart image uploads cannot exceed 20MB." });
369
- if (options.part.isFinal && partSizeInBytes < require_CalmLensWebhooks.MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL, { message: "Multipart image uploads must total at least 10MB." });
370
- }
371
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
372
- multipart: true,
373
- isFinal: options.part.isFinal
374
- });
375
- if (!validation.ok) throw invalidPayload(validation.error);
376
- }
377
- return options;
378
- };
379
- const getEnsureLensCacheKey = (options) => require_CalmLensWebhooks.stringifyJsonCanonical({
380
- identifier: options.identifier,
381
- name: options.name,
382
- description: options.description ?? null,
383
- type: options.type,
384
- active: options.active,
385
- transforms: options.transforms ?? null,
386
- version: options.version
387
- });
388
- const newIdempotencyKey = () => crypto.randomUUID();
389
- /** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
390
- const isTextPayload = (payload) => "plainText" in payload;
391
- const requireId = (value, name) => require_CalmLensWebhooks.encodePathSegment(value, name);
392
- const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
393
- const inspectSymbol = Symbol.for("nodejs.util.inspect.custom");
394
- /**
395
- * The CalmLens API client. Create one per project and reuse it; it holds no connections.
396
- * Every method takes one options object and throws `CalmLensError` on failure.
397
- */
398
- var CalmLensClient = class CalmLensClient {
399
- #apiKey;
400
- #state;
401
- #transport;
402
- /**
403
- * Verifies that a webhook delivery came from CalmLens: checks the `v1=` HMAC-SHA256 in
404
- * `x-calmlens-signature` over `"<x-calmlens-timestamp>.<raw body>"` and rejects timestamps
405
- * more than `toleranceSeconds` (default 300) from now, which stops replayed deliveries.
406
- * Also exported as `verifyWebhookSignature`; `parseWebhookEvent` verifies and parses.
407
- *
408
- * @example
409
- * ```ts
410
- * const payload = await request.text();
411
- * const isValid = await CalmLensClient.verifyWebhookSignature({
412
- * payload,
413
- * headers: request.headers,
414
- * secret: process.env.CALMLENS_WEBHOOK_SECRET!,
415
- * });
416
- * ```
417
- */
418
- static async verifyWebhookSignature(options) {
419
- return require_CalmLensWebhooks.verifyWebhookSignature(options);
420
- }
421
- constructor(options, state) {
422
- require_CalmLensWebhooks.validateClientOptions(options);
423
- if (isBrowserRuntime() && !options.dangerouslyAllowBrowser) throw require_CalmLensWebhooks.invalidConfig("CalmLensClient is running in a browser, where every visitor could read the API key. Call CalmLens from your server, or set dangerouslyAllowBrowser: true for trusted internal tools.");
424
- const { apiKey, ...rest } = options;
425
- this.#apiKey = apiKey;
426
- this.#state = {
427
- options: {
428
- ...rest,
429
- baseUrl: (rest.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, ""),
430
- cdnBaseUrl: (rest.cdnBaseUrl ?? `https://cdn.calmlens.com/v1`).replace(/\/+$/, "")
431
- },
432
- ensuredLenses: state?.ensuredLenses ?? /* @__PURE__ */ new Map(),
433
- pendingEnsureLenses: state?.pendingEnsureLenses ?? /* @__PURE__ */ new Map()
434
- };
435
- const resolveFetch = () => {
436
- const fetchImpl = this.#state.options.fetch ?? globalThis.fetch;
437
- if (typeof fetchImpl !== "function") throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.NO_FETCH, { message: "No fetch implementation is available; pass one as the fetch option" });
438
- return fetchImpl;
439
- };
440
- this.#transport = new CalmLensTransport({
441
- apiKey,
442
- fetch: ((input, init) => resolveFetch()(input, init)),
443
- signal: rest.signal,
444
- maxRetries: rest.maxRetries ?? DEFAULT_MAX_RETRIES,
445
- maxResponseBytes: rest.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
446
- });
447
- }
448
- /**
449
- * A client with different cancellation, deadlines or retries for the calls made through it,
450
- * sharing this client's credentials and lens cache. This client is unchanged.
451
- */
452
- withRequestOptions(options) {
453
- require_CalmLensWebhooks.validateRequestOptions(options);
454
- return new CalmLensClient({
455
- ...this.#state.options,
456
- ...options,
457
- apiKey: this.#apiKey
458
- }, this.#state);
459
- }
460
- /** Keeps the API key out of `console.log`, `util.inspect` and `JSON.stringify`. */
461
- [inspectSymbol]() {
462
- const { projectId, baseUrl } = this.#state.options;
463
- return `CalmLensClient { projectId: ${JSON.stringify(projectId)}, baseUrl: ${JSON.stringify(baseUrl)} }`;
464
- }
465
- toJSON() {
466
- const { projectId, baseUrl, cdnBaseUrl } = this.#state.options;
467
- return {
468
- projectId,
469
- baseUrl,
470
- cdnBaseUrl
471
- };
472
- }
473
- get timeoutMs() {
474
- return this.#state.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
475
- }
476
- apiUrl(path, query) {
477
- const search = query ? require_CalmLensWebhooks.stringifyQuery(query) : "";
478
- return `${this.#state.options.baseUrl}${path}${search ? `?${search}` : ""}`;
479
- }
480
- get projectPath() {
481
- return `/projects/${encodeURIComponent(this.#state.options.projectId)}`;
482
- }
483
- request(method, path, { query, body } = {}) {
484
- return this.#transport.json({
485
- method,
486
- url: this.apiUrl(path, query),
487
- headers: body === void 0 ? void 0 : { "Content-Type": "application/json" },
488
- body: body === void 0 ? void 0 : JSON.stringify(body),
489
- timeoutMs: this.timeoutMs,
490
- deadline: "body"
491
- });
492
- }
493
- uploadRequest(url, payload, headers, idempotencyKey) {
494
- const options = this.#state.options;
495
- return this.#transport.json({
496
- method: "POST",
497
- url,
498
- headers,
499
- body: toRawUploadBody(payload),
500
- idempotencyKey,
501
- timeoutMs: isTextPayload(payload) ? this.timeoutMs : options.uploadTimeoutMs,
502
- deadline: "body",
503
- fetch: options.uploadFetch
504
- });
505
- }
506
- async submitRawAssetUpload(options, queue) {
507
- const query = new URLSearchParams();
508
- query.set("name", options.name);
509
- query.set("queue", String(queue));
510
- setOptionalQueryValue(query, "description", options.description);
511
- setOptionalQueryValue(query, "processingOptions", options.processingOptions);
512
- setOptionalQueryValue(query, "visibility", options.visibility);
513
- setOptionalQueryValue(query, "metadata", options.metadata);
514
- setOptionalQueryValue(query, "externalId", options.externalId);
515
- setOptionalQueryValue(query, "externalUrl", options.externalUrl);
516
- setOptionalQueryValue(query, "folderId", options.folderId);
517
- const isMultipart = "part" in options;
518
- const payload = isMultipart ? options.part : options.content;
519
- query.set("contentType", payload.type);
520
- setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(payload));
521
- if (isMultipart) {
522
- const part = payload;
523
- const multipartOptions = options;
524
- query.set("partNumber", String(part.partNumber ?? 1));
525
- query.set("isFinal", String(part.isFinal ?? false));
526
- setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
527
- setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
528
- }
529
- const path = isMultipart ? "assets/multipart-upload" : "assets/upload";
530
- const data = await this.uploadRequest(this.apiUrl(`${this.projectPath}/${path}?${query}`), payload, {
531
- "Content-Type": payload.type,
532
- "X-CalmLens-Upload": isMultipart ? "part" : "content"
533
- }, options.idempotencyKey ?? newIdempotencyKey());
534
- return this.expectAsset(data);
535
- }
536
- expectAsset(data) {
537
- if (!isAsset(data)) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, {
538
- message: "Expected an asset response",
539
- status: 200
540
- });
541
- return data;
542
- }
543
- async submitAsset(options, queue) {
544
- const body = prepareSubmitAssetOptions(options);
545
- if ("content" in body || "part" in body) return this.submitRawAssetUpload(body, queue);
546
- const { idempotencyKey, ...fields } = body;
547
- const data = await this.#transport.json({
548
- method: "POST",
549
- url: this.apiUrl(`${this.projectPath}/assets`, { queue }),
550
- headers: { "Content-Type": "application/json" },
551
- body: JSON.stringify(fields),
552
- idempotencyKey: idempotencyKey ?? newIdempotencyKey(),
553
- timeoutMs: this.timeoutMs,
554
- deadline: "body"
555
- });
556
- return this.expectAsset(data);
557
- }
558
- get cdn() {
559
- return {
560
- /**
561
- * Builds a CDN URL for asset content or a preview, without a request. Private assets
562
- * need a signed URL (`assets.createSignedUrl`) or an authenticated request.
563
- */
564
- getAssetUrl: ({ assetIdentifier, ...options }) => {
565
- const resource = options.resource ?? "content";
566
- const url = `${this.#state.options.cdnBaseUrl}/assets/${encodeURIComponent(this.#state.options.projectId)}/${requireId(assetIdentifier, "Asset identifier")}/${resource}`;
567
- return require_CalmLensWebhooks.appendCdnSelector(url, options);
568
- },
569
- /**
570
- * Fetches asset content or a preview from the CDN with the client's API key, and returns
571
- * the `Response` as soon as headers arrive, so the body streams. Non-2xx responses are
572
- * returned, not thrown; `timeoutMs` covers only the wait for headers.
573
- */
574
- serve: async ({ assetIdentifier, init, ...urlOptions }) => {
575
- const url = this.cdn.getAssetUrl({
576
- assetIdentifier,
577
- ...urlOptions
578
- });
579
- const method = init?.method?.toUpperCase() ?? "GET";
580
- const { response } = await this.#transport.send({
581
- method,
582
- url,
583
- init,
584
- timeoutMs: this.timeoutMs,
585
- deadline: "headers",
586
- fetch: this.#state.options.cdnFetch
587
- }, { allowHttpErrors: true });
588
- return response;
589
- }
590
- };
591
- }
592
- get text() {
593
- return {
594
- /**
595
- * Moderates text and waits for the decision. Sends `text` as `text/plain` content named
596
- * `text` unless you pass `name`.
597
- */
598
- moderate: async (options) => this.assets.moderate(require_CalmLensWebhooks.toTextAssetOptions(options)) };
599
- }
600
- get assets() {
601
- return {
602
- /** Searches accessible assets by keyword or a natural-language description. */
603
- search: async (options) => this.request("GET", `${this.projectPath}/assets/search`, { query: options }),
604
- /** Lists one page of the project's assets, with optional filter and ordering. */
605
- list: async (options = {}) => this.request("GET", `${this.projectPath}/assets/page`, { query: {
606
- ...options,
607
- pageIndex: options.pageIndex ?? 0
608
- } }),
609
- /**
610
- * Yields every asset matching `options`, fetching pages as needed. Assets created or
611
- * deleted while iterating can shift pages, so an asset may be skipped or repeated.
612
- */
613
- iterate: (options = {}) => this.iteratePages((pageIndex) => this.assets.list({
614
- ...options,
615
- pageIndex
616
- })),
617
- /** Retrieves an asset with its status and moderation report. */
618
- retrieve: async ({ assetId }) => this.request("GET", `/assets/${requireId(assetId, "Asset ID")}`),
619
- /** Updates an asset's editable fields. */
620
- update: async ({ assetId, ...body }) => this.request("PATCH", `/assets/${requireId(assetId, "Asset ID")}`, { body }),
621
- /** Lists one page of the assets extracted from a website or multimedia document. */
622
- listChildren: async ({ assetId, ...options } = {}) => this.request("GET", `/assets/${requireId(assetId, "Asset ID")}/children`, { query: {
623
- ...options,
624
- pageIndex: options.pageIndex ?? 0
625
- } }),
626
- /** Yields every child of an asset, fetching pages as needed. */
627
- iterateChildren: (options) => {
628
- requireId(options?.assetId, "Asset ID");
629
- return this.iteratePages((pageIndex) => this.assets.listChildren({
630
- ...options,
631
- pageIndex
632
- }));
633
- },
634
- /**
635
- * Uploads an asset and queues it for moderation, returning immediately with status
636
- * `pending` (or `uploading` for the first multipart part). Retried safely with an
637
- * `Idempotency-Key`.
638
- */
639
- create: async (options) => this.submitAsset(options, true),
640
- /**
641
- * Uploads an asset and waits for its moderation decision. Audio and video still finish
642
- * in the background. Retried safely with an `Idempotency-Key`.
643
- */
644
- moderate: async (options) => {
645
- if (options && "content" in options && "plainText" in options.content && (typeof options.content.plainText !== "string" || !options.content.plainText.trim())) throw invalidPayload("Text to moderate must be a non-empty string");
646
- return this.submitAsset(options, false);
647
- },
648
- /**
649
- * Uploads the next part of a multipart upload. The upload's queue mode, chosen when it
650
- * started, applies when the final part arrives; pass `queue` to override it.
651
- */
652
- uploadPart: async ({ assetId, part, queue, idempotencyKey }) => {
653
- const encodedAssetId = requireId(assetId, "Asset ID");
654
- if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
655
- const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
656
- multipart: true,
657
- isFinal: part.isFinal
658
- });
659
- if (!validation.ok) throw invalidPayload(validation.error);
660
- const query = new URLSearchParams({
661
- partNumber: String(part.partNumber),
662
- isFinal: String(part.isFinal ?? false)
663
- });
664
- if (queue !== void 0) query.set("queue", String(queue));
665
- setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(part));
666
- const data = await this.uploadRequest(this.apiUrl(`/assets/${encodedAssetId}/part?${query}`), part, { "X-CalmLens-Upload": "part" }, idempotencyKey ?? newIdempotencyKey());
667
- return this.expectAsset(data);
668
- },
669
- /**
670
- * Creates a short-lived signed CDN URL, so browsers can load private content without
671
- * an API key.
672
- */
673
- createSignedUrl: async ({ assetId, ...options }) => {
674
- const encodedAssetId = requireId(assetId, "Asset ID");
675
- if (options.lens && options.transform) throw require_CalmLensWebhooks.invalidArgument("Specify either a lens or a transform, not both");
676
- return this.request("POST", `/assets/${encodedAssetId}/signed-url`, { body: {
677
- resource: options.resource ?? "content",
678
- expiresInSeconds: options.expiresInSeconds ?? 900,
679
- lens: options.lens,
680
- transform: require_CalmLensWebhooks.transformSelector(options.transform)
681
- } });
682
- },
683
- /** Moves an asset into a folder, or to the project root with `folderId: null`. */
684
- move: async ({ assetId, folderId }) => this.request("POST", `/assets/${requireId(assetId, "Asset ID")}/move`, { body: { folderId } }),
685
- /**
686
- * Processes an asset that failed (status `error`) again. It returns to `pending` and is
687
- * moderated like a new upload; its content must still be stored.
688
- */
689
- retry: async ({ assetId }) => this.request("POST", `/assets/${requireId(assetId, "Asset ID")}/retry`),
690
- /**
691
- * Removes an asset's stored content (source, preview and retained extracted text) while
692
- * keeping the asset record, metadata and moderation report. The returned asset has
693
- * `storageDeletedAt` set; the CDN no longer serves it.
694
- */
695
- deleteContent: async ({ assetId }) => this.request("DELETE", `/assets/${requireId(assetId, "Asset ID")}/content`),
696
- /** Deletes an asset record together with its stored content. */
697
- delete: async ({ assetId }) => {
698
- await this.request("DELETE", `/assets/${requireId(assetId, "Asset ID")}`);
699
- }
700
- };
701
- }
702
- get events() {
703
- return {
704
- /**
705
- * Lists project events after a cursor, oldest first. Call without `after` to get a cursor
706
- * for "now", then poll with the returned `cursor`. Webhook deliveries carry the same events
707
- * reshaped as `{ id, type, createdAt, projectId, data }`.
708
- */
709
- list: async (options = {}) => this.request("GET", `${this.projectPath}/events`, { query: { ...options } }),
710
- /**
711
- * Yields pages of events after `after` until no more are waiting. Save each page's
712
- * `cursor` to resume later. Without `after`, yields one empty page whose cursor is "now".
713
- */
714
- pages: (options = {}) => this.iterateEventPages(options)
715
- };
716
- }
717
- async *iterateEventPages(options) {
718
- let after = options.after;
719
- for (;;) {
720
- const page = await this.events.list({
721
- ...options,
722
- after
723
- });
724
- yield page;
725
- if (!page.hasMore || page.cursor === after) return;
726
- after = page.cursor;
727
- }
728
- }
729
- async *iteratePages(listPage) {
730
- for (let pageIndex = 0;; pageIndex++) {
731
- const page = await listPage(pageIndex);
732
- yield* page.items;
733
- if (!page.hasMore || page.items.length === 0) return;
734
- }
735
- }
736
- get folders() {
737
- return {
738
- /** Lists every folder in the project. */
739
- list: async () => (await this.request("GET", `${this.projectPath}/folders`)).folders,
740
- retrieve: async ({ folderId }) => this.request("GET", `/folders/${requireId(folderId, "Folder ID")}`),
741
- /** Creates a folder at the project root or inside `parentId`. */
742
- create: async (options) => this.request("POST", `${this.projectPath}/folders`, { body: options }),
743
- update: async ({ folderId, ...body }) => this.request("PATCH", `/folders/${requireId(folderId, "Folder ID")}`, { body }),
744
- /** Deletes an empty folder; throws `folder/not-empty` otherwise. */
745
- delete: async ({ folderId }) => {
746
- await this.request("DELETE", `/folders/${requireId(folderId, "Folder ID")}`);
747
- },
748
- /** Moves a folder under `parentId`, or to the project root with `parentId: null`. */
749
- move: async ({ folderId, parentId }) => this.request("POST", `/folders/${requireId(folderId, "Folder ID")}/move`, { body: { parentId } })
750
- };
751
- }
752
- /**
753
- * Create a named lens at any positive version, or update it with a greater version.
754
- * Supplying the same definition and version is idempotent.
755
- */
756
- async ensureLens(options) {
757
- if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
758
- const [lens] = await this.ensureLenses({ lenses: [options] });
759
- return lens;
760
- }
761
- /**
762
- * Create or update several named lenses in one API request.
763
- * Each lens follows the same versioning and idempotency rules as ensureLens().
764
- */
765
- async ensureLenses({ lenses }) {
766
- if (!Array.isArray(lenses) || lenses.length === 0) throw require_CalmLensWebhooks.invalidArgument("At least one lens is required");
767
- if (lenses.some((option) => !option?.identifier)) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
768
- const { ensuredLenses, pendingEnsureLenses } = this.#state;
769
- const missingOptions = [];
770
- const missingKeys = /* @__PURE__ */ new Set();
771
- for (const option of lenses) {
772
- const cacheKey = getEnsureLensCacheKey(option);
773
- if (!ensuredLenses.has(cacheKey) && !missingKeys.has(cacheKey)) {
774
- missingKeys.add(cacheKey);
775
- missingOptions.push(option);
776
- }
777
- }
778
- if (missingOptions.length > 0) {
779
- const batchCacheKey = JSON.stringify([...missingKeys].sort());
780
- let pending = pendingEnsureLenses.get(batchCacheKey);
781
- if (!pending) {
782
- pending = this.submitEnsureLenses(missingOptions);
783
- pendingEnsureLenses.set(batchCacheKey, pending);
784
- pending.then(() => pendingEnsureLenses.delete(batchCacheKey), () => pendingEnsureLenses.delete(batchCacheKey));
785
- }
786
- await pending;
787
- }
788
- return lenses.map((option) => {
789
- const lens = ensuredLenses.get(getEnsureLensCacheKey(option));
790
- if (!lens) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, { message: `CalmLens did not return lens "${option.identifier}"` });
791
- return lens;
792
- });
793
- }
794
- async submitEnsureLenses(options) {
795
- const result = await this.request("PUT", `${this.projectPath}/lenses`, { body: { lenses: options } });
796
- const returned = Array.isArray(result?.lenses) ? result.lenses : [];
797
- if (returned.length !== options.length) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, { message: `Expected ${options.length} lenses but CalmLens returned ${returned.length}` });
798
- const { ensuredLenses } = this.#state;
799
- for (const [index, lens] of returned.entries()) {
800
- const optionsForLens = options[index];
801
- if (lens?.identifier !== optionsForLens.identifier) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, { message: `Expected lens "${optionsForLens.identifier}" at position ${index}` });
802
- for (const [cacheKey, cachedLens] of ensuredLenses) if (cachedLens.identifier === optionsForLens.identifier) ensuredLenses.delete(cacheKey);
803
- ensuredLenses.set(getEnsureLensCacheKey(optionsForLens), lens);
804
- }
805
- }
806
- };
807
- //#endregion
808
- exports.CALMLENS_ERROR_CODES = require_CalmLensWebhooks.CALMLENS_ERROR_CODES;
809
- exports.CalmLensClient = CalmLensClient;
810
- exports.CalmLensError = require_CalmLensWebhooks.CalmLensError;
811
- exports.SDK_VERSION = SDK_VERSION;
812
- exports.WEBHOOK_DELIVERY_HEADER = require_CalmLensWebhooks.WEBHOOK_DELIVERY_HEADER;
813
- exports.WEBHOOK_SIGNATURE_HEADER = require_CalmLensWebhooks.WEBHOOK_SIGNATURE_HEADER;
814
- exports.WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = require_CalmLensWebhooks.WEBHOOK_SIGNATURE_TOLERANCE_SECONDS;
815
- exports.WEBHOOK_TIMESTAMP_HEADER = require_CalmLensWebhooks.WEBHOOK_TIMESTAMP_HEADER;
816
- exports.parseWebhookEvent = require_CalmLensWebhooks.parseWebhookEvent;
817
- exports.verifyWebhookSignature = require_CalmLensWebhooks.verifyWebhookSignature;
2
+ const require_CalmLensError = require("./CalmLensError-CKwMKV0S.js");
3
+ const require_src = require("./src-Dsc8Pn6v.js");
4
+ exports.CALMLENS_ERROR_CODES = require_CalmLensError.CALMLENS_ERROR_CODES;
5
+ exports.CalmLensError = require_CalmLensError.CalmLensError;
6
+ exports.SDK_VERSION = require_src.SDK_VERSION;