@calmlens/sdk 0.10.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/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-DJnaLpRy.js");
24
+ const require_CalmLensError = require("./CalmLensError-CKwMKV0S.js");
25
+ const require_CalmLensWebhooks = require("./CalmLensWebhooks-BTPxKvcD.js");
26
+ const require_CalmLensUtils = require("./CalmLensUtils-Dg3s4loH.js");
25
27
  let zod = require("zod");
26
28
  zod = __toESM(zod);
27
29
  require("zod/v4");
@@ -112,6 +114,7 @@ const SHARED_QUALIFIERS = [
112
114
  [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
113
115
  //#endregion
114
116
  //#region ../../node_modules/zod-meta/esm/index.js
117
+ const ZOD_META_KEY = "__zod_meta__";
115
118
  const createMetaType = (type) => {
116
119
  return Object.assign((value) => {
117
120
  return {
@@ -120,7 +123,117 @@ const createMetaType = (type) => {
120
123
  };
121
124
  }, type);
122
125
  };
123
- createMetaType({ id: "docPropertyInfo" });
126
+ const metaStore$1 = (meta) => {
127
+ const metaMap = meta.reduce((acc, meta) => {
128
+ if (meta) acc[meta.type.id] = meta;
129
+ return acc;
130
+ }, {});
131
+ return { [ZOD_META_KEY]: {
132
+ itemMap: metaMap,
133
+ get itemList() {
134
+ return Object.values(metaMap);
135
+ }
136
+ } };
137
+ };
138
+ //#endregion
139
+ //#region ../shared/src/DocMetaTypes.ts
140
+ const docPropertyInfo = createMetaType({ id: "docPropertyInfo" });
141
+ //#endregion
142
+ //#region ../shared/src/ProcessingFeatures.ts
143
+ /**
144
+ * Optional analyses a request can turn on with `processingOptions.features.<id>`. The catalog is
145
+ * the single source for the request and result schemas, cache keys and metering. Features are
146
+ * chosen per request only; there is no project-level default.
147
+ */
148
+ /** How mature a feature is. Beta results may change shape. */
149
+ const PROCESSING_FEATURE_STAGES = ["beta", "ga"];
150
+ const createFeatureOptionsSchemas = (booleanSchema) => {
151
+ const enabled = booleanSchema.meta(metaStore$1([docPropertyInfo({ description: "Whether to run the feature. Every feature is off unless this is true." })]));
152
+ return {
153
+ ocr: zod.object({ enabled }).strict().meta(metaStore$1([docPropertyInfo({
154
+ description: "Extract image text into a child asset and run normal text moderation. Applies to images, including those extracted from multimedia documents and websites.",
155
+ namedType: "OcrFeatureOptions"
156
+ })])),
157
+ personDetection: zod.object({ enabled }).strict().meta(metaStore$1([docPropertyInfo({
158
+ description: "Beta. Locate people in images, including people whose faces are not visible. Results are informational and never change the moderation decision.",
159
+ namedType: "PersonDetectionFeatureOptions"
160
+ })])),
161
+ faceDetection: zod.object({
162
+ enabled,
163
+ ageEstimation: booleanSchema.optional().meta(metaStore$1([docPropertyInfo({
164
+ description: "Also estimate an age range for each face",
165
+ defaultValue: false
166
+ })]))
167
+ }).strict().meta(metaStore$1([docPropertyInfo({
168
+ 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.",
169
+ namedType: "FaceDetectionFeatureOptions"
170
+ })]))
171
+ };
172
+ };
173
+ const createProcessingFeaturesSchema = (booleanSchema) => {
174
+ const options = createFeatureOptionsSchemas(booleanSchema);
175
+ return zod.object({
176
+ ocr: options.ocr.nullish(),
177
+ personDetection: options.personDetection.nullish(),
178
+ faceDetection: options.faceDetection.nullish()
179
+ }).strict().meta(metaStore$1([docPropertyInfo({
180
+ description: "Optional analyses to run on this asset, keyed by feature. Unknown features are rejected.",
181
+ namedType: "ProcessingFeatures"
182
+ })]));
183
+ };
184
+ createProcessingFeaturesSchema(zod.boolean());
185
+ /** Position and size as fractions of the image width and height, from 0 to 1. */
186
+ const BOUNDING_BOX_SCHEMA = zod.object({
187
+ left: zod.number(),
188
+ top: zod.number(),
189
+ width: zod.number().min(0),
190
+ height: zod.number().min(0)
191
+ }).strict().meta(metaStore$1([docPropertyInfo({
192
+ 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.",
193
+ namedType: "BoundingBox"
194
+ })]));
195
+ const DETECTION_CONFIDENCE_SCHEMA = zod.number().min(0).max(1).describe("Detection confidence from 0 to 1");
196
+ const AGE_RANGE_SCHEMA = zod.object({
197
+ low: zod.number().int().nonnegative(),
198
+ high: zod.number().int().nonnegative()
199
+ }).strict().meta(metaStore$1([docPropertyInfo({
200
+ description: "Estimated age range in years. Estimates can be off by several years.",
201
+ namedType: "AgeRange"
202
+ })]));
203
+ const DETECTED_PERSON_SCHEMA = zod.object({
204
+ box: BOUNDING_BOX_SCHEMA,
205
+ confidence: DETECTION_CONFIDENCE_SCHEMA
206
+ }).strict().meta(metaStore$1([docPropertyInfo({
207
+ description: "A detected person",
208
+ namedType: "DetectedPerson"
209
+ })]));
210
+ const DETECTED_FACE_SCHEMA = zod.object({
211
+ box: BOUNDING_BOX_SCHEMA,
212
+ confidence: DETECTION_CONFIDENCE_SCHEMA,
213
+ ageRange: AGE_RANGE_SCHEMA.optional()
214
+ }).strict().meta(metaStore$1([docPropertyInfo({
215
+ description: "A detected face",
216
+ namedType: "DetectedFace"
217
+ })]));
218
+ const NOT_EVALUATED_FEATURE_RESULT_SCHEMA = zod.object({
219
+ status: zod.literal("not-evaluated"),
220
+ stage: zod.enum(PROCESSING_FEATURE_STAGES),
221
+ reason: zod.enum(["not-supported", "unavailable"])
222
+ }).strict();
223
+ const createFeatureResultSchema = (shape) => zod.discriminatedUnion("status", [zod.object({
224
+ status: zod.literal("evaluated"),
225
+ stage: zod.enum(PROCESSING_FEATURE_STAGES),
226
+ ...shape
227
+ }).strict(), NOT_EVALUATED_FEATURE_RESULT_SCHEMA]);
228
+ const PERSON_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ people: zod.array(DETECTED_PERSON_SCHEMA) });
229
+ const FACE_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ faces: zod.array(DETECTED_FACE_SCHEMA) });
230
+ const PROCESSING_FEATURE_RESULTS_SCHEMA = zod.object({
231
+ personDetection: PERSON_DETECTION_RESULT_SCHEMA.optional(),
232
+ faceDetection: FACE_DETECTION_RESULT_SCHEMA.optional()
233
+ }).strict().meta(metaStore$1([docPropertyInfo({
234
+ description: "Results of the optional features requested in `processingOptions.features`, keyed by feature",
235
+ namedType: "ProcessingFeatureResults"
236
+ })]));
124
237
  //#endregion
