@calmlens/sdk 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/testing.js CHANGED
@@ -21,7 +21,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
21
21
  enumerable: true
22
22
  }) : target, mod));
23
23
  //#endregion
24
- const require_CalmLensWebhooks = require("./CalmLensWebhooks-CH2PaPs0.js");
24
+ const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
25
+ const require_CalmLensWebhooks = require("./CalmLensWebhooks-hVquX5L1.js");
26
+ const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.js");
25
27
  let zod = require("zod");
26
28
  zod = __toESM(zod);
27
29
  require("zod/v4");
@@ -58,60 +60,66 @@ const MODERATION_CATEGORY_DEFINITIONS = {
58
60
  docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
59
61
  },
60
62
  "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.",
63
+ summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act, and illegal drugs or contraband.",
64
+ 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. It also covers illegal drug use, dealing, production or trafficking, and contraband such as stolen goods, counterfeit money or documents and smuggled or illegally traded weapons.",
65
+ exclusions: "Neutral reporting, prevention, lawful safety advice, fiction without criminal assistance, and vague dangerous references do not qualify. Medical, pharmaceutical, clinical, research and educational content does not qualify, including prescriptions, dosages, side effects, controlled medicines used as prescribed, pharmacies, hospitals and drug manufacturing by pharmaceutical companies. Lawful alcohol and tobacco, harm reduction, addiction recovery and drug-policy discussion do not qualify either. 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
66
  docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
65
67
  }
66
68
  };
67
69
  //#endregion
68
70
  //#region ../shared/src/ModerationPolicyDimensions.ts
69
71
  const MODERATION_QUALIFIERS = [
70
- "nudity",
72
+ "sexual-nudity",
71
73
  "sexual-activity",
72
- "explicit-sexual-language",
74
+ "sexual-explicit-language",
73
75
  "sexual-solicitation",
74
- "physical-attack",
75
- "violent-threat",
76
- "violent-preparation",
77
- "violent-encouragement",
78
- "injury",
79
- "death",
80
- "graphic",
76
+ "sexual-historical",
77
+ "sexual-documentary",
78
+ "sexual-fictional",
79
+ "sexual-support-prevention",
80
+ "violence-attack",
81
+ "violence-threat",
82
+ "violence-preparation",
83
+ "violence-encouragement",
84
+ "violence-injury",
85
+ "violence-death",
86
+ "violence-graphic",
87
+ "violence-historical",
88
+ "violence-documentary",
89
+ "violence-fictional",
90
+ "violence-support-prevention",
81
91
  "self-harm-thoughts",
82
92
  "self-harm-preparation",
83
93
  "self-harm-act",
84
94
  "self-harm-methods",
85
95
  "self-harm-encouragement",
86
- "personal-abuse",
87
- "targeted-intimidation",
96
+ "self-harm-documentary",
97
+ "self-harm-fictional",
98
+ "self-harm-support-prevention",
99
+ "harassment-abuse",
100
+ "harassment-intimidation",
88
101
  "harassment-threat",
89
- "sexual-harassment",
102
+ "harassment-sexual",
90
103
  "harassment-encouragement",
91
- "identity-abuse",
92
- "dehumanization",
93
- "identity-inferiority",
94
- "discriminatory-exclusion",
104
+ "harassment-fictional",
105
+ "hate-abuse",
106
+ "hate-dehumanization",
107
+ "hate-inferiority",
108
+ "hate-exclusion",
95
109
  "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"
110
+ "hate-historical",
111
+ "hate-fictional",
112
+ "criminal-activity-demonstration",
113
+ "criminal-activity-preparation",
114
+ "criminal-activity-request",
115
+ "criminal-activity-instructions",
116
+ "criminal-activity-promotion",
117
+ "criminal-activity-substances",
118
+ "criminal-activity-fictional"
105
119
  ];
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
120
  //#endregion
114
121
  //#region ../../node_modules/zod-meta/esm/index.js
