@calmlens/sdk 0.9.1 → 0.10.1

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/testing.js CHANGED
@@ -1,7 +1,269 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_CalmLensUtils = require("./CalmLensUtils-B6vwJijv.js");
2
+ //#region \0rolldown/runtime.js
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
11
+ key = keys[i];
12
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
13
+ get: ((k) => from[k]).bind(null, key),
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
20
+ value: mod,
21
+ enumerable: true
22
+ }) : target, mod));
23
+ //#endregion
24
+ const require_CalmLensWebhooks = require("./CalmLensWebhooks-DJnaLpRy.js");
3
25
  let zod = require("zod");
4
- zod = require_CalmLensUtils.__toESM(zod);
26
+ zod = __toESM(zod);
27
+ require("zod/v4");
28
+ //#region ../shared/src/ModerationCategoryDefinitions.ts
29
+ const MODERATION_CATEGORY_DEFINITIONS = {
30
+ sexual: {
31
+ summary: "Sexual acts, explicit sexual anatomy, sexual services, sexual assault depictions and clearly erotic material.",
32
+ 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.",
33
+ 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.",
34
+ docsHref: "/docs/guides/moderation-categories#sexual-content"
35
+ },
36
+ violence: {
37
+ summary: "Physical attacks, injury, death, threats against others, and planning or encouraging physical violence.",
38
+ 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.",
39
+ 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.",
40
+ docsHref: "/docs/guides/moderation-categories#violence"
41
+ },
42
+ "self-harm": {
43
+ summary: "Intentional self-injury, suicide, preparation, methods or encouragement; text also includes current or recurring self-harm thoughts.",
44
+ 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.",
45
+ 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.",
46
+ docsHref: "/docs/guides/moderation-categories#self-harm"
47
+ },
48
+ harassment: {
49
+ summary: "Targeted personal abuse, humiliation, bullying or serious intimidation; ordinary criticism and neutral reporting are excluded.",
50
+ 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.",
51
+ 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.",
52
+ docsHref: "/docs/guides/moderation-categories#harassment-and-bullying"
53
+ },
54
+ hate: {
55
+ summary: "Identity-based abuse, dehumanization, discriminatory exclusion or threats against people because of protected characteristics.",
56
+ 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.",
57
+ 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.",
58
+ docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
59
+ },
60
+ "criminal-activity": {
61
+ summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act.",
62
+ 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.",
63
+ 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.",
64
+ docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
65
+ }
66
+ };
67
+ //#endregion
68
+ //#region ../shared/src/ModerationPolicyDimensions.ts
69
+ const MODERATION_QUALIFIERS = [
70
+ "nudity",
71
+ "sexual-activity",
72
+ "explicit-sexual-language",
73
+ "sexual-solicitation",
74
+ "physical-attack",
75
+ "violent-threat",
76
+ "violent-preparation",
77
+ "violent-encouragement",
78
+ "injury",
79
+ "death",
80
+ "graphic",
81
+ "self-harm-thoughts",
82
+ "self-harm-preparation",
83
+ "self-harm-act",
84
+ "self-harm-methods",
85
+ "self-harm-encouragement",
86
+ "personal-abuse",
87
+ "targeted-intimidation",
88
+ "harassment-threat",
89
+ "sexual-harassment",
90
+ "harassment-encouragement",
91
+ "identity-abuse",
92
+ "dehumanization",
93
+ "identity-inferiority",
94
+ "discriminatory-exclusion",
95
+ "hate-threat",
96
+ "criminal-demonstration",
97
+ "criminal-preparation",
98
+ "criminal-request",
99
+ "criminal-instructions",
100
+ "criminal-promotion",
101
+ "historical",
102
+ "documentary",
103
+ "fictional",
104
+ "support-prevention"
105
+ ];
106
+ const SHARED_QUALIFIERS = [
107
+ "historical",
108
+ "documentary",
109
+ "fictional",
110
+ "support-prevention"
111
+ ];
112
+ [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
113
+ //#endregion
114
+ //#region ../../node_modules/zod-meta/esm/index.js
115
+ const createMetaType = (type) => {
116
+ return Object.assign((value) => {
117
+ return {
118
+ type,
119
+ data: value ?? {}
120
+ };
121
+ }, type);
122
+ };
123
+ createMetaType({ id: "docPropertyInfo" });
124
+ //#endregion
125
+ //#region ../shared/src/ZodUtils.ts
126
+ const SIZE_LIMITS = {
127
+ small: 64,
128
+ medium: 200,
129
+ large: 1500,
130
+ xlarge: 1e4,
131
+ token: 1e4
132
+ };
133
+ const saneStringField = (options) => {
134
+ return zod.string().transform((value) => value.trim()).refine((value) => {
135
+ if (options?.minWidth && value.length < options.minWidth) return false;
136
+ return true;
137
+ }, "Not long enough").refine((value) => {
138
+ const sizeLimit = options?.maxLength ?? SIZE_LIMITS[options?.type ?? "medium"];
139
+ return typeof value === "string" && value.length < sizeLimit;
140
+ }, "Too long");
141
+ };
142
+ const CLASSIFICATION_INPUT_TYPE_SCHEMA = zod.enum(["text", "image"]);
143
+ const CLASSIFICATION_CATEGORIES = [
144
+ "sexual",
145
+ "violence",
146
+ "self-harm",
147
+ "harassment",
148
+ "hate",
149
+ "criminal-activity"
150
+ ];
151
+ const CLASSIFICATION_CATEGORY_SCHEMA = zod.enum(CLASSIFICATION_CATEGORIES);
152
+ zod.object({
153
+ category: CLASSIFICATION_CATEGORY_SCHEMA,
154
+ displayName: saneStringField(),
155
+ description: saneStringField(),
156
+ supportedInputTypes: zod.array(CLASSIFICATION_INPUT_TYPE_SCHEMA).min(1)
157
+ });
158
+ 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;
159
+ const LIKELIHOOD_SCHEMA = zod.enum([
160
+ "very-unlikely",
161
+ "unlikely",
162
+ "possible",
163
+ "likely",
164
+ "very-likely"
165
+ ]);
166
+ const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum(["not-supported", "unavailable"]);
167
+ const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
168
+ status: zod.literal("evaluated"),
169
+ score: zod.number().min(0).max(1),
170
+ scopeScore: zod.number().min(0).max(1).optional(),
171
+ likelihood: LIKELIHOOD_SCHEMA
172
+ }).strict().refine(({ score, scopeScore }) => scopeScore === void 0 || scopeScore <= score, {
173
+ message: "Only confidence cannot exceed Any confidence",
174
+ path: ["scopeScore"]
175
+ }), zod.object({
176
+ status: zod.literal("not-evaluated"),
177
+ reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA
178
+ }).strict()]));
179
+ const CLASSIFICATION_CATEGORY_REPORT_SCHEMA = zod.discriminatedUnion("status", [
180
+ zod.object({
181
+ status: zod.literal("evaluated"),
182
+ likelihood: LIKELIHOOD_SCHEMA,
183
+ score: zod.number().min(0).max(1),
184
+ qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
185
+ }).strict(),
186
+ zod.object({
187
+ status: zod.literal("partial"),
188
+ likelihood: LIKELIHOOD_SCHEMA,
189
+ score: zod.number().min(0).max(1),
190
+ qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
191
+ }).strict(),
192
+ zod.object({
193
+ status: zod.literal("not-evaluated"),
194
+ reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA,
195
+ qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
196
+ }).strict()
197
+ ]);
198
+ const CATEGORIES_SCHEMA = zod.object({
199
+ sexual: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
200
+ violence: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
201
+ "self-harm": CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
202
+ harassment: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
203
+ hate: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
204
+ "criminal-activity": CLASSIFICATION_CATEGORY_REPORT_SCHEMA
205
+ }).strict();
206
+ const CLASSIFICATION_LABEL_SCHEMA = zod.object({
207
+ description: saneStringField(),
208
+ score: zod.number(),
209
+ topicalityScore: zod.number().nullish(),
210
+ likelihood: LIKELIHOOD_SCHEMA,
211
+ knowledgeBaseId: saneStringField().nullish()
212
+ });
213
+ zod.object({
214
+ processedAt: zod.number(),
215
+ categories: CATEGORIES_SCHEMA,
216
+ flagged: zod.boolean(),
217
+ score: zod.number(),
218
+ notes: saneStringField().nullish(),
219
+ media: zod.object({
220
+ durationSeconds: zod.number().positive(),
221
+ frameIntervalSeconds: zod.number().positive().optional(),
222
+ sampledFrames: zod.number().int().nonnegative(),
223
+ audioTranscribed: zod.boolean(),
224
+ segments: zod.array(zod.object({
225
+ source: zod.enum([
226
+ "frame",
227
+ "speech",
228
+ "frame-text",
229
+ "clip"
230
+ ]),
231
+ startSeconds: zod.number().nonnegative(),
232
+ endSeconds: zod.number().nonnegative(),
233
+ categories: CATEGORIES_SCHEMA
234
+ }).strict())
235
+ }).strict().optional(),
236
+ labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
237
+ }).strict();
238
+ //#endregion
239
+ //#region ../shared/src/MediaTestTriggers.ts
240
+ const MEDIA_TEST_SIGNALS = [...CLASSIFICATION_CATEGORIES, "violence.graphic"];
241
+ const HEX_WORDS = [
242
+ "zero",
243
+ "one",
244
+ "two",
245
+ "three",
246
+ "four",
247
+ "five",
248
+ "six",
249
+ "seven",
250
+ "eight",
251
+ "nine",
252
+ "alpha",
253
+ "bravo",
254
+ "charlie",
255
+ "delta",
256
+ "echo",
257
+ "foxtrot"
258
+ ];
259
+ /** Read this aloud, or render it as large, high-contrast text in a video frame. */
260
+ function generateMediaTestCode(signal, projectId) {
261
+ if (!MEDIA_TEST_SIGNALS.includes(signal)) throw new Error("Unsupported media test signal");
262
+ const suffix = projectId.slice(-8).toLowerCase();
263
+ if (!/^[a-f0-9]{8}$/.test(suffix)) throw new Error("Expected a project ID with an eight-character hexadecimal suffix");
264
+ return `Calm Lens test ${[...suffix].map((character) => HEX_WORDS[Number.parseInt(character, 16)]).join(" ")} ${signal.replace(/[.-]/g, " ")} end test`;
265
+ }
266
+ //#endregion
5
267
  //#region ../shared/src/Permissions.ts
