@calmlens/sdk 0.9.0 → 0.10.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,403 +1,327 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_CalmLensUtils = require("./CalmLensUtils-CArkVfj0.js");
3
- let zod = require("zod");
4
- zod = require_CalmLensUtils.__toESM(zod);
5
- require("zod/v4");
6
- let api_def = require("api-def");
7
- //#region ../shared/src/ModerationCategoryDefinitions.ts
8
- const MODERATION_CATEGORY_DEFINITIONS = {
9
- sexual: {
10
- summary: "Sexual acts, explicit sexual anatomy, sexual services, sexual assault depictions and clearly erotic material.",
11
- definition: "covers sexual acts, explicit sexual anatomy, sexual services and clearly erotic material. Descriptions of sexual assault can qualify without erotic intent. Fictional, illustrated and generated depictions can also qualify.",
12
- exclusions: "Ordinary affection, swimwear, fitness, fashion, non-erotic medical diagrams and general sexual-health education do not qualify on their own. A person accepting money or wearing revealing clothing does not establish sexual content without a qualifying act, display or offer.",
13
- docsHref: "/docs/guides/moderation-categories#sexual-content"
14
- },
15
- violence: {
16
- summary: "Physical attacks, injury, death, threats against others, and planning or encouraging physical violence.",
17
- definition: "covers physical attacks, bodily injury, death and threats against others. Accidental or medical injuries and natural death also count; an attacker or intent to harm is not required. It also includes planning, requesting, encouraging or praising physical violence. A depicted or described act can qualify in fiction or news reporting even when the author condemns it.",
18
- exclusions: "Solely intentional self-injury or suicide belongs under self-harm. Property damage alone, figurative language, abstract discussion of violence, injury or death and merely possessing a weapon do not establish violence. Consensual sport or training without injury or abuse is excluded.",
19
- docsHref: "/docs/guides/moderation-categories#violence"
20
- },
21
- "self-harm": {
22
- summary: "Intentional self-injury, suicide, preparation, methods or encouragement; text also includes current or recurring self-harm thoughts.",
23
- definition: "covers intentional self-injury, suicide, current self-harm risk, preparation, methods and encouragement. For text, it also includes current or recurring self-harm thoughts and an explicitly expressed desire to die. Asking for support does not cancel a separate disclosure of current risk or behavior.",
24
- exclusions: "For images, require evidence of a self-directed act, preparation, harmful methods or encouragement. An injury or covered body does not establish that harm was self-inflicted. General sadness, neutral statistics, prevention, recovery and healed scars alone are excluded.",
25
- docsHref: "/docs/guides/moderation-categories#self-harm"
26
- },
27
- harassment: {
28
- summary: "Targeted personal abuse, humiliation, bullying or serious intimidation; ordinary criticism and neutral reporting are excluded.",
29
- definition: "covers personal abuse, humiliation, bullying and serious intimidation directed at a person or identifiable group. The target can be named, implied or defined by an activity or affiliation. A protected identity is not required.",
30
- exclusions: "Disagreement, ordinary criticism of conduct or competence, untargeted profanity and neutral reporting of abuse do not qualify without a separate personal attack. Criticism can still qualify when it also contains personal degradation.",
31
- docsHref: "/docs/guides/moderation-categories#harassment-and-bullying"
32
- },
33
- hate: {
34
- summary: "Identity-based abuse, dehumanization, discriminatory exclusion or threats against people because of protected characteristics.",
35
- definition: "covers attacks on people because of race, ethnicity, nationality, religion, caste, sex, gender identity, sexual orientation or disability. It includes dehumanization, asserted inferiority, discriminatory exclusion, identity-based abuse and threats.",
36
- exclusions: "Both the protected target and the identity-based attack must be supported. Identity mentions, cultural clothing, religious imagery, criticism of beliefs or an unrelated threat do not establish hate. Neutral reporting, condemnation and opposition to hate are excluded unless a separate attack is expressed or endorsed.",
37
- docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
38
- },
39
- "criminal-activity": {
40
- summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act.",
41
- definition: "covers planning, requests for assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act. A request can qualify even when no act occurs and a later reply refuses it. Dangerous activity requires a concrete serious risk of physical harm.",
42
- exclusions: "Neutral reporting, prevention, lawful safety advice, fiction without criminal assistance, and vague dangerous references do not qualify. Masks, computers, payment cards, detention or ordinary tools do not establish criminal facilitation by themselves. A clear visual demonstration of how to carry out a crime can qualify.",
43
- docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
44
- }
45
- };
2
+ const require_CalmLensWebhooks = require("./CalmLensWebhooks-DJnaLpRy.js");
46
3
  //#endregion
47
- //#region ../shared/src/ModerationPolicyDimensions.ts
48
- const MODERATION_QUALIFIERS = [
49
- "nudity",
50
- "sexual-activity",
51
- "explicit-sexual-language",
52
- "sexual-solicitation",
53
- "physical-attack",
54
- "violent-threat",
55
- "violent-preparation",
56
- "violent-encouragement",
57
- "injury",
58
- "death",
59
- "graphic",
60
- "self-harm-thoughts",
61
- "self-harm-preparation",
62
- "self-harm-act",
63
- "self-harm-methods",
64
- "self-harm-encouragement",
65
- "personal-abuse",
66
- "targeted-intimidation",
67
- "harassment-threat",
68
- "sexual-harassment",
69
- "harassment-encouragement",
70
- "identity-abuse",
71
- "dehumanization",
72
- "identity-inferiority",
73
- "discriminatory-exclusion",
74
- "hate-threat",
75
- "criminal-demonstration",
76
- "criminal-preparation",
77
- "criminal-request",
78
- "criminal-instructions",
79
- "criminal-promotion",
80
- "historical",
81
- "documentary",
82
- "fictional",
83
- "support-prevention"
84
- ];
85
- const SHARED_QUALIFIERS = [
86
- "historical",
87
- "documentary",
88
- "fictional",
89
- "support-prevention"
90
- ];
91
- [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
92
- //#endregion
93
- //#region ../../node_modules/zod-meta/esm/index.js
94
- const createMetaType = (type) => {
95
- return Object.assign((value) => {
96
- return {
97
- type,
98
- data: value !== null && value !== void 0 ? value : {}
99
- };
100
- }, type);
4
+ //#region src/client/CalmLensVersion.ts
5
+ /** This SDK's version, from its package.json. */
6
+ const SDK_VERSION = "0.10.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";
101
14
  };
102
- createMetaType({ id: "docPropertyInfo" });
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";
103
19
  //#endregion
104
- //#region ../shared/src/ZodUtils.ts
105
- const SIZE_LIMITS = {
106
- small: 64,
107
- medium: 200,
108
- large: 1500,
109
- xlarge: 1e4,
110
- token: 1e4
111
- };
112
- const saneStringField = (options) => {
113
- return zod.string().transform((value) => value.trim()).refine((value) => {
114
- if ((options === null || options === void 0 ? void 0 : options.minWidth) && value.length < options.minWidth) return false;
115
- return true;
116
- }, "Not long enough").refine((value) => {
117
- var _options$maxLength, _options$type;
118
- const sizeLimit = (_options$maxLength = options === null || options === void 0 ? void 0 : options.maxLength) !== null && _options$maxLength !== void 0 ? _options$maxLength : SIZE_LIMITS[(_options$type = options === null || options === void 0 ? void 0 : options.type) !== null && _options$type !== void 0 ? _options$type : "medium"];
119
- return typeof value === "string" && value.length < sizeLimit;
120
- }, "Too long");
121
- };
122
- const CLASSIFICATION_INPUT_TYPE_SCHEMA = zod.enum(["text", "image"]);
123
- const CLASSIFICATION_CATEGORIES = [
124
- "sexual",
125
- "violence",
126
- "self-harm",
127
- "harassment",
128
- "hate",
129
- "criminal-activity"
130
- ];
131
- const CLASSIFICATION_CATEGORY_SCHEMA = zod.enum(CLASSIFICATION_CATEGORIES);
132
- zod.object({
133
- category: CLASSIFICATION_CATEGORY_SCHEMA,
134
- displayName: saneStringField(),
135
- description: saneStringField(),
136
- supportedInputTypes: zod.array(CLASSIFICATION_INPUT_TYPE_SCHEMA).min(1)
137
- });
138
- MODERATION_CATEGORY_DEFINITIONS.sexual.summary, MODERATION_CATEGORY_DEFINITIONS.violence.summary, MODERATION_CATEGORY_DEFINITIONS["self-harm"].summary, MODERATION_CATEGORY_DEFINITIONS.harassment.summary, MODERATION_CATEGORY_DEFINITIONS.hate.summary, MODERATION_CATEGORY_DEFINITIONS["criminal-activity"].summary;
139
- const LIKELIHOOD_SCHEMA = zod.enum([
140
- "very-unlikely",
141
- "unlikely",
142
- "possible",
143
- "likely",
144
- "very-likely"
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
145
31
  ]);
146
- const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum(["not-supported", "unavailable"]);
147
- const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
148
- status: zod.literal("evaluated"),
149
- score: zod.number().min(0).max(1),
150
- scopeScore: zod.number().min(0).max(1).optional(),
151
- likelihood: LIKELIHOOD_SCHEMA
152
- }).strict().refine(({ score, scopeScore }) => scopeScore === void 0 || scopeScore <= score, {
153
- message: "Only confidence cannot exceed Any confidence",
154
- path: ["scopeScore"]
155
- }), zod.object({
156
- status: zod.literal("not-evaluated"),
157
- reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA
158
- }).strict()]));
159
- const CLASSIFICATION_CATEGORY_REPORT_SCHEMA = zod.discriminatedUnion("status", [
160
- zod.object({
161
- status: zod.literal("evaluated"),
162
- likelihood: LIKELIHOOD_SCHEMA,
163
- score: zod.number().min(0).max(1),
164
- qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
165
- }).strict(),
166
- zod.object({
167
- status: zod.literal("partial"),
168
- likelihood: LIKELIHOOD_SCHEMA,
169
- score: zod.number().min(0).max(1),
170
- qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
171
- }).strict(),
172
- zod.object({
173
- status: zod.literal("not-evaluated"),
174
- reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA,
175
- qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
176
- }).strict()
177
- ]);
178
- const CATEGORIES_SCHEMA = zod.object({
179
- sexual: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
180
- violence: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
181
- "self-harm": CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
182
- harassment: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
183
- hate: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
184
- "criminal-activity": CLASSIFICATION_CATEGORY_REPORT_SCHEMA
185
- }).strict();
186
- const CLASSIFICATION_LABEL_SCHEMA = zod.object({
187
- description: saneStringField(),
188
- score: zod.number(),
189
- topicalityScore: zod.number().nullish(),
190
- likelihood: LIKELIHOOD_SCHEMA,
191
- knowledgeBaseId: saneStringField().nullish()
192
- });
193
- zod.object({
194
- processedAt: zod.number(),
195
- categories: CATEGORIES_SCHEMA,
196
- flagged: zod.boolean(),
197
- score: zod.number(),
198
- notes: saneStringField().nullish(),
199
- media: zod.object({
200
- durationSeconds: zod.number().positive(),
201
- frameIntervalSeconds: zod.number().positive().optional(),
202
- sampledFrames: zod.number().int().nonnegative(),
203
- audioTranscribed: zod.boolean(),
204
- segments: zod.array(zod.object({
205
- source: zod.enum([
206
- "frame",
207
- "speech",
208
- "frame-text",
209
- "clip"
210
- ]),
211
- startSeconds: zod.number().nonnegative(),
212
- endSeconds: zod.number().nonnegative(),
213
- categories: CATEGORIES_SCHEMA
214
- }).strict())
215
- }).strict().optional(),
216
- labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
217
- }).strict();
218
- //#endregion
219
- //#region ../shared/src/MediaTestTriggers.ts
220
- const MEDIA_TEST_SIGNALS = [...CLASSIFICATION_CATEGORIES, "violence.graphic"];
221
- const HEX_WORDS = [
222
- "zero",
223
- "one",
224
- "two",
225
- "three",
226
- "four",
227
- "five",
228
- "six",
229
- "seven",
230
- "eight",
231
- "nine",
232
- "alpha",
233
- "bravo",
234
- "charlie",
235
- "delta",
236
- "echo",
237
- "foxtrot"
238
- ];
239
- /** Read this aloud, or render it as large, high-contrast text in a video frame. */
240
- function generateMediaTestCode(signal, projectId) {
241
- if (!MEDIA_TEST_SIGNALS.includes(signal)) throw new Error("Unsupported media test signal");
242
- const suffix = projectId.slice(-8).toLowerCase();
243
- if (!/^[a-f0-9]{8}$/.test(suffix)) throw new Error("Expected a project ID with an eight-character hexadecimal suffix");
244
- return `Calm Lens test ${[...suffix].map((character) => HEX_WORDS[Number.parseInt(character, 16)]).join(" ")} ${signal.replace(/[.-]/g, " ")} end test`;
245
- }
246
- //#endregion
247
- //#region ../shared/src/CryptoUtils.ts
248
- const encoder = new TextEncoder();
249
- /** Lowercase hex of `bytes`, two characters per byte. */
250
- const bytesToHex = (bytes) => Array.from(bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
251
- (function() {
252
- var _ref = require_CalmLensUtils._asyncToGenerator(function* (value) {
253
- return bytesToHex(yield crypto.subtle.digest("SHA-256", encoder.encode(value)));
254
- });
255
- return function sha256Hex(_x) {
256
- return _ref.apply(this, arguments);
257
- };
258
- })();
259
- /** HMAC-SHA256 of `message` keyed with `secret` (both UTF-8 encoded), as lowercase hex. */
260
- const hmacSha256Hex = function() {
261
- var _ref2 = require_CalmLensUtils._asyncToGenerator(function* (secret, message) {
262
- const key = yield crypto.subtle.importKey("raw", encoder.encode(secret), {
263
- name: "HMAC",
264
- hash: "SHA-256"
265
- }, false, ["sign"]);
266
- return bytesToHex(yield crypto.subtle.sign("HMAC", key, encoder.encode(message)));
267
- });
268
- return function hmacSha256Hex(_x2, _x3) {
269
- return _ref2.apply(this, arguments);
270
- };
271
- }();
32
+ const timeoutReason = () => {
33
+ const reason = /* @__PURE__ */ new Error("The operation timed out.");
34
+ reason.name = "TimeoutError";
35
+ return reason;
36
+ };
272
37
  /**
273
- * Compares two strings without leaking, through timing, where they first differ. Strings of
274
- * different lengths are unequal immediately (the length itself is not secret here).
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.
275
41
  */
276
- const timingSafeEqual = (a, b) => {
277
- if (a.length !== b.length) return false;
278
- let difference = 0;
279
- for (let index = 0; index < a.length; index++) difference |= a.charCodeAt(index) ^ b.charCodeAt(index);
280
- return difference === 0;
281
- };
282
- //#endregion
283
- //#region src/client/CalmLensWebhooks.ts
284
- const WEBHOOK_DELIVERY_HEADER = "x-calmlens-delivery";
285
- const WEBHOOK_TIMESTAMP_HEADER = "x-calmlens-timestamp";
286
- const WEBHOOK_SIGNATURE_HEADER = "x-calmlens-signature";
287
- /** Deliveries whose timestamp is further than this from the receiver's clock are rejected. */
288
- const WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = 300;
289
- /** The JSON body of a webhook delivery for a stored event. */
290
- const toWebhookEventPayload = (event) => {
291
- var _event$description, _event$assetId, _event$asset;
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);
292
60
  return {
293
- id: event.id,
294
- type: event.type,
295
- createdAt: event.createdAt,
296
- projectId: event.projectId,
297
- data: {
298
- severity: event.severity,
299
- title: event.title,
300
- description: (_event$description = event.description) !== null && _event$description !== void 0 ? _event$description : null,
301
- assetId: (_event$assetId = event.assetId) !== null && _event$assetId !== void 0 ? _event$assetId : null,
302
- asset: (_event$asset = event.asset) !== null && _event$asset !== void 0 ? _event$asset : null,
303
- facts: event.facts
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);
304
72
  }
305
73
  };