122
+ const ZOD_META_KEY = "__zod_meta__";
115
123
  const createMetaType = (type) => {
116
124
  return Object.assign((value) => {
117
125
  return {
@@ -120,7 +128,117 @@ const createMetaType = (type) => {
120
128
  };
121
129
  }, type);
122
130
  };
123
- createMetaType({ id: "docPropertyInfo" });
131
+ const metaStore$1 = (meta) => {
132
+ const metaMap = meta.reduce((acc, meta) => {
133
+ if (meta) acc[meta.type.id] = meta;
134
+ return acc;
135
+ }, {});
136
+ return { [ZOD_META_KEY]: {
137
+ itemMap: metaMap,
138
+ get itemList() {
139
+ return Object.values(metaMap);
140
+ }
141
+ } };
142
+ };
143
+ //#endregion
144
+ //#region ../shared/src/DocMetaTypes.ts
145
+ const docPropertyInfo = createMetaType({ id: "docPropertyInfo" });
146
+ //#endregion
147
+ //#region ../shared/src/ProcessingFeatures.ts
148
+ /**
149
+ * Optional analyses a request can turn on with `processingOptions.features.<id>`. The catalog is
150
+ * the single source for the request and result schemas, cache keys and metering. Features are
151
+ * chosen per request only; there is no project-level default.
152
+ */
153
+ /** How mature a feature is. Beta results may change shape. */
154
+ const PROCESSING_FEATURE_STAGES = ["beta", "ga"];
155
+ const createFeatureOptionsSchemas = (booleanSchema) => {
156
+ const enabled = booleanSchema.meta(metaStore$1([docPropertyInfo({ description: "Whether to run the feature. Every feature is off unless this is true." })]));
157
+ return {
158
+ ocr: zod.object({ enabled }).strict().meta(metaStore$1([docPropertyInfo({
159
+ description: "Extract image text into a child asset and run normal text moderation. Applies to images, including those extracted from multimedia documents and websites.",
160
+ namedType: "OcrFeatureOptions"
161
+ })])),
162
+ personDetection: zod.object({ enabled }).strict().meta(metaStore$1([docPropertyInfo({
163
+ description: "Beta. Locate people in images, including people whose faces are not visible. Results are informational and never change the moderation decision.",
164
+ namedType: "PersonDetectionFeatureOptions"
165
+ })])),
166
+ faceDetection: zod.object({
167
+ enabled,
168
+ ageEstimation: booleanSchema.optional().meta(metaStore$1([docPropertyInfo({
169
+ description: "Also estimate an age range for each face",
170
+ defaultValue: false
171
+ })]))
172
+ }).strict().meta(metaStore$1([docPropertyInfo({
173
+ description: "Beta. Locate faces in images and optionally estimate an age range for each. Faces are not identified or stored as biometric templates. Results are informational and never change the moderation decision.",
174
+ namedType: "FaceDetectionFeatureOptions"
175
+ })]))
176
+ };
177
+ };
178
+ const createProcessingFeaturesSchema = (booleanSchema) => {
179
+ const options = createFeatureOptionsSchemas(booleanSchema);
180
+ return zod.object({
181
+ ocr: options.ocr.nullish(),
182
+ personDetection: options.personDetection.nullish(),
183
+ faceDetection: options.faceDetection.nullish()
184
+ }).strict().meta(metaStore$1([docPropertyInfo({
185
+ description: "Optional analyses to run on this asset, keyed by feature. Unknown features are rejected.",
186
+ namedType: "ProcessingFeatures"
187
+ })]));
188
+ };
189
+ createProcessingFeaturesSchema(zod.boolean());
190
+ /** Position and size as fractions of the image width and height, from 0 to 1. */
191
+ const BOUNDING_BOX_SCHEMA = zod.object({
192
+ left: zod.number(),
193
+ top: zod.number(),
194
+ width: zod.number().min(0),
195
+ height: zod.number().min(0)
196
+ }).strict().meta(metaStore$1([docPropertyInfo({
197
+ description: "Position and size as fractions of the image width and height, from 0 to 1. `left` and `top` can be slightly negative when the subject extends past the edge.",
198
+ namedType: "BoundingBox"
199
+ })]));
200
+ const DETECTION_CONFIDENCE_SCHEMA = zod.number().min(0).max(1).describe("Detection confidence from 0 to 1");
201
+ const AGE_RANGE_SCHEMA = zod.object({
202
+ low: zod.number().int().nonnegative(),
203
+ high: zod.number().int().nonnegative()
204
+ }).strict().meta(metaStore$1([docPropertyInfo({
205
+ description: "Estimated age range in years. Estimates can be off by several years.",
206
+ namedType: "AgeRange"
207
+ })]));
208
+ const DETECTED_PERSON_SCHEMA = zod.object({
209
+ box: BOUNDING_BOX_SCHEMA,
210
+ confidence: DETECTION_CONFIDENCE_SCHEMA
211
+ }).strict().meta(metaStore$1([docPropertyInfo({
212
+ description: "A detected person",
213
+ namedType: "DetectedPerson"
214
+ })]));
215
+ const DETECTED_FACE_SCHEMA = zod.object({
216
+ box: BOUNDING_BOX_SCHEMA,
217
+ confidence: DETECTION_CONFIDENCE_SCHEMA,
218
+ ageRange: AGE_RANGE_SCHEMA.optional()
219
+ }).strict().meta(metaStore$1([docPropertyInfo({
220
+ description: "A detected face",
221
+ namedType: "DetectedFace"
222
+ })]));
223
+ const NOT_EVALUATED_FEATURE_RESULT_SCHEMA = zod.object({
224
+ status: zod.literal("not-evaluated"),
225
+ stage: zod.enum(PROCESSING_FEATURE_STAGES),
226
+ reason: zod.enum(["not-supported", "unavailable"])
227
+ }).strict();
228
+ const createFeatureResultSchema = (shape) => zod.discriminatedUnion("status", [zod.object({
229
+ status: zod.literal("evaluated"),
230
+ stage: zod.enum(PROCESSING_FEATURE_STAGES),
231
+ ...shape
232
+ }).strict(), NOT_EVALUATED_FEATURE_RESULT_SCHEMA]);
233
+ const PERSON_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ people: zod.array(DETECTED_PERSON_SCHEMA) });
234
+ const FACE_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ faces: zod.array(DETECTED_FACE_SCHEMA) });
235
+ const PROCESSING_FEATURE_RESULTS_SCHEMA = zod.object({
236
+ personDetection: PERSON_DETECTION_RESULT_SCHEMA.optional(),
237
+ faceDetection: FACE_DETECTION_RESULT_SCHEMA.optional()
238
+ }).strict().meta(metaStore$1([docPropertyInfo({
239
+ description: "Results of the optional features requested in `processingOptions.features`, keyed by feature",
240
+ namedType: "ProcessingFeatureResults"
241
+ })]));
124
242
  //#endregion