125
238
  //#region ../shared/src/ZodUtils.ts
126
239
  const SIZE_LIMITS = {
@@ -233,7 +346,8 @@ zod.object({
233
346
  categories: CATEGORIES_SCHEMA
234
347
  }).strict())
235
348
  }).strict().optional(),
236
- labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
349
+ labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish(),
350
+ features: PROCESSING_FEATURE_RESULTS_SCHEMA.optional()
237
351
  }).strict();
238
352
  //#endregion
239
353
  //#region ../shared/src/MediaTestTriggers.ts
@@ -288,6 +402,8 @@ const ACTION_SCHEMA = zod.enum([
288
402
  "metadata:write",
289
403
  "lenses:read",
290
404
  "lenses:write",
405
+ "upload-presets:read",
406
+ "upload-presets:write",
291
407
  "project:manage"
292
408
  ]);
293
409
  const PROJECT_ACTIONS = [...[
@@ -296,7 +412,8 @@ const PROJECT_ACTIONS = [...[
296
412
  "api-keys",
297
413
  "connectors",
298
414
  "metadata",
299
- "lenses"
415
+ "lenses",
416
+ "upload-presets"
300
417
  ].flatMap((feature) => [`${feature}:read`, `${feature}:write`]), "project:manage"];
301
418
  const isProjectAction = (action) => PROJECT_ACTIONS.includes(action);
302
419
  /** The actions that include `action`: its feature's write action and project:manage. */
@@ -357,9 +474,9 @@ var InMemoryMockImageStorage = class {
357
474
  this.content = /* @__PURE__ */ new Map();
358
475
  }
359
476
  async putContent(assetId, content) {
360
- const validation = require_CalmLensWebhooks.validateAssetPayload(content);
477
+ const validation = require_CalmLensUtils.validateAssetPayload(content);
361
478
  if (!validation.ok) throw new Error(validation.error);
362
- const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(content));
479
+ const bytes = await require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(content));
363
480
  this.content.set(assetId, {
364
481
  assetId,
365
482
  parts: /* @__PURE__ */ new Map([[1, bytes]]),
@@ -367,12 +484,12 @@ var InMemoryMockImageStorage = class {
367
484
  });
368
485
  }
369
486
  async putPart(assetId, part) {
370
- const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
487
+ const validation = require_CalmLensUtils.validateAssetPayload(part, {
371
488
  multipart: true,
372
489
  isFinal: part.isFinal
373
490
  });
374
491
  if (!validation.ok) throw new Error(validation.error);
375
- const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(part));
492
+ const bytes = await require_CalmLensUtils.assetDataToUint8Array(require_CalmLensUtils.assetToData(part));
376
493
  const existing = this.content.get(assetId) ?? {
377
494
  assetId,
378
495
  parts: /* @__PURE__ */ new Map(),
@@ -455,8 +572,8 @@ const inferAssetType = (options) => {
455
572
  const createPage = (items, query) => {
456
573
  const pageIndex = query.pageIndex ?? 0;
457
574
  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");
575
+ if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensError.invalidArgument("pageIndex must be a non-negative integer");
576
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensError.invalidArgument("pageSize must be an integer from 1 to 100");
460
577
  const start = pageIndex * pageSize;
461
578
  return {
462
579
  items: items.slice(start, start + pageSize),
@@ -499,7 +616,7 @@ const parseFilterValue = (raw) => {
499
616
  return value;
500
617
  };
501
618
  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.`);
619
+ const unsupportedFilter = (expression) => require_CalmLensError.invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
503
620
  const matchesComparison = (item, comparison) => {
504
621
  const match = COMPARISON.exec(comparison.trim());
505
622
  if (!match) throw unsupportedFilter(comparison);
@@ -556,7 +673,7 @@ const filterAssets = (items, filter) => {
556
673
  return items.filter((item) => alternatives.some((conditions) => conditions.every((condition) => matchesComparison(item, condition))));
557
674
  };
558
675
  //#endregion
559
- //#region src/testing/MockCalmLensClient.ts
676
+ //#region src/testing/MockCalmLensServerClient.ts
560
677
  const DEFAULT_EVENTS_LIMIT = 50;
561
678
  /** Stands in for binary payloads when comparing idempotent requests, like the API's size check. */
562
679
  const describeBinary = (value) => {
@@ -567,14 +684,14 @@ const describeBinary = (value) => {
567
684
  };
568
685
  const MAX_EVENTS_LIMIT = 100;
569
686
  const toFailureError = (failure) => {
570
- if (failure === "transport-error") return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
687
+ if (failure === "transport-error") return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TRANSPORT_ERROR, {
571
688
  message: "Request failed",
572
689
  cause: /* @__PURE__ */ new TypeError("Simulated network failure")
573
690
  });
574
691
  if (failure === "timeout") {
575
692
  const cause = /* @__PURE__ */ new Error("The operation timed out.");
576
693
  cause.name = "TimeoutError";
577
- return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
694
+ return new require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TIMEOUT, {
578
695
  message: "Request timed out",
579
696
  cause
580
697
  });
@@ -582,12 +699,12 @@ const toFailureError = (failure) => {
582
699
  return failure;
583
700
  };
584
701
  /**
585
- * An in-memory stand-in for `CalmLensClient` with the same methods, validation and error
702
+ * An in-memory stand-in for `CalmLensServerClient` with the same methods, validation and error
586
703
  * codes. API errors carry the HTTP status, a `requestId` and response headers like the real
587
704
  * client's, so error handling can be tested without a network.
588
705
  */
589
- var MockCalmLensClient = class {
590
- /** Accepts the same options as `CalmLensClient` (validated the same way), plus `mockOptions`. */
706
+ var MockCalmLensServerClient = class {
707
+ /** Accepts the same options as `CalmLensServerClient` (validated the same way), plus `mockOptions`. */
591
708
  constructor(options) {
592
709
  this.lenses = /* @__PURE__ */ new Map();
593
710
  this.eventFeed = [];
@@ -596,8 +713,9 @@ var MockCalmLensClient = class {
596
713
  this.requestCount = 0;
597
714
  this.pendingFailures = [];
598
715
  this.calls = [];
599
- require_CalmLensWebhooks.validateClientOptions(options);
716
+ require_CalmLensUtils.validateServerClientOptions(options);
600
717
  this.projectId = options.projectId;
718
+ this.apiKey = options.apiKey;
601
719
  this.databaseStorage = options.mockOptions?.databaseStorage ?? new InMemoryMockDatabaseStorage();
602
720
  this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
603
721
  this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
@@ -610,7 +728,7 @@ var MockCalmLensClient = class {
610
728
  }
611
729
  /** Keeps the API key out of `console.log` and `util.inspect`. */
612
730
  [Symbol.for("nodejs.util.inspect.custom")]() {
613
- return `MockCalmLensClient { projectId: ${JSON.stringify(this.projectId)} }`;
731
+ return `MockCalmLensServerClient { projectId: ${JSON.stringify(this.projectId)} }`;
614
732
  }
615
733
  /**
616
734
  * Makes the next call (or the next call to `operation`) throw `failure` instead of running,
@@ -647,9 +765,9 @@ var MockCalmLensClient = class {
647
765
  }
648
766
  /** An API error as the real client throws it: status, default message and request ID. */
649
767
  apiError(code, options = {}) {
650
- const definition = require_CalmLensWebhooks.API_ERROR_DEFINITIONS[code];
768
+ const definition = require_CalmLensError.API_ERROR_DEFINITIONS[code];
651
769
  const requestId = `mock-request-${++this.requestCount}`;
652
- return new require_CalmLensWebhooks.CalmLensError(code, {
770
+ return new require_CalmLensError.CalmLensError(code, {
653
771
  message: options.message ?? definition.message,
654
772
  status: definition.status,
655
773
  details: options.details,
@@ -661,18 +779,18 @@ var MockCalmLensClient = class {
661
779
  });
662
780
  }
663
781
  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),
782
+ if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
783
+ message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
666
784
  details: { mimeType }
667
785
  });
668
786
  }
669
787
  invalidPayload(message) {
670
- return this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
788
+ return this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
671
789
  }
672
790
  async findOwnAsset(assetId) {
673
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
791
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
674
792
  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);
793
+ if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensError.API_ERROR_CODES.ASSET_NOT_FOUND);
676
794
  return asset;
677
795
  }
678
796
  begin(operation, options) {
@@ -702,8 +820,8 @@ var MockCalmLensClient = class {
702
820
  return {
703
821
  getAssetUrl: ({ assetIdentifier, ...options }) => {
704
822
  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);
823
+ const url = `${this.cdnBaseUrl}/assets/${encodeURIComponent(this.projectId)}/${require_CalmLensUtils.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
824
+ return require_CalmLensUtils.appendCdnSelector(url, options);
707
825
  },
708
826
  /**
709
827
  * Serves stored content like the CDN: `200` with `Content-Type`, `Content-Length`,
@@ -771,7 +889,7 @@ var MockCalmLensClient = class {
771
889
  }
772
890
  listEvents(options) {
773
891
  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}` });
892
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
775
893
  if (!options.after) return {
776
894
  events: [],
777
895
  cursor: `mock_${this.eventFeed.length}`,
@@ -779,7 +897,7 @@ var MockCalmLensClient = class {
779
897
  };
780
898
  const match = /^mock_(\d+)$/.exec(options.after);
781
899
  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);
900
+ if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_EVENT_CURSOR);
783
901
  const events = this.eventFeed.slice(position, position + limit);
784
902
  const next = position + events.length;
785
903
  return {
@@ -810,7 +928,7 @@ var MockCalmLensClient = class {
810
928
  get text() {
811
929
  return { moderate: async (options) => {
812
930
  this.begin("text.moderate", options);
813
- return this.moderateAsset(require_CalmLensWebhooks.toTextAssetOptions(options), "text.moderate");
931
+ return this.moderateAsset(require_CalmLensUtils.toTextAssetOptions(options), "text.moderate");
814
932
  } };
815
933
  }
816
934
  get assets() {
@@ -869,7 +987,7 @@ var MockCalmLensClient = class {
869
987
  listChildren: async (childrenOptions = {}) => {
870
988
  this.begin("assets.listChildren", childrenOptions);
871
989
  const { assetId, ...options } = childrenOptions;
872
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
990
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
873
991
  const assets = await this.databaseStorage.listAssets(this.projectId);
874
992
  return createPage(orderItems(assets.filter((asset) => asset.parentId === assetId), options.ordering), {
875
993
  ...options,
@@ -877,7 +995,7 @@ var MockCalmLensClient = class {
877
995
  });
878
996
  },
879
997
  iterateChildren: (options) => {
880
- require_CalmLensWebhooks.encodePathSegment(options?.assetId, "Asset ID");
998
+ require_CalmLensUtils.encodePathSegment(options?.assetId, "Asset ID");
881
999
  return this.iteratePages((pageIndex) => this.assets.listChildren({
882
1000
  ...options,
883
1001
  pageIndex
@@ -906,18 +1024,18 @@ var MockCalmLensClient = class {
906
1024
  uploadPart: async (options) => {
907
1025
  this.begin("assets.uploadPart", options);
908
1026
  const { assetId, part } = options;
909
- require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
910
- if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
1027
+ require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
1028
+ if (!part) throw require_CalmLensError.invalidArgument("Part options are required");
911
1029
  return this.withIdempotency("assets.uploadPart", options, async () => {
912
1030
  const asset = await this.findOwnAsset(assetId);
913
- const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
1031
+ const validation = require_CalmLensUtils.validateAssetPayload(part, {
914
1032
  multipart: true,
915
1033
  isFinal: part.isFinal
916
1034
  });
917
1035
  if (!validation.ok) throw this.invalidPayload(validation.error);
918
- if (asset.status !== "uploading") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.UPLOAD_NOT_OPEN);
1036
+ if (asset.status !== "uploading") throw this.apiError(require_CalmLensError.API_ERROR_CODES.UPLOAD_NOT_OPEN);
919
1037
  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 } });
1038
+ if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
921
1039
  await this.imageStorage.putPart(assetId, part);
922
1040
  const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
923
1041
  if (part.isFinal) this.uploadQueues.delete(assetId);
@@ -933,10 +1051,11 @@ var MockCalmLensClient = class {
933
1051
  this.begin("assets.createSignedUrl", signedUrlOptions);
934
1052
  const { assetId, ...options } = signedUrlOptions;
935
1053
  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");
1054
+ if (options.lens && options.transform) throw require_CalmLensError.invalidArgument("Specify either a lens or a transform, not both");
937
1055
  const expiresInSeconds = options.expiresInSeconds ?? 900;
938
1056
  const resource = options.resource ?? "content";
939
- const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : options.transform ? `&transform=${encodeURIComponent(options.transform)}` : "";
1057
+ const transform = require_CalmLensUtils.transformSelector(options.transform);
1058
+ const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : transform ? `&transform=${encodeURIComponent(transform)}` : "";
940
1059
  return {
941
1060
  url: `${this.signedUrlBaseUrl}/assets/${asset.id}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
942
1061
  expiresAt: this.now() + expiresInSeconds * 1e3
@@ -947,7 +1066,7 @@ var MockCalmLensClient = class {
947
1066
  const { assetId, folderId } = options;
948
1067
  const asset = await this.findOwnAsset(assetId);
949
1068
  if (folderId !== null && folderId !== void 0) {
950
- if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
1069
+ if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_FOUND);
951
1070
  }
952
1071
  return this.databaseStorage.updateAsset({
953
1072
  ...asset,
@@ -958,8 +1077,8 @@ var MockCalmLensClient = class {
958
1077
  retry: async (options) => {
959
1078
  this.begin("assets.retry", options);
960
1079
  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);
1080
+ if (asset.status !== "error") throw this.apiError(require_CalmLensError.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
1081
+ if (asset.storageDeletedAt) throw this.apiError(require_CalmLensError.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
963
1082
  return this.databaseStorage.updateAsset({
964
1083
  ...asset,
965
1084
  status: "pending",
@@ -985,6 +1104,16 @@ var MockCalmLensClient = class {
985
1104
  }
986
1105
  };
987
1106
  }
1107
+ /** Signs real upload tokens with the mock's API key, like the real client, without a request. */
1108
+ get uploadTokens() {
1109
+ return { sign: async (options) => {
1110
+ this.begin("uploadTokens.sign", options);
1111
+ return require_CalmLensWebhooks.signUploadTokenWithApiKey(this.apiKey, options, {
1112
+ now: this.now(),
1113
+ nonce: this.createId()
1114
+ });
1115
+ } };
1116
+ }
988
1117
  get folders() {
989
1118
  return {
990
1119
  list: async () => {
@@ -1028,7 +1157,7 @@ var MockCalmLensClient = class {
1028
1157
  await this.findOwnFolder(folderId);
1029
1158
  const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
1030
1159
  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: {
1160
+ if (hasAssets || hasChildren) throw this.apiError(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
1032
1161
  hasAssets,
1033
1162
  hasChildren
1034
1163
  } });
@@ -1043,7 +1172,7 @@ var MockCalmLensClient = class {
1043
1172
  let candidate = await this.findOwnFolder(targetParentId);
1044
1173
  const visited = /* @__PURE__ */ new Set();
1045
1174
  while (candidate) {
1046
- if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1175
+ if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensError.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1047
1176
  visited.add(candidate.id);
1048
1177
  candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
1049
1178
  }
@@ -1060,7 +1189,7 @@ var MockCalmLensClient = class {
1060
1189
  }
1061
1190
  async ensureLens(options) {
1062
1191
  this.begin("ensureLens", options);
1063
- if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
1192
+ if (!options?.identifier) throw require_CalmLensError.invalidArgument("Lens identifier is required");
1064
1193
  return this.ensureLensDefinition(options);
1065
1194
  }
1066
1195
  ensureLensDefinition(options) {
@@ -1087,7 +1216,7 @@ var MockCalmLensClient = class {
1087
1216
  this.lenses.set(lens.identifier, lens);
1088
1217
  return lens;
1089
1218
  }
1090
- throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
1219
+ throw this.apiError(require_CalmLensError.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
1091
1220
  currentVersion: existing.version,
1092
1221
  minimumVersion: existing.version + 1,
1093
1222
  requestedVersion: options.version
@@ -1095,19 +1224,19 @@ var MockCalmLensClient = class {
1095
1224
  }
1096
1225
  async ensureLenses(options) {
1097
1226
  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");
1227
+ if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensError.invalidArgument("At least one lens is required");
1228
+ if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensError.invalidArgument("Lens identifier is required");
1100
1229
  return options.lenses.map((option) => this.ensureLensDefinition(option));
1101
1230
  }
1102
1231
  /** Replays the first result for a reused idempotency key, like the API. */
1103
1232
  async withIdempotency(operation, options, run) {
1104
1233
  const key = options?.idempotencyKey;
1105
1234
  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);
1235
+ if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
1107
1236
  const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
1108
1237
  const saved = this.idempotentResults.get(key);
1109
1238
  if (saved) {
1110
- if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
1239
+ if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensError.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
1111
1240
  return saved.asset;
1112
1241
  }
1113
1242
  const asset = await run();
@@ -1122,28 +1251,28 @@ var MockCalmLensClient = class {
1122
1251
  return this.withIdempotency(operation, options, () => this.createAsset(options, false));
1123
1252
  }
1124
1253
  async createAsset(options, queue) {
1125
- if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
1254
+ if (!options || typeof options !== "object") throw require_CalmLensError.invalidArgument("Asset options are required");
1126
1255
  if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
1127
1256
  if (!options.name) throw this.invalidPayload("Asset name is required");
1128
1257
  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);
1258
+ if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensError.API_ERROR_CODES.USE_UPLOAD_PART);
1130
1259
  if ("content" in options) {
1131
1260
  this.assertValidUploadMimeType(options.content.type);
1132
1261
  if (!("plainText" in options.content)) {
1133
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
1262
+ const validation = require_CalmLensUtils.validateAssetPayload(options.content);
1134
1263
  if (!validation.ok) throw this.invalidPayload(validation.error);
1135
1264
  }
1136
1265
  }
1137
1266
  if ("part" in options) {
1138
1267
  this.assertValidUploadMimeType(options.part.type);
1139
- const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
1268
+ const validation = require_CalmLensUtils.validateAssetPayload(options.part, {
1140
1269
  multipart: true,
1141
1270
  isFinal: options.part.isFinal
1142
1271
  });
1143
1272
  if (!validation.ok) throw this.invalidPayload(validation.error);
1144
1273
  }
1145
1274
  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);
1275
+ if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensError.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
1147
1276
  }
1148
1277
  const id = this.createId();
1149
1278
  const now = this.now();
@@ -1181,20 +1310,20 @@ var MockCalmLensClient = class {
1181
1310
  return folder;
1182
1311
  }
1183
1312
  async findOwnFolder(folderId) {
1184
- require_CalmLensWebhooks.encodePathSegment(folderId, "Folder ID");
1313
+ require_CalmLensUtils.encodePathSegment(folderId, "Folder ID");
1185
1314
  const folder = await this.resolveFolder(folderId);
1186
- if (!folder) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
1315
+ if (!folder) throw this.apiError(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_FOUND);
1187
1316
  return folder;
1188
1317
  }
1189
1318
  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.` });
1319
+ 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_CalmLensError.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
1191
1320
  }
1192
1321
  getSizeInBytes(options) {
1193
1322
  if ("content" in options) {
1194
1323
  if ("plainText" in options.content) return new TextEncoder().encode(options.content.plainText).byteLength;
1195
- return require_CalmLensWebhooks.getAssetPayloadByteLength(options.content);
1324
+ return require_CalmLensUtils.getAssetPayloadByteLength(options.content);
1196
1325
  }
1197
- if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
1326
+ if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensUtils.getAssetPayloadByteLength(options.part);
1198
1327
  }
1199
1328
  };
1200
1329
  //#endregion
@@ -1231,7 +1360,7 @@ exports.InMemoryMockDatabaseStorage = InMemoryMockDatabaseStorage;
1231
1360
  exports.InMemoryMockImageStorage = InMemoryMockImageStorage;
1232
1361
  exports.MEDIA_TEST_SIGNALS = MEDIA_TEST_SIGNALS;
1233
1362
  exports.MOCK_API_KEY_PREFIX = MOCK_API_KEY_PREFIX;
1234
- exports.MockCalmLensClient = MockCalmLensClient;
1363
+ exports.MockCalmLensServerClient = MockCalmLensServerClient;
1235
1364
  exports.createMockApiKey = createMockApiKey;
1236
1365
  exports.createWebhookHeaders = createWebhookHeaders;
1237
1366
  exports.createWebhookSignatureHeader = createWebhookSignatureHeader;