@calmlens/sdk 0.12.0 → 0.14.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 +54 -37
- package/cjs/CalmLensConnection-Bth5fJyp.js +234 -0
- package/cjs/CalmLensUtils-DNdmYeX6.js +699 -0
- package/cjs/{CalmLensError-CKwMKV0S.js → CalmLensVersion-Bs0DreBV.js} +114 -1
- package/cjs/{CalmLensWebhooks-BTPxKvcD.js → CalmLensWebhooks-6thkvr_F.js} +9 -9
- package/cjs/WebhookPayload-vy8HNMaK.d.ts +15834 -0
- package/cjs/app.d.ts +3 -3
- package/cjs/app.js +10 -10
- package/cjs/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
- package/cjs/index.d.ts +3 -3
- package/cjs/index.js +4 -5
- package/cjs/server.d.ts +16 -7
- package/cjs/server.js +35 -21
- package/cjs/testing.d.ts +21 -42
- package/cjs/testing.js +181 -94
- package/esm/CalmLensConnection-DrCOL9h9.js +193 -0
- package/esm/{CalmLensUtils-Cx32j2zt.js → CalmLensUtils-BQo79TA1.js} +316 -2
- package/esm/{CalmLensError-CihGG26K.js → CalmLensVersion-DPnL_Ebv.js} +91 -2
- package/esm/{CalmLensWebhooks-DShmAal4.js → CalmLensWebhooks-CWlD-f_P.js} +2 -2
- package/esm/WebhookPayload-vy8HNMaK.d.ts +15834 -0
- package/esm/app.d.ts +3 -3
- package/esm/app.js +4 -4
- package/esm/{index-BgmWj3KL.d.ts → index-Dz340UQ9.d.ts} +29 -1
- package/esm/index.d.ts +3 -3
- package/esm/index.js +1 -3
- package/esm/server.d.ts +16 -7
- package/esm/server.js +24 -10
- package/esm/testing.d.ts +21 -42
- package/esm/testing.js +152 -65
- package/package.json +1 -1
- package/cjs/CalmLensConnection-CYtBP-vu.js +0 -516
- package/cjs/CalmLensUtils-Dg3s4loH.js +0 -367
- package/cjs/WebhookPayload-Cgb32aSH.d.ts +0 -10187
- package/cjs/src-Dsc8Pn6v.js +0 -45
- package/esm/CalmLensConnection-wLNy0OIP.js +0 -475
- package/esm/WebhookPayload-Cgb32aSH.d.ts +0 -10187
- package/esm/src-CJhrILvM.js +0 -22
package/cjs/testing.js
CHANGED
|
@@ -21,9 +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
|
|
25
|
-
const require_CalmLensWebhooks = require("./CalmLensWebhooks-
|
|
26
|
-
const require_CalmLensUtils = require("./CalmLensUtils-
|
|
24
|
+
const require_CalmLensVersion = require("./CalmLensVersion-Bs0DreBV.js");
|
|
25
|
+
const require_CalmLensWebhooks = require("./CalmLensWebhooks-6thkvr_F.js");
|
|
26
|
+
const require_CalmLensUtils = require("./CalmLensUtils-DNdmYeX6.js");
|
|
27
27
|
let zod = require("zod");
|
|
28
28
|
zod = __toESM(zod);
|
|
29
29
|
require("zod/v4");
|
|
@@ -60,58 +60,63 @@ const MODERATION_CATEGORY_DEFINITIONS = {
|
|
|
60
60
|
docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
|
|
61
61
|
},
|
|
62
62
|
"criminal-activity": {
|
|
63
|
-
summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act.",
|
|
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.",
|
|
65
|
-
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.",
|
|
66
66
|
docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
69
|
//#endregion
|
|
70
70
|
//#region ../shared/src/ModerationPolicyDimensions.ts
|
|
71
71
|
const MODERATION_QUALIFIERS = [
|
|
72
|
-
"nudity",
|
|
72
|
+
"sexual-nudity",
|
|
73
73
|
"sexual-activity",
|
|
74
|
-
"explicit-
|
|
74
|
+
"sexual-explicit-language",
|
|
75
75
|
"sexual-solicitation",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
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",
|
|
83
91
|
"self-harm-thoughts",
|
|
84
92
|
"self-harm-preparation",
|
|
85
93
|
"self-harm-act",
|
|
86
94
|
"self-harm-methods",
|
|
87
95
|
"self-harm-encouragement",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
96
|
+
"self-harm-documentary",
|
|
97
|
+
"self-harm-fictional",
|
|
98
|
+
"self-harm-support-prevention",
|
|
99
|
+
"harassment-abuse",
|
|
100
|
+
"harassment-intimidation",
|
|
90
101
|
"harassment-threat",
|
|
91
|
-
"sexual
|
|
102
|
+
"harassment-sexual",
|
|
92
103
|
"harassment-encouragement",
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
96
|
-
"
|
|
104
|
+
"harassment-fictional",
|
|
105
|
+
"hate-abuse",
|
|
106
|
+
"hate-dehumanization",
|
|
107
|
+
"hate-inferiority",
|
|
108
|
+
"hate-exclusion",
|
|
97
109
|
"hate-threat",
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"criminal-
|
|
101
|
-
"criminal-
|
|
102
|
-
"criminal-
|
|
103
|
-
"
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
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"
|
|
107
119
|
];
|
|
108
|
-
const SHARED_QUALIFIERS = [
|
|
109
|
-
"historical",
|
|
110
|
-
"documentary",
|
|
111
|
-
"fictional",
|
|
112
|
-
"support-prevention"
|
|
113
|
-
];
|
|
114
|
-
[...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
|
|
115
120
|
//#endregion
|
|
116
121
|
//#region ../../node_modules/zod-meta/esm/index.js
|
|
117
122
|
const ZOD_META_KEY = "__zod_meta__";
|
|
@@ -276,7 +281,11 @@ const LIKELIHOOD_SCHEMA = zod.enum([
|
|
|
276
281
|
"likely",
|
|
277
282
|
"very-likely"
|
|
278
283
|
]);
|
|
279
|
-
const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum([
|
|
284
|
+
const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum([
|
|
285
|
+
"not-supported",
|
|
286
|
+
"unavailable",
|
|
287
|
+
"not-requested"
|
|
288
|
+
]);
|
|
280
289
|
const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
|
|
281
290
|
status: zod.literal("evaluated"),
|
|
282
291
|
score: zod.number().min(0).max(1),
|
|
@@ -326,7 +335,6 @@ const CLASSIFICATION_LABEL_SCHEMA = zod.object({
|
|
|
326
335
|
zod.object({
|
|
327
336
|
processedAt: zod.number(),
|
|
328
337
|
categories: CATEGORIES_SCHEMA,
|
|
329
|
-
flagged: zod.boolean(),
|
|
330
338
|
score: zod.number(),
|
|
331
339
|
notes: saneStringField().nullish(),
|
|
332
340
|
media: zod.object({
|
|
@@ -404,6 +412,8 @@ const ACTION_SCHEMA = zod.enum([
|
|
|
404
412
|
"lenses:write",
|
|
405
413
|
"upload-presets:read",
|
|
406
414
|
"upload-presets:write",
|
|
415
|
+
"reviews:read",
|
|
416
|
+
"reviews:write",
|
|
407
417
|
"project:manage"
|
|
408
418
|
]);
|
|
409
419
|
const PROJECT_ACTIONS = [...[
|
|
@@ -413,7 +423,8 @@ const PROJECT_ACTIONS = [...[
|
|
|
413
423
|
"connectors",
|
|
414
424
|
"metadata",
|
|
415
425
|
"lenses",
|
|
416
|
-
"upload-presets"
|
|
426
|
+
"upload-presets",
|
|
427
|
+
"reviews"
|
|
417
428
|
].flatMap((feature) => [`${feature}:read`, `${feature}:write`]), "project:manage"];
|
|
418
429
|
const isProjectAction = (action) => PROJECT_ACTIONS.includes(action);
|
|
419
430
|
/** The actions that include `action`: its feature's write action and project:manage. */
|
|
@@ -426,7 +437,7 @@ const GRANT_SCHEMA = zod.object({
|
|
|
426
437
|
const GRANTS_SCHEMA = zod.array(GRANT_SCHEMA).max(100);
|
|
427
438
|
zod.enum([
|
|
428
439
|
"admin",
|
|
429
|
-
"
|
|
440
|
+
"editor",
|
|
430
441
|
"viewer",
|
|
431
442
|
"custom"
|
|
432
443
|
]);
|
|
@@ -572,8 +583,8 @@ const inferAssetType = (options) => {
|
|
|
572
583
|
const createPage = (items, query) => {
|
|
573
584
|
const pageIndex = query.pageIndex ?? 0;
|
|
574
585
|
const pageSize = query.pageSize ?? 10;
|
|
575
|
-
if (!Number.isInteger(pageIndex) || pageIndex < 0) throw
|
|
576
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw
|
|
586
|
+
if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensVersion.invalidArgument("pageIndex must be a non-negative integer");
|
|
587
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensVersion.invalidArgument("pageSize must be an integer from 1 to 100");
|
|
577
588
|
const start = pageIndex * pageSize;
|
|
578
589
|
return {
|
|
579
590
|
items: items.slice(start, start + pageSize),
|
|
@@ -616,7 +627,7 @@ const parseFilterValue = (raw) => {
|
|
|
616
627
|
return value;
|
|
617
628
|
};
|
|
618
629
|
const readField = (item, path) => path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, item);
|
|
619
|
-
const unsupportedFilter = (expression) =>
|
|
630
|
+
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.`);
|
|
620
631
|
const matchesComparison = (item, comparison) => {
|
|
621
632
|
const match = COMPARISON.exec(comparison.trim());
|
|
622
633
|
if (!match) throw unsupportedFilter(comparison);
|
|
@@ -684,14 +695,14 @@ const describeBinary = (value) => {
|
|
|
684
695
|
};
|
|
685
696
|
const MAX_EVENTS_LIMIT = 100;
|
|
686
697
|
const toFailureError = (failure) => {
|
|
687
|
-
if (failure === "transport-error") return new
|
|
698
|
+
if (failure === "transport-error") return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR, {
|
|
688
699
|
message: "Request failed",
|
|
689
700
|
cause: /* @__PURE__ */ new TypeError("Simulated network failure")
|
|
690
701
|
});
|
|
691
702
|
if (failure === "timeout") {
|
|
692
703
|
const cause = /* @__PURE__ */ new Error("The operation timed out.");
|
|
693
704
|
cause.name = "TimeoutError";
|
|
694
|
-
return new
|
|
705
|
+
return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
|
|
695
706
|
message: "Request timed out",
|
|
696
707
|
cause
|
|
697
708
|
});
|
|
@@ -720,8 +731,8 @@ var MockCalmLensServerClient = class {
|
|
|
720
731
|
this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
|
|
721
732
|
this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
|
|
722
733
|
this.now = options.mockOptions?.now ?? Date.now;
|
|
723
|
-
this.
|
|
724
|
-
this.
|
|
734
|
+
this.queuedOutcome = options.mockOptions?.queuedOutcome ?? "pending";
|
|
735
|
+
this.analyzedOutcome = options.mockOptions?.analyzedOutcome ?? "approved";
|
|
725
736
|
this.apiKeyGrants = getMockApiKeyGrants(options.apiKey);
|
|
726
737
|
this.signedUrlBaseUrl = options.mockOptions?.signedUrlBaseUrl ?? "mock://calmlens";
|
|
727
738
|
this.cdnBaseUrl = (options.cdnBaseUrl ?? "mock://calmlens/v1").replace(/\/+$/, "");
|
|
@@ -765,9 +776,9 @@ var MockCalmLensServerClient = class {
|
|
|
765
776
|
}
|
|
766
777
|
/** An API error as the real client throws it: status, default message and request ID. */
|
|
767
778
|
apiError(code, options = {}) {
|
|
768
|
-
const definition =
|
|
779
|
+
const definition = require_CalmLensVersion.API_ERROR_DEFINITIONS[code];
|
|
769
780
|
const requestId = `mock-request-${++this.requestCount}`;
|
|
770
|
-
return new
|
|
781
|
+
return new require_CalmLensVersion.CalmLensError(code, {
|
|
771
782
|
message: options.message ?? definition.message,
|
|
772
783
|
status: definition.status,
|
|
773
784
|
details: options.details,
|
|
@@ -779,18 +790,18 @@ var MockCalmLensServerClient = class {
|
|
|
779
790
|
});
|
|
780
791
|
}
|
|
781
792
|
assertValidUploadMimeType(mimeType) {
|
|
782
|
-
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(
|
|
793
|
+
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
783
794
|
message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
|
|
784
795
|
details: { mimeType }
|
|
785
796
|
});
|
|
786
797
|
}
|
|
787
798
|
invalidPayload(message) {
|
|
788
|
-
return this.apiError(
|
|
799
|
+
return this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
789
800
|
}
|
|
790
801
|
async findOwnAsset(assetId) {
|
|
791
802
|
require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
|
|
792
803
|
const asset = await this.databaseStorage.getAsset(assetId);
|
|
793
|
-
if (!asset || asset.projectId !== this.projectId) throw this.apiError(
|
|
804
|
+
if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_FOUND);
|
|
794
805
|
return asset;
|
|
795
806
|
}
|
|
796
807
|
begin(operation, options) {
|
|
@@ -889,7 +900,7 @@ var MockCalmLensServerClient = class {
|
|
|
889
900
|
}
|
|
890
901
|
listEvents(options) {
|
|
891
902
|
const limit = options.limit ?? DEFAULT_EVENTS_LIMIT;
|
|
892
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(
|
|
903
|
+
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}` });
|
|
893
904
|
if (!options.after) return {
|
|
894
905
|
events: [],
|
|
895
906
|
cursor: `mock_${this.eventFeed.length}`,
|
|
@@ -897,7 +908,7 @@ var MockCalmLensServerClient = class {
|
|
|
897
908
|
};
|
|
898
909
|
const match = /^mock_(\d+)$/.exec(options.after);
|
|
899
910
|
const position = match ? Number(match[1]) : NaN;
|
|
900
|
-
if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(
|
|
911
|
+
if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_EVENT_CURSOR);
|
|
901
912
|
const events = this.eventFeed.slice(position, position + limit);
|
|
902
913
|
const next = position + events.length;
|
|
903
914
|
return {
|
|
@@ -1025,7 +1036,7 @@ var MockCalmLensServerClient = class {
|
|
|
1025
1036
|
this.begin("assets.uploadPart", options);
|
|
1026
1037
|
const { assetId, part } = options;
|
|
1027
1038
|
require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
|
|
1028
|
-
if (!part) throw
|
|
1039
|
+
if (!part) throw require_CalmLensVersion.invalidArgument("Part options are required");
|
|
1029
1040
|
return this.withIdempotency("assets.uploadPart", options, async () => {
|
|
1030
1041
|
const asset = await this.findOwnAsset(assetId);
|
|
1031
1042
|
const validation = require_CalmLensUtils.validateAssetPayload(part, {
|
|
@@ -1033,25 +1044,29 @@ var MockCalmLensServerClient = class {
|
|
|
1033
1044
|
isFinal: part.isFinal
|
|
1034
1045
|
});
|
|
1035
1046
|
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
1036
|
-
if (asset.status !== "uploading") throw this.apiError(
|
|
1037
|
-
const expectedPartNumber = (asset.uploadPartsReceived ?? 1) + 1;
|
|
1038
|
-
if (part.partNumber !== expectedPartNumber) throw this.apiError(
|
|
1047
|
+
if (asset.processing.status !== "uploading") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.UPLOAD_NOT_OPEN);
|
|
1048
|
+
const expectedPartNumber = (asset.processing.uploadPartsReceived ?? 1) + 1;
|
|
1049
|
+
if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
|
|
1039
1050
|
await this.imageStorage.putPart(assetId, part);
|
|
1040
1051
|
const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
|
|
1041
1052
|
if (part.isFinal) this.uploadQueues.delete(assetId);
|
|
1042
|
-
|
|
1053
|
+
const now = this.now();
|
|
1054
|
+
const received = {
|
|
1043
1055
|
...asset,
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1056
|
+
processing: {
|
|
1057
|
+
...asset.processing,
|
|
1058
|
+
uploadPartsReceived: part.partNumber
|
|
1059
|
+
},
|
|
1060
|
+
updatedAt: now
|
|
1061
|
+
};
|
|
1062
|
+
return this.databaseStorage.updateAsset(part.isFinal ? await this.applyOutcome(received, queue ? this.queuedOutcome : this.analyzedOutcome) : received);
|
|
1048
1063
|
});
|
|
1049
1064
|
},
|
|
1050
1065
|
createSignedUrl: async (signedUrlOptions) => {
|
|
1051
1066
|
this.begin("assets.createSignedUrl", signedUrlOptions);
|
|
1052
1067
|
const { assetId, ...options } = signedUrlOptions;
|
|
1053
1068
|
const asset = await this.findOwnAsset(assetId);
|
|
1054
|
-
if (options.lens && options.transform) throw
|
|
1069
|
+
if (options.lens && options.transform) throw require_CalmLensVersion.invalidArgument("Specify either a lens or a transform, not both");
|
|
1055
1070
|
const expiresInSeconds = options.expiresInSeconds ?? 900;
|
|
1056
1071
|
const resource = options.resource ?? "content";
|
|
1057
1072
|
const transform = require_CalmLensUtils.transformSelector(options.transform);
|
|
@@ -1066,7 +1081,7 @@ var MockCalmLensServerClient = class {
|
|
|
1066
1081
|
const { assetId, folderId } = options;
|
|
1067
1082
|
const asset = await this.findOwnAsset(assetId);
|
|
1068
1083
|
if (folderId !== null && folderId !== void 0) {
|
|
1069
|
-
if (!await this.resolveFolder(folderId)) throw this.apiError(
|
|
1084
|
+
if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
1070
1085
|
}
|
|
1071
1086
|
return this.databaseStorage.updateAsset({
|
|
1072
1087
|
...asset,
|
|
@@ -1077,23 +1092,31 @@ var MockCalmLensServerClient = class {
|
|
|
1077
1092
|
retry: async (options) => {
|
|
1078
1093
|
this.begin("assets.retry", options);
|
|
1079
1094
|
const asset = await this.findOwnAsset(options?.assetId);
|
|
1080
|
-
if (asset.status !== "
|
|
1081
|
-
if (asset.
|
|
1082
|
-
return this.databaseStorage.updateAsset(
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1095
|
+
if (asset.processing.status !== "failed") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
|
|
1096
|
+
if (asset.content.status === "removed") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
|
|
1097
|
+
return this.databaseStorage.updateAsset(await this.applyOutcome(asset, "pending"));
|
|
1098
|
+
},
|
|
1099
|
+
/** Waits like the real client, checking the mock's storage for a decision. */
|
|
1100
|
+
waitForDecision: async (options) => {
|
|
1101
|
+
this.begin("assets.waitForDecision", options);
|
|
1102
|
+
const { assetId, ...waitOptions } = options;
|
|
1103
|
+
return require_CalmLensUtils.pollForDecision(() => this.findOwnAsset(assetId), waitOptions, require_CalmLensUtils.sleep);
|
|
1087
1104
|
},
|
|
1088
1105
|
deleteContent: async (options) => {
|
|
1089
1106
|
this.begin("assets.deleteContent", options);
|
|
1090
1107
|
const asset = await this.findOwnAsset(options?.assetId);
|
|
1091
1108
|
await this.imageStorage.deleteContent(asset.id);
|
|
1109
|
+
const now = this.now();
|
|
1092
1110
|
return this.databaseStorage.updateAsset({
|
|
1093
1111
|
...asset,
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1112
|
+
content: asset.content.status === "removed" ? asset.content : {
|
|
1113
|
+
...asset.content,
|
|
1114
|
+
status: "removed",
|
|
1115
|
+
removalReason: "requested",
|
|
1116
|
+
updatedAt: now,
|
|
1117
|
+
extractedText: null
|
|
1118
|
+
},
|
|
1119
|
+
updatedAt: now
|
|
1097
1120
|
});
|
|
1098
1121
|
},
|
|
1099
1122
|
delete: async (options) => {
|
|
@@ -1157,7 +1180,7 @@ var MockCalmLensServerClient = class {
|
|
|
1157
1180
|
await this.findOwnFolder(folderId);
|
|
1158
1181
|
const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
|
|
1159
1182
|
const hasChildren = (this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).some((f) => f.parentId === folderId);
|
|
1160
|
-
if (hasAssets || hasChildren) throw this.apiError(
|
|
1183
|
+
if (hasAssets || hasChildren) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
|
|
1161
1184
|
hasAssets,
|
|
1162
1185
|
hasChildren
|
|
1163
1186
|
} });
|
|
@@ -1172,7 +1195,7 @@ var MockCalmLensServerClient = class {
|
|
|
1172
1195
|
let candidate = await this.findOwnFolder(targetParentId);
|
|
1173
1196
|
const visited = /* @__PURE__ */ new Set();
|
|
1174
1197
|
while (candidate) {
|
|
1175
|
-
if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(
|
|
1198
|
+
if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
|
|
1176
1199
|
visited.add(candidate.id);
|
|
1177
1200
|
candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
|
|
1178
1201
|
}
|
|
@@ -1189,7 +1212,7 @@ var MockCalmLensServerClient = class {
|
|
|
1189
1212
|
}
|
|
1190
1213
|
async ensureLens(options) {
|
|
1191
1214
|
this.begin("ensureLens", options);
|
|
1192
|
-
if (!options?.identifier) throw
|
|
1215
|
+
if (!options?.identifier) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
|
|
1193
1216
|
return this.ensureLensDefinition(options);
|
|
1194
1217
|
}
|
|
1195
1218
|
ensureLensDefinition(options) {
|
|
@@ -1216,7 +1239,7 @@ var MockCalmLensServerClient = class {
|
|
|
1216
1239
|
this.lenses.set(lens.identifier, lens);
|
|
1217
1240
|
return lens;
|
|
1218
1241
|
}
|
|
1219
|
-
throw this.apiError(
|
|
1242
|
+
throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
|
|
1220
1243
|
currentVersion: existing.version,
|
|
1221
1244
|
minimumVersion: existing.version + 1,
|
|
1222
1245
|
requestedVersion: options.version
|
|
@@ -1224,19 +1247,19 @@ var MockCalmLensServerClient = class {
|
|
|
1224
1247
|
}
|
|
1225
1248
|
async ensureLenses(options) {
|
|
1226
1249
|
this.begin("ensureLenses", options);
|
|
1227
|
-
if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw
|
|
1228
|
-
if (options.lenses.some((lens) => !lens?.identifier)) throw
|
|
1250
|
+
if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensVersion.invalidArgument("At least one lens is required");
|
|
1251
|
+
if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
|
|
1229
1252
|
return options.lenses.map((option) => this.ensureLensDefinition(option));
|
|
1230
1253
|
}
|
|
1231
1254
|
/** Replays the first result for a reused idempotency key, like the API. */
|
|
1232
1255
|
async withIdempotency(operation, options, run) {
|
|
1233
1256
|
const key = options?.idempotencyKey;
|
|
1234
1257
|
if (key === void 0) return run();
|
|
1235
|
-
if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(
|
|
1258
|
+
if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
|
|
1236
1259
|
const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
|
|
1237
1260
|
const saved = this.idempotentResults.get(key);
|
|
1238
1261
|
if (saved) {
|
|
1239
|
-
if (saved.fingerprint !== fingerprint) throw this.apiError(
|
|
1262
|
+
if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
|
|
1240
1263
|
return saved.asset;
|
|
1241
1264
|
}
|
|
1242
1265
|
const asset = await run();
|
|
@@ -1251,11 +1274,11 @@ var MockCalmLensServerClient = class {
|
|
|
1251
1274
|
return this.withIdempotency(operation, options, () => this.createAsset(options, false));
|
|
1252
1275
|
}
|
|
1253
1276
|
async createAsset(options, queue) {
|
|
1254
|
-
if (!options || typeof options !== "object") throw
|
|
1277
|
+
if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidArgument("Asset options are required");
|
|
1255
1278
|
if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
|
|
1256
1279
|
if (!options.name) throw this.invalidPayload("Asset name is required");
|
|
1257
1280
|
if (!("part" in options) && "isFinal" in options) throw this.invalidPayload("isFinal is only valid for multipart uploads");
|
|
1258
|
-
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(
|
|
1281
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.USE_UPLOAD_PART);
|
|
1259
1282
|
if ("content" in options) {
|
|
1260
1283
|
this.assertValidUploadMimeType(options.content.type);
|
|
1261
1284
|
if (!("plainText" in options.content)) {
|
|
@@ -1272,11 +1295,12 @@ var MockCalmLensServerClient = class {
|
|
|
1272
1295
|
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
1273
1296
|
}
|
|
1274
1297
|
if (options.externalId) {
|
|
1275
|
-
if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(
|
|
1298
|
+
if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
|
|
1276
1299
|
}
|
|
1277
1300
|
const id = this.createId();
|
|
1278
1301
|
const now = this.now();
|
|
1279
1302
|
const isOpenUpload = "part" in options && !options.part.isFinal;
|
|
1303
|
+
const keepContent = options.processingOptions?.keepContentAfterProcessing === true;
|
|
1280
1304
|
const asset = {
|
|
1281
1305
|
id,
|
|
1282
1306
|
projectId: this.projectId,
|
|
@@ -1289,11 +1313,37 @@ var MockCalmLensServerClient = class {
|
|
|
1289
1313
|
externalUrl: "url" in options ? options.url : "content" in options ? options.externalUrl : void 0,
|
|
1290
1314
|
type: inferAssetType(options),
|
|
1291
1315
|
metadata: options.metadata,
|
|
1292
|
-
status: isOpenUpload ? "uploading" : queue ? this.queuedStatus : this.analyzedStatus,
|
|
1293
1316
|
processingOptions: options.processingOptions,
|
|
1294
1317
|
visibility: options.visibility ?? "private",
|
|
1295
|
-
|
|
1296
|
-
|
|
1318
|
+
processing: {
|
|
1319
|
+
status: isOpenUpload ? "uploading" : "processing",
|
|
1320
|
+
updatedAt: now,
|
|
1321
|
+
uploadPartsReceived: "part" in options ? options.part.partNumber ?? 1 : null
|
|
1322
|
+
},
|
|
1323
|
+
moderation: {
|
|
1324
|
+
status: "pending",
|
|
1325
|
+
action: null,
|
|
1326
|
+
note: null,
|
|
1327
|
+
reviewerAction: null,
|
|
1328
|
+
reviewerNote: null,
|
|
1329
|
+
reviewerUserId: null,
|
|
1330
|
+
reviewedAt: null,
|
|
1331
|
+
decidedAt: null,
|
|
1332
|
+
report: null
|
|
1333
|
+
},
|
|
1334
|
+
content: {
|
|
1335
|
+
status: keepContent ? "stored" : "temporary",
|
|
1336
|
+
removalReason: null,
|
|
1337
|
+
updatedAt: now,
|
|
1338
|
+
safetyHold: null,
|
|
1339
|
+
format: this.getContentFormat(options),
|
|
1340
|
+
extension: null,
|
|
1341
|
+
sizeInBytes: this.getSizeInBytes(options) ?? null,
|
|
1342
|
+
checksums: null,
|
|
1343
|
+
image: null,
|
|
1344
|
+
previewImage: null,
|
|
1345
|
+
extractedText: null
|
|
1346
|
+
}
|
|
1297
1347
|
};
|
|
1298
1348
|
if ("content" in options) {
|
|
1299
1349
|
if (!("plainText" in options.content)) await this.imageStorage.putContent(id, options.content);
|
|
@@ -1301,7 +1351,39 @@ var MockCalmLensServerClient = class {
|
|
|
1301
1351
|
await this.imageStorage.putPart(id, options.part);
|
|
1302
1352
|
if (isOpenUpload) this.uploadQueues.set(id, queue);
|
|
1303
1353
|
}
|
|
1304
|
-
return this.databaseStorage.createAsset(asset);
|
|
1354
|
+
return this.databaseStorage.createAsset(isOpenUpload ? asset : await this.applyOutcome(asset, queue ? this.queuedOutcome : this.analyzedOutcome));
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Moves an asset to `outcome` like the pipeline would: a decision finishes processing, and
|
|
1358
|
+
* finished processing removes content kept only temporarily (`keepContentAfterProcessing` off).
|
|
1359
|
+
*/
|
|
1360
|
+
async applyOutcome(asset, outcome) {
|
|
1361
|
+
const now = this.now();
|
|
1362
|
+
const decided = outcome === "approved" || outcome === "rejected";
|
|
1363
|
+
const processingStatus = decided ? "done" : outcome === "failed" ? "failed" : "processing";
|
|
1364
|
+
const removeContent = processingStatus === "done" && asset.content.status === "temporary";
|
|
1365
|
+
if (removeContent) await this.imageStorage.deleteContent(asset.id);
|
|
1366
|
+
return {
|
|
1367
|
+
...asset,
|
|
1368
|
+
updatedAt: now,
|
|
1369
|
+
processing: {
|
|
1370
|
+
...asset.processing,
|
|
1371
|
+
status: processingStatus,
|
|
1372
|
+
updatedAt: now
|
|
1373
|
+
},
|
|
1374
|
+
moderation: {
|
|
1375
|
+
...asset.moderation,
|
|
1376
|
+
status: decided ? outcome : "pending",
|
|
1377
|
+
decidedAt: decided ? now : null
|
|
1378
|
+
},
|
|
1379
|
+
content: removeContent ? {
|
|
1380
|
+
...asset.content,
|
|
1381
|
+
status: "removed",
|
|
1382
|
+
removalReason: "retention",
|
|
1383
|
+
updatedAt: now,
|
|
1384
|
+
extractedText: null
|
|
1385
|
+
} : asset.content
|
|
1386
|
+
};
|
|
1305
1387
|
}
|
|
1306
1388
|
async resolveFolder(folderId) {
|
|
1307
1389
|
if (!this.databaseStorage.getFolder) return void 0;
|
|
@@ -1312,11 +1394,16 @@ var MockCalmLensServerClient = class {
|
|
|
1312
1394
|
async findOwnFolder(folderId) {
|
|
1313
1395
|
require_CalmLensUtils.encodePathSegment(folderId, "Folder ID");
|
|
1314
1396
|
const folder = await this.resolveFolder(folderId);
|
|
1315
|
-
if (!folder) throw this.apiError(
|
|
1397
|
+
if (!folder) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
1316
1398
|
return folder;
|
|
1317
1399
|
}
|
|
1318
1400
|
async assertUniqueFolderName(name, parentId, excludeFolderId) {
|
|
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(
|
|
1401
|
+
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.` });
|
|
1402
|
+
}
|
|
1403
|
+
getContentFormat(options) {
|
|
1404
|
+
if ("content" in options) return options.content.type;
|
|
1405
|
+
if ("part" in options) return options.part.type;
|
|
1406
|
+
return null;
|
|
1320
1407
|
}
|
|
1321
1408
|
getSizeInBytes(options) {
|
|
1322
1409
|
if ("content" in options) {
|