125
243
  //#region ../shared/src/ZodUtils.ts
126
244
  const SIZE_LIMITS = {
@@ -163,7 +281,11 @@ const LIKELIHOOD_SCHEMA = zod.enum([
163
281
  "likely",
164
282
  "very-likely"
165
283
  ]);
166
- const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum(["not-supported", "unavailable"]);
284
+ const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum([
285
+ "not-supported",
286
+ "unavailable",
287
+ "not-requested"
288
+ ]);
167
289
  const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
168
290
  status: zod.literal("evaluated"),
169
291
  score: zod.number().min(0).max(1),
@@ -233,7 +355,8 @@ zod.object({
233
355
  categories: CATEGORIES_SCHEMA
234
356
  }).strict())
235
357
  }).strict().optional(),
236
- labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
358
+ labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish(),
359
+ features: PROCESSING_FEATURE_RESULTS_SCHEMA.optional()
237
360
  }).strict();
238
361
  //#endregion
239
362
  //#region ../shared/src/MediaTestTriggers.ts
@@ -288,6 +411,10 @@ const ACTION_SCHEMA = zod.enum([
288
411
  "metadata:write",
289
412
  "lenses:read",
290
413
  "lenses:write",
414
+ "upload-presets:read",
415
+ "upload-presets:write",
416
+ "reviews:read",
417
+ "reviews:write",
291
418
  "project:manage"
292
419
  ]);