306
74
  };
307
- /** `v1=<hex HMAC-SHA256 of "<timestamp>.<body>">`, the `x-calmlens-signature` header value. */
308
- const createWebhookSignatureHeader = function() {
309
- var _ref = require_CalmLensUtils._asyncToGenerator(function* (secret, timestamp, body) {
310
- return `v1=${yield hmacSha256Hex(secret, `${timestamp}.${body}`)}`;
75
+ /** Turns anything thrown while sending or reading a request into a `CalmLensError`. */
76
+ const toFailure = (cause, attempt) => {
77
+ if (require_CalmLensWebhooks.CalmLensError.isCalmLensError(cause)) return cause;
78
+ if (attempt.isCancelled()) return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, {
79
+ message: "Request cancelled",
80
+ cause
311
81
  });
312
- return function createWebhookSignatureHeader(_x, _x2, _x3) {
313
- return _ref.apply(this, arguments);
314
- };
315
- }();
316
- /** Headers for one signed delivery of `body`. */
317
- const createWebhookHeaders = function() {
318
- var _ref2 = require_CalmLensUtils._asyncToGenerator(function* (options) {
319
- var _options$timestampSec;
320
- const timestamp = (_options$timestampSec = options.timestampSeconds) !== null && _options$timestampSec !== void 0 ? _options$timestampSec : Math.floor(Date.now() / 1e3);
321
- return {
322
- "content-type": "application/json",
323
- [WEBHOOK_DELIVERY_HEADER]: options.deliveryId,
324
- [WEBHOOK_TIMESTAMP_HEADER]: String(timestamp),
325
- [WEBHOOK_SIGNATURE_HEADER]: yield createWebhookSignatureHeader(options.secret, timestamp, options.body)
326
- };
82
+ if (attempt.isTimedOut()) return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
83
+ message: "Request timed out",
84
+ cause
327
85
  });
328
- return function createWebhookHeaders(_x4) {
329
- return _ref2.apply(this, arguments);
86
+ return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
87
+ message: "Request failed",
88
+ cause
89
+ });
90
+ };
91
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
92
+ if (signal?.aborted) {
93
+ reject(new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
94
+ return;
95
+ }
96
+ const onAbort = () => {
97
+ clearTimeout(timer);
98
+ reject(new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.CANCELLED, { message: "Request cancelled" }));
330
99
  };
331
- }();
332
- /**
333
- * Verifies a webhook delivery: `x-calmlens-signature` must contain a `v1=` HMAC-SHA256 of
334
- * `"<x-calmlens-timestamp>.<raw body>"`, and the timestamp must be within `toleranceSeconds`
335
- * (default 300) of now. Returns `false` for anything missing or malformed.
336
- */
337
- function verifyWebhookSignature(_x5) {
338
- return _verifyWebhookSignature.apply(this, arguments);
339
- }
340
- function _verifyWebhookSignature() {
341
- _verifyWebhookSignature = require_CalmLensUtils._asyncToGenerator(function* ({ payload, signature, timestamp, secret, toleranceSeconds = 300, now }) {
342
- if (!secret || !signature || timestamp === null || timestamp === void 0) return false;
343
- const timestampText = String(timestamp).trim();
344
- if (!/^\d+$/.test(timestampText)) return false;
345
- const nowMs = now === void 0 ? Date.now() : now instanceof Date ? now.getTime() : now;
346
- if (Math.abs(Math.floor(nowMs / 1e3) - Number(timestampText)) > toleranceSeconds) return false;
347
- const candidates = signature.split(",").map((part) => part.trim()).filter((part) => part.startsWith("v1=")).map((part) => part.slice(3).toLowerCase());
348
- if (candidates.length === 0) return false;
349
- const body = typeof payload === "string" ? payload : JSON.stringify(payload);
350
- const expected = yield hmacSha256Hex(secret, `${timestampText}.${body}`);
351
- let matched = false;
352
- for (const candidate of candidates) if (timingSafeEqual(expected, candidate)) matched = true;
353
- return matched;
100
+ const timer = setTimeout(() => {
101
+ signal?.removeEventListener("abort", onAbort);
102
+ resolve();
103
+ }, ms);
104
+ signal?.addEventListener("abort", onAbort, { once: true });
105
+ });
106
+ /** Exponential backoff with jitter: half the delay is fixed, the other half random. */
107
+ const getRetryDelayMs = (attempt, random = Math.random) => {
108
+ const delay = Math.min(RETRY_MAX_DELAY_MS, RETRY_BASE_DELAY_MS * 2 ** attempt);
109
+ return Math.round(delay / 2 + random() * (delay / 2));
110
+ };
111
+ /** Parses `Retry-After` as seconds or an HTTP date, in seconds from `now`. */
112
+ const parseRetryAfter = (value, now = Date.now()) => {
113
+ if (!value) return void 0;
114
+ const trimmed = value.trim();
115
+ if (/^\d+$/.test(trimmed)) return Number(trimmed);
116
+ const date = Date.parse(trimmed);
117
+ if (Number.isNaN(date)) return void 0;
118
+ return Math.max(0, Math.ceil((date - now) / 1e3));
119
+ };
120
+ /** Bodies that can be sent again unchanged. Streams are consumed by the first attempt. */
121
+ const isReplayableBody = (body) => body === void 0 || body === null || typeof body === "string" || body instanceof Blob || body instanceof ArrayBuffer || ArrayBuffer.isView(body) || body instanceof URLSearchParams;
122
+ const isApiErrorPayload = (value) => !!value && typeof value === "object" && typeof value.code === "string";
123
+ /** Reads at most `limit` bytes of a body, failing with `sdk/response-too-large` beyond that. */
124
+ const readBody = async (response, limit) => {
125
+ const declared = Number(response.headers.get("content-length"));
126
+ const tooLarge = () => new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.RESPONSE_TOO_LARGE, {
127
+ message: `Response body exceeds ${limit} bytes`,
128
+ status: response.status,
129
+ requestId: response.headers.get("x-request-id") ?? void 0,
130
+ headers: response.headers
131
+ });
132
+ if (Number.isFinite(declared) && declared > limit) {
133
+ await response.body?.cancel().catch(() => {});
134
+ throw tooLarge();
135
+ }
136
+ if (!response.body) return /* @__PURE__ */ new Uint8Array(0);
137
+ const reader = response.body.getReader();
138
+ const chunks = [];
139
+ let total = 0;
140
+ for (;;) {
141
+ const { done, value } = await reader.read();
142
+ if (done) break;
143
+ total += value.byteLength;
144
+ if (total > limit) {
145
+ await reader.cancel().catch(() => {});
146
+ throw tooLarge();
147
+ }
148
+ chunks.push(value);
149
+ }
150
+ const bytes = new Uint8Array(total);
151
+ let offset = 0;
152
+ for (const chunk of chunks) {
153
+ bytes.set(chunk, offset);
154
+ offset += chunk.byteLength;
155
+ }
156
+ return bytes;
157
+ };
158
+ const responseInfo = (response) => ({
159
+ status: response.status,
160
+ requestId: response.headers.get("x-request-id") ?? void 0,
161
+ headers: response.headers,
162
+ retryAfter: parseRetryAfter(response.headers.get("retry-after"))
163
+ });
164
+ /** The `CalmLensError` for a non-2xx response whose body has been read. */
165
+ const toHttpError = (response, bytes) => {
166
+ let data;
167
+ try {
168
+ data = JSON.parse(new TextDecoder().decode(bytes));
169
+ } catch {}
170
+ if (isApiErrorPayload(data)) {
171
+ const { code, message, details, traceId } = data;
172
+ return new require_CalmLensWebhooks.CalmLensError(code, {
173
+ message: message ?? code,
174
+ ...responseInfo(response),
175
+ details: details || traceId ? {
176
+ ...details,
177
+ ...traceId ? { traceId } : {}
178
+ } : void 0
179
+ });
180
+ }
181
+ return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.HTTP_ERROR, {
182
+ message: `HTTP ${response.status}`,
183
+ ...responseInfo(response)
354
184
  });
