@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/README.md +60 -16
- package/cjs/CalmLensConnection-CYtBP-vu.js +516 -0
- package/cjs/CalmLensError-CKwMKV0S.js +362 -0
- package/cjs/CalmLensUtils-Dg3s4loH.js +367 -0
- package/cjs/CalmLensWebhooks-BTPxKvcD.js +201 -0
- package/cjs/{CalmLensTypes-DjYFaTyh.d.ts → WebhookPayload-Cgb32aSH.d.ts} +5423 -3844
- package/cjs/app.d.ts +43 -0
- package/cjs/app.js +91 -0
- package/cjs/index-BgmWj3KL.d.ts +350 -0
- package/cjs/index.d.ts +3 -382
- package/cjs/index.js +5 -816
- package/cjs/server.d.ts +202 -0
- package/cjs/server.js +371 -0
- package/cjs/src-Dsc8Pn6v.js +45 -0
- package/cjs/testing.d.ts +54 -18
- package/cjs/testing.js +191 -62
- package/esm/CalmLensConnection-wLNy0OIP.js +475 -0
- package/esm/CalmLensError-CihGG26K.js +321 -0
- package/esm/CalmLensUtils-Cx32j2zt.js +260 -0
- package/esm/CalmLensWebhooks-DShmAal4.js +148 -0
- package/esm/{CalmLensTypes-DjYFaTyh.d.ts → WebhookPayload-Cgb32aSH.d.ts} +5423 -3844
- package/esm/app.d.ts +43 -0
- package/esm/app.js +87 -0
- package/esm/index-BgmWj3KL.d.ts +350 -0
- package/esm/index.d.ts +3 -382
- package/esm/index.js +3 -807
- package/esm/server.d.ts +202 -0
- package/esm/server.js +361 -0
- package/esm/src-CJhrILvM.js +22 -0
- package/esm/testing.d.ts +54 -18
- package/esm/testing.js +142 -13
- package/package.json +23 -1
- package/cjs/CalmLensWebhooks-DJnaLpRy.js +0 -741
- package/esm/CalmLensWebhooks-DLUozliX.js +0 -556
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
|
|
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
|
-
|
|
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 =
|
|
477
|
+
const validation = require_CalmLensUtils.validateAssetPayload(content);
|
|
361
478
|
if (!validation.ok) throw new Error(validation.error);
|
|
362
|
-
const bytes = await
|
|
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 =
|
|
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
|
|
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
|
|
459
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw
|
|
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) =>
|
|
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/
|
|
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
|
|
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
|
|
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 `
|
|
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
|
|
590
|
-
/** Accepts the same options as `
|
|
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
|
-
|
|
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 `
|
|
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 =
|
|
768
|
+
const definition = require_CalmLensError.API_ERROR_DEFINITIONS[code];
|
|
651
769
|
const requestId = `mock-request-${++this.requestCount}`;
|
|
652
|
-
return new
|
|
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 (
|
|
665
|
-
message:
|
|
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(
|
|
788
|
+
return this.apiError(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
671
789
|
}
|
|
672
790
|
async findOwnAsset(assetId) {
|
|
673
|
-
|
|
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(
|
|
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)}/${
|
|
706
|
-
return
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
910
|
-
if (!part) throw
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
962
|
-
if (asset.storageDeletedAt) throw this.apiError(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
1099
|
-
if (options.lenses.some((lens) => !lens?.identifier)) throw
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
1313
|
+
require_CalmLensUtils.encodePathSegment(folderId, "Folder ID");
|
|
1185
1314
|
const folder = await this.resolveFolder(folderId);
|
|
1186
|
-
if (!folder) throw this.apiError(
|
|
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(
|
|
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
|
|
1324
|
+
return require_CalmLensUtils.getAssetPayloadByteLength(options.content);
|
|
1196
1325
|
}
|
|
1197
|
-
if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ??
|
|
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.
|
|
1363
|
+
exports.MockCalmLensServerClient = MockCalmLensServerClient;
|
|
1235
1364
|
exports.createMockApiKey = createMockApiKey;
|
|
1236
1365
|
exports.createWebhookHeaders = createWebhookHeaders;
|
|
1237
1366
|
exports.createWebhookSignatureHeader = createWebhookSignatureHeader;
|