293
420
  const PROJECT_ACTIONS = [...[
@@ -296,7 +423,9 @@ const PROJECT_ACTIONS = [...[
296
423
  "api-keys",
297
424
  "connectors",
298
425
  "metadata",
299
- "lenses"
426
+ "lenses",
427
+ "upload-presets",
428
+ "reviews"
300
429
  ].flatMap((feature) => [`${feature}:read`, `${feature}:write`]), "project:manage"];
301
430
  const isProjectAction = (action) => PROJECT_ACTIONS.includes(action);
302
431
  /** The actions that include `action`: its feature's write action and project:manage. */
@@ -309,7 +438,7 @@ const GRANT_SCHEMA = zod.object({
309
438
  const GRANTS_SCHEMA = zod.array(GRANT_SCHEMA).max(100);
310
439
  zod.enum([
311
440
  "admin",
312
- "member",
441
+ "editor",
313
442
  "viewer",
314
443
  "custom"
315
444
  ]);
@@ -357,9 +486,9 @@ var InMemoryMockImageStorage = class {
357
486
  this.content = /* @__PURE__ */ new Map();
358
487
  }
359
488
  async putContent(assetId, content) {
360
- const validation = require_CalmLensWebhooks.validateAssetPayload(content);
489
+ const validation = require_CalmLensUtils.validateAssetPayload(content);
361
490
  if (!validation.ok) throw new Error(validation.error);
362
- const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(content));
491
+ const bytes = await require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(content));
363
492
  this.content.set(assetId, {
364
493
  assetId,
365
494
  parts: /* @__PURE__ */ new Map([[1, bytes]]),
@@ -367,12 +496,12 @@ var InMemoryMockImageStorage = class {
367
496
  });
368
497
  }
369
498
  async putPart(assetId, part) {
370
- const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
499
+ const validation = require_CalmLensUtils.validateAssetPayload(part, {
371
500
  multipart: true,
372
501
  isFinal: part.isFinal
373
502
  });
374
503
  if (!validation.ok) throw new Error(validation.error);
375
- const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(part));
504
+ const bytes = await require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(part));
376
505
  const existing = this.content.get(assetId) ?? {
377
506
  assetId,
378
507
  parts: /* @__PURE__ */ new Map(),
@@ -455,8 +584,8 @@ const inferAssetType = (options) => {
455
584
  const createPage = (items, query) => {
456
585
  const pageIndex = query.pageIndex ?? 0;
457
586
  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");
587
+ if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensVersion.invalidArgument("pageIndex must be a non-negative integer");
588
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensVersion.invalidArgument("pageSize must be an integer from 1 to 100");
460
589
  const start = pageIndex * pageSize;
461
590
  return {
462
591
  items: items.slice(start, start + pageSize),
@@ -499,7 +628,7 @@ const parseFilterValue = (raw) => {
499
628
  return value;
500
629
  };
501
630
  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.`);
631
+ const unsupportedFilter = (expression) => require_CalmLensVersion.invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
503
632
  const matchesComparison = (item, comparison) => {
504
633
  const match = COMPARISON.exec(comparison.trim());
505
634
  if (!match) throw unsupportedFilter(comparison);
@@ -556,7 +685,7 @@ const filterAssets = (items, filter) => {
556
685
  return items.filter((item) => alternatives.some((conditions) => conditions.every((condition) => matchesComparison(item, condition))));
557
686
  };
558
687
  //#endregion
559
- //#region src/testing/MockCalmLensClient.ts
688
+ //#region src/testing/MockCalmLensServerClient.ts
560
689
  const DEFAULT_EVENTS_LIMIT = 50;
561
690
  /** Stands in for binary payloads when comparing idempotent requests, like the API's size check. */
562
691
  const describeBinary = (value) => {
@@ -567,14 +696,14 @@ const describeBinary = (value) => {
567
696
  };
568
697
  const MAX_EVENTS_LIMIT = 100;
569
698
  const toFailureError = (failure) => {
570
- if (failure === "transport-error") return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
699
+ if (failure === "transport-error") return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR, {
571
700
  message: "Request failed",
572
701
  cause: /* @__PURE__ */ new TypeError("Simulated network failure")
573
702
  });
574
703
  if (failure === "timeout") {
575
704
  const cause = /* @__PURE__ */ new Error("The operation timed out.");
576
705
  cause.name = "TimeoutError";
577
- return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
706
+ return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
578
707
  message: "Request timed out",
579
708
  cause
580
709
  });
@@ -582,12 +711,12 @@ const toFailureError = (failure) => {
582
711
  return failure;
583
712
  };
584
713
  /**
585
- * An in-memory stand-in for `CalmLensClient` with the same methods, validation and error
714
+ * An in-memory stand-in for `CalmLensServerClient` with the same methods, validation and error
586
715
  * codes. API errors carry the HTTP status, a `requestId` and response headers like the real
587
716
  * client's, so error handling can be tested without a network.
588
717
  */
589
- var MockCalmLensClient = class {
590
- /** Accepts the same options as `CalmLensClient` (validated the same way), plus `mockOptions`. */
718
+ var MockCalmLensServerClient = class {
719
+ /** Accepts the same options as `CalmLensServerClient` (validated the same way), plus `mockOptions`. */
591
720
  constructor(options) {
592
721
  this.lenses = /* @__PURE__ */ new Map();
593
722
  this.eventFeed = [];
@@ -596,8 +725,9 @@ var MockCalmLensClient = class {
596
725
  this.requestCount = 0;
597
726
  this.pendingFailures = [];
598
727
  this.calls = [];
599
- require_CalmLensWebhooks.validateClientOptions(options);
728
+ require_CalmLensUtils.validateServerClientOptions(options);
600
729
  this.projectId = options.projectId;
730
+ this.apiKey = options.apiKey;
601
731
  this.databaseStorage = options.mockOptions?.databaseStorage ?? new InMemoryMockDatabaseStorage();
602
732
  this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
603
733
  this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
@@ -610,7 +740,7 @@ var MockCalmLensClient = class {
610
740
  }
611
741
  /** Keeps the API key out of `console.log` and `util.inspect`. */
612
742
  [Symbol.for("nodejs.util.inspect.custom")]() {
613
- return `MockCalmLensClient { projectId: ${JSON.stringify(this.projectId)} }`;
743
+ return `MockCalmLensServerClient { projectId: ${JSON.stringify(this.projectId)} }`;
614
744
  }
615
745
  /**
616
746
  * Makes the next call (or the next call to `operation`) throw `failure` instead of running,
@@ -647,9 +777,9 @@ var MockCalmLensClient = class {
647
777
  }
648
778
  /** An API error as the real client throws it: status, default message and request ID. */
649
779
  apiError(code, options = {}) {
650
- const definition = require_CalmLensWebhooks.API_ERROR_DEFINITIONS[code];
780
+ const definition = require_CalmLensVersion.API_ERROR_DEFINITIONS[code];
651
781
  const requestId = `mock-request-${++this.requestCount}`;
652
- return new require_CalmLensWebhooks.CalmLensError(code, {
782
+ return new require_CalmLensVersion.CalmLensError(code, {
653
783
  message: options.message ?? definition.message,
654
784
  status: definition.status,
655
785
  details: options.details,
@@ -661,18 +791,18 @@ var MockCalmLensClient = class {
661
791
  });
662
792
  }
663
793
  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),
794
+ if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
795
+ message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
666
796
  details: { mimeType }
667
797
  });
668
798
  }
669
799
  invalidPayload(message) {
670
- return this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
800
+ return this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
671
801
  }
672
802
  async findOwnAsset(assetId) {
673
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
803
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
674
804
  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);
805
+ if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_FOUND);
676
806
  return asset;
677
807
  }
678
808
  begin(operation, options) {
@@ -702,8 +832,8 @@ var MockCalmLensClient = class {
702
832
  return {
703
833
  getAssetUrl: ({ assetIdentifier, ...options }) => {
704
834
  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);
835
+ const url = `${this.cdnBaseUrl}/assets/${encodeURIComponent(this.projectId)}/${require_CalmLensUtils.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
836
+ return require_CalmLensUtils.appendCdnSelector(url, options);
707
837
  },
708
838
  /**
709
839
  * Serves stored content like the CDN: `200` with `Content-Type`, `Content-Length`,
@@ -771,7 +901,7 @@ var MockCalmLensClient = class {
771
901
  }
772
902
  listEvents(options) {
773
903
  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}` });
904
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
775
905
  if (!options.after) return {
776
906
  events: [],
777
907
  cursor: `mock_${this.eventFeed.length}`,
@@ -779,7 +909,7 @@ var MockCalmLensClient = class {
779
909
  };
780
910
  const match = /^mock_(\d+)$/.exec(options.after);
781
911
  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);
912
+ if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_EVENT_CURSOR);
783
913
  const events = this.eventFeed.slice(position, position + limit);
784
914
  const next = position + events.length;
785
915
  return {
@@ -810,7 +940,7 @@ var MockCalmLensClient = class {
810
940
  get text() {
811
941
  return { moderate: async (options) => {
812
942
  this.begin("text.moderate", options);
813
- return this.moderateAsset(require_CalmLensWebhooks.toTextAssetOptions(options), "text.moderate");
943
+ return this.moderateAsset(require_CalmLensUtils.toTextAssetOptions(options), "text.moderate");
814
944
  } };
815
945
  }
816
946
  get assets() {
@@ -869,7 +999,7 @@ var MockCalmLensClient = class {
869
999
  listChildren: async (childrenOptions = {}) => {
870
1000
  this.begin("assets.listChildren", childrenOptions);
871
1001
  const { assetId, ...options } = childrenOptions;
872
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
1002
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
873
1003
  const assets = await this.databaseStorage.listAssets(this.projectId);
874
1004
  return createPage(orderItems(assets.filter((asset) => asset.parentId === assetId), options.ordering), {
875
1005
  ...options,
@@ -877,7 +1007,7 @@ var MockCalmLensClient = class {
877
1007
  });
878
1008
  },
879
1009
  iterateChildren: (options) => {
880
- require_CalmLensWebhooks.encodePathSegment(options?.assetId, "Asset ID");
1010
+ require_CalmLensUtils.encodePathSegment(options?.assetId, "Asset ID");
881
1011
  return this.iteratePages((pageIndex) => this.assets.listChildren({
882
1012
  ...options,
883
1013
  pageIndex
@@ -906,18 +1036,18 @@ var MockCalmLensClient = class {
906
1036
  uploadPart: async (options) => {
907
1037
  this.begin("assets.uploadPart", options);
908
1038
  const { assetId, part } = options;
909
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
910
- if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
1039
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
1040
+ if (!part) throw require_CalmLensVersion.invalidArgument("Part options are required");
911
1041
  return this.withIdempotency("assets.uploadPart", options, async () => {
912
1042
  const asset = await this.findOwnAsset(assetId);
913
- const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
1043
+ const validation = require_CalmLensUtils.validateAssetPayload(part, {
914
1044
  multipart: true,
915
1045
  isFinal: part.isFinal
916
1046
  });
917
1047
  if (!validation.ok) throw this.invalidPayload(validation.error);
918
- if (asset.status !== "uploading") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.UPLOAD_NOT_OPEN);
1048
+ if (asset.status !== "uploading") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.UPLOAD_NOT_OPEN);
919
1049
  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 } });
1050
+ if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
921
1051
  await this.imageStorage.putPart(assetId, part);
922
1052
  const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
923
1053
  if (part.isFinal) this.uploadQueues.delete(assetId);
@@ -933,10 +1063,10 @@ var MockCalmLensClient = class {
933
1063
  this.begin("assets.createSignedUrl", signedUrlOptions);
934
1064
  const { assetId, ...options } = signedUrlOptions;
935
1065
  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");
1066
+ if (options.lens && options.transform) throw require_CalmLensVersion.invalidArgument("Specify either a lens or a transform, not both");
937
1067
  const expiresInSeconds = options.expiresInSeconds ?? 900;
938
1068
  const resource = options.resource ?? "content";
939
- const transform = require_CalmLensWebhooks.transformSelector(options.transform);
1069
+ const transform = require_CalmLensUtils.transformSelector(options.transform);
940
1070
  const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : transform ? `&transform=${encodeURIComponent(transform)}` : "";
941
1071
  return {
942
1072
  url: `${this.signedUrlBaseUrl}/assets/${asset.id}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
@@ -948,7 +1078,7 @@ var MockCalmLensClient = class {
948
1078
  const { assetId, folderId } = options;
949
1079
  const asset = await this.findOwnAsset(assetId);
950
1080
  if (folderId !== null && folderId !== void 0) {
951
- if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
1081
+ if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
952
1082
  }
953
1083
  return this.databaseStorage.updateAsset({
954
1084
  ...asset,
@@ -959,14 +1089,20 @@ var MockCalmLensClient = class {
959
1089
  retry: async (options) => {
960
1090
  this.begin("assets.retry", options);
961
1091
  const asset = await this.findOwnAsset(options?.assetId);
962
- if (asset.status !== "error") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
963
- if (asset.storageDeletedAt) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
1092
+ if (asset.status !== "error") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
1093
+ if (asset.storageDeletedAt) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
964
1094
  return this.databaseStorage.updateAsset({
965
1095
  ...asset,
966
1096
  status: "pending",
967
1097
  updatedAt: this.now()
968
1098
  });
969
1099
  },
1100
+ /** Waits like the real client, checking the mock's storage for a decision. */
1101
+ waitForDecision: async (options) => {
1102
+ this.begin("assets.waitForDecision", options);
1103
+ const { assetId, ...waitOptions } = options;
1104
+ return require_CalmLensUtils.pollForDecision(() => this.findOwnAsset(assetId), waitOptions, require_CalmLensUtils.sleep);
1105
+ },
970
1106
  deleteContent: async (options) => {
971
1107
  this.begin("assets.deleteContent", options);
972
1108
  const asset = await this.findOwnAsset(options?.assetId);
@@ -986,6 +1122,16 @@ var MockCalmLensClient = class {
986
1122
  }
987
1123
  };
988
1124
  }
1125
+ /** Signs real upload tokens with the mock's API key, like the real client, without a request. */
1126
+ get uploadTokens() {
1127
+ return { sign: async (options) => {
1128
+ this.begin("uploadTokens.sign", options);
1129
+ return require_CalmLensWebhooks.signUploadTokenWithApiKey(this.apiKey, options, {
1130
+ now: this.now(),
1131
+ nonce: this.createId()
1132
+ });
1133
+ } };
1134
+ }
989
1135
  get folders() {
990
1136
  return {
991
1137
  list: async () => {
@@ -1029,7 +1175,7 @@ var MockCalmLensClient = class {
1029
1175
  await this.findOwnFolder(folderId);
1030
1176
  const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
1031
1177
  const hasChildren = (this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).some((f) => f.parentId === folderId);
1032
- if (hasAssets || hasChildren) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
1178
+ if (hasAssets || hasChildren) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
1033
1179
  hasAssets,
1034
1180
  hasChildren
1035
1181
  } });
@@ -1044,7 +1190,7 @@ var MockCalmLensClient = class {
1044
1190
  let candidate = await this.findOwnFolder(targetParentId);
1045
1191
  const visited = /* @__PURE__ */ new Set();
1046
1192
  while (candidate) {
1047
- if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1193
+ if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1048
1194
  visited.add(candidate.id);
1049
1195
  candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
1050
1196
  }
@@ -1061,7 +1207,7 @@ var MockCalmLensClient = class {
1061
1207
  }
1062
1208
  async ensureLens(options) {
1063
1209
  this.begin("ensureLens", options);
1064
- if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
1210
+ if (!options?.identifier) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
1065
1211
  return this.ensureLensDefinition(options);
1066
1212
  }
1067
1213
  ensureLensDefinition(options) {
@@ -1088,7 +1234,7 @@ var MockCalmLensClient = class {
1088
1234
  this.lenses.set(lens.identifier, lens);
1089
1235
  return lens;
1090
1236
  }
1091
- throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
1237
+ throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
1092
1238
  currentVersion: existing.version,
1093
1239
  minimumVersion: existing.version + 1,
1094
1240
  requestedVersion: options.version
@@ -1096,19 +1242,19 @@ var MockCalmLensClient = class {
1096
1242
  }
1097
1243
  async ensureLenses(options) {
1098
1244
  this.begin("ensureLenses", options);
1099
- if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensWebhooks.invalidArgument("At least one lens is required");
1100
- if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
1245
+ if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensVersion.invalidArgument("At least one lens is required");
1246
+ if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
1101
1247
  return options.lenses.map((option) => this.ensureLensDefinition(option));
1102
1248
  }
1103
1249
  /** Replays the first result for a reused idempotency key, like the API. */
1104
1250
  async withIdempotency(operation, options, run) {
1105
1251
  const key = options?.idempotencyKey;
1106
1252
  if (key === void 0) return run();
1107
- if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
1253
+ if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
1108
1254
  const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
1109
1255
  const saved = this.idempotentResults.get(key);
1110
1256
  if (saved) {
1111
- if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
1257
+ if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
1112
1258
  return saved.asset;
1113
1259
  }
1114
1260
  const asset = await run();
@@ -1123,28 +1269,28 @@ var MockCalmLensClient = class {
1123
1269
  return this.withIdempotency(operation, options, () => this.createAsset(options, false));
1124
1270
  }
1125
1271
  async createAsset(options, queue) {
1126
- if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
1272
+ if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidArgument("Asset options are required");
1127
1273
  if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
1128
1274
  if (!options.name) throw this.invalidPayload("Asset name is required");
1129
1275
  if (!("part" in options) && "isFinal" in options) throw this.invalidPayload("isFinal is only valid for multipart uploads");
1130
- if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.USE_UPLOAD_PART);
1276
+ if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.USE_UPLOAD_PART);
1131
1277
  if ("content" in options) {
1132
1278
  this.assertValidUploadMimeType(options.content.type);
1133
1279
  if (!("plainText" in options.content)) {
1134
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
1280
+ const validation = require_CalmLensUtils.validateAssetPayload(options.content);
1135
1281
  if (!validation.ok) throw this.invalidPayload(validation.error);
1136
1282
  }
1137
1283
  }
1138
1284
  if ("part" in options) {
1139
1285
  this.assertValidUploadMimeType(options.part.type);
1140
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
1286
+ const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
1141
1287
  multipart: true,
1142
1288
  isFinal: options.part.isFinal
1143
1289
  });
1144
1290
  if (!validation.ok) throw this.invalidPayload(validation.error);
1145
1291
  }
1146
1292
  if (options.externalId) {
1147
- if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
1293
+ if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
1148
1294
  }
1149
1295
  const id = this.createId();
1150
1296
  const now = this.now();
@@ -1182,20 +1328,20 @@ var MockCalmLensClient = class {
1182
1328
  return folder;
1183
1329
  }
1184
1330
  async findOwnFolder(folderId) {
1185
- require_CalmLensWebhooks.encodePathSegment(folderId, "Folder ID");
1331
+ require_CalmLensUtils.encodePathSegment(folderId, "Folder ID");
1186
1332
  const folder = await this.resolveFolder(folderId);
1187
- if (!folder) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
1333
+ if (!folder) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
1188
1334
  return folder;
1189
1335
  }
1190
1336
  async assertUniqueFolderName(name, parentId, excludeFolderId) {
1191
- 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.` });
1337
+ 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_CalmLensVersion.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
1192
1338
  }
1193
1339
  getSizeInBytes(options) {
1194
1340
  if ("content" in options) {
1195
1341
  if ("plainText" in options.content) return new TextEncoder().encode(options.content.plainText).byteLength;
1196
- return require_CalmLensWebhooks.getAssetPayloadByteLength(options.content);
1342
+ return require_CalmLensUtils.getAssetPayloadByteLength(options.content);
1197
1343
  }
1198
- if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
1344
+ if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(options.part);
1199
1345
  }
1200
1346
  };
1201
1347
  //#endregion
@@ -1232,7 +1378,7 @@ exports.InMemoryMockDatabaseStorage = InMemoryMockDatabaseStorage;
1232
1378
  exports.InMemoryMockImageStorage = InMemoryMockImageStorage;
1233
1379
  exports.MEDIA_TEST_SIGNALS = MEDIA_TEST_SIGNALS;
1234
1380
  exports.MOCK_API_KEY_PREFIX = MOCK_API_KEY_PREFIX;
1235
- exports.MockCalmLensClient = MockCalmLensClient;
1381
+ exports.MockCalmLensServerClient = MockCalmLensServerClient;
1236
1382
  exports.createMockApiKey = createMockApiKey;
1237
1383
  exports.createWebhookHeaders = createWebhookHeaders;
1238
1384
  exports.createWebhookSignatureHeader = createWebhookSignatureHeader;