355
- return _verifyWebhookSignature.apply(this, arguments);
356
- }
185
+ };
186
+ /** Sends requests with the client's credentials, deadlines, retries and response limits. */
187
+ var CalmLensTransport = class {
188
+ constructor(settings) {
189
+ this.settings = settings;
190
+ }
191
+ headersFor(request) {
192
+ const headers = new Headers(request.init?.headers);
193
+ for (const [name, value] of Object.entries(request.headers ?? {})) headers.set(name, value);
194
+ if (!headers.has("accept") && request.deadline === "body") headers.set("accept", "application/json");
195
+ headers.set("x-api-key", this.settings.apiKey);
196
+ if (!isBrowserRuntime()) headers.set("user-agent", getUserAgent());
197
+ if (request.idempotencyKey) headers.set("idempotency-key", request.idempotencyKey);
198
+ return Object.fromEntries(headers.entries());
199
+ }
200
+ canRetry(request) {
201
+ return this.settings.maxRetries > 0 && (request.method === "GET" || request.method === "HEAD" || !!request.idempotencyKey) && isReplayableBody(request.body);
202
+ }
203
+ /**
204
+ * Sends a request and returns the response, retrying transport failures, 429, 502–504 and
205
+ * `idempotency/request-in-progress` when the request is safe to repeat. Non-2xx responses
206
+ * throw unless `allowHttpErrors` is set. Call `release` once the body has been read.
207
+ */
208
+ async send(request, { allowHttpErrors = false } = {}) {
209
+ const fetchImpl = request.fetch ?? this.settings.fetch;
210
+ const retryable = this.canRetry(request);
211
+ const headers = this.headersFor(request);
212
+ for (let attemptNumber = 0;; attemptNumber++) {
213
+ const canRetryAgain = retryable && attemptNumber < this.settings.maxRetries;
214
+ const attempt = createAttemptSignal([this.settings.signal, request.init?.signal ?? void 0], request.timeoutMs);
215
+ let response;
216
+ try {
217
+ attempt.signal.throwIfAborted();
218
+ response = await fetchImpl(request.url, {
219
+ ...request.init,
220
+ method: request.method,
221
+ headers,
222
+ body: request.body,
223
+ redirect: "error",
224
+ signal: attempt.signal,
225
+ duplex: "half"
226
+ });
227
+ } catch (cause) {
228
+ attempt.release();
229
+ const failure = toFailure(cause, attempt);
230
+ if (canRetryAgain && failure.code === require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR) {
231
+ await sleep(getRetryDelayMs(attemptNumber), this.settings.signal);
232
+ continue;
233
+ }
234
+ throw failure;
235
+ }
236
+ if (request.deadline === "headers") attempt.clearDeadline();
237
+ if (response.ok) return {
238
+ response,
239
+ attempt
240
+ };
241
+ if (allowHttpErrors) {
242
+ const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
243
+ const delayMs = retryAfter !== void 0 ? retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
244
+ if (!canRetryAgain || !RETRYABLE_STATUSES.has(response.status) || delayMs > RETRY_AFTER_MAX_MS) return {
245
+ response,
246
+ attempt
247
+ };
248
+ attempt.release();
249
+ await response.body?.cancel().catch(() => {});
250
+ await sleep(delayMs, this.settings.signal);
251
+ continue;
252
+ }
253
+ let error;
254
+ try {
255
+ error = toHttpError(response, await readBody(response, this.settings.maxResponseBytes));
256
+ } catch (cause) {
257
+ error = toFailure(cause, attempt);
258
+ } finally {
259
+ attempt.release();
260
+ }
261
+ const retryDelayMs = error.retryAfter !== void 0 ? error.retryAfter * 1e3 : getRetryDelayMs(attemptNumber);
262
+ 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)) {
263
+ await sleep(retryDelayMs, this.settings.signal);
264
+ continue;
265
+ }
266
+ throw error;
267
+ }
268
+ }
269
+ /** Sends a request and parses its JSON response body. */
270
+ async json(request) {
271
+ const { response, attempt } = await this.send(request);
272
+ try {
273
+ const bytes = await readBody(response, this.settings.maxResponseBytes);
274
+ if (bytes.byteLength === 0 && (response.status === 204 || request.method === "DELETE")) return;
275
+ try {
276
+ return JSON.parse(new TextDecoder().decode(bytes));
277
+ } catch (cause) {
278
+ throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, {
279
+ message: `Invalid JSON response (HTTP ${response.status})`,
280
+ ...responseInfo(response),
281
+ cause
282
+ });
283
+ }
284
+ } catch (cause) {
285
+ throw toFailure(cause, attempt);
286
+ } finally {
287
+ attempt.release();
288
+ }
289
+ }
290
+ };
357
291
  //#endregion
358
292
  //#region src/client/CalmLensClient.ts
359
- const _excluded = ["uint8Array"];
360
- const _excluded2 = ["buffer"];
361
- const _excluded3 = ["assetIdentifier"];
362
- const _excluded4 = ["assetIdentifier", "init"];
363
- const _excluded5 = ["assetId"];
364
- const _excluded6 = ["assetId"];
365
- const _excluded7 = ["assetId"];
366
- const _excluded8 = ["folderId"];
293
+ const DEFAULT_TIMEOUT_MS = 12e4;
294
+ const DEFAULT_MAX_RETRIES = 2;
295
+ const DEFAULT_MAX_RESPONSE_BYTES = 33554432;
367
296
  const toBlobPartBytes = (value) => {
368
297
  if (value instanceof ArrayBuffer) return new Uint8Array(value);
369
298
  return Uint8Array.from(value);
370
299
  };
371
300
  const normalizePayloadForRequest = (payload) => {
372
- if ("base64" in payload && payload.base64) return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, payload), {}, { base64: require_CalmLensUtils.getRawBase64(payload.base64) });
301
+ if ("base64" in payload && payload.base64) return {
302
+ ...payload,
303
+ base64: require_CalmLensWebhooks.getRawBase64(payload.base64)
304
+ };
373
305
  if ("uint8Array" in payload && payload.uint8Array) {
374
- const { uint8Array } = payload, rest = require_CalmLensUtils._objectWithoutProperties(payload, _excluded);
375
- return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, rest), {}, { data: new Blob([toBlobPartBytes(uint8Array)]) });
306
+ const { uint8Array, ...rest } = payload;
307
+ return {
308
+ ...rest,
309
+ data: new Blob([toBlobPartBytes(uint8Array)])
310
+ };
376
311
  }
377
312
  if ("buffer" in payload && payload.buffer) {
378
- const { buffer } = payload, rest = require_CalmLensUtils._objectWithoutProperties(payload, _excluded2);
379
- return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, rest), {}, { data: new Blob([toBlobPartBytes(buffer)]) });
313
+ const { buffer, ...rest } = payload;
314
+ return {
315
+ ...rest,
316
+ data: new Blob([toBlobPartBytes(buffer)])
317
+ };
380
318
  }
381
319
  return payload;
382
320
  };
383
- const normalizeMetadataForMultipart = (metadata) => {
384
- if (!Array.isArray(metadata)) return metadata;
385
- return Object.fromEntries(metadata.map((item, index) => [index, item]));
386
- };
387
- const normalizeMetadataOptionsForRequest = (options) => {
388
- if (!("metadata" in options) || !Array.isArray(options.metadata)) return options;
389
- return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { metadata: normalizeMetadataForMultipart(options.metadata) });
390
- };
391
- const normalizeAssetOptionsForRequest = (options) => {
392
- const normalizedOptions = normalizeMetadataOptionsForRequest(options);
393
- if ("part" in normalizedOptions) return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, normalizedOptions), {}, { part: normalizePayloadForRequest(normalizedOptions.part) });
394
- if ("content" in normalizedOptions && !("plainText" in normalizedOptions.content)) return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, normalizedOptions), {}, { content: normalizePayloadForRequest(normalizedOptions.content) });
395
- return normalizedOptions;
396
- };
397
321
  const toRawUploadBody = (payload) => {
398
- const data = require_CalmLensUtils.assetToData(normalizePayloadForRequest(payload));
322
+ const data = require_CalmLensWebhooks.assetToData(normalizePayloadForRequest(payload));
399
323
  if (data instanceof Response) {
400
- if (!data.body) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Response upload data must have a body" });
324
+ if (!data.body) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Response upload data must have a body" });
401
325
  return data.body;
402
326
  }
403
327
  if (data instanceof ArrayBuffer || data instanceof Blob) return data;
@@ -408,923 +332,477 @@ const setOptionalQueryValue = (params, key, value) => {
408
332
  if (value !== void 0 && value !== null) params.set(key, typeof value === "string" ? value : JSON.stringify(value));
409
333
  };
410
334
  const assertValidUploadMimeType = (mimeType) => {
411
- if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
412
- message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
335
+ if (require_CalmLensWebhooks.isRejectedUploadMimeType(mimeType)) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
336
+ message: require_CalmLensWebhooks.getRejectedUploadMimeTypeError(mimeType),
413
337
  details: { mimeType }
414
338
  });
415
339
  };
416
340
  const isImageMimeType = (mimeType) => mimeType.toLowerCase().startsWith("image/");
341
+ const invalidPayload = (message) => new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
417
342
  const prepareSubmitAssetOptions = (options) => {
418
- if (!("content" in options) && !("url" in options) && !("part" in options)) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Either file content, URL, or multipart part is required" });
419
- if (!options.name) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset name is required" });
420
- if (!("part" in options) && "isFinal" in options) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "isFinal is only valid for multipart uploads" });
421
- if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.USE_UPLOAD_PART);
343
+ if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
344
+ if (!("content" in options) && !("url" in options) && !("part" in options)) throw invalidPayload("Either file content, URL, or multipart part is required");
345
+ if (!options.name) throw invalidPayload("Asset name is required");
346
+ if (!("part" in options) && "isFinal" in options) throw invalidPayload("isFinal is only valid for multipart uploads");
347
+ 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" });
422
348
  if ("content" in options) {
423
349
  assertValidUploadMimeType(options.content.type);
424
350
  if (!("plainText" in options.content)) {
425
- const validation = require_CalmLensUtils.validateAssetPayload(options.content);
426
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
351
+ const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
352
+ if (!validation.ok) throw invalidPayload(validation.error);
427
353
  }
428
354
  }
429
355
  if ("part" in options) {
430
- var _options$part$sizeInB;
431
356
  assertValidUploadMimeType(options.part.type);
432
- const partSizeInBytes = (_options$part$sizeInB = options.part.sizeInBytes) !== null && _options$part$sizeInB !== void 0 ? _options$part$sizeInB : require_CalmLensUtils.getAssetPayloadByteLength(options.part);
357
+ const partSizeInBytes = options.part.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
433
358
  if (isImageMimeType(options.part.type) && partSizeInBytes !== void 0) {
434
- if (partSizeInBytes > require_CalmLensUtils.MAX_IMAGE_UPLOAD_BYTES) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE);
435
- if (options.part.isFinal && partSizeInBytes < require_CalmLensUtils.MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL);
359
+ 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." });
360
+ 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." });
436
361
  }
437
- const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
362
+ const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
438
363
  multipart: true,
439
364
  isFinal: options.part.isFinal
440
365
  });
441
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
366
+ if (!validation.ok) throw invalidPayload(validation.error);
442
367
  }
443
368
  return options;
444
369
  };