6
268
  const ACTION_SCHEMA = zod.enum([
7
269
  "assets:read",
@@ -57,11 +319,10 @@ const permits = (grants, action, folderId, folders) => grants.some((grant) => {
57
319
  let current = folderId;
58
320
  const visited = /* @__PURE__ */ new Set();
59
321
  while (current && !visited.has(current)) {
60
- var _folders$find$parentI, _folders$find;
61
322
  if (current === grant.folderId) return true;
62
323
  if (!grant.includeSubfolders) return false;
63
324
  visited.add(current);
64
- current = (_folders$find$parentI = (_folders$find = folders.find((folder) => folder.id === current)) === null || _folders$find === void 0 ? void 0 : _folders$find.parentId) !== null && _folders$find$parentI !== void 0 ? _folders$find$parentI : null;
325
+ current = folders.find((folder) => folder.id === current)?.parentId ?? null;
65
326
  }
66
327
  return false;
67
328
  });
@@ -85,7 +346,7 @@ const getMockApiKeyGrants = (apiKey) => {
85
346
  if (!apiKey.startsWith("sk_test_")) return [];
86
347
  try {
87
348
  return GRANTS_SCHEMA.parse(JSON.parse(fromBase64Url(apiKey.slice(8))));
88
- } catch (_unused) {
349
+ } catch {
89
350
  return [];
90
351
  }
91
352
  };
@@ -95,50 +356,37 @@ var InMemoryMockImageStorage = class {
95
356
  constructor() {
96
357
  this.content = /* @__PURE__ */ new Map();
97
358
  }
98
- putContent(assetId, content) {
99
- var _this = this;
100
- return require_CalmLensUtils._asyncToGenerator(function* () {
101
- const validation = require_CalmLensUtils.validateAssetPayload(content);
102
- if (!validation.ok) throw new Error(validation.error);
103
- const bytes = yield require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(content));
104
- _this.content.set(assetId, {
105
- assetId,
106
- parts: /* @__PURE__ */ new Map([[1, bytes]]),
107
- type: content.type
108
- });
109
- })();
359
+ async putContent(assetId, content) {
360
+ const validation = require_CalmLensWebhooks.validateAssetPayload(content);
361
+ if (!validation.ok) throw new Error(validation.error);
362
+ const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(content));
363
+ this.content.set(assetId, {
364
+ assetId,
365
+ parts: /* @__PURE__ */ new Map([[1, bytes]]),
366
+ type: content.type
367
+ });
110
368
  }
111
- putPart(assetId, part) {
112
- var _this2 = this;
113
- return require_CalmLensUtils._asyncToGenerator(function* () {
114
- var _this$content$get, _part$partNumber, _part$type;
115
- const validation = require_CalmLensUtils.validateAssetPayload(part, {
116
- multipart: true,
117
- isFinal: part.isFinal
118
- });
119
- if (!validation.ok) throw new Error(validation.error);
120
- const bytes = yield require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(part));
121
- const existing = (_this$content$get = _this2.content.get(assetId)) !== null && _this$content$get !== void 0 ? _this$content$get : {
122
- assetId,
123
- parts: /* @__PURE__ */ new Map(),
124
- type: part.type
125
- };
126
- existing.parts.set((_part$partNumber = part.partNumber) !== null && _part$partNumber !== void 0 ? _part$partNumber : 1, bytes);
127
- existing.type = (_part$type = part.type) !== null && _part$type !== void 0 ? _part$type : existing.type;
128
- _this2.content.set(assetId, existing);
129
- })();
130
- }
131
- getContent(assetId) {
132
- var _this3 = this;
133
- return require_CalmLensUtils._asyncToGenerator(function* () {
134
- return _this3.content.get(assetId);
135
- })();
136
- }
137
- deleteContent(assetId) {
138
- var _this4 = this;
139
- return require_CalmLensUtils._asyncToGenerator(function* () {
140
- _this4.content.delete(assetId);
141
- })();
369
+ async putPart(assetId, part) {
370
+ const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
371
+ multipart: true,
372
+ isFinal: part.isFinal
373
+ });
374
+ if (!validation.ok) throw new Error(validation.error);
375
+ const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(part));
376
+ const existing = this.content.get(assetId) ?? {
377
+ assetId,
378
+ parts: /* @__PURE__ */ new Map(),
379
+ type: part.type
380
+ };
381
+ existing.parts.set(part.partNumber ?? 1, bytes);
382
+ existing.type = part.type ?? existing.type;
383
+ this.content.set(assetId, existing);
384
+ }
385
+ async getContent(assetId) {
386
+ return this.content.get(assetId);
387
+ }
388
+ async deleteContent(assetId) {
389
+ this.content.delete(assetId);
142
390
  }
143
391
  };