445
- const getEnsureLensCacheKey = (options) => {
446
- var _options$description, _options$transforms;
447
- return require_CalmLensUtils.stringifyJsonCanonical({
448
- identifier: options.identifier,
449
- name: options.name,
450
- description: (_options$description = options.description) !== null && _options$description !== void 0 ? _options$description : null,
451
- type: options.type,
452
- active: options.active,
453
- transforms: (_options$transforms = options.transforms) !== null && _options$transforms !== void 0 ? _options$transforms : null,
454
- version: options.version
455
- });
456
- };
457
- const withApiKeyHeader = (init, apiKey) => {
458
- const headers = new Headers(init === null || init === void 0 ? void 0 : init.headers);
459
- headers.set("X-Api-Key", apiKey);
460
- return require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, init), {}, {
461
- redirect: "error",
462
- headers: Object.fromEntries(headers.entries())
463
- });
464
- };
465
- const requestSignal = (options, signal) => {
466
- var _options$timeoutMs;
467
- const signals = [
468
- options.signal,
469
- signal,
470
- AbortSignal.timeout((_options$timeoutMs = options.timeoutMs) !== null && _options$timeoutMs !== void 0 ? _options$timeoutMs : 12e4)
471
- ].filter((value) => value != null);
472
- return AbortSignal.any(signals);
473
- };
474
- /** Copies a fully read body into a new Response, so later JSON parsing cannot hit the network. */
475
- const toBufferedResponse = function() {
476
- var _ref = require_CalmLensUtils._asyncToGenerator(function* (response) {
477
- const body = response.status === 204 || response.status === 304 ? null : yield response.arrayBuffer();
478
- const headers = new Headers(response.headers);
479
- headers.delete("content-encoding");
480
- headers.delete("content-length");
481
- const buffered = new Response(body, {
482
- status: response.status,
483
- statusText: response.statusText,
484
- headers
485
- });
486
- Object.defineProperty(buffered, "url", { value: response.url });
487
- return buffered;
488
- });
489
- return function toBufferedResponse(_x) {
490
- return _ref.apply(this, arguments);
491
- };
492
- }();
493
- /**
494
- * Wraps fetch with the client's deadline, cancellation and redirect policy. Every failure,
495
- * including body reads, decompression and truncated bodies when `bufferBody` is set, becomes a
496
- * `CalmLensError` (`sdk/transport-error`, `sdk/timeout` or `sdk/cancelled`) with the original
497
- * error as its `cause`.
498
- */
499
- const safeFetch = (fetchImpl, options, { bufferBody }) => function() {
500
- var _ref2 = require_CalmLensUtils._asyncToGenerator(function* (input, init) {
501
- var _init$signal;
502
- const signal = requestSignal(options, (_init$signal = init === null || init === void 0 ? void 0 : init.signal) !== null && _init$signal !== void 0 ? _init$signal : input instanceof Request ? input.signal : void 0);
503
- try {
504
- signal.throwIfAborted();
505
- const response = yield fetchImpl(input, require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, init), {}, {
506
- redirect: "error",
507
- signal
508
- }));
509
- return bufferBody ? yield toBufferedResponse(response) : response;
510
- } catch (cause) {
511
- var _signal$reason;
512
- throw require_CalmLensUtils.calmLensError({ id: signal.aborted ? ((_signal$reason = signal.reason) === null || _signal$reason === void 0 ? void 0 : _signal$reason.name) === "TimeoutError" ? "sdk/timeout" : "sdk/cancelled" : "sdk/transport-error" }, {
513
- message: signal.aborted ? "Request aborted" : "Request failed",
514
- cause
515
- });
516
- }
517
- });
518
- return function(_x2, _x3) {
519
- return _ref2.apply(this, arguments);
520
- };
521
- }();
522
- const createApi = (options) => {
523
- return new api_def.Api({
524
- name: "Calm Lens",
525
- baseUrl: options.baseUrl,
526
- config: { queryHandling: { stringify: require_CalmLensUtils.stringifyQuery } },
527
- middleware: [
528
- (0, api_def.RequestCoalescingMiddleware)(),
529
- { beforeSend: (context) => {
530
- context.updateHeaders({ "X-Api-Key": options.apiKey });
531
- } },
532
- { [api_def.RequestEvent.ERROR]: (context) => {
533
- if (context.error) throw normalizeApiError(context.error);
534
- } }
535
- ],
536
- requestBackend: options.requestBackend ? Object.assign(Object.create(options.requestBackend), "fetch" in options.requestBackend && typeof options.requestBackend.fetch === "function" ? { fetch: safeFetch(options.requestBackend.fetch, options, { bufferBody: true }) } : {}) : new api_def.FetchRequestBackend(safeFetch(globalThis.fetch, options, { bufferBody: true }))
537
- });
538
- };
539
- const isApiErrorPayload = (value) => {
540
- return !!value && typeof value === "object" && typeof value.code === "string";
541
- };
542
- const normalizeApiError = (error) => {
543
- let cause = error;
544
- const seen = /* @__PURE__ */ new Set();
545
- while (cause instanceof Error && !seen.has(cause)) {
546
- seen.add(cause);
547
- if (cause instanceof require_CalmLensUtils.CalmLensError) return cause;
548
- if (cause.name === "AbortError" || cause.name === "TimeoutError") return require_CalmLensUtils.calmLensError({ id: cause.name === "TimeoutError" ? "sdk/timeout" : "sdk/cancelled" }, {
549
- message: "Request aborted",
550
- cause
551
- });
552
- cause = cause.cause;
553
- }
554
- const response = error instanceof Error ? (0, api_def.getErrorResponse)(error) : void 0;
555
- if (error instanceof Error && (0, api_def.isRequestError)(error) && isApiErrorPayload(response === null || response === void 0 ? void 0 : response.data)) {
556
- const { code, message, details, traceId } = response.data;
557
- return require_CalmLensUtils.calmLensError({
558
- id: code,
559
- defaultMessage: message
560
- }, {
561
- message,
562
- status: response === null || response === void 0 ? void 0 : response.status,
563
- cause: error,
564
- details: require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, details), {}, {
565
- traceId,
566
- network: true
567
- })
568
- });
569
- }
570
- if (error instanceof require_CalmLensUtils.CalmLensError) return error;
571
- return require_CalmLensUtils.calmLensError({ id: response ? response.ok ? "sdk/invalid-response" : "sdk/http-error" : "sdk/transport-error" }, {
572
- message: response ? `HTTP ${response.status}` : error.message,
573
- status: response === null || response === void 0 ? void 0 : response.status,
574
- cause: error
575
- });
576
- };
577
- const createEndpoints = (api) => {
578
- const getFolders = api.endpoint().paramsOf().responseOf().build({
579
- id: "getFolders",
580
- method: "get",
581
- path: "/projects/:projectId/folders"
582
- });
583
- const getFolder = api.endpoint().paramsOf().responseOf().build({
584
- id: "getFolder",
585
- method: "get",
586
- path: "/folders/:folderId"
587
- });
588
- const postFolder = api.endpoint().paramsOf().bodyOf().responseOf().build({
589
- id: "postFolder",
590
- method: "post",
591
- path: "/projects/:projectId/folders"
592
- });
593
- const patchFolder = api.endpoint().paramsOf().bodyOf().responseOf().build({
594
- id: "patchFolder",
595
- method: "patch",
596
- path: "/folders/:folderId"
597
- });
598
- const deleteFolder = api.endpoint().paramsOf().responseOf().build({
599
- id: "deleteFolder",
600
- method: "delete",
601
- path: "/folders/:folderId"
602
- });
603
- const moveFolder = api.endpoint().paramsOf().bodyOf().responseOf().build({
604
- id: "moveFolder",
605
- method: "post",
606
- path: "/folders/:folderId/move"
607
- });
608
- const getAssetsPage = api.endpoint().queryOf().paramsOf().responseOf().build({
609
- id: "getAssetsPage",
610
- method: "get",
611
- path: "/projects/:projectId/assets/page"
612
- });
613
- const searchAssets = api.endpoint().paramsOf().queryOf().responseOf().build({
614
- id: "searchAssets",
615
- method: "get",
616
- path: "/projects/:projectId/assets/search"
617
- });
618
- const getAsset = api.endpoint().paramsOf().responseOf().build({
619
- id: "getAsset",
620
- method: "get",
621
- path: "/assets/:assetId"
622
- });
623
- const getAssetChildren = api.endpoint().paramsOf().queryOf().responseOf().build({
624
- id: "getAssetChildren",
625
- method: "get",
626
- path: "/assets/:assetId/children"
627
- });
628
- const patchAsset = api.endpoint().paramsOf().bodyOf().responseOf().build({
629
- id: "patchAsset",
630
- method: "patch",
631
- path: "/assets/:assetId"
632
- });
633
- const postUploadAsset = api.endpoint().bodyOf({ encoding: "multipart/form-data" }).paramsOf().queryOf().responseOf().build({
634
- id: "postUploadAsset",
635
- method: "post",
636
- path: "/projects/:projectId/assets"
637
- });
638
- const postAssetSignedUrl = api.endpoint().bodyOf().paramsOf().responseOf().build({
639
- id: "postAssetSignedUrl",
640
- method: "post",
641
- path: "/assets/:assetId/signed-url"
642
- });
643
- const postAssetPart = api.endpoint().bodyOf({ encoding: "multipart/form-data" }).paramsOf().queryOf().responseOf().build({
644
- id: "postAssetPart",
645
- method: "post",
646
- path: "/assets/:assetId/part"
647
- });
648
- const moveAsset = api.endpoint().paramsOf().bodyOf().responseOf().build({
649
- id: "moveAsset",
650
- method: "post",
651
- path: "/assets/:assetId/move"
652
- });
653
- const retryAsset = api.endpoint().paramsOf().responseOf().build({
654
- id: "retryAsset",
655
- method: "post",
656
- path: "/assets/:assetId/retry"
657
- });
658
- const deleteAsset = api.endpoint().paramsOf().responseOf().build({
659
- id: "deleteAsset",
660
- method: "delete",
661
- path: "/assets/:assetId"
662
- });
663
- const deleteAssetContent = api.endpoint().paramsOf().responseOf().build({
664
- id: "deleteAssetContent",
665
- method: "delete",
666
- path: "/assets/:assetId/content"
667
- });
668
- const getEvents = api.endpoint().paramsOf().queryOf().responseOf().build({
669
- id: "getEvents",
670
- method: "get",
671
- path: "/projects/:projectId/events"
672
- });
673
- const ensureLens = api.endpoint().bodyOf().paramsOf().responseOf().build({
674
- id: "ensureLens",
675
- method: "put",
676
- path: "/lenses/:lensIdentifier"
677
- });
678
- const ensureLenses = api.endpoint().bodyOf().paramsOf().responseOf().build({
679
- id: "ensureLenses",
680
- method: "put",
681
- path: "/projects/:projectId/lenses"
682
- });
683
- return encodePathParams({
684
- getFolders,
685
- getFolder,
686
- postFolder,
687
- patchFolder,
688
- deleteFolder,
689
- moveFolder,
690
- getAssetsPage,
691
- searchAssets,
692
- getAsset,
693
- getAssetChildren,
694
- patchAsset,
695
- postUploadAsset,
696
- postAssetSignedUrl,
697
- postAssetPart,
698
- moveAsset,
699
- retryAsset,
700
- deleteAsset,
701
- deleteAssetContent,
702
- ensureLens,
703
- ensureLenses,
704
- getEvents
705
- });
706
- };
370
+ const getEnsureLensCacheKey = (options) => require_CalmLensWebhooks.stringifyJsonCanonical({
371
+ identifier: options.identifier,
372
+ name: options.name,
373
+ description: options.description ?? null,
374
+ type: options.type,
375
+ active: options.active,
376
+ transforms: options.transforms ?? null,
377
+ version: options.version
378
+ });
379
+ const newIdempotencyKey = () => crypto.randomUUID();
380
+ /** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
381
+ const isTextPayload = (payload) => "plainText" in payload;
382
+ const requireId = (value, name) => require_CalmLensWebhooks.encodePathSegment(value, name);
383
+ const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
384
+ const inspectSymbol = Symbol.for("nodejs.util.inspect.custom");
707
385
  /**
708
- * api-def inserts `/:param` values into the path verbatim, so every endpoint encodes its path
709
- * params here: an ID can never add path segments, a query string or a fragment.
386
+ * The CalmLens API client. Create one per project and reuse it; it holds no connections.
387
+ * Every method takes one options object and throws `CalmLensError` on failure.
710
388
  */
711
- const encodePathParams = (endpoints) => {
712
- for (const endpoint of Object.values(endpoints)) {
713
- const submit = endpoint.submit.bind(endpoint);
714
- endpoint.submit = (config) => submit(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, config), {}, { params: config.params && Object.fromEntries(Object.entries(config.params).map(([key, value]) => [key, require_CalmLensUtils.encodePathSegment(value, key)])) }));
715
- }
716
- return endpoints;
717
- };
718
389
  var CalmLensClient = class CalmLensClient {
390
+ #apiKey;
391
+ #state;
392
+ #transport;
719
393
  /**
720
394
  * Verifies that a webhook delivery came from CalmLens: checks the `v1=` HMAC-SHA256 in
721
395
  * `x-calmlens-signature` over `"<x-calmlens-timestamp>.<raw body>"` and rejects timestamps
722
396
  * more than `toleranceSeconds` (default 300) from now, which stops replayed deliveries.
723
- *
724
- * @param options.payload - The raw request body, exactly as received (verify before parsing)
725
- * @param options.signature - The 'x-calmlens-signature' header value
726
- * @param options.timestamp - The 'x-calmlens-timestamp' header value
727
- * @param options.secret - The connector's signing secret
728
- * @param options.toleranceSeconds - Allowed clock distance in seconds (default 300)
729
- * @param options.now - Override the current time (Date or epoch milliseconds), for tests
730
- * @returns Promise<boolean> - True if the signature is valid and fresh, false otherwise
397
+ * Also exported as `verifyWebhookSignature`; `parseWebhookEvent` verifies and parses.
731
398
  *
732
399
  * @example
733
- * ```javascript
734
- * // In your webhook handler (e.g. a Fetch API Request)
400
+ * ```ts
735
401
  * const payload = await request.text();
736
402
  * const isValid = await CalmLensClient.verifyWebhookSignature({
737
403
  * payload,
738
- * signature: request.headers.get('x-calmlens-signature'),
739
- * timestamp: request.headers.get('x-calmlens-timestamp'),
740
- * secret: WEBHOOK_SECRET,
404
+ * headers: request.headers,
405
+ * secret: process.env.CALMLENS_WEBHOOK_SECRET!,
741
406
  * });
742
- *
743
- * if (!isValid) {
744
- * return new Response('Unauthorized', { status: 401 });
745
- * }
746
- * const event = JSON.parse(payload); // { id, type, createdAt, projectId, data }
747
407
  * ```
748
408
  */
749
- static verifyWebhookSignature(options) {
750
- return require_CalmLensUtils._asyncToGenerator(function* () {
751
- return verifyWebhookSignature(options);
752
- })();
409
+ static async verifyWebhookSignature(options) {
410
+ return require_CalmLensWebhooks.verifyWebhookSignature(options);
753
411
  }
754
- constructor(options) {
755
- var _this$options$baseUrl, _this$options$cdnBase;
756
- this.options = options;
757
- this.ensuredLenses = /* @__PURE__ */ new Map();
758
- this.pendingEnsureLenses = /* @__PURE__ */ new Map();
759
- this.multipartImageUploadSizes = /* @__PURE__ */ new Map();
760
- this.multipartUploadQueues = /* @__PURE__ */ new Map();
761
- require_CalmLensUtils.validateClientOptions(options);
762
- this.options = require_CalmLensUtils._objectSpread2({}, options);
763
- this.options.baseUrl = (_this$options$baseUrl = this.options.baseUrl) !== null && _this$options$baseUrl !== void 0 ? _this$options$baseUrl : `https://api.${require_CalmLensUtils.DOMAIN}/v1`;
764
- this.options.cdnBaseUrl = (_this$options$cdnBase = this.options.cdnBaseUrl) !== null && _this$options$cdnBase !== void 0 ? _this$options$cdnBase : `https://cdn.${require_CalmLensUtils.DOMAIN}/v1`;
765
- this.api = createApi(this.options);
766
- this.endpoints = createEndpoints(this.api);
412
+ constructor(options, state) {
413
+ require_CalmLensWebhooks.validateClientOptions(options);
414
+ 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.");
415
+ const { apiKey, ...rest } = options;
416
+ this.#apiKey = apiKey;
417
+ this.#state = {
418
+ options: {
419
+ ...rest,
420
+ baseUrl: (rest.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, ""),
421
+ cdnBaseUrl: (rest.cdnBaseUrl ?? `https://cdn.calmlens.com/v1`).replace(/\/+$/, "")
422
+ },
423
+ ensuredLenses: state?.ensuredLenses ?? /* @__PURE__ */ new Map(),
424
+ pendingEnsureLenses: state?.pendingEnsureLenses ?? /* @__PURE__ */ new Map()
425
+ };
426
+ const resolveFetch = () => {
427
+ const fetchImpl = this.#state.options.fetch ?? globalThis.fetch;
428
+ 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" });
429
+ return fetchImpl;
430
+ };
431
+ this.#transport = new CalmLensTransport({
432
+ apiKey,
433
+ fetch: ((input, init) => resolveFetch()(input, init)),
434
+ signal: rest.signal,
435
+ maxRetries: rest.maxRetries ?? DEFAULT_MAX_RETRIES,
436
+ maxResponseBytes: rest.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
437
+ });
767
438
  }
768
- /** Scope cancellation/timeout to an operation without changing this client's defaults. */
439
+ /**
440
+ * A client with different cancellation, deadlines or retries for the calls made through it,
441
+ * sharing this client's credentials and lens cache. This client is unchanged.
442
+ */
769
443
  withRequestOptions(options) {
770
- const client = new CalmLensClient(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, this.options), options));
771
- client.multipartUploadQueues = this.multipartUploadQueues;
772
- client.multipartImageUploadSizes = this.multipartImageUploadSizes;
773
- return client;
444
+ require_CalmLensWebhooks.validateRequestOptions(options);
445
+ return new CalmLensClient({
446
+ ...this.#state.options,
447
+ ...options,
448
+ apiKey: this.#apiKey
449
+ }, this.#state);
774
450
  }
775
- getUploadFetch() {
776
- var _this$options$request, _ref3, _this$options$uploadF;
777
- const requestBackendFetch = (_this$options$request = this.options.requestBackend) === null || _this$options$request === void 0 ? void 0 : _this$options$request.fetch;
778
- const fetchImpl = (_ref3 = (_this$options$uploadF = this.options.uploadFetch) !== null && _this$options$uploadF !== void 0 ? _this$options$uploadF : requestBackendFetch) !== null && _ref3 !== void 0 ? _ref3 : globalThis.fetch;
779
- if (!fetchImpl) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.SDK_ERRORS.NO_CDN_FETCH);
780
- return safeFetch(fetchImpl, this.options, { bufferBody: true });
451
+ /** Keeps the API key out of `console.log`, `util.inspect` and `JSON.stringify`. */
452
+ [inspectSymbol]() {
453
+ const { projectId, baseUrl } = this.#state.options;
454
+ return `CalmLensClient { projectId: ${JSON.stringify(projectId)}, baseUrl: ${JSON.stringify(baseUrl)} }`;
781
455
  }
782
- submitRawAssetUpload(options, queue) {
783
- var _this = this;
784
- return require_CalmLensUtils._asyncToGenerator(function* () {
785
- var _payload$sizeInBytes;
786
- const query = new URLSearchParams();
787
- query.set("name", options.name);
788
- query.set("queue", String(queue));
789
- setOptionalQueryValue(query, "description", options.description);
790
- setOptionalQueryValue(query, "processingOptions", options.processingOptions);
791
- setOptionalQueryValue(query, "visibility", options.visibility);
792
- setOptionalQueryValue(query, "metadata", options.metadata);
793
- setOptionalQueryValue(query, "externalId", options.externalId);
794
- setOptionalQueryValue(query, "externalUrl", options.externalUrl);
795
- setOptionalQueryValue(query, "folderId", options.folderId);
796
- const isMultipart = "part" in options;
797
- const payload = isMultipart ? options.part : options.content;
798
- query.set("contentType", payload.type);
799
- setOptionalQueryValue(query, "sizeInBytes", (_payload$sizeInBytes = payload.sizeInBytes) !== null && _payload$sizeInBytes !== void 0 ? _payload$sizeInBytes : require_CalmLensUtils.getAssetPayloadByteLength(payload));
800
- if (isMultipart) {
801
- var _part$partNumber, _part$isFinal, _part$sizeInBytes;
802
- const part = payload;
803
- const multipartOptions = options;
804
- query.set("partNumber", String((_part$partNumber = part.partNumber) !== null && _part$partNumber !== void 0 ? _part$partNumber : 1));
805
- query.set("isFinal", String((_part$isFinal = part.isFinal) !== null && _part$isFinal !== void 0 ? _part$isFinal : false));
806
- setOptionalQueryValue(query, "sizeInBytes", (_part$sizeInBytes = part.sizeInBytes) !== null && _part$sizeInBytes !== void 0 ? _part$sizeInBytes : multipartOptions.sizeInBytes);
807
- }
808
- const baseUrl = _this.options.baseUrl.replace(/\/$/, "");
809
- const path = isMultipart ? "assets/multipart-upload" : "assets/upload";
810
- const url = `${baseUrl}/projects/${encodeURIComponent(_this.options.projectId)}/${path}?${query}`;
811
- const response = yield _this.getUploadFetch()(url, {
812
- method: "POST",
813
- headers: {
814
- "Content-Type": payload.type,
815
- "X-Api-Key": _this.options.apiKey,
816
- "X-CalmLens-Upload": isMultipart ? "part" : "content"
817
- },
818
- body: toRawUploadBody(payload),
819
- duplex: "half"
820
- });
821
- const asset = yield _this.parseRawUploadResponse(response);
822
- if (isMultipart && asset.status === "uploading") {
823
- _this.multipartUploadQueues.set(asset.id, queue);
824
- if (isImageMimeType(payload.type)) {
825
- var _asset$sizeInBytes;
826
- _this.multipartImageUploadSizes.set(asset.id, (_asset$sizeInBytes = asset.sizeInBytes) !== null && _asset$sizeInBytes !== void 0 ? _asset$sizeInBytes : 0);
827
- }
828
- }
829
- return asset;
830
- })();
456
+ toJSON() {
457
+ const { projectId, baseUrl, cdnBaseUrl } = this.#state.options;
458
+ return {
459
+ projectId,
460
+ baseUrl,
461
+ cdnBaseUrl
462
+ };
831
463
  }
832
- submitRawAssetPart(assetId, part, queue) {
833
- var _this2 = this;
834
- return require_CalmLensUtils._asyncToGenerator(function* () {
835
- var _part$isFinal2, _part$sizeInBytes2;
836
- const query = new URLSearchParams({
837
- queue: String(queue),
838
- partNumber: String(part.partNumber),
839
- isFinal: String((_part$isFinal2 = part.isFinal) !== null && _part$isFinal2 !== void 0 ? _part$isFinal2 : false)
840
- });
841
- setOptionalQueryValue(query, "sizeInBytes", (_part$sizeInBytes2 = part.sizeInBytes) !== null && _part$sizeInBytes2 !== void 0 ? _part$sizeInBytes2 : require_CalmLensUtils.getAssetPayloadByteLength(part));
842
- const url = `${_this2.options.baseUrl.replace(/\/$/, "")}/assets/${require_CalmLensUtils.encodePathSegment(assetId, "Asset ID")}/part?${query}`;
843
- const response = yield _this2.getUploadFetch()(url, {
844
- method: "POST",
845
- headers: {
846
- "X-Api-Key": _this2.options.apiKey,
847
- "X-CalmLens-Upload": "part"
848
- },
849
- body: toRawUploadBody(part),
850
- duplex: "half"
851
- });
852
- const asset = yield _this2.parseRawUploadResponse(response);
853
- if (asset.status === "uploading") {
854
- var _asset$sizeInBytes2;
855
- _this2.multipartImageUploadSizes.set(asset.id, (_asset$sizeInBytes2 = asset.sizeInBytes) !== null && _asset$sizeInBytes2 !== void 0 ? _asset$sizeInBytes2 : 0);
856
- } else {
857
- _this2.multipartImageUploadSizes.delete(assetId);
858
- _this2.multipartUploadQueues.delete(assetId);
859
- }
860
- return asset;
861
- })();
464
+ get timeoutMs() {
465
+ return this.#state.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
862
466
  }
863
- parseRawUploadResponse(response) {
864
- return require_CalmLensUtils._asyncToGenerator(function* () {
865
- let data;
866
- try {
867
- data = yield response.json();
868
- } catch (cause) {
869
- throw require_CalmLensUtils.calmLensError({ id: response.ok ? "sdk/invalid-response" : "sdk/http-error" }, {
870
- message: `Invalid JSON response (HTTP ${response.status})`,
871
- status: response.status,
872
- cause
873
- });
874
- }
875
- if (!response.ok) {
876
- if (isApiErrorPayload(data)) throw require_CalmLensUtils.calmLensError({
877
- id: data.code,
878
- defaultMessage: data.message
879
- }, {
880
- message: data.message,
881
- status: response.status,
882
- details: require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, data.details), {}, {
883
- traceId: data.traceId,
884
- network: true
885
- })
886
- });
887
- throw require_CalmLensUtils.calmLensError({ id: "sdk/http-error" }, {
888
- message: `HTTP ${response.status}`,
889
- status: response.status
890
- });
891
- }
892
- if (!data || typeof data !== "object" || !("id" in data) || typeof data.id !== "string" || !data.id || !("status" in data) || ![
893
- "uploading",
894
- "pending",
895
- "approved",
896
- "rejected",
897
- "error"
898
- ].includes(String(data.status))) throw require_CalmLensUtils.calmLensError({ id: "sdk/invalid-response" }, {
899
- message: "Expected an asset response",
900
- status: response.status
901
- });
902
- return data;
903
- })();
467
+ apiUrl(path, query) {
468
+ const search = query ? require_CalmLensWebhooks.stringifyQuery(query) : "";
469
+ return `${this.#state.options.baseUrl}${path}${search ? `?${search}` : ""}`;
470
+ }
471
+ get projectPath() {
472
+ return `/projects/${encodeURIComponent(this.#state.options.projectId)}`;
473
+ }
474
+ request(method, path, { query, body } = {}) {
475
+ return this.#transport.json({
476
+ method,
477
+ url: this.apiUrl(path, query),
478
+ headers: body === void 0 ? void 0 : { "Content-Type": "application/json" },
479
+ body: body === void 0 ? void 0 : JSON.stringify(body),
480
+ timeoutMs: this.timeoutMs,
481
+ deadline: "body"
482
+ });
483
+ }
484
+ uploadRequest(url, payload, headers, idempotencyKey) {
485
+ const options = this.#state.options;
486
+ return this.#transport.json({
487
+ method: "POST",
488
+ url,
489
+ headers,
490
+ body: toRawUploadBody(payload),
491
+ idempotencyKey,
492
+ timeoutMs: isTextPayload(payload) ? this.timeoutMs : options.uploadTimeoutMs,
493
+ deadline: "body",
494
+ fetch: options.uploadFetch
495
+ });
496
+ }
497
+ async submitRawAssetUpload(options, queue) {
498
+ const query = new URLSearchParams();
499
+ query.set("name", options.name);
500
+ query.set("queue", String(queue));
501
+ setOptionalQueryValue(query, "description", options.description);
502
+ setOptionalQueryValue(query, "processingOptions", options.processingOptions);
503
+ setOptionalQueryValue(query, "visibility", options.visibility);
504
+ setOptionalQueryValue(query, "metadata", options.metadata);
505
+ setOptionalQueryValue(query, "externalId", options.externalId);
506
+ setOptionalQueryValue(query, "externalUrl", options.externalUrl);
507
+ setOptionalQueryValue(query, "folderId", options.folderId);
508
+ const isMultipart = "part" in options;
509
+ const payload = isMultipart ? options.part : options.content;
510
+ query.set("contentType", payload.type);
511
+ setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(payload));
512
+ if (isMultipart) {
513
+ const part = payload;
514
+ const multipartOptions = options;
515
+ query.set("partNumber", String(part.partNumber ?? 1));
516
+ query.set("isFinal", String(part.isFinal ?? false));
517
+ setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
518
+ setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
519
+ }
520
+ const path = isMultipart ? "assets/multipart-upload" : "assets/upload";
521
+ const data = await this.uploadRequest(this.apiUrl(`${this.projectPath}/${path}?${query}`), payload, {
522
+ "Content-Type": payload.type,
523
+ "X-CalmLens-Upload": isMultipart ? "part" : "content"
524
+ }, options.idempotencyKey ?? newIdempotencyKey());
525
+ return this.expectAsset(data);
526
+ }
527
+ expectAsset(data) {
528
+ if (!isAsset(data)) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, {
529
+ message: "Expected an asset response",
530
+ status: 200
531
+ });
532
+ return data;
533
+ }
534
+ async submitAsset(options, queue) {
535
+ const body = prepareSubmitAssetOptions(options);
536
+ if ("content" in body || "part" in body) return this.submitRawAssetUpload(body, queue);
537
+ const { idempotencyKey, ...fields } = body;
538
+ const data = await this.#transport.json({
539
+ method: "POST",
540
+ url: this.apiUrl(`${this.projectPath}/assets`, { queue }),
541
+ headers: { "Content-Type": "application/json" },
542
+ body: JSON.stringify(fields),
543
+ idempotencyKey: idempotencyKey ?? newIdempotencyKey(),
544
+ timeoutMs: this.timeoutMs,
545
+ deadline: "body"
546
+ });
547
+ return this.expectAsset(data);
904
548
  }