144
392
  var InMemoryMockDatabaseStorage = class {
@@ -146,69 +394,39 @@ var InMemoryMockDatabaseStorage = class {
146
394
  this.assets = /* @__PURE__ */ new Map();
147
395
  this.folders = /* @__PURE__ */ new Map();
148
396
  }
149
- createAsset(asset) {
150
- var _this5 = this;
151
- return require_CalmLensUtils._asyncToGenerator(function* () {
152
- _this5.assets.set(asset.id, asset);
153
- return asset;
154
- })();
155
- }
156
- updateAsset(asset) {
157
- var _this6 = this;
158
- return require_CalmLensUtils._asyncToGenerator(function* () {
159
- _this6.assets.set(asset.id, asset);
160
- return asset;
161
- })();
162
- }
163
- getAsset(assetId) {
164
- var _this7 = this;
165
- return require_CalmLensUtils._asyncToGenerator(function* () {
166
- return _this7.assets.get(assetId);
167
- })();
168
- }
169
- listAssets(projectId) {
170
- var _this8 = this;
171
- return require_CalmLensUtils._asyncToGenerator(function* () {
172
- return [..._this8.assets.values()].filter((asset) => asset.projectId === projectId);
173
- })();
174
- }
175
- deleteAsset(assetId) {
176
- var _this9 = this;
177
- return require_CalmLensUtils._asyncToGenerator(function* () {
178
- _this9.assets.delete(assetId);
179
- })();
180
- }
181
- createFolder(folder) {
182
- var _this10 = this;
183
- return require_CalmLensUtils._asyncToGenerator(function* () {
184
- _this10.folders.set(folder.id, folder);
185
- return folder;
186
- })();
187
- }
188
- updateFolder(folder) {
189
- var _this11 = this;
190
- return require_CalmLensUtils._asyncToGenerator(function* () {
191
- _this11.folders.set(folder.id, folder);
192
- return folder;
193
- })();
194
- }
195
- getFolder(folderId) {
196
- var _this12 = this;
197
- return require_CalmLensUtils._asyncToGenerator(function* () {
198
- return _this12.folders.get(folderId);
199
- })();
200
- }
201
- listFolders(projectId) {
202
- var _this13 = this;
203
- return require_CalmLensUtils._asyncToGenerator(function* () {
204
- return [..._this13.folders.values()].filter((folder) => folder.projectId === projectId).sort((a, b) => a.name.localeCompare(b.name));
205
- })();
206
- }
207
- deleteFolder(folderId) {
208
- var _this14 = this;
209
- return require_CalmLensUtils._asyncToGenerator(function* () {
210
- _this14.folders.delete(folderId);
211
- })();
397
+ async createAsset(asset) {
398
+ this.assets.set(asset.id, asset);
399
+ return asset;
400
+ }
401
+ async updateAsset(asset) {
402
+ this.assets.set(asset.id, asset);
403
+ return asset;
404
+ }
405
+ async getAsset(assetId) {
406
+ return this.assets.get(assetId);
407
+ }
408
+ async listAssets(projectId) {
409
+ return [...this.assets.values()].filter((asset) => asset.projectId === projectId);
410
+ }
411
+ async deleteAsset(assetId) {
412
+ this.assets.delete(assetId);
413
+ }
414
+ async createFolder(folder) {
415
+ this.folders.set(folder.id, folder);
416
+ return folder;
417
+ }
418
+ async updateFolder(folder) {
419
+ this.folders.set(folder.id, folder);
420
+ return folder;
421
+ }
422
+ async getFolder(folderId) {
423
+ return this.folders.get(folderId);
424
+ }
425
+ async listFolders(projectId) {
426
+ return [...this.folders.values()].filter((folder) => folder.projectId === projectId).sort((a, b) => a.name.localeCompare(b.name));
427
+ }
428
+ async deleteFolder(folderId) {
429
+ this.folders.delete(folderId);
212
430
  }
213
431
  };
214
432
  //#endregion
@@ -235,62 +453,164 @@ const inferAssetType = (options) => {
235
453
  return "file";
236
454
  };
237
455
  const createPage = (items, query) => {
238
- var _query$pageIndex, _query$pageSize;
239
- const pageIndex = (_query$pageIndex = query.pageIndex) !== null && _query$pageIndex !== void 0 ? _query$pageIndex : 0;
240
- const pageSize = (_query$pageSize = query.pageSize) !== null && _query$pageSize !== void 0 ? _query$pageSize : 10;
456
+ const pageIndex = query.pageIndex ?? 0;
457
+ const pageSize = query.pageSize ?? 10;
458
+ if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensWebhooks.invalidArgument("pageIndex must be a non-negative integer");
459
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensWebhooks.invalidArgument("pageSize must be an integer from 1 to 100");
241
460
  const start = pageIndex * pageSize;
242
461
  return {
243
462
  items: items.slice(start, start + pageSize),
244
463
  total: items.length,
245
464
  pageIndex,
246
465
  pageSize,
247
- ordering: [],
466
+ ordering: query.ordering ?? [],
248
467
  hasMore: start + pageSize < items.length
249
468
  };
250
469
  };
470
+ const compareValues = (left, right) => {
471
+ if (left === right) return 0;
472
+ if (left === void 0 || left === null) return -1;
473
+ if (right === void 0 || right === null) return 1;
474
+ if (typeof left === "number" && typeof right === "number") return left - right;
475
+ return String(left).localeCompare(String(right));
476
+ };
477
+ /** Sorts like the API's `ordering`; without one, newest first. */
478
+ const orderItems = (items, ordering) => {
479
+ const rules = ordering?.length ? ordering : [{
480
+ field: "createdAt",
481
+ direction: "DESC"
482
+ }];
483
+ return [...items].sort((a, b) => {
484
+ for (const rule of rules) {
485
+ const result = compareValues(a[rule.field], b[rule.field]);
486
+ if (result !== 0) return rule.direction === "DESC" ? -result : result;
487
+ }
488
+ return 0;
489
+ });
490
+ };
491
+ const COMPARISON = /^([A-Za-z][\w.]*)(==|!=|=in=|=out=|>=|<=|=gt=|=ge=|=lt=|=le=|>|<)(.+)$/;
492
+ const parseFilterValue = (raw) => {
493
+ const value = raw.trim();
494
+ if (/^".*"$/.test(value) || /^'.*'$/.test(value)) return value.slice(1, -1);
495
+ if (value === "null") return null;
496
+ if (value === "true") return true;
497
+ if (value === "false") return false;
498
+ if (value !== "" && !Number.isNaN(Number(value))) return Number(value);
499
+ return value;
500
+ };
501
+ const readField = (item, path) => path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, item);
502
+ const unsupportedFilter = (expression) => require_CalmLensWebhooks.invalidArgument(`MockCalmLensClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
503
+ const matchesComparison = (item, comparison) => {
504
+ const match = COMPARISON.exec(comparison.trim());
505
+ if (!match) throw unsupportedFilter(comparison);
506
+ const [, field, operator, rawValue] = match;
507
+ const actual = readField(item, field);
508
+ const isList = operator === "=in=" || operator === "=out=";
509
+ if (isList && !/^\(.*\)$/.test(rawValue.trim())) throw unsupportedFilter(comparison);
510
+ const values = isList ? rawValue.trim().slice(1, -1).split(",").map(parseFilterValue) : [parseFilterValue(rawValue)];
511
+ const equals = (expected) => Array.isArray(actual) ? actual.includes(expected) : expected === null ? actual === null || actual === void 0 : actual === expected;
512
+ switch (operator) {
513
+ case "==": return equals(values[0]);
514
+ case "!=": return !equals(values[0]);
515
+ case "=in=": return values.some(equals);
516
+ case "=out=": return !values.some(equals);
517
+ case ">":
518
+ case "=gt=": return compareValues(actual, values[0]) > 0;
519
+ case ">=":
520
+ case "=ge=": return compareValues(actual, values[0]) >= 0;
521
+ case "<":
522
+ case "=lt=": return compareValues(actual, values[0]) < 0;
523
+ default: return compareValues(actual, values[0]) <= 0;
524
+ }
525
+ };
526
+ /** Splits on `separator` outside quotes and parentheses; grouping parentheses are refused. */
527
+ const splitTopLevel = (text, separator) => {
528
+ const parts = [];
529
+ let depth = 0;
530
+ let quote = null;
531
+ let current = "";
532
+ for (const char of text) {
533
+ if (quote) {
534
+ if (char === quote) quote = null;
535
+ } else if (char === "\"" || char === "'") quote = char;
536
+ else if (char === "(") depth++;
537
+ else if (char === ")") depth--;
538
+ else if (char === separator && depth === 0) {
539
+ parts.push(current);
540
+ current = "";
541
+ continue;
542
+ }
543
+ current += char;
544
+ }
545
+ parts.push(current);
546
+ return parts;
547
+ };
548
+ /**
549
+ * Applies an RSQL `filter` like the API for simple expressions: comparisons joined by `;`
550
+ * (and) and `,` (or). Anything it can't evaluate throws instead of returning wrong results.
551
+ */
552
+ const filterAssets = (items, filter) => {
553
+ if (!filter) return items;
554
+ const alternatives = splitTopLevel(filter, ",").map((group) => splitTopLevel(group, ";"));
555
+ for (const condition of alternatives.flat()) if (condition.trim().startsWith("(")) throw unsupportedFilter(condition);
556
+ return items.filter((item) => alternatives.some((conditions) => conditions.every((condition) => matchesComparison(item, condition))));
557
+ };
251
558
  //#endregion
252
559
  //#region src/testing/MockCalmLensClient.ts
253
- const _excluded = ["assetIdentifier"];
254
- const _excluded2 = ["assetIdentifier"];
255
- const _excluded3 = ["assetId"];
256
- const _excluded4 = ["assetId"];
257
- const _excluded5 = ["assetId"];
258
- const _excluded6 = ["folderId"];
259
- const assertValidUploadMimeType = (mimeType) => {
260
- if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
261
- message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
262
- details: { mimeType }
263
- });
560
+ const DEFAULT_EVENTS_LIMIT = 50;
561
+ /** Stands in for binary payloads when comparing idempotent requests, like the API's size check. */
562
+ const describeBinary = (value) => {
563
+ if (value instanceof Blob) return `[blob:${value.size}]`;
564
+ if (value instanceof ArrayBuffer) return `[bytes:${value.byteLength}]`;
565
+ if (ArrayBuffer.isView(value)) return `[bytes:${value.byteLength}]`;
566
+ return value;
264
567
  };
568
+ const MAX_EVENTS_LIMIT = 100;
265
569
  const toFailureError = (failure) => {
266
- if (failure === "transport-error") return require_CalmLensUtils.calmLensError({ id: "sdk/transport-error" }, {
570
+ if (failure === "transport-error") return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
267
571
  message: "Request failed",
268
572
  cause: /* @__PURE__ */ new TypeError("Simulated network failure")
269
573
  });
270
- if (failure === "timeout") return require_CalmLensUtils.calmLensError({ id: "sdk/timeout" }, {
271
- message: "Request aborted",
272
- cause: new DOMException("The operation timed out.", "TimeoutError")
273
- });
574
+ if (failure === "timeout") {
575
+ const cause = /* @__PURE__ */ new Error("The operation timed out.");
576
+ cause.name = "TimeoutError";
577
+ return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
578
+ message: "Request timed out",
579
+ cause
580
+ });
581
+ }
274
582
  return failure;
275
583
  };
584
+ /**
585
+ * An in-memory stand-in for `CalmLensClient` with the same methods, validation and error
586
+ * codes. API errors carry the HTTP status, a `requestId` and response headers like the real
587
+ * client's, so error handling can be tested without a network.
588
+ */
276
589
  var MockCalmLensClient = class {
277
590
  /** Accepts the same options as `CalmLensClient` (validated the same way), plus `mockOptions`. */
278
591
  constructor(options) {
279
- var _options$mockOptions$, _options$mockOptions, _options$mockOptions$2, _options$mockOptions2, _options$mockOptions$3, _options$mockOptions3, _options$mockOptions$4, _options$mockOptions4, _options$mockOptions$5, _options$mockOptions5, _options$mockOptions$6, _options$mockOptions6, _options$mockOptions$7, _options$mockOptions7, _options$cdnBaseUrl;
280
- this.options = options;
281
592
  this.lenses = /* @__PURE__ */ new Map();
593
+ this.eventFeed = [];
594
+ this.uploadQueues = /* @__PURE__ */ new Map();
595
+ this.idempotentResults = /* @__PURE__ */ new Map();
596
+ this.requestCount = 0;
282
597
  this.pendingFailures = [];
283
598
  this.calls = [];
284
- require_CalmLensUtils.validateClientOptions(options);
285
- this.databaseStorage = (_options$mockOptions$ = (_options$mockOptions = options.mockOptions) === null || _options$mockOptions === void 0 ? void 0 : _options$mockOptions.databaseStorage) !== null && _options$mockOptions$ !== void 0 ? _options$mockOptions$ : new InMemoryMockDatabaseStorage();
286
- this.imageStorage = (_options$mockOptions$2 = (_options$mockOptions2 = options.mockOptions) === null || _options$mockOptions2 === void 0 ? void 0 : _options$mockOptions2.imageStorage) !== null && _options$mockOptions$2 !== void 0 ? _options$mockOptions$2 : new InMemoryMockImageStorage();
287
- this.createId = (_options$mockOptions$3 = (_options$mockOptions3 = options.mockOptions) === null || _options$mockOptions3 === void 0 ? void 0 : _options$mockOptions3.createId) !== null && _options$mockOptions$3 !== void 0 ? _options$mockOptions$3 : crypto.randomUUID.bind(crypto);
288
- this.now = (_options$mockOptions$4 = (_options$mockOptions4 = options.mockOptions) === null || _options$mockOptions4 === void 0 ? void 0 : _options$mockOptions4.now) !== null && _options$mockOptions$4 !== void 0 ? _options$mockOptions$4 : Date.now;
289
- this.queuedStatus = (_options$mockOptions$5 = (_options$mockOptions5 = options.mockOptions) === null || _options$mockOptions5 === void 0 ? void 0 : _options$mockOptions5.queuedStatus) !== null && _options$mockOptions$5 !== void 0 ? _options$mockOptions$5 : "pending";
290
- this.analyzedStatus = (_options$mockOptions$6 = (_options$mockOptions6 = options.mockOptions) === null || _options$mockOptions6 === void 0 ? void 0 : _options$mockOptions6.analyzedStatus) !== null && _options$mockOptions$6 !== void 0 ? _options$mockOptions$6 : "approved";
599
+ require_CalmLensWebhooks.validateClientOptions(options);
600
+ this.projectId = options.projectId;
601
+ this.databaseStorage = options.mockOptions?.databaseStorage ?? new InMemoryMockDatabaseStorage();
602
+ this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
603
+ this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
604
+ this.now = options.mockOptions?.now ?? Date.now;
605
+ this.queuedStatus = options.mockOptions?.queuedStatus ?? "pending";
606
+ this.analyzedStatus = options.mockOptions?.analyzedStatus ?? "approved";
291
607
  this.apiKeyGrants = getMockApiKeyGrants(options.apiKey);
292
- this.signedUrlBaseUrl = (_options$mockOptions$7 = (_options$mockOptions7 = options.mockOptions) === null || _options$mockOptions7 === void 0 ? void 0 : _options$mockOptions7.signedUrlBaseUrl) !== null && _options$mockOptions$7 !== void 0 ? _options$mockOptions$7 : "mock://calmlens";
293
- this.cdnBaseUrl = (_options$cdnBaseUrl = options.cdnBaseUrl) !== null && _options$cdnBaseUrl !== void 0 ? _options$cdnBaseUrl : "mock://calmlens/v1";
608
+ this.signedUrlBaseUrl = options.mockOptions?.signedUrlBaseUrl ?? "mock://calmlens";
609
+ this.cdnBaseUrl = (options.cdnBaseUrl ?? "mock://calmlens/v1").replace(/\/+$/, "");
610
+ }
611
+ /** Keeps the API key out of `console.log` and `util.inspect`. */
612
+ [Symbol.for("nodejs.util.inspect.custom")]() {
613
+ return `MockCalmLensClient { projectId: ${JSON.stringify(this.projectId)} }`;
294
614
  }
295
615
  /**
296
616
  * Makes the next call (or the next call to `operation`) throw `failure` instead of running,
@@ -306,14 +626,54 @@ var MockCalmLensClient = class {
306
626
  callsTo(operation) {
307
627
  return this.calls.filter((call) => call.operation === operation);
308
628
  }
309
- findOwnAsset(assetId) {
310
- var _this = this;
311
- return require_CalmLensUtils._asyncToGenerator(function* () {
312
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
313
- const asset = yield _this.databaseStorage.getAsset(assetId);
314
- if (!asset || asset.projectId !== _this.options.projectId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
315
- return asset;
316
- })();
629
+ /**
630
+ * Adds an event to the feed `events.list` reads, as if CalmLens had recorded it. Returns the
631
+ * stored event; pass it to `toWebhookEventPayload` to build a matching webhook body.
632
+ */
633
+ enqueueEvent(input) {
634
+ const now = this.now();
635
+ const event = {
636
+ id: this.createId(),
637
+ projectId: this.projectId,
638
+ severity: "low",
639
+ title: input.type,
640
+ facts: [],
641
+ createdAt: now,
642
+ updatedAt: now,
643
+ ...input
644
+ };
645
+ this.eventFeed.push(event);
646
+ return event;
647
+ }
648
+ /** An API error as the real client throws it: status, default message and request ID. */
649
+ apiError(code, options = {}) {
650
+ const definition = require_CalmLensWebhooks.API_ERROR_DEFINITIONS[code];
651
+ const requestId = `mock-request-${++this.requestCount}`;
652
+ return new require_CalmLensWebhooks.CalmLensError(code, {
653
+ message: options.message ?? definition.message,
654
+ status: definition.status,
655
+ details: options.details,
656
+ requestId,
657
+ headers: new Headers({
658
+ "content-type": "application/json",
659
+ "x-request-id": requestId
660
+ })
661
+ });
662
+ }
663
+ assertValidUploadMimeType(mimeType) {
664
+ if (require_CalmLensWebhooks.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
665
+ message: require_CalmLensWebhooks.getRejectedUploadMimeTypeError(mimeType),
666
+ details: { mimeType }
667
+ });
668
+ }
669
+ invalidPayload(message) {
670
+ return this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
671
+ }
672
+ async findOwnAsset(assetId) {
673
+ require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
674
+ const asset = await this.databaseStorage.getAsset(assetId);
675
+ if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_NOT_FOUND);
676
+ return asset;
317
677
  }
318
678
  begin(operation, options) {
319
679
  this.calls.push({
@@ -326,519 +686,554 @@ var MockCalmLensClient = class {
326
686
  throw toFailureError(pending.failure);
327
687
  }
328
688
  }
689
+ jsonResponse(status, body, url) {
690
+ const requestId = `mock-request-${++this.requestCount}`;
691
+ const response = new Response(JSON.stringify(body), {
692
+ status,
693
+ headers: {
694
+ "content-type": "application/json",
695
+ "x-request-id": requestId
696
+ }
697
+ });
698
+ Object.defineProperty(response, "url", { value: url });
699
+ return response;
700
+ }
329
701
  get cdn() {
330
- var _this2 = this;
331
702
  return {
332
- getAssetUrl: (_ref) => {
333
- var _options$resource;
334
- let { assetIdentifier } = _ref, options = require_CalmLensUtils._objectWithoutProperties(_ref, _excluded);
335
- if (!assetIdentifier) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset identifier is required" });
336
- const resource = (_options$resource = options.resource) !== null && _options$resource !== void 0 ? _options$resource : "content";
337
- const url = `${this.cdnBaseUrl.replace(/\/$/, "")}/assets/${encodeURIComponent(this.options.projectId)}/${require_CalmLensUtils.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
338
- return require_CalmLensUtils.appendCdnSelector(url, options);
703
+ getAssetUrl: ({ assetIdentifier, ...options }) => {
704
+ const resource = options.resource ?? "content";
705
+ const url = `${this.cdnBaseUrl}/assets/${encodeURIComponent(this.projectId)}/${require_CalmLensWebhooks.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
706
+ return require_CalmLensWebhooks.appendCdnSelector(url, options);
339
707
  },
340
- serve: function() {
341
- var _ref2 = require_CalmLensUtils._asyncToGenerator(function* (serveOptions) {
342
- var _asset$folderId, _await$this$databaseS, _this$databaseStorage, _this$databaseStorage2, _storedContent$type;
343
- _this2.begin("cdn.serve", serveOptions);
344
- const { assetIdentifier } = serveOptions, options = require_CalmLensUtils._objectWithoutProperties(serveOptions, _excluded2);
345
- if (!assetIdentifier) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset identifier is required" });
346
- const asset = (yield _this2.databaseStorage.listAssets(_this2.options.projectId)).find((asset) => asset.id === assetIdentifier || asset.externalId === assetIdentifier);
347
- if (!asset) return new Response("Asset not found", { status: 404 });
348
- if (asset.visibility !== "public" && !permits(_this2.apiKeyGrants, "assets:download", (_asset$folderId = asset.folderId) !== null && _asset$folderId !== void 0 ? _asset$folderId : null, (_await$this$databaseS = yield (_this$databaseStorage = (_this$databaseStorage2 = _this2.databaseStorage).listFolders) === null || _this$databaseStorage === void 0 ? void 0 : _this$databaseStorage.call(_this$databaseStorage2, _this2.options.projectId)) !== null && _await$this$databaseS !== void 0 ? _await$this$databaseS : [])) return new Response(JSON.stringify({
349
- code: "asset/api-key-missing-permission",
350
- message: "This API key needs assets:download permission for this asset’s folder."
351
- }), {
352
- status: 403,
353
- headers: { "Content-Type": "application/json" }
354
- });
355
- const storedContent = yield _this2.imageStorage.getContent(asset.id);
356
- if (!storedContent) return new Response("Asset content not found", { status: 404 });
357
- const parts = [...storedContent.parts.entries()].sort(([left], [right]) => left - right).map(([, value]) => value);
358
- const byteLength = parts.reduce((sum, value) => sum + value.byteLength, 0);
359
- const bytes = new Uint8Array(byteLength);
360
- let offset = 0;
361
- for (const part of parts) {
362
- bytes.set(part, offset);
363
- offset += part.byteLength;
708
+ /**
709
+ * Serves stored content like the CDN: `200` with `Content-Type`, `Content-Length`,
710
+ * `ETag` and `X-Request-Id`, or a JSON error body with `404`/`403`. Never throws for
711
+ * HTTP errors, like the real client.
712
+ */
713
+ serve: async (serveOptions) => {
714
+ this.begin("cdn.serve", serveOptions);
715
+ const { assetIdentifier, init, ...options } = serveOptions;
716
+ const url = this.cdn.getAssetUrl({
717
+ assetIdentifier,
718
+ ...options
719
+ });
720
+ const asset = (await this.databaseStorage.listAssets(this.projectId)).find((asset) => asset.id === assetIdentifier || asset.externalId === assetIdentifier);
721
+ const notFound = () => this.jsonResponse(404, {
722
+ code: "asset/not-found",
723
+ message: "Asset not found"
724
+ }, url);
725
+ if (!asset) return notFound();
726
+ if (asset.visibility !== "public" && !permits(this.apiKeyGrants, "assets:download", asset.folderId ?? null, await this.databaseStorage.listFolders?.(this.projectId) ?? [])) return this.jsonResponse(403, {
727
+ code: "asset/api-key-missing-permission",
728
+ message: "This API key needs assets:download permission for this asset’s folder."
729
+ }, url);
730
+ const storedContent = await this.imageStorage.getContent(asset.id);
731
+ if (!storedContent) return notFound();
732
+ const parts = [...storedContent.parts.entries()].sort(([left], [right]) => left - right).map(([, value]) => value);
733
+ const byteLength = parts.reduce((sum, value) => sum + value.byteLength, 0);
734
+ const bytes = new Uint8Array(byteLength);
735
+ let offset = 0;
736
+ for (const part of parts) {
737
+ bytes.set(part, offset);
738
+ offset += part.byteLength;
739
+ }
740
+ const requestId = `mock-request-${++this.requestCount}`;
741
+ const isHead = init?.method?.toUpperCase() === "HEAD";
742
+ const response = new Response(isHead ? null : bytes, {
743
+ status: 200,
744
+ headers: {
745
+ "Content-Type": storedContent.type ?? "application/octet-stream",
746
+ "Content-Length": String(byteLength),
747
+ "Cache-Control": asset.visibility === "public" ? "public, max-age=31536000" : "private, no-store",
748
+ ETag: `"${asset.id}-${asset.updatedAt}"`,
749
+ "X-Request-Id": requestId,
750
+ "X-Mock-CalmLens-CDN-URL": url
364
751
  }
365
- return new Response(bytes, {
366
- status: 200,
367
- headers: {
368
- "Content-Type": (_storedContent$type = storedContent.type) !== null && _storedContent$type !== void 0 ? _storedContent$type : "application/octet-stream",
369
- "X-Mock-CalmLens-CDN-URL": _this2.cdn.getAssetUrl(require_CalmLensUtils._objectSpread2({ assetIdentifier }, options))
370
- }
371
- });
372
752
  });
373
- return function serve(_x) {
374
- return _ref2.apply(this, arguments);
375
- };
376
- }()
753
+ Object.defineProperty(response, "url", { value: url });
754
+ return response;
755
+ }
377
756
  };
378
757
  }
379
758
  get events() {
380
- var _this3 = this;
381
- return {
382
- /** The mock does not record events; it always returns an empty page at "now". */
383
- list: function() {
384
- var _ref3 = require_CalmLensUtils._asyncToGenerator(function* (options = {}) {
385
- var _options$after;
386
- _this3.begin("events.list", options);
387
- return {
388
- events: [],
389
- cursor: (_options$after = options.after) !== null && _options$after !== void 0 ? _options$after : `${_this3.now()}_`,
390
- hasMore: false
391
- };
759
+ return {
760
+ /**
761
+ * Reads the mock feed like the API: without `after`, no events and a cursor for "now";
762
+ * with `after`, up to `limit` events added since, oldest first. Add events with
763
+ * `enqueueEvent`.
764
+ */
765
+ list: async (options = {}) => {
766
+ this.begin("events.list", options);
767
+ return this.listEvents(options);
768
+ },
769
+ pages: (options = {}) => this.iterateEventPages(options)
770
+ };
771
+ }
772
+ listEvents(options) {
773
+ const limit = options.limit ?? DEFAULT_EVENTS_LIMIT;
774
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
775
+ if (!options.after) return {
776
+ events: [],
777
+ cursor: `mock_${this.eventFeed.length}`,
778
+ hasMore: false
779
+ };
780
+ const match = /^mock_(\d+)$/.exec(options.after);
781
+ const position = match ? Number(match[1]) : NaN;
782
+ if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_EVENT_CURSOR);
783
+ const events = this.eventFeed.slice(position, position + limit);
784
+ const next = position + events.length;
785
+ return {
786
+ events,
787
+ cursor: `mock_${next}`,
788
+ hasMore: next < this.eventFeed.length
789
+ };
790
+ }
791
+ async *iterateEventPages(options) {
792
+ let after = options.after;
793
+ for (;;) {
794
+ const page = await this.events.list({
795
+ ...options,
796
+ after
392
797
  });
393
- return function list() {
394
- return _ref3.apply(this, arguments);
395
- };
396
- }() };
798
+ yield page;
799
+ if (!page.hasMore || page.cursor === after) return;
800
+ after = page.cursor;
801
+ }
802
+ }
803
+ async *iteratePages(listPage) {
804
+ for (let pageIndex = 0;; pageIndex++) {
805
+ const page = await listPage(pageIndex);
806
+ yield* page.items;
807
+ if (!page.hasMore || page.items.length === 0) return;
808
+ }
397
809
  }
398
810
  get text() {
399
- var _this4 = this;
400
- return { moderate: function() {
401
- var _ref4 = require_CalmLensUtils._asyncToGenerator(function* (options) {
402
- _this4.begin("text.moderate", options);
403
- return _this4.moderateAsset(require_CalmLensUtils.toTextAssetOptions(options));
404
- });
405
- return function moderate(_x2) {
406
- return _ref4.apply(this, arguments);
407
- };
408
- }() };
811
+ return { moderate: async (options) => {
812
+ this.begin("text.moderate", options);
813
+ return this.moderateAsset(require_CalmLensWebhooks.toTextAssetOptions(options), "text.moderate");
814
+ } };
409
815
  }
410
816
  get assets() {
411
- var _this5 = this;
412
817
  return {
413
- list: function() {
414
- var _ref5 = require_CalmLensUtils._asyncToGenerator(function* (options = {}) {
415
- var _options$pageIndex;
416
- _this5.begin("assets.list", options);
417
- const assets = yield _this5.databaseStorage.listAssets(_this5.options.projectId);
418
- return createPage(assets, require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { pageIndex: (_options$pageIndex = options.pageIndex) !== null && _options$pageIndex !== void 0 ? _options$pageIndex : 0 }));
419
- });
420
- return function list() {
421
- return _ref5.apply(this, arguments);
422
- };
423
- }(),
424
- retrieve: function() {
425
- var _ref6 = require_CalmLensUtils._asyncToGenerator(function* (options) {
426
- _this5.begin("assets.retrieve", options);
427
- const { assetId } = options;
428
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
429
- const asset = yield _this5.databaseStorage.getAsset(assetId);
430
- if (!asset) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
431
- return asset;
432
- });
433
- return function retrieve(_x3) {
434
- return _ref6.apply(this, arguments);
435
- };
436
- }(),
437
- listChildren: function() {
438
- var _ref7 = require_CalmLensUtils._asyncToGenerator(function* (childrenOptions = {}) {
439
- var _options$pageIndex2;
440
- _this5.begin("assets.listChildren", childrenOptions);
441
- const { assetId } = childrenOptions, options = require_CalmLensUtils._objectWithoutProperties(childrenOptions, _excluded3);
442
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
443
- const assets = yield _this5.databaseStorage.listAssets(_this5.options.projectId);
444
- return createPage(assets.filter((asset) => asset.parentId === assetId), require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { pageIndex: (_options$pageIndex2 = options.pageIndex) !== null && _options$pageIndex2 !== void 0 ? _options$pageIndex2 : 0 }));
445
- });
446
- return function listChildren() {
447
- return _ref7.apply(this, arguments);
818
+ /**
819
+ * Matches `query` (case-insensitively) against each asset's name, ID, external ID,
820
+ * description and tags, best match first. Mock scores are 1 for an exact match and 0.5
821
+ * otherwise; `mode` is accepted but there is no semantic search.
822
+ */
823
+ search: async (options) => {
824
+ this.begin("assets.search", options);
825
+ const query = typeof options?.query === "string" ? options.query.trim() : "";
826
+ if (!query || query.length > 200) throw this.invalidPayload("query must be 1 to 200 characters");
827
+ const limit = options.limit ?? 10;
828
+ const needle = query.toLowerCase();
829
+ const fields = [
830
+ "name",
831
+ "id",
832
+ "externalId",
833
+ "description"
834
+ ];
835
+ return {
836
+ query,
837
+ items: (await this.databaseStorage.listAssets(this.projectId)).map((asset) => {
838
+ const matchedFields = [];
839
+ for (const field of fields) if (String(asset[field] ?? "").toLowerCase().includes(needle)) matchedFields.push(field);
840
+ if (asset.tags?.some((tag) => tag.toLowerCase().includes(needle))) matchedFields.push("tags");
841
+ return {
842
+ asset,
843
+ score: fields.some((field) => String(asset[field] ?? "").toLowerCase() === needle) ? 1 : .5,
844
+ matchedFields
845
+ };
846
+ }).filter((result) => result.matchedFields.length > 0).sort((a, b) => b.score - a.score).slice(0, limit),
847
+ semanticStatus: "disabled"
448
848
  };
449
- }(),
450
- update: function() {
451
- var _ref8 = require_CalmLensUtils._asyncToGenerator(function* (options) {
452
- _this5.begin("assets.update", options);
453
- const { assetId } = options, body = require_CalmLensUtils._objectWithoutProperties(options, _excluded4);
454
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
455
- const asset = yield _this5.databaseStorage.getAsset(assetId);
456
- if (!asset || asset.projectId !== _this5.options.projectId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
457
- const changes = Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
458
- return _this5.databaseStorage.updateAsset(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), changes), {}, { updatedAt: _this5.now() }));
849
+ },
850
+ /** Lists assets with the real client's paging, `ordering`, `folderId` and `filter`. */
851
+ list: async (options = {}) => {
852
+ this.begin("assets.list", options);
853
+ let assets = await this.databaseStorage.listAssets(this.projectId);
854
+ if (options.folderId !== void 0) assets = assets.filter((asset) => (asset.folderId ?? null) === options.folderId);
855
+ assets = filterAssets(assets, options.filter);
856
+ return createPage(orderItems(assets, options.ordering), {
857
+ ...options,
858
+ pageIndex: options.pageIndex ?? 0
459
859
  });
460
- return function update(_x4) {
461
- return _ref8.apply(this, arguments);
462
- };
463
- }(),
464
- create: function() {
465
- var _ref9 = require_CalmLensUtils._asyncToGenerator(function* (options) {
466
- _this5.begin("assets.create", options);
467
- return _this5.createAsset(options, _this5.queuedStatus);
860
+ },
861
+ iterate: (options = {}) => this.iteratePages((pageIndex) => this.assets.list({
862
+ ...options,
863
+ pageIndex
864
+ })),
865
+ retrieve: async (options) => {
866
+ this.begin("assets.retrieve", options);
867
+ return this.findOwnAsset(options?.assetId);
868
+ },
869
+ listChildren: async (childrenOptions = {}) => {
870
+ this.begin("assets.listChildren", childrenOptions);
871
+ const { assetId, ...options } = childrenOptions;
872
+ require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
873
+ const assets = await this.databaseStorage.listAssets(this.projectId);
874
+ return createPage(orderItems(assets.filter((asset) => asset.parentId === assetId), options.ordering), {
875
+ ...options,
876
+ pageIndex: options.pageIndex ?? 0
468
877
  });
469
- return function create(_x5) {
470
- return _ref9.apply(this, arguments);
471
- };
472
- }(),
473
- moderate: function() {
474
- var _ref10 = require_CalmLensUtils._asyncToGenerator(function* (options) {
475
- _this5.begin("assets.moderate", options);
476
- return _this5.moderateAsset(options);
878
+ },
879
+ iterateChildren: (options) => {
880
+ require_CalmLensWebhooks.encodePathSegment(options?.assetId, "Asset ID");
881
+ return this.iteratePages((pageIndex) => this.assets.listChildren({
882
+ ...options,
883
+ pageIndex
884
+ }));
885
+ },
886
+ update: async (options) => {
887
+ this.begin("assets.update", options);
888
+ const { assetId, ...body } = options;
889
+ const asset = await this.findOwnAsset(assetId);
890
+ const changes = Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
891
+ return this.databaseStorage.updateAsset({
892
+ ...asset,
893
+ ...changes,
894
+ updatedAt: this.now()
477
895
  });
478
- return function moderate(_x6) {
479
- return _ref10.apply(this, arguments);
480
- };
481
- }(),
482
- uploadPart: function() {
483
- var _ref11 = require_CalmLensUtils._asyncToGenerator(function* (options) {
484
- var _asset$uploadPartsRec;
485
- _this5.begin("assets.uploadPart", options);
486
- const { assetId, part } = options;
487
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
488
- if (!part) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Part options are required" });
489
- const asset = yield _this5.databaseStorage.getAsset(assetId);
490
- if (!asset) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
491
- const validation = require_CalmLensUtils.validateAssetPayload(part, {
896
+ },
897
+ create: async (options) => {
898
+ this.begin("assets.create", options);
899
+ return this.withIdempotency("assets.create", options, () => this.createAsset(options, true));
900
+ },
901
+ moderate: async (options) => {
902
+ this.begin("assets.moderate", options);
903
+ return this.moderateAsset(options, "assets.moderate");
904
+ },
905
+ /** Continues a multipart upload: parts must arrive in order while it is `uploading`. */
906
+ uploadPart: async (options) => {
907
+ this.begin("assets.uploadPart", options);
908
+ const { assetId, part } = options;
909
+ require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
910
+ if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
911
+ return this.withIdempotency("assets.uploadPart", options, async () => {
912
+ const asset = await this.findOwnAsset(assetId);
913
+ const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
492
914
  multipart: true,
493
915
  isFinal: part.isFinal
494
916
  });
495
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
496
- yield _this5.imageStorage.putPart(assetId, part);
497
- const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
498
- status: part.isFinal ? _this5.queuedStatus : "uploading",
499
- uploadPartsReceived: Math.max((_asset$uploadPartsRec = asset.uploadPartsReceived) !== null && _asset$uploadPartsRec !== void 0 ? _asset$uploadPartsRec : 0, part.partNumber),
500
- updatedAt: _this5.now()
501
- });
502
- return _this5.databaseStorage.updateAsset(updated);
503
- });
504
- return function uploadPart(_x7) {
505
- return _ref11.apply(this, arguments);
506
- };
507
- }(),
508
- createSignedUrl: function() {
509
- var _ref12 = require_CalmLensUtils._asyncToGenerator(function* (signedUrlOptions) {
510
- var _options$expiresInSec, _options$resource2;
511
- _this5.begin("assets.createSignedUrl", signedUrlOptions);
512
- const { assetId } = signedUrlOptions, options = require_CalmLensUtils._objectWithoutProperties(signedUrlOptions, _excluded5);
513
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
514
- if (!(yield _this5.databaseStorage.getAsset(assetId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
515
- const expiresInSeconds = (_options$expiresInSec = options.expiresInSeconds) !== null && _options$expiresInSec !== void 0 ? _options$expiresInSec : 900;
516
- const resource = (_options$resource2 = options.resource) !== null && _options$resource2 !== void 0 ? _options$resource2 : "content";
517
- if (options.lens && options.transform) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Specify either a lens or a transform, not both" });
518
- const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : options.transform ? `&transform=${encodeURIComponent(options.transform)}` : "";
519
- return {
520
- url: `${_this5.signedUrlBaseUrl}/assets/${assetId}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
521
- expiresAt: _this5.now() + expiresInSeconds * 1e3
522
- };
523
- });
524
- return function createSignedUrl(_x8) {
525
- return _ref12.apply(this, arguments);
526
- };
527
- }(),
528
- move: function() {
529
- var _ref13 = require_CalmLensUtils._asyncToGenerator(function* (options) {
530
- _this5.begin("assets.move", options);
531
- const { assetId, folderId } = options;
532
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
533
- const asset = yield _this5.databaseStorage.getAsset(assetId);
534
- if (!asset || asset.projectId !== _this5.options.projectId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
535
- if (folderId !== null && folderId !== void 0) {
536
- if (!(yield _this5.resolveFolder(folderId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
537
- }
538
- const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
539
- folderId: folderId !== null && folderId !== void 0 ? folderId : null,
540
- updatedAt: _this5.now()
917
+ if (!validation.ok) throw this.invalidPayload(validation.error);
918
+ if (asset.status !== "uploading") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.UPLOAD_NOT_OPEN);
919
+ const expectedPartNumber = (asset.uploadPartsReceived ?? 1) + 1;
920
+ if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
921
+ await this.imageStorage.putPart(assetId, part);
922
+ const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
923
+ if (part.isFinal) this.uploadQueues.delete(assetId);
924
+ return this.databaseStorage.updateAsset({
925
+ ...asset,
926
+ status: part.isFinal ? queue ? this.queuedStatus : this.analyzedStatus : "uploading",
927
+ uploadPartsReceived: part.partNumber,
928
+ updatedAt: this.now()
541
929
  });
542
- return _this5.databaseStorage.updateAsset(updated);
543
930
  });
544
- return function move(_x9) {
545
- return _ref13.apply(this, arguments);
931
+ },
932
+ createSignedUrl: async (signedUrlOptions) => {
933
+ this.begin("assets.createSignedUrl", signedUrlOptions);
934
+ const { assetId, ...options } = signedUrlOptions;
935
+ const asset = await this.findOwnAsset(assetId);
936
+ if (options.lens && options.transform) throw require_CalmLensWebhooks.invalidArgument("Specify either a lens or a transform, not both");
937
+ const expiresInSeconds = options.expiresInSeconds ?? 900;
938
+ const resource = options.resource ?? "content";
939
+ const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : options.transform ? `&transform=${encodeURIComponent(options.transform)}` : "";
940
+ return {
941
+ url: `${this.signedUrlBaseUrl}/assets/${asset.id}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
942
+ expiresAt: this.now() + expiresInSeconds * 1e3
546
943
  };
547
- }(),
548
- retry: function() {
549
- var _ref14 = require_CalmLensUtils._asyncToGenerator(function* (options) {
550
- _this5.begin("assets.retry", options);
551
- const { assetId } = options;
552
- if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
553
- const asset = yield _this5.databaseStorage.getAsset(assetId);
554
- if (!asset || asset.projectId !== _this5.options.projectId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
555
- if (asset.status !== "error") throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_RETRYABLE);
556
- return _this5.databaseStorage.updateAsset(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
557
- status: "pending",
558
- updatedAt: _this5.now()
559
- }));
944
+ },
945
+ move: async (options) => {
946
+ this.begin("assets.move", options);
947
+ const { assetId, folderId } = options;
948
+ const asset = await this.findOwnAsset(assetId);
949
+ if (folderId !== null && folderId !== void 0) {
950
+ if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
951
+ }
952
+ return this.databaseStorage.updateAsset({
953
+ ...asset,
954
+ folderId: folderId ?? null,
955
+ updatedAt: this.now()
560
956
  });
561
- return function retry(_x10) {
562
- return _ref14.apply(this, arguments);
563
- };
564
- }(),
565
- deleteContent: function() {
566
- var _ref15 = require_CalmLensUtils._asyncToGenerator(function* (options) {
567
- var _asset$storageDeleted;
568
- _this5.begin("assets.deleteContent", options);
569
- const asset = yield _this5.findOwnAsset(options.assetId);
570
- yield _this5.imageStorage.deleteContent(asset.id);
571
- return _this5.databaseStorage.updateAsset(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
572
- extractedText: null,
573
- storageDeletedAt: (_asset$storageDeleted = asset.storageDeletedAt) !== null && _asset$storageDeleted !== void 0 ? _asset$storageDeleted : _this5.now(),
574
- updatedAt: _this5.now()
575
- }));
957
+ },
958
+ retry: async (options) => {
959
+ this.begin("assets.retry", options);
960
+ const asset = await this.findOwnAsset(options?.assetId);
961
+ if (asset.status !== "error") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
962
+ if (asset.storageDeletedAt) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
963
+ return this.databaseStorage.updateAsset({
964
+ ...asset,
965
+ status: "pending",
966
+ updatedAt: this.now()
576
967
  });
577
- return function deleteContent(_x11) {
578
- return _ref15.apply(this, arguments);
579
- };
580
- }(),
581
- delete: function() {
582
- var _ref16 = require_CalmLensUtils._asyncToGenerator(function* (options) {
583
- _this5.begin("assets.delete", options);
584
- const asset = yield _this5.findOwnAsset(options.assetId);
585
- yield _this5.imageStorage.deleteContent(asset.id);
586
- yield _this5.databaseStorage.deleteAsset(asset.id);
968
+ },
969
+ deleteContent: async (options) => {
970
+ this.begin("assets.deleteContent", options);
971
+ const asset = await this.findOwnAsset(options?.assetId);
972
+ await this.imageStorage.deleteContent(asset.id);
973
+ return this.databaseStorage.updateAsset({
974
+ ...asset,
975
+ extractedText: null,
976
+ storageDeletedAt: asset.storageDeletedAt ?? this.now(),
977
+ updatedAt: this.now()
587
978
  });
588
- return function _delete(_x12) {
589
- return _ref16.apply(this, arguments);
590
- };
591
- }()
979
+ },
980
+ delete: async (options) => {
981
+ this.begin("assets.delete", options);
982
+ const asset = await this.findOwnAsset(options?.assetId);
983
+ await this.imageStorage.deleteContent(asset.id);
984
+ await this.databaseStorage.deleteAsset(asset.id);
985
+ }
592
986
  };
593
987
  }
594
988
  get folders() {
595
- var _this6 = this;
596
989
  return {
597
- list: function() {
598
- var _ref17 = require_CalmLensUtils._asyncToGenerator(function* () {
599
- _this6.begin("folders.list", void 0);
600
- return [..._this6.databaseStorage.listFolders ? yield _this6.databaseStorage.listFolders(_this6.options.projectId) : []].sort((a, b) => a.name.localeCompare(b.name));
601
- });
602
- return function list() {
603
- return _ref17.apply(this, arguments);
604
- };
605
- }(),
606
- retrieve: function() {
607
- var _ref18 = require_CalmLensUtils._asyncToGenerator(function* (options) {
608
- _this6.begin("folders.retrieve", options);
609
- const { folderId } = options;
610
- if (!folderId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder ID is required" });
611
- const folder = yield _this6.resolveFolder(folderId);
612
- if (!folder) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
613
- return folder;
614
- });
615
- return function retrieve(_x13) {
616
- return _ref18.apply(this, arguments);
617
- };
618
- }(),
619
- create: function() {
620
- var _ref19 = require_CalmLensUtils._asyncToGenerator(function* (options) {
621
- var _options$parentId, _options$parentId2;
622
- _this6.begin("folders.create", options);
623
- if (!options.name) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder name is required" });
624
- if (options.parentId) {
625
- if (!(yield _this6.resolveFolder(options.parentId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
626
- }
627
- yield _this6.assertUniqueFolderName(options.name, (_options$parentId = options.parentId) !== null && _options$parentId !== void 0 ? _options$parentId : null);
628
- const folder = {
629
- id: _this6.createId(),
630
- projectId: _this6.options.projectId,
631
- parentId: (_options$parentId2 = options.parentId) !== null && _options$parentId2 !== void 0 ? _options$parentId2 : null,
632
- name: options.name,
633
- createdAt: _this6.now(),
634
- updatedAt: _this6.now()
635
- };
636
- if (_this6.databaseStorage.createFolder) return yield _this6.databaseStorage.createFolder(folder);
637
- return folder;
638
- });
639
- return function create(_x14) {
640
- return _ref19.apply(this, arguments);
641
- };
642
- }(),
643
- update: function() {
644
- var _ref20 = require_CalmLensUtils._asyncToGenerator(function* (options) {
645
- _this6.begin("folders.update", options);
646
- const { folderId } = options, body = require_CalmLensUtils._objectWithoutProperties(options, _excluded6);
647
- if (!folderId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder ID is required" });
648
- const folder = yield _this6.resolveFolder(folderId);
649
- if (!folder) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
650
- if (body.name !== void 0 && body.name !== folder.name) {
651
- var _folder$parentId;
652
- yield _this6.assertUniqueFolderName(body.name, (_folder$parentId = folder.parentId) !== null && _folder$parentId !== void 0 ? _folder$parentId : null, folderId);
653
- }
654
- const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, folder), body.name !== void 0 ? { name: body.name } : {}), {}, { updatedAt: _this6.now() });
655
- if (_this6.databaseStorage.updateFolder) return yield _this6.databaseStorage.updateFolder(updated);
656
- return updated;
657
- });
658
- return function update(_x15) {
659
- return _ref20.apply(this, arguments);
990
+ list: async () => {
991
+ this.begin("folders.list", void 0);
992
+ return [...this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []].sort((a, b) => a.name.localeCompare(b.name));
993
+ },
994
+ retrieve: async (options) => {
995
+ this.begin("folders.retrieve", options);
996
+ return this.findOwnFolder(options?.folderId);
997
+ },
998
+ create: async (options) => {
999
+ this.begin("folders.create", options);
1000
+ if (!options?.name) throw this.invalidPayload("Folder name is required");
1001
+ if (options.parentId) await this.findOwnFolder(options.parentId);
1002
+ await this.assertUniqueFolderName(options.name, options.parentId ?? null);
1003
+ const folder = {
1004
+ id: this.createId(),
1005
+ projectId: this.projectId,
1006
+ parentId: options.parentId ?? null,
1007
+ name: options.name,
1008
+ createdAt: this.now(),
1009
+ updatedAt: this.now()
660
1010
  };
661
- }(),
662
- delete: function() {
663
- var _ref21 = require_CalmLensUtils._asyncToGenerator(function* (options) {
664
- _this6.begin("folders.delete", options);
665
- const { folderId } = options;
666
- if (!folderId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder ID is required" });
667
- if (!(yield _this6.resolveFolder(folderId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
668
- const hasAssets = (yield _this6.databaseStorage.listAssets(_this6.options.projectId)).some((asset) => asset.folderId === folderId);
669
- const hasChildren = (_this6.databaseStorage.listFolders ? yield _this6.databaseStorage.listFolders(_this6.options.projectId) : []).some((f) => f.parentId === folderId);
670
- if (hasAssets || hasChildren) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
671
- hasAssets,
672
- hasChildren
673
- } });
674
- if (_this6.databaseStorage.deleteFolder) yield _this6.databaseStorage.deleteFolder(folderId);
675
- });
676
- return function _delete(_x16) {
677
- return _ref21.apply(this, arguments);
1011
+ return this.databaseStorage.createFolder ? await this.databaseStorage.createFolder(folder) : folder;
1012
+ },
1013
+ update: async (options) => {
1014
+ this.begin("folders.update", options);
1015
+ const { folderId, ...body } = options;
1016
+ const folder = await this.findOwnFolder(folderId);
1017
+ if (body.name !== void 0 && body.name !== folder.name) await this.assertUniqueFolderName(body.name, folder.parentId ?? null, folderId);
1018
+ const updated = {
1019
+ ...folder,
1020
+ ...body.name !== void 0 ? { name: body.name } : {},
1021
+ updatedAt: this.now()
678
1022
  };
679
- }(),
680
- move: function() {
681
- var _ref22 = require_CalmLensUtils._asyncToGenerator(function* (options) {
682
- _this6.begin("folders.move", options);
683
- const { folderId, parentId } = options;
684
- if (!folderId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder ID is required" });
685
- const folder = yield _this6.resolveFolder(folderId);
686
- if (!folder) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
687
- const targetParentId = parentId !== null && parentId !== void 0 ? parentId : null;
688
- if (targetParentId) {
689
- const parent = yield _this6.resolveFolder(targetParentId);
690
- if (!parent) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
691
- let candidate = parent;
692
- const visited = /* @__PURE__ */ new Set();
693
- while (candidate) {
694
- if (candidate.id === folderId || visited.has(candidate.id)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_PARENT_CYCLE);
695
- visited.add(candidate.id);
696
- candidate = candidate.parentId ? yield _this6.resolveFolder(candidate.parentId) : void 0;
697
- }
1023
+ return this.databaseStorage.updateFolder ? await this.databaseStorage.updateFolder(updated) : updated;
1024
+ },
1025
+ delete: async (options) => {
1026
+ this.begin("folders.delete", options);
1027
+ const { folderId } = options;
1028
+ await this.findOwnFolder(folderId);
1029
+ const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
1030
+ const hasChildren = (this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).some((f) => f.parentId === folderId);
1031
+ if (hasAssets || hasChildren) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
1032
+ hasAssets,
1033
+ hasChildren
1034
+ } });
1035
+ await this.databaseStorage.deleteFolder?.(folderId);
1036
+ },
1037
+ move: async (options) => {
1038
+ this.begin("folders.move", options);
1039
+ const { folderId, parentId } = options;
1040
+ const folder = await this.findOwnFolder(folderId);
1041
+ const targetParentId = parentId ?? null;
1042
+ if (targetParentId) {
1043
+ let candidate = await this.findOwnFolder(targetParentId);
1044
+ const visited = /* @__PURE__ */ new Set();
1045
+ while (candidate) {
1046
+ if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1047
+ visited.add(candidate.id);
1048
+ candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
698
1049
  }
699
- if (targetParentId !== folder.parentId) yield _this6.assertUniqueFolderName(folder.name, targetParentId, folderId);
700
- const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, folder), {}, {
701
- parentId: targetParentId,
702
- updatedAt: _this6.now()
703
- });
704
- if (_this6.databaseStorage.updateFolder) return yield _this6.databaseStorage.updateFolder(updated);
705
- return updated;
706
- });
707
- return function move(_x17) {
708
- return _ref22.apply(this, arguments);
1050
+ }
1051
+ if (targetParentId !== folder.parentId) await this.assertUniqueFolderName(folder.name, targetParentId, folderId);
1052
+ const updated = {
1053
+ ...folder,
1054
+ parentId: targetParentId,
1055
+ updatedAt: this.now()
709
1056
  };
710
- }()
1057
+ return this.databaseStorage.updateFolder ? await this.databaseStorage.updateFolder(updated) : updated;
1058
+ }
711
1059
  };
712
1060
  }
713
- ensureLens(options) {
714
- var _this7 = this;
715
- return require_CalmLensUtils._asyncToGenerator(function* () {
716
- _this7.begin("ensureLens", options);
717
- return _this7.ensureLensDefinition(options);
718
- })();
1061
+ async ensureLens(options) {
1062
+ this.begin("ensureLens", options);
1063
+ if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
1064
+ return this.ensureLensDefinition(options);
719
1065
  }
720
1066
  ensureLensDefinition(options) {
721
- var _existing$description, _options$description, _existing$transforms, _options$transforms;
722
1067
  const existing = this.lenses.get(options.identifier);
723
1068
  if (!existing) {
724
- const lens = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({
1069
+ const lens = {
725
1070
  id: this.createId(),
726
- projectId: this.options.projectId,
727
- userId: null
728
- }, options), {}, {
1071
+ projectId: this.projectId,
1072
+ userId: null,
1073
+ ...options,
729
1074
  createdAt: this.now(),
730
1075
  updatedAt: this.now()
731
- });
1076
+ };
732
1077
  this.lenses.set(lens.identifier, lens);
733
1078
  return lens;
734
1079
  }
735
- if (existing.name === options.name && ((_existing$description = existing.description) !== null && _existing$description !== void 0 ? _existing$description : null) === ((_options$description = options.description) !== null && _options$description !== void 0 ? _options$description : null) && existing.type === options.type && existing.active === options.active && require_CalmLensUtils.stringifyJsonCanonical((_existing$transforms = existing.transforms) !== null && _existing$transforms !== void 0 ? _existing$transforms : null) === require_CalmLensUtils.stringifyJsonCanonical((_options$transforms = options.transforms) !== null && _options$transforms !== void 0 ? _options$transforms : null) && existing.version === options.version) return existing;
1080
+ if (existing.name === options.name && (existing.description ?? null) === (options.description ?? null) && existing.type === options.type && existing.active === options.active && require_CalmLensWebhooks.stringifyJsonCanonical(existing.transforms ?? null) === require_CalmLensWebhooks.stringifyJsonCanonical(options.transforms ?? null) && existing.version === options.version) return existing;
736
1081
  if (options.version > existing.version) {
737
- const lens = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, existing), options), {}, { updatedAt: this.now() });
1082
+ const lens = {
1083
+ ...existing,
1084
+ ...options,
1085
+ updatedAt: this.now()
1086
+ };
738
1087
  this.lenses.set(lens.identifier, lens);
739
1088
  return lens;
740
1089
  }
741
- throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
1090
+ throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
742
1091
  currentVersion: existing.version,
743
1092
  minimumVersion: existing.version + 1,
744
1093
  requestedVersion: options.version
745
1094
  } });
746
1095
  }
747
- ensureLenses(options) {
748
- var _this8 = this;
749
- return require_CalmLensUtils._asyncToGenerator(function* () {
750
- _this8.begin("ensureLenses", options);
751
- if (options.lenses.length === 0) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "At least one lens is required" });
752
- return options.lenses.map((option) => _this8.ensureLensDefinition(option));
753
- })();
754
- }
755
- moderateAsset(options) {
756
- var _this9 = this;
757
- return require_CalmLensUtils._asyncToGenerator(function* () {
758
- 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" });
759
- return _this9.createAsset(options, _this9.analyzedStatus);
760
- })();
761
- }
762
- createAsset(options, status) {
763
- var _this10 = this;
764
- return require_CalmLensUtils._asyncToGenerator(function* () {
765
- var _options$folderId, _options$visibility, _options$part$partNum;
766
- if (!("content" in options) && !("url" in options) && !("part" in options)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Either file content, URL, or multipart part is required" });
767
- if (!options.name) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset name is required" });
768
- 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" });
769
- if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.USE_UPLOAD_PART);
770
- if ("content" in options) {
771
- assertValidUploadMimeType(options.content.type);
772
- if (!("plainText" in options.content)) {
773
- const validation = require_CalmLensUtils.validateAssetPayload(options.content);
774
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
775
- }
776
- }
777
- if ("part" in options) {
778
- assertValidUploadMimeType(options.part.type);
779
- const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
780
- multipart: true,
781
- isFinal: options.part.isFinal
782
- });
783
- if (!validation.ok) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: validation.error });
1096
+ async ensureLenses(options) {
1097
+ this.begin("ensureLenses", options);
1098
+ if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensWebhooks.invalidArgument("At least one lens is required");
1099
+ if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
1100
+ return options.lenses.map((option) => this.ensureLensDefinition(option));
1101
+ }
1102
+ /** Replays the first result for a reused idempotency key, like the API. */
1103
+ async withIdempotency(operation, options, run) {
1104
+ const key = options?.idempotencyKey;
1105
+ if (key === void 0) return run();
1106
+ if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
1107
+ const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
1108
+ const saved = this.idempotentResults.get(key);
1109
+ if (saved) {
1110
+ if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
1111
+ return saved.asset;
1112
+ }
1113
+ const asset = await run();
1114
+ this.idempotentResults.set(key, {
1115
+ fingerprint,
1116
+ asset
1117
+ });
1118
+ return asset;
1119
+ }
1120
+ async moderateAsset(options, operation) {
1121
+ if (options && "content" in options && "plainText" in options.content && (typeof options.content.plainText !== "string" || !options.content.plainText.trim())) throw this.invalidPayload("Text to moderate must be a non-empty string");
1122
+ return this.withIdempotency(operation, options, () => this.createAsset(options, false));
1123
+ }
1124
+ async createAsset(options, queue) {
1125
+ if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
1126
+ if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
1127
+ if (!options.name) throw this.invalidPayload("Asset name is required");
1128
+ if (!("part" in options) && "isFinal" in options) throw this.invalidPayload("isFinal is only valid for multipart uploads");
1129
+ if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.USE_UPLOAD_PART);
1130
+ if ("content" in options) {
1131
+ this.assertValidUploadMimeType(options.content.type);
1132
+ if (!("plainText" in options.content)) {
1133
+ const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
1134
+ if (!validation.ok) throw this.invalidPayload(validation.error);
784
1135
  }
785
- const id = _this10.createId();
786
- const now = _this10.now();
787
- const type = inferAssetType(options);
788
- const sizeInBytes = _this10.getSizeInBytes(options);
789
- const asset = {
790
- id,
791
- projectId: _this10.options.projectId,
792
- folderId: (_options$folderId = options.folderId) !== null && _options$folderId !== void 0 ? _options$folderId : null,
793
- createdAt: now,
794
- updatedAt: now,
795
- name: options.name,
796
- description: options.description,
797
- externalId: options.externalId,
798
- externalUrl: "url" in options ? options.url : "content" in options ? options.externalUrl : void 0,
799
- type,
800
- metadata: options.metadata,
801
- status: "part" in options && !options.part.isFinal ? "uploading" : status,
802
- processingOptions: options.processingOptions,
803
- visibility: (_options$visibility = options.visibility) !== null && _options$visibility !== void 0 ? _options$visibility : "private",
804
- sizeInBytes,
805
- uploadPartsReceived: "part" in options ? (_options$part$partNum = options.part.partNumber) !== null && _options$part$partNum !== void 0 ? _options$part$partNum : 1 : void 0
806
- };
807
- if ("content" in options) {
808
- if (!("plainText" in options.content)) yield _this10.imageStorage.putContent(id, options.content);
809
- } else if ("part" in options) yield _this10.imageStorage.putPart(id, options.part);
810
- return _this10.databaseStorage.createAsset(asset);
811
- })();
812
- }
813
- resolveFolder(folderId) {
814
- var _this11 = this;
815
- return require_CalmLensUtils._asyncToGenerator(function* () {
816
- if (!_this11.databaseStorage.getFolder) return void 0;
817
- const folder = yield _this11.databaseStorage.getFolder(folderId);
818
- if (!folder || folder.projectId !== _this11.options.projectId) return;
819
- return folder;
820
- })();
821
- }
822
- assertUniqueFolderName(name, parentId, excludeFolderId) {
823
- var _this12 = this;
824
- return require_CalmLensUtils._asyncToGenerator(function* () {
825
- if ((_this12.databaseStorage.listFolders ? yield _this12.databaseStorage.listFolders(_this12.options.projectId) : []).find((f) => f.id !== excludeFolderId && f.parentId === (parentId !== null && parentId !== void 0 ? parentId : null) && f.name === name)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
826
- })();
1136
+ }
1137
+ if ("part" in options) {
1138
+ this.assertValidUploadMimeType(options.part.type);
1139
+ const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
1140
+ multipart: true,
1141
+ isFinal: options.part.isFinal
1142
+ });
1143
+ if (!validation.ok) throw this.invalidPayload(validation.error);
1144
+ }
1145
+ if (options.externalId) {
1146
+ if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
1147
+ }
1148
+ const id = this.createId();
1149
+ const now = this.now();
1150
+ const isOpenUpload = "part" in options && !options.part.isFinal;
1151
+ const asset = {
1152
+ id,
1153
+ projectId: this.projectId,
1154
+ folderId: options.folderId ?? null,
1155
+ createdAt: now,
1156
+ updatedAt: now,
1157
+ name: options.name,
1158
+ description: options.description,
1159
+ externalId: options.externalId,
1160
+ externalUrl: "url" in options ? options.url : "content" in options ? options.externalUrl : void 0,
1161
+ type: inferAssetType(options),
1162
+ metadata: options.metadata,
1163
+ status: isOpenUpload ? "uploading" : queue ? this.queuedStatus : this.analyzedStatus,
1164
+ processingOptions: options.processingOptions,
1165
+ visibility: options.visibility ?? "private",
1166
+ sizeInBytes: this.getSizeInBytes(options),
1167
+ uploadPartsReceived: "part" in options ? options.part.partNumber ?? 1 : void 0
1168
+ };
1169
+ if ("content" in options) {
1170
+ if (!("plainText" in options.content)) await this.imageStorage.putContent(id, options.content);
1171
+ } else if ("part" in options) {
1172
+ await this.imageStorage.putPart(id, options.part);
1173
+ if (isOpenUpload) this.uploadQueues.set(id, queue);
1174
+ }
1175
+ return this.databaseStorage.createAsset(asset);
1176
+ }
1177
+ async resolveFolder(folderId) {
1178
+ if (!this.databaseStorage.getFolder) return void 0;
1179
+ const folder = await this.databaseStorage.getFolder(folderId);
1180
+ if (!folder || folder.projectId !== this.projectId) return;
1181
+ return folder;
1182
+ }
1183
+ async findOwnFolder(folderId) {
1184
+ require_CalmLensWebhooks.encodePathSegment(folderId, "Folder ID");
1185
+ const folder = await this.resolveFolder(folderId);
1186
+ if (!folder) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
1187
+ return folder;
1188
+ }
1189
+ async assertUniqueFolderName(name, parentId, excludeFolderId) {
1190
+ if ((this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).find((f) => f.id !== excludeFolderId && f.parentId === (parentId ?? null) && f.name === name)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
827
1191
  }
828
1192
  getSizeInBytes(options) {
829
1193
  if ("content" in options) {
830
1194
  if ("plainText" in options.content) return new TextEncoder().encode(options.content.plainText).byteLength;
831
- return require_CalmLensUtils.getAssetPayloadByteLength(options.content);
832
- }
833
- if ("part" in options) {
834
- var _options$sizeInBytes;
835
- return (_options$sizeInBytes = options.sizeInBytes) !== null && _options$sizeInBytes !== void 0 ? _options$sizeInBytes : require_CalmLensUtils.getAssetPayloadByteLength(options.part);
1195
+ return require_CalmLensWebhooks.getAssetPayloadByteLength(options.content);
836
1196
  }
1197
+ if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
1198
+ }
1199
+ };
1200
+ //#endregion
1201
+ //#region src/testing/MockWebhooks.ts
1202
+ /** The JSON body of a webhook delivery for a stored event (or an event from `events.list`). */
1203
+ const toWebhookEventPayload = (event) => ({
1204
+ id: event.id,
1205
+ type: event.type,
1206
+ createdAt: event.createdAt,
1207
+ projectId: event.projectId,
1208
+ data: {
1209
+ severity: event.severity,
1210
+ title: event.title,
1211
+ description: event.description ?? null,
1212
+ assetId: event.assetId ?? null,
1213
+ asset: event.asset ?? null,
1214
+ facts: event.facts
837
1215
  }
1216
+ });
1217
+ /** `v1=<hex HMAC-SHA256 of "<timestamp>.<body>">`, the `x-calmlens-signature` header value. */
1218
+ const createWebhookSignatureHeader = async (secret, timestamp, body) => `v1=${await require_CalmLensWebhooks.hmacSha256Hex(secret, `${timestamp}.${body}`)}`;
1219
+ /** Headers for one signed delivery of `body`, as CalmLens sends them. */
1220
+ const createWebhookHeaders = async (options) => {
1221
+ const timestamp = options.timestampSeconds ?? Math.floor(Date.now() / 1e3);
1222
+ return {
1223
+ "content-type": "application/json",
1224
+ [require_CalmLensWebhooks.WEBHOOK_DELIVERY_HEADER]: options.deliveryId,
1225
+ [require_CalmLensWebhooks.WEBHOOK_TIMESTAMP_HEADER]: String(timestamp),
1226
+ [require_CalmLensWebhooks.WEBHOOK_SIGNATURE_HEADER]: await createWebhookSignatureHeader(options.secret, timestamp, options.body)
1227
+ };
838
1228
  };
839
1229
  //#endregion
840
1230
  exports.InMemoryMockDatabaseStorage = InMemoryMockDatabaseStorage;
841
1231
  exports.InMemoryMockImageStorage = InMemoryMockImageStorage;
1232
+ exports.MEDIA_TEST_SIGNALS = MEDIA_TEST_SIGNALS;
842
1233
  exports.MOCK_API_KEY_PREFIX = MOCK_API_KEY_PREFIX;
843
1234
  exports.MockCalmLensClient = MockCalmLensClient;
844
1235
  exports.createMockApiKey = createMockApiKey;
1236
+ exports.createWebhookHeaders = createWebhookHeaders;
1237
+ exports.createWebhookSignatureHeader = createWebhookSignatureHeader;
1238
+ exports.generateMediaTestCode = generateMediaTestCode;
1239
+ exports.toWebhookEventPayload = toWebhookEventPayload;