905
549
  get cdn() {
906
- var _this3 = this;
907
550
  return {
908
551
  /**
909
- * Create a CDN URL for asset content or previews.
910
- * Private assets require an authenticated request or signed URL.
552
+ * Builds a CDN URL for asset content or a preview, without a request. Private assets
553
+ * need a signed URL (`assets.createSignedUrl`) or an authenticated request.
911
554
  */
912
- getAssetUrl: (_ref4) => {
913
- var _options$resource;
914
- let { assetIdentifier } = _ref4, options = require_CalmLensUtils._objectWithoutProperties(_ref4, _excluded3);
915
- if (!assetIdentifier) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset identifier is required" });
916
- const resource = (_options$resource = options.resource) !== null && _options$resource !== void 0 ? _options$resource : "content";
917
- const url = `${this.options.cdnBaseUrl.replace(/\/$/, "")}/assets/${encodeURIComponent(this.options.projectId)}/${require_CalmLensUtils.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
918
- return require_CalmLensUtils.appendCdnSelector(url, options);
555
+ getAssetUrl: ({ assetIdentifier, ...options }) => {
556
+ const resource = options.resource ?? "content";
557
+ const url = `${this.#state.options.cdnBaseUrl}/assets/${encodeURIComponent(this.#state.options.projectId)}/${requireId(assetIdentifier, "Asset identifier")}/${resource}`;
558
+ return require_CalmLensWebhooks.appendCdnSelector(url, options);
919
559
  },
920
560
  /**
921
- * Fetch asset content or previews from the CDN.
922
- * Private assets are authenticated with the client's API key.
561
+ * Fetches asset content or a preview from the CDN with the client's API key, and returns
562
+ * the `Response` as soon as headers arrive, so the body streams. Non-2xx responses are
563
+ * returned, not thrown; `timeoutMs` covers only the wait for headers.
923
564
  */
924
- serve: function() {
925
- var _ref6 = require_CalmLensUtils._asyncToGenerator(function* (_ref5) {
926
- var _this$options$cdnFetc;
927
- let { assetIdentifier, init } = _ref5, urlOptions = require_CalmLensUtils._objectWithoutProperties(_ref5, _excluded4);
928
- const fetchImpl = (_this$options$cdnFetc = _this3.options.cdnFetch) !== null && _this$options$cdnFetc !== void 0 ? _this$options$cdnFetc : globalThis.fetch;
929
- if (!fetchImpl) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.SDK_ERRORS.NO_CDN_FETCH);
930
- return safeFetch(fetchImpl, _this3.options, { bufferBody: false })(_this3.cdn.getAssetUrl(require_CalmLensUtils._objectSpread2({ assetIdentifier }, urlOptions)), withApiKeyHeader(init, _this3.options.apiKey));
565
+ serve: async ({ assetIdentifier, init, ...urlOptions }) => {
566
+ const url = this.cdn.getAssetUrl({
567
+ assetIdentifier,
568
+ ...urlOptions
931
569
  });
932
- return function serve(_x4) {
933
- return _ref6.apply(this, arguments);
934
- };
935
- }()
570
+ const method = init?.method?.toUpperCase() ?? "GET";
571
+ const { response } = await this.#transport.send({
572
+ method,
573
+ url,
574
+ init,
575
+ timeoutMs: this.timeoutMs,
576
+ deadline: "headers",
577
+ fetch: this.#state.options.cdnFetch
578
+ }, { allowHttpErrors: true });
579
+ return response;
580
+ }
936
581
  };
937
582
  }
938
583
  get text() {
939
- var _this4 = this;
940
- return { moderate: function() {
941
- var _ref7 = require_CalmLensUtils._asyncToGenerator(function* (options) {
942
- return _this4.assets.moderate(require_CalmLensUtils.toTextAssetOptions(options));
943
- });
944
- return function moderate(_x5) {
945
- return _ref7.apply(this, arguments);
946
- };
947
- }() };
584
+ return {
585
+ /**
586
+ * Moderates text and waits for the decision. Sends `text` as `text/plain` content named
587
+ * `text` unless you pass `name`.
588
+ */
589
+ moderate: async (options) => this.assets.moderate(require_CalmLensWebhooks.toTextAssetOptions(options)) };
948
590
  }
949
591
  get assets() {
950
- var _this5 = this;
951
592
  return {
952
- search: function() {
953
- var _ref8 = require_CalmLensUtils._asyncToGenerator(function* (options) {
954
- return (yield _this5.endpoints.searchAssets.submit({
955
- params: { projectId: _this5.options.projectId },
956
- query: options
957
- })).data;
958
- });
959
- return function search(_x6) {
960
- return _ref8.apply(this, arguments);
961
- };
962
- }(),
593
+ /** Searches accessible assets by keyword or a natural-language description. */
594
+ search: async (options) => this.request("GET", `${this.projectPath}/assets/search`, { query: options }),
595
+ /** Lists one page of the project's assets, with optional filter and ordering. */
596
+ list: async (options = {}) => this.request("GET", `${this.projectPath}/assets/page`, { query: {
597
+ ...options,
598
+ pageIndex: options.pageIndex ?? 0
599
+ } }),
963
600
  /**
964
- * List all assets for a project
965
- * @param options - Pagination and filtering options
966
- * @returns Promise with paginated assets
601
+ * Yields every asset matching `options`, fetching pages as needed. Assets created or
602
+ * deleted while iterating can shift pages, so an asset may be skipped or repeated.
967
603
  */
968
- list: function() {
969
- var _ref9 = require_CalmLensUtils._asyncToGenerator(function* (options = {}) {
970
- var _options$pageIndex;
971
- return (yield _this5.endpoints.getAssetsPage.submit({
972
- params: { projectId: _this5.options.projectId },
973
- query: require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { pageIndex: (_options$pageIndex = options.pageIndex) !== null && _options$pageIndex !== void 0 ? _options$pageIndex : 0 })
974
- })).data;
975
- });
976
- return function list() {
977
- return _ref9.apply(this, arguments);
978
- };
979
- }(),
980
- /**
981
- * Retrieve a specific asset by ID
982
- * @param assetId - The asset ID
983
- * @returns Promise with asset data
984
- */
985
- retrieve: function() {
986
- var _ref10 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId }) {
987
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
988
- return (yield _this5.endpoints.getAsset.submit({ params: { assetId } })).data;
989
- });
990
- return function retrieve(_x7) {
991
- return _ref10.apply(this, arguments);
992
- };
993
- }(),
994
- update: function() {
995
- var _ref12 = require_CalmLensUtils._asyncToGenerator(function* (_ref11) {
996
- let { assetId } = _ref11, body = require_CalmLensUtils._objectWithoutProperties(_ref11, _excluded5);
997
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
998
- return (yield _this5.endpoints.patchAsset.submit({
999
- params: { assetId },
1000
- body
1001
- })).data;
1002
- });
1003
- return function update(_x8) {
1004
- return _ref12.apply(this, arguments);
1005
- };
1006
- }(),
604
+ iterate: (options = {}) => this.iteratePages((pageIndex) => this.assets.list({
605
+ ...options,
606
+ pageIndex
607
+ })),
608
+ /** Retrieves an asset with its status and moderation report. */
609
+ retrieve: async ({ assetId }) => this.request("GET", `/assets/${requireId(assetId, "Asset ID")}`),
610
+ /** Updates an asset's editable fields. */
611
+ update: async ({ assetId, ...body }) => this.request("PATCH", `/assets/${requireId(assetId, "Asset ID")}`, { body }),
612
+ /** Lists one page of the assets extracted from a website or multimedia document. */
613
+ listChildren: async ({ assetId, ...options } = {}) => this.request("GET", `/assets/${requireId(assetId, "Asset ID")}/children`, { query: {
614
+ ...options,
615
+ pageIndex: options.pageIndex ?? 0
616
+ } }),
617
+ /** Yields every child of an asset, fetching pages as needed. */
618
+ iterateChildren: (options) => {
619
+ requireId(options?.assetId, "Asset ID");
620
+ return this.iteratePages((pageIndex) => this.assets.listChildren({
621
+ ...options,
622
+ pageIndex
623
+ }));
624
+ },
1007
625
  /**
1008
- * Get child assets of a parent asset
1009
- * @param assetId - The parent asset ID
1010
- * @param options - Pagination options
1011
- * @returns Promise with paginated child assets
626
+ * Uploads an asset and queues it for moderation, returning immediately with status
627
+ * `pending` (or `uploading` for the first multipart part). Retried safely with an
628
+ * `Idempotency-Key`.
1012
629
  */
1013
- listChildren: function() {
1014
- var _ref14 = require_CalmLensUtils._asyncToGenerator(function* (_ref13 = {}) {
1015
- var _options$pageIndex2;
1016
- let { assetId } = _ref13, options = require_CalmLensUtils._objectWithoutProperties(_ref13, _excluded6);
1017
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1018
- return (yield _this5.endpoints.getAssetChildren.submit({
1019
- params: { assetId },
1020
- query: require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { pageIndex: (_options$pageIndex2 = options.pageIndex) !== null && _options$pageIndex2 !== void 0 ? _options$pageIndex2 : 0 })
1021
- })).data;
1022
- });
1023
- return function listChildren() {
1024
- return _ref14.apply(this, arguments);
1025
- };
1026
- }(),
630
+ create: async (options) => this.submitAsset(options, true),
1027
631
  /**
1028
- * Submit a new asset for processing (queued asynchronously)
1029
- * The asset will be queued for classification and returned immediately.
1030
- * @param options - Asset submission options
1031
- * @returns Promise with created asset
632
+ * Uploads an asset and waits for its moderation decision. Audio and video still finish
633
+ * in the background. Retried safely with an `Idempotency-Key`.
1032
634
  */
1033
- create: function() {
1034
- var _ref15 = require_CalmLensUtils._asyncToGenerator(function* (options) {
1035
- const body = prepareSubmitAssetOptions(options);
1036
- if ("content" in body || "part" in body) return yield _this5.submitRawAssetUpload(body, true);
1037
- return (yield _this5.endpoints.postUploadAsset.submit({
1038
- body: normalizeAssetOptionsForRequest(body),
1039
- params: { projectId: _this5.options.projectId },
1040
- query: { queue: true }
1041
- })).data;
1042
- });
1043
- return function create(_x9) {
1044
- return _ref15.apply(this, arguments);
1045
- };
1046
- }(),
635
+ moderate: async (options) => {
636
+ 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");
637
+ return this.submitAsset(options, false);
638
+ },
1047
639
  /**
1048
- * Submit a new asset and moderate it synchronously
1049
- * The asset will be classified immediately before the response is returned.
1050
- * This may take longer but ensures the asset is fully processed.
1051
- * @param options - Asset submission options
1052
- * @returns Promise with created and analyzed asset
640
+ * Uploads the next part of a multipart upload. The upload's queue mode, chosen when it
641
+ * started, applies when the final part arrives; pass `queue` to override it.
1053
642
  */
1054
- moderate: function() {
1055
- var _ref16 = require_CalmLensUtils._asyncToGenerator(function* (options) {
1056
- if ("content" in options && "plainText" in options.content && (typeof options.content.plainText !== "string" || !options.content.plainText.trim())) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Text to moderate must be a non-empty string" });
1057
- const body = prepareSubmitAssetOptions(options);
1058
- if ("content" in body || "part" in body) return yield _this5.submitRawAssetUpload(body, false);
1059
- return (yield _this5.endpoints.postUploadAsset.submit({
1060
- body: normalizeAssetOptionsForRequest(body),
1061
- params: { projectId: _this5.options.projectId },
1062
- query: { queue: false }
1063
- })).data;
643
+ uploadPart: async ({ assetId, part, queue, idempotencyKey }) => {
644
+ const encodedAssetId = requireId(assetId, "Asset ID");
645
+ if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
646
+ const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
647
+ multipart: true,
648
+ isFinal: part.isFinal
1064
649
  });
1065
- return function moderate(_x10) {
1066
- return _ref16.apply(this, arguments);
1067
- };
1068
- }(),
1069
- /**
1070
- * Upload a part for a multipart upload
1071
- * @param assetId - The asset ID
1072
- * @param part - The part to upload
1073
- * @returns Promise with asset data
1074
- */
1075
- uploadPart: function() {
1076
- var _ref17 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId, part }) {
1077
- var _part$sizeInBytes3, _this$multipartUpload;
1078
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1079
- if (!part) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Part options are required" });
1080
- const validation = require_CalmLensUtils.validateAssetPayload(part, {
1081
- multipart: true,
1082
- isFinal: part.isFinal
1083
- });
1084
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
1085
- const existingImageSizeInBytes = _this5.multipartImageUploadSizes.get(assetId);
1086
- const partSizeInBytes = (_part$sizeInBytes3 = part.sizeInBytes) !== null && _part$sizeInBytes3 !== void 0 ? _part$sizeInBytes3 : require_CalmLensUtils.getAssetPayloadByteLength(part);
1087
- if (existingImageSizeInBytes !== void 0 && partSizeInBytes !== void 0) {
1088
- const totalSizeInBytes = existingImageSizeInBytes + partSizeInBytes;
1089
- if (totalSizeInBytes > require_CalmLensUtils.MAX_IMAGE_UPLOAD_BYTES) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE);
1090
- if (part.isFinal && totalSizeInBytes < require_CalmLensUtils.MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL);
1091
- }
1092
- return yield _this5.submitRawAssetPart(assetId, part, (_this$multipartUpload = _this5.multipartUploadQueues.get(assetId)) !== null && _this$multipartUpload !== void 0 ? _this$multipartUpload : false);
650
+ if (!validation.ok) throw invalidPayload(validation.error);
651
+ const query = new URLSearchParams({
652
+ partNumber: String(part.partNumber),
653
+ isFinal: String(part.isFinal ?? false)
1093
654
  });
1094
- return function uploadPart(_x11) {
1095
- return _ref17.apply(this, arguments);
1096
- };
1097
- }(),
655
+ if (queue !== void 0) query.set("queue", String(queue));
656
+ setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(part));
657
+ const data = await this.uploadRequest(this.apiUrl(`/assets/${encodedAssetId}/part?${query}`), part, { "X-CalmLens-Upload": "part" }, idempotencyKey ?? newIdempotencyKey());
658
+ return this.expectAsset(data);
659
+ },
1098
660
  /**
1099
- * Create a short-lived signed CDN URL for an asset.
1100
- * This is useful for private assets where clients should not send app
1101
- * auth cookies or API keys to the CDN on every media request.
661
+ * Creates a short-lived signed CDN URL, so browsers can load private content without
662
+ * an API key.
1102
663
  */
1103
- createSignedUrl: function() {
1104
- var _ref19 = require_CalmLensUtils._asyncToGenerator(function* (_ref18) {
1105
- var _options$resource2, _options$expiresInSec;
1106
- let { assetId } = _ref18, options = require_CalmLensUtils._objectWithoutProperties(_ref18, _excluded7);
1107
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1108
- if (options.lens && options.transform) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Specify either a lens or a transform, not both" });
1109
- return (yield _this5.endpoints.postAssetSignedUrl.submit({
1110
- body: {
1111
- resource: (_options$resource2 = options.resource) !== null && _options$resource2 !== void 0 ? _options$resource2 : "content",
1112
- expiresInSeconds: (_options$expiresInSec = options.expiresInSeconds) !== null && _options$expiresInSec !== void 0 ? _options$expiresInSec : 900,
1113
- lens: options.lens,
1114
- transform: options.transform
1115
- },
1116
- params: { assetId }
1117
- })).data;
1118
- });
1119
- return function createSignedUrl(_x12) {
1120
- return _ref19.apply(this, arguments);
1121
- };
1122
- }(),
1123
- move: function() {
1124
- var _ref20 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId, folderId }) {
1125
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1126
- return (yield _this5.endpoints.moveAsset.submit({
1127
- params: { assetId },
1128
- body: { folderId }
1129
- })).data;
1130
- });
1131
- return function move(_x13) {
1132
- return _ref20.apply(this, arguments);
1133
- };
1134
- }(),
664
+ createSignedUrl: async ({ assetId, ...options }) => {
665
+ const encodedAssetId = requireId(assetId, "Asset ID");
666
+ if (options.lens && options.transform) throw require_CalmLensWebhooks.invalidArgument("Specify either a lens or a transform, not both");
667
+ return this.request("POST", `/assets/${encodedAssetId}/signed-url`, { body: {
668
+ resource: options.resource ?? "content",
669
+ expiresInSeconds: options.expiresInSeconds ?? 900,
670
+ lens: options.lens,
671
+ transform: options.transform
672
+ } });
673
+ },
674
+ /** Moves an asset into a folder, or to the project root with `folderId: null`. */
675
+ move: async ({ assetId, folderId }) => this.request("POST", `/assets/${requireId(assetId, "Asset ID")}/move`, { body: { folderId } }),
1135
676
  /**
1136
677
  * Processes an asset that failed (status `error`) again. It returns to `pending` and is
1137
678
  * moderated like a new upload; its content must still be stored.
1138
679
  */
1139
- retry: function() {
1140
- var _ref21 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId }) {
1141
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1142
- return (yield _this5.endpoints.retryAsset.submit({ params: { assetId } })).data;
1143
- });
1144
- return function retry(_x14) {
1145
- return _ref21.apply(this, arguments);
1146
- };
1147
- }(),
680
+ retry: async ({ assetId }) => this.request("POST", `/assets/${requireId(assetId, "Asset ID")}/retry`),
1148
681
  /**
1149
682
  * Removes an asset's stored content (source, preview and retained extracted text) while
1150
683
  * keeping the asset record, metadata and moderation report. The returned asset has
1151
684
  * `storageDeletedAt` set; the CDN no longer serves it.
1152
685
  */
1153
- deleteContent: function() {
1154
- var _ref22 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId }) {
1155
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1156
- return (yield _this5.endpoints.deleteAssetContent.submit({ params: { assetId } })).data;
1157
- });
1158
- return function deleteContent(_x15) {
1159
- return _ref22.apply(this, arguments);
1160
- };
1161
- }(),
686
+ deleteContent: async ({ assetId }) => this.request("DELETE", `/assets/${requireId(assetId, "Asset ID")}/content`),
1162
687
  /** Deletes an asset record together with its stored content. */
1163
- delete: function() {
1164
- var _ref23 = require_CalmLensUtils._asyncToGenerator(function* ({ assetId }) {
1165
- if (!assetId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Asset ID is required" });
1166
- yield _this5.endpoints.deleteAsset.submit({ params: { assetId } });
1167
- });
1168
- return function _delete(_x16) {
1169
- return _ref23.apply(this, arguments);
1170
- };
1171
- }()
688
+ delete: async ({ assetId }) => {
689
+ await this.request("DELETE", `/assets/${requireId(assetId, "Asset ID")}`);
690
+ }
1172
691
  };
1173
692
  }
1174
693
  get events() {
1175
- var _this6 = this;
1176
- return {
1177
- /**
1178
- * List project events after a cursor, oldest first. Call without `after` to get a cursor
1179
- * for "now", then poll with the returned `cursor`. Webhook deliveries carry the same events
1180
- * reshaped as `{ id, type, createdAt, projectId, data }`.
1181
- */
1182
- list: function() {
1183
- var _ref24 = require_CalmLensUtils._asyncToGenerator(function* (options = {}) {
1184
- return (yield _this6.endpoints.getEvents.submit({
1185
- params: { projectId: _this6.options.projectId },
1186
- query: options
1187
- })).data;
694
+ return {
695
+ /**
696
+ * Lists project events after a cursor, oldest first. Call without `after` to get a cursor
697
+ * for "now", then poll with the returned `cursor`. Webhook deliveries carry the same events
698
+ * reshaped as `{ id, type, createdAt, projectId, data }`.
699
+ */
700
+ list: async (options = {}) => this.request("GET", `${this.projectPath}/events`, { query: { ...options } }),
701
+ /**
702
+ * Yields pages of events after `after` until no more are waiting. Save each page's
703
+ * `cursor` to resume later. Without `after`, yields one empty page whose cursor is "now".
704
+ */
705
+ pages: (options = {}) => this.iterateEventPages(options)
706
+ };
707
+ }
708
+ async *iterateEventPages(options) {
709
+ let after = options.after;
710
+ for (;;) {
711
+ const page = await this.events.list({
712
+ ...options,
713
+ after
1188
714
  });
1189
- return function list() {
1190
- return _ref24.apply(this, arguments);
1191
- };
1192
- }() };
715
+ yield page;
716
+ if (!page.hasMore || page.cursor === after) return;
717
+ after = page.cursor;
718
+ }
719
+ }
720
+ async *iteratePages(listPage) {
721
+ for (let pageIndex = 0;; pageIndex++) {
722
+ const page = await listPage(pageIndex);
723
+ yield* page.items;
724
+ if (!page.hasMore || page.items.length === 0) return;
725
+ }
1193
726
  }
1194
727
  get folders() {
1195
- var _this7 = this;
1196
728
  return {
1197
- list: function() {
1198
- var _ref25 = require_CalmLensUtils._asyncToGenerator(function* () {
1199
- return (yield _this7.endpoints.getFolders.submit({ params: { projectId: _this7.options.projectId } })).data.folders;
1200
- });
1201
- return function list() {
1202
- return _ref25.apply(this, arguments);
1203
- };
1204
- }(),
1205
- retrieve: function() {
1206
- var _ref26 = require_CalmLensUtils._asyncToGenerator(function* ({ folderId }) {
1207
- if (!folderId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Folder ID is required" });
1208
- return (yield _this7.endpoints.getFolder.submit({ params: { folderId } })).data;
1209
- });
1210
- return function retrieve(_x17) {
1211
- return _ref26.apply(this, arguments);
1212
- };
1213
- }(),
1214
- create: function() {
1215
- var _ref27 = require_CalmLensUtils._asyncToGenerator(function* (options) {
1216
- return (yield _this7.endpoints.postFolder.submit({
1217
- params: { projectId: _this7.options.projectId },
1218
- body: options
1219
- })).data;
1220
- });
1221
- return function create(_x18) {
1222
- return _ref27.apply(this, arguments);
1223
- };
1224
- }(),
1225
- update: function() {
1226
- var _ref29 = require_CalmLensUtils._asyncToGenerator(function* (_ref28) {
1227
- let { folderId } = _ref28, body = require_CalmLensUtils._objectWithoutProperties(_ref28, _excluded8);
1228
- if (!folderId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Folder ID is required" });
1229
- return (yield _this7.endpoints.patchFolder.submit({
1230
- params: { folderId },
1231
- body
1232
- })).data;
1233
- });
1234
- return function update(_x19) {
1235
- return _ref29.apply(this, arguments);
1236
- };
1237
- }(),
1238
- delete: function() {
1239
- var _ref30 = require_CalmLensUtils._asyncToGenerator(function* ({ folderId }) {
1240
- if (!folderId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Folder ID is required" });
1241
- yield _this7.endpoints.deleteFolder.submit({ params: { folderId } });
1242
- });
1243
- return function _delete(_x20) {
1244
- return _ref30.apply(this, arguments);
1245
- };
1246
- }(),
1247
- move: function() {
1248
- var _ref31 = require_CalmLensUtils._asyncToGenerator(function* ({ folderId, parentId }) {
1249
- if (!folderId) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Folder ID is required" });
1250
- return (yield _this7.endpoints.moveFolder.submit({
1251
- params: { folderId },
1252
- body: { parentId }
1253
- })).data;
1254
- });
1255
- return function move(_x21) {
1256
- return _ref31.apply(this, arguments);
1257
- };
1258
- }()
729
+ /** Lists every folder in the project. */
730
+ list: async () => (await this.request("GET", `${this.projectPath}/folders`)).folders,
731
+ retrieve: async ({ folderId }) => this.request("GET", `/folders/${requireId(folderId, "Folder ID")}`),
732
+ /** Creates a folder at the project root or inside `parentId`. */
733
+ create: async (options) => this.request("POST", `${this.projectPath}/folders`, { body: options }),
734
+ update: async ({ folderId, ...body }) => this.request("PATCH", `/folders/${requireId(folderId, "Folder ID")}`, { body }),
735
+ /** Deletes an empty folder; throws `folder/not-empty` otherwise. */
736
+ delete: async ({ folderId }) => {
737
+ await this.request("DELETE", `/folders/${requireId(folderId, "Folder ID")}`);
738
+ },
739
+ /** Moves a folder under `parentId`, or to the project root with `parentId: null`. */
740
+ move: async ({ folderId, parentId }) => this.request("POST", `/folders/${requireId(folderId, "Folder ID")}/move`, { body: { parentId } })
1259
741
  };
1260
742
  }
1261
743
  /**
1262
744
  * Create a named lens at any positive version, or update it with a greater version.
1263
745
  * Supplying the same definition and version is idempotent.
1264
746
  */
1265
- ensureLens(options) {
1266
- var _this8 = this;
1267
- return require_CalmLensUtils._asyncToGenerator(function* () {
1268
- if (!options.identifier) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Lens identifier is required" });
1269
- const [lens] = yield _this8.ensureLenses({ lenses: [options] });
1270
- return lens;
1271
- })();
747
+ async ensureLens(options) {
748
+ if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
749
+ const [lens] = await this.ensureLenses({ lenses: [options] });
750
+ return lens;
1272
751
  }
1273
752
  /**
1274
753
  * Create or update several named lenses in one API request.
1275
754
  * Each lens follows the same versioning and idempotency rules as ensureLens().
1276
755
  */
1277
- ensureLenses({ lenses }) {
1278
- var _this9 = this;
1279
- return require_CalmLensUtils._asyncToGenerator(function* () {
1280
- if (lenses.length === 0) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "At least one lens is required" });
1281
- if (lenses.some((option) => !option.identifier)) throw require_CalmLensUtils.calmLensError({ id: "upload/invalid-payload" }, { message: "Lens identifier is required" });
1282
- const missingOptions = [];
1283
- const missingKeys = /* @__PURE__ */ new Set();
1284
- for (const option of lenses) {
1285
- const cacheKey = getEnsureLensCacheKey(option);
1286
- if (!_this9.ensuredLenses.has(cacheKey) && !missingKeys.has(cacheKey)) {
1287
- missingKeys.add(cacheKey);
1288
- missingOptions.push(option);
1289
- }
756
+ async ensureLenses({ lenses }) {
757
+ if (!Array.isArray(lenses) || lenses.length === 0) throw require_CalmLensWebhooks.invalidArgument("At least one lens is required");
758
+ if (lenses.some((option) => !option?.identifier)) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
759
+ const { ensuredLenses, pendingEnsureLenses } = this.#state;
760
+ const missingOptions = [];
761
+ const missingKeys = /* @__PURE__ */ new Set();
762
+ for (const option of lenses) {
763
+ const cacheKey = getEnsureLensCacheKey(option);
764
+ if (!ensuredLenses.has(cacheKey) && !missingKeys.has(cacheKey)) {
765
+ missingKeys.add(cacheKey);
766
+ missingOptions.push(option);
1290
767
  }
1291
- if (missingOptions.length === 0) return lenses.map((option) => _this9.ensuredLenses.get(getEnsureLensCacheKey(option)));
768
+ }
769
+ if (missingOptions.length > 0) {
1292
770
  const batchCacheKey = JSON.stringify([...missingKeys].sort());
1293
- let pending = _this9.pendingEnsureLenses.get(batchCacheKey);
771
+ let pending = pendingEnsureLenses.get(batchCacheKey);
1294
772
  if (!pending) {
1295
- pending = _this9.submitEnsureLenses(missingOptions);
1296
- _this9.pendingEnsureLenses.set(batchCacheKey, pending);
1297
- pending.then(() => _this9.pendingEnsureLenses.delete(batchCacheKey), () => _this9.pendingEnsureLenses.delete(batchCacheKey));
773
+ pending = this.submitEnsureLenses(missingOptions);
774
+ pendingEnsureLenses.set(batchCacheKey, pending);
775
+ pending.then(() => pendingEnsureLenses.delete(batchCacheKey), () => pendingEnsureLenses.delete(batchCacheKey));
1298
776
  }
1299
- yield pending;
1300
- return lenses.map((option) => _this9.ensuredLenses.get(getEnsureLensCacheKey(option)));
1301
- })();
777
+ await pending;
778
+ }
779
+ return lenses.map((option) => {
780
+ const lens = ensuredLenses.get(getEnsureLensCacheKey(option));
781
+ if (!lens) throw new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.INVALID_RESPONSE, { message: `CalmLens did not return lens "${option.identifier}"` });
782
+ return lens;
783
+ });
1302
784
  }
1303
- submitEnsureLenses(options) {
1304
- var _this10 = this;
1305
- return require_CalmLensUtils._asyncToGenerator(function* () {
1306
- const result = yield _this10.endpoints.ensureLenses.submit({
1307
- body: { lenses: options },
1308
- params: { projectId: _this10.options.projectId }
1309
- });
1310
- for (const [index, lens] of result.data.lenses.entries()) {
1311
- const optionsForLens = options[index];
1312
- if (!optionsForLens) throw require_CalmLensUtils.calmLensError({ id: "sdk/invalid-response" }, { message: "CalmLens returned an unexpected lens response" });
1313
- for (const [cacheKey, cachedLens] of _this10.ensuredLenses) if (cachedLens.identifier === optionsForLens.identifier) _this10.ensuredLenses.delete(cacheKey);
1314
- _this10.ensuredLenses.set(getEnsureLensCacheKey(optionsForLens), lens);
1315
- }
1316
- })();
785
+ async submitEnsureLenses(options) {
786
+ const result = await this.request("PUT", `${this.projectPath}/lenses`, { body: { lenses: options } });
787
+ const returned = Array.isArray(result?.lenses) ? result.lenses : [];
788
+ 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}` });
789
+ const { ensuredLenses } = this.#state;
790
+ for (const [index, lens] of returned.entries()) {
791
+ const optionsForLens = options[index];
792
+ 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}` });
793
+ for (const [cacheKey, cachedLens] of ensuredLenses) if (cachedLens.identifier === optionsForLens.identifier) ensuredLenses.delete(cacheKey);
794
+ ensuredLenses.set(getEnsureLensCacheKey(optionsForLens), lens);
795
+ }
1317
796
  }
1318
797
  };
1319
798
  //#endregion
799
+ exports.CALMLENS_ERROR_CODES = require_CalmLensWebhooks.CALMLENS_ERROR_CODES;
1320
800
  exports.CalmLensClient = CalmLensClient;
1321
- exports.CalmLensError = require_CalmLensUtils.CalmLensError;
1322
- exports.MEDIA_TEST_SIGNALS = MEDIA_TEST_SIGNALS;
1323
- exports.WEBHOOK_DELIVERY_HEADER = WEBHOOK_DELIVERY_HEADER;
1324
- exports.WEBHOOK_SIGNATURE_HEADER = WEBHOOK_SIGNATURE_HEADER;
1325
- exports.WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = WEBHOOK_SIGNATURE_TOLERANCE_SECONDS;
1326
- exports.WEBHOOK_TIMESTAMP_HEADER = WEBHOOK_TIMESTAMP_HEADER;
1327
- exports.createWebhookHeaders = createWebhookHeaders;
1328
- exports.generateMediaTestCode = generateMediaTestCode;
1329
- exports.toWebhookEventPayload = toWebhookEventPayload;
1330
- exports.verifyWebhookSignature = verifyWebhookSignature;
801
+ exports.CalmLensError = require_CalmLensWebhooks.CalmLensError;
802
+ exports.SDK_VERSION = SDK_VERSION;
803
+ exports.WEBHOOK_DELIVERY_HEADER = require_CalmLensWebhooks.WEBHOOK_DELIVERY_HEADER;
804
+ exports.WEBHOOK_SIGNATURE_HEADER = require_CalmLensWebhooks.WEBHOOK_SIGNATURE_HEADER;
805
+ exports.WEBHOOK_SIGNATURE_TOLERANCE_SECONDS = require_CalmLensWebhooks.WEBHOOK_SIGNATURE_TOLERANCE_SECONDS;
806
+ exports.WEBHOOK_TIMESTAMP_HEADER = require_CalmLensWebhooks.WEBHOOK_TIMESTAMP_HEADER;
807
+ exports.parseWebhookEvent = require_CalmLensWebhooks.parseWebhookEvent;
808
+ exports.verifyWebhookSignature = require_CalmLensWebhooks.verifyWebhookSignature;