@calmlens/sdk 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -1
- package/cjs/CalmLensConnection-DzRDCMOP.js +233 -0
- package/cjs/CalmLensUtils-vX5H_vMn.js +699 -0
- package/cjs/{CalmLensError-CKwMKV0S.js → CalmLensVersion-DjhsJ1gj.js} +48 -0
- package/cjs/{CalmLensWebhooks-BTPxKvcD.js → CalmLensWebhooks-hVquX5L1.js} +9 -9
- package/cjs/{WebhookPayload-Cgb32aSH.d.ts → WebhookPayload-B7CgSmI5.d.ts} +4996 -3255
- package/cjs/app.d.ts +3 -3
- package/cjs/app.js +10 -10
- package/cjs/{index-BgmWj3KL.d.ts → index-BywTQbRq.d.ts} +3 -1
- package/cjs/index.d.ts +3 -3
- package/cjs/index.js +4 -5
- package/cjs/server.d.ts +12 -4
- package/cjs/server.js +31 -18
- package/cjs/testing.d.ts +6 -1
- package/cjs/testing.js +91 -73
- package/esm/CalmLensConnection-D8VsTdsA.js +192 -0
- package/esm/{CalmLensUtils-Cx32j2zt.js → CalmLensUtils-B4i3Z2He.js} +316 -2
- package/esm/{CalmLensError-CihGG26K.js → CalmLensVersion-DK4BMEST.js} +25 -1
- package/esm/{CalmLensWebhooks-DShmAal4.js → CalmLensWebhooks-DyCoEvpf.js} +2 -2
- package/esm/{WebhookPayload-Cgb32aSH.d.ts → WebhookPayload-B7CgSmI5.d.ts} +4996 -3255
- package/esm/app.d.ts +3 -3
- package/esm/app.js +4 -4
- package/esm/{index-BgmWj3KL.d.ts → index-BywTQbRq.d.ts} +3 -1
- package/esm/index.d.ts +3 -3
- package/esm/index.js +1 -3
- package/esm/server.d.ts +12 -4
- package/esm/server.js +20 -7
- package/esm/testing.d.ts +6 -1
- package/esm/testing.js +59 -41
- package/package.json +1 -1
- package/cjs/CalmLensConnection-CYtBP-vu.js +0 -516
- package/cjs/CalmLensUtils-Dg3s4loH.js +0 -367
- package/cjs/src-Dsc8Pn6v.js +0 -45
- package/esm/CalmLensConnection-wLNy0OIP.js +0 -475
- 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-DjhsJ1gj.js");
|
|
25
|
+
const require_CalmLensWebhooks = require("./CalmLensWebhooks-hVquX5L1.js");
|
|
26
|
+
const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.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),
|
|
@@ -404,6 +413,8 @@ const ACTION_SCHEMA = zod.enum([
|
|
|
404
413
|
"lenses:write",
|
|
405
414
|
"upload-presets:read",
|
|
406
415
|
"upload-presets:write",
|
|
416
|
+
"reviews:read",
|
|
417
|
+
"reviews:write",
|
|
407
418
|
"project:manage"
|
|
408
419
|
]);
|
|
409
420
|
const PROJECT_ACTIONS = [...[
|
|
@@ -413,7 +424,8 @@ const PROJECT_ACTIONS = [...[
|
|
|
413
424
|
"connectors",
|
|
414
425
|
"metadata",
|
|
415
426
|
"lenses",
|
|
416
|
-
"upload-presets"
|
|
427
|
+
"upload-presets",
|
|
428
|
+
"reviews"
|
|
417
429
|
].flatMap((feature) => [`${feature}:read`, `${feature}:write`]), "project:manage"];
|
|
418
430
|
const isProjectAction = (action) => PROJECT_ACTIONS.includes(action);
|
|
419
431
|
/** The actions that include `action`: its feature's write action and project:manage. */
|
|
@@ -426,7 +438,7 @@ const GRANT_SCHEMA = zod.object({
|
|
|
426
438
|
const GRANTS_SCHEMA = zod.array(GRANT_SCHEMA).max(100);
|
|
427
439
|
zod.enum([
|
|
428
440
|
"admin",
|
|
429
|
-
"
|
|
441
|
+
"editor",
|
|
430
442
|
"viewer",
|
|
431
443
|
"custom"
|
|
432
444
|
]);
|
|
@@ -572,8 +584,8 @@ const inferAssetType = (options) => {
|
|
|
572
584
|
const createPage = (items, query) => {
|
|
573
585
|
const pageIndex = query.pageIndex ?? 0;
|
|
574
586
|
const pageSize = query.pageSize ?? 10;
|
|
575
|
-
if (!Number.isInteger(pageIndex) || pageIndex < 0) throw
|
|
576
|
-
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw
|
|
587
|
+
if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensVersion.invalidArgument("pageIndex must be a non-negative integer");
|
|
588
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensVersion.invalidArgument("pageSize must be an integer from 1 to 100");
|
|
577
589
|
const start = pageIndex * pageSize;
|
|
578
590
|
return {
|
|
579
591
|
items: items.slice(start, start + pageSize),
|
|
@@ -616,7 +628,7 @@ const parseFilterValue = (raw) => {
|
|
|
616
628
|
return value;
|
|
617
629
|
};
|
|
618
630
|
const readField = (item, path) => path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, item);
|
|
619
|
-
const unsupportedFilter = (expression) =>
|
|
631
|
+
const unsupportedFilter = (expression) => require_CalmLensVersion.invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
|
|
620
632
|
const matchesComparison = (item, comparison) => {
|
|
621
633
|
const match = COMPARISON.exec(comparison.trim());
|
|
622
634
|
if (!match) throw unsupportedFilter(comparison);
|
|
@@ -684,14 +696,14 @@ const describeBinary = (value) => {
|
|
|
684
696
|
};
|
|
685
697
|
const MAX_EVENTS_LIMIT = 100;
|
|
686
698
|
const toFailureError = (failure) => {
|
|
687
|
-
if (failure === "transport-error") return new
|
|
699
|
+
if (failure === "transport-error") return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TRANSPORT_ERROR, {
|
|
688
700
|
message: "Request failed",
|
|
689
701
|
cause: /* @__PURE__ */ new TypeError("Simulated network failure")
|
|
690
702
|
});
|
|
691
703
|
if (failure === "timeout") {
|
|
692
704
|
const cause = /* @__PURE__ */ new Error("The operation timed out.");
|
|
693
705
|
cause.name = "TimeoutError";
|
|
694
|
-
return new
|
|
706
|
+
return new require_CalmLensVersion.CalmLensError(require_CalmLensVersion.SDK_ERROR_CODES.TIMEOUT, {
|
|
695
707
|
message: "Request timed out",
|
|
696
708
|
cause
|
|
697
709
|
});
|
|
@@ -765,9 +777,9 @@ var MockCalmLensServerClient = class {
|
|
|
765
777
|
}
|
|
766
778
|
/** An API error as the real client throws it: status, default message and request ID. */
|
|
767
779
|
apiError(code, options = {}) {
|
|
768
|
-
const definition =
|
|
780
|
+
const definition = require_CalmLensVersion.API_ERROR_DEFINITIONS[code];
|
|
769
781
|
const requestId = `mock-request-${++this.requestCount}`;
|
|
770
|
-
return new
|
|
782
|
+
return new require_CalmLensVersion.CalmLensError(code, {
|
|
771
783
|
message: options.message ?? definition.message,
|
|
772
784
|
status: definition.status,
|
|
773
785
|
details: options.details,
|
|
@@ -779,18 +791,18 @@ var MockCalmLensServerClient = class {
|
|
|
779
791
|
});
|
|
780
792
|
}
|
|
781
793
|
assertValidUploadMimeType(mimeType) {
|
|
782
|
-
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(
|
|
794
|
+
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
783
795
|
message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
|
|
784
796
|
details: { mimeType }
|
|
785
797
|
});
|
|
786
798
|
}
|
|
787
799
|
invalidPayload(message) {
|
|
788
|
-
return this.apiError(
|
|
800
|
+
return this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
789
801
|
}
|
|
790
802
|
async findOwnAsset(assetId) {
|
|
791
803
|
require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
|
|
792
804
|
const asset = await this.databaseStorage.getAsset(assetId);
|
|
793
|
-
if (!asset || asset.projectId !== this.projectId) throw this.apiError(
|
|
805
|
+
if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_FOUND);
|
|
794
806
|
return asset;
|
|
795
807
|
}
|
|
796
808
|
begin(operation, options) {
|
|
@@ -889,7 +901,7 @@ var MockCalmLensServerClient = class {
|
|
|
889
901
|
}
|
|
890
902
|
listEvents(options) {
|
|
891
903
|
const limit = options.limit ?? DEFAULT_EVENTS_LIMIT;
|
|
892
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(
|
|
904
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
|
|
893
905
|
if (!options.after) return {
|
|
894
906
|
events: [],
|
|
895
907
|
cursor: `mock_${this.eventFeed.length}`,
|
|
@@ -897,7 +909,7 @@ var MockCalmLensServerClient = class {
|
|
|
897
909
|
};
|
|
898
910
|
const match = /^mock_(\d+)$/.exec(options.after);
|
|
899
911
|
const position = match ? Number(match[1]) : NaN;
|
|
900
|
-
if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(
|
|
912
|
+
if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_EVENT_CURSOR);
|
|
901
913
|
const events = this.eventFeed.slice(position, position + limit);
|
|
902
914
|
const next = position + events.length;
|
|
903
915
|
return {
|
|
@@ -1025,7 +1037,7 @@ var MockCalmLensServerClient = class {
|
|
|
1025
1037
|
this.begin("assets.uploadPart", options);
|
|
1026
1038
|
const { assetId, part } = options;
|
|
1027
1039
|
require_CalmLensUtils.encodePathSegment(assetId, "Asset ID");
|
|
1028
|
-
if (!part) throw
|
|
1040
|
+
if (!part) throw require_CalmLensVersion.invalidArgument("Part options are required");
|
|
1029
1041
|
return this.withIdempotency("assets.uploadPart", options, async () => {
|
|
1030
1042
|
const asset = await this.findOwnAsset(assetId);
|
|
1031
1043
|
const validation = require_CalmLensUtils.validateAssetPayload(part, {
|
|
@@ -1033,9 +1045,9 @@ var MockCalmLensServerClient = class {
|
|
|
1033
1045
|
isFinal: part.isFinal
|
|
1034
1046
|
});
|
|
1035
1047
|
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
1036
|
-
if (asset.status !== "uploading") throw this.apiError(
|
|
1048
|
+
if (asset.status !== "uploading") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.UPLOAD_NOT_OPEN);
|
|
1037
1049
|
const expectedPartNumber = (asset.uploadPartsReceived ?? 1) + 1;
|
|
1038
|
-
if (part.partNumber !== expectedPartNumber) throw this.apiError(
|
|
1050
|
+
if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
|
|
1039
1051
|
await this.imageStorage.putPart(assetId, part);
|
|
1040
1052
|
const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
|
|
1041
1053
|
if (part.isFinal) this.uploadQueues.delete(assetId);
|
|
@@ -1051,7 +1063,7 @@ var MockCalmLensServerClient = class {
|
|
|
1051
1063
|
this.begin("assets.createSignedUrl", signedUrlOptions);
|
|
1052
1064
|
const { assetId, ...options } = signedUrlOptions;
|
|
1053
1065
|
const asset = await this.findOwnAsset(assetId);
|
|
1054
|
-
if (options.lens && options.transform) throw
|
|
1066
|
+
if (options.lens && options.transform) throw require_CalmLensVersion.invalidArgument("Specify either a lens or a transform, not both");
|
|
1055
1067
|
const expiresInSeconds = options.expiresInSeconds ?? 900;
|
|
1056
1068
|
const resource = options.resource ?? "content";
|
|
1057
1069
|
const transform = require_CalmLensUtils.transformSelector(options.transform);
|
|
@@ -1066,7 +1078,7 @@ var MockCalmLensServerClient = class {
|
|
|
1066
1078
|
const { assetId, folderId } = options;
|
|
1067
1079
|
const asset = await this.findOwnAsset(assetId);
|
|
1068
1080
|
if (folderId !== null && folderId !== void 0) {
|
|
1069
|
-
if (!await this.resolveFolder(folderId)) throw this.apiError(
|
|
1081
|
+
if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
1070
1082
|
}
|
|
1071
1083
|
return this.databaseStorage.updateAsset({
|
|
1072
1084
|
...asset,
|
|
@@ -1077,14 +1089,20 @@ var MockCalmLensServerClient = class {
|
|
|
1077
1089
|
retry: async (options) => {
|
|
1078
1090
|
this.begin("assets.retry", options);
|
|
1079
1091
|
const asset = await this.findOwnAsset(options?.assetId);
|
|
1080
|
-
if (asset.status !== "error") throw this.apiError(
|
|
1081
|
-
if (asset.storageDeletedAt) throw this.apiError(
|
|
1092
|
+
if (asset.status !== "error") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
|
|
1093
|
+
if (asset.storageDeletedAt) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
|
|
1082
1094
|
return this.databaseStorage.updateAsset({
|
|
1083
1095
|
...asset,
|
|
1084
1096
|
status: "pending",
|
|
1085
1097
|
updatedAt: this.now()
|
|
1086
1098
|
});
|
|
1087
1099
|
},
|
|
1100
|
+
/** Waits like the real client, checking the mock's storage for a decision. */
|
|
1101
|
+
waitForDecision: async (options) => {
|
|
1102
|
+
this.begin("assets.waitForDecision", options);
|
|
1103
|
+
const { assetId, ...waitOptions } = options;
|
|
1104
|
+
return require_CalmLensUtils.pollForDecision(() => this.findOwnAsset(assetId), waitOptions, require_CalmLensUtils.sleep);
|
|
1105
|
+
},
|
|
1088
1106
|
deleteContent: async (options) => {
|
|
1089
1107
|
this.begin("assets.deleteContent", options);
|
|
1090
1108
|
const asset = await this.findOwnAsset(options?.assetId);
|
|
@@ -1157,7 +1175,7 @@ var MockCalmLensServerClient = class {
|
|
|
1157
1175
|
await this.findOwnFolder(folderId);
|
|
1158
1176
|
const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
|
|
1159
1177
|
const hasChildren = (this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).some((f) => f.parentId === folderId);
|
|
1160
|
-
if (hasAssets || hasChildren) throw this.apiError(
|
|
1178
|
+
if (hasAssets || hasChildren) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
|
|
1161
1179
|
hasAssets,
|
|
1162
1180
|
hasChildren
|
|
1163
1181
|
} });
|
|
@@ -1172,7 +1190,7 @@ var MockCalmLensServerClient = class {
|
|
|
1172
1190
|
let candidate = await this.findOwnFolder(targetParentId);
|
|
1173
1191
|
const visited = /* @__PURE__ */ new Set();
|
|
1174
1192
|
while (candidate) {
|
|
1175
|
-
if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(
|
|
1193
|
+
if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
|
|
1176
1194
|
visited.add(candidate.id);
|
|
1177
1195
|
candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
|
|
1178
1196
|
}
|
|
@@ -1189,7 +1207,7 @@ var MockCalmLensServerClient = class {
|
|
|
1189
1207
|
}
|
|
1190
1208
|
async ensureLens(options) {
|
|
1191
1209
|
this.begin("ensureLens", options);
|
|
1192
|
-
if (!options?.identifier) throw
|
|
1210
|
+
if (!options?.identifier) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
|
|
1193
1211
|
return this.ensureLensDefinition(options);
|
|
1194
1212
|
}
|
|
1195
1213
|
ensureLensDefinition(options) {
|
|
@@ -1216,7 +1234,7 @@ var MockCalmLensServerClient = class {
|
|
|
1216
1234
|
this.lenses.set(lens.identifier, lens);
|
|
1217
1235
|
return lens;
|
|
1218
1236
|
}
|
|
1219
|
-
throw this.apiError(
|
|
1237
|
+
throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
|
|
1220
1238
|
currentVersion: existing.version,
|
|
1221
1239
|
minimumVersion: existing.version + 1,
|
|
1222
1240
|
requestedVersion: options.version
|
|
@@ -1224,19 +1242,19 @@ var MockCalmLensServerClient = class {
|
|
|
1224
1242
|
}
|
|
1225
1243
|
async ensureLenses(options) {
|
|
1226
1244
|
this.begin("ensureLenses", options);
|
|
1227
|
-
if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw
|
|
1228
|
-
if (options.lenses.some((lens) => !lens?.identifier)) throw
|
|
1245
|
+
if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensVersion.invalidArgument("At least one lens is required");
|
|
1246
|
+
if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensVersion.invalidArgument("Lens identifier is required");
|
|
1229
1247
|
return options.lenses.map((option) => this.ensureLensDefinition(option));
|
|
1230
1248
|
}
|
|
1231
1249
|
/** Replays the first result for a reused idempotency key, like the API. */
|
|
1232
1250
|
async withIdempotency(operation, options, run) {
|
|
1233
1251
|
const key = options?.idempotencyKey;
|
|
1234
1252
|
if (key === void 0) return run();
|
|
1235
|
-
if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(
|
|
1253
|
+
if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
|
|
1236
1254
|
const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
|
|
1237
1255
|
const saved = this.idempotentResults.get(key);
|
|
1238
1256
|
if (saved) {
|
|
1239
|
-
if (saved.fingerprint !== fingerprint) throw this.apiError(
|
|
1257
|
+
if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
|
|
1240
1258
|
return saved.asset;
|
|
1241
1259
|
}
|
|
1242
1260
|
const asset = await run();
|
|
@@ -1251,11 +1269,11 @@ var MockCalmLensServerClient = class {
|
|
|
1251
1269
|
return this.withIdempotency(operation, options, () => this.createAsset(options, false));
|
|
1252
1270
|
}
|
|
1253
1271
|
async createAsset(options, queue) {
|
|
1254
|
-
if (!options || typeof options !== "object") throw
|
|
1272
|
+
if (!options || typeof options !== "object") throw require_CalmLensVersion.invalidArgument("Asset options are required");
|
|
1255
1273
|
if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
|
|
1256
1274
|
if (!options.name) throw this.invalidPayload("Asset name is required");
|
|
1257
1275
|
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(
|
|
1276
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.USE_UPLOAD_PART);
|
|
1259
1277
|
if ("content" in options) {
|
|
1260
1278
|
this.assertValidUploadMimeType(options.content.type);
|
|
1261
1279
|
if (!("plainText" in options.content)) {
|
|
@@ -1272,7 +1290,7 @@ var MockCalmLensServerClient = class {
|
|
|
1272
1290
|
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
1273
1291
|
}
|
|
1274
1292
|
if (options.externalId) {
|
|
1275
|
-
if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(
|
|
1293
|
+
if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
|
|
1276
1294
|
}
|
|
1277
1295
|
const id = this.createId();
|
|
1278
1296
|
const now = this.now();
|
|
@@ -1312,11 +1330,11 @@ var MockCalmLensServerClient = class {
|
|
|
1312
1330
|
async findOwnFolder(folderId) {
|
|
1313
1331
|
require_CalmLensUtils.encodePathSegment(folderId, "Folder ID");
|
|
1314
1332
|
const folder = await this.resolveFolder(folderId);
|
|
1315
|
-
if (!folder) throw this.apiError(
|
|
1333
|
+
if (!folder) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
1316
1334
|
return folder;
|
|
1317
1335
|
}
|
|
1318
1336
|
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(
|
|
1337
|
+
if ((this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).find((f) => f.id !== excludeFolderId && f.parentId === (parentId ?? null) && f.name === name)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
|
|
1320
1338
|
}
|
|
1321
1339
|
getSizeInBytes(options) {
|
|
1322
1340
|
if ("content" in options) {
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { c as invalidArgument, o as CalmLensError, s as SDK_ERROR_CODES, u as API_ERROR_CODES } from "./CalmLensVersion-DK4BMEST.js";
|
|
2
|
+
import { a as stringifyQuery, b as getRejectedUploadMimeTypeError, d as CalmLensTransport, g as validateAssetPayload, h as getAssetPayloadByteLength, i as randomUuid, m as assetToData, v as MAX_IMAGE_UPLOAD_BYTES, x as isRejectedUploadMimeType, y as MIN_MULTIPART_IMAGE_UPLOAD_BYTES } from "./CalmLensUtils-B4i3Z2He.js";
|
|
3
|
+
//#region src/client/CalmLensConnection.ts
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
5
|
+
const DEFAULT_MAX_RETRIES = 2;
|
|
6
|
+
const DEFAULT_MAX_RESPONSE_BYTES = 33554432;
|
|
7
|
+
/**
|
|
8
|
+
* Bytes as a fetch body. They're sent as they are rather than in a Blob: React Native can't
|
|
9
|
+
* build a Blob from bytes, and every fetch accepts a Uint8Array.
|
|
10
|
+
*/
|
|
11
|
+
const toBodyBytes = (value) => value.buffer instanceof ArrayBuffer ? value : Uint8Array.from(value);
|
|
12
|
+
const toRawUploadBody = (payload) => {
|
|
13
|
+
const data = assetToData(payload);
|
|
14
|
+
if (data instanceof Response) {
|
|
15
|
+
if (!data.body) throw new CalmLensError(API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Response upload data must have a body" });
|
|
16
|
+
return data.body;
|
|
17
|
+
}
|
|
18
|
+
if (data instanceof ArrayBuffer || data instanceof Blob) return data;
|
|
19
|
+
if ("getReader" in data) return data;
|
|
20
|
+
return toBodyBytes(data);
|
|
21
|
+
};
|
|
22
|
+
const setOptionalQueryValue = (params, key, value) => {
|
|
23
|
+
if (value !== void 0 && value !== null) params.set(key, typeof value === "string" ? value : JSON.stringify(value));
|
|
24
|
+
};
|
|
25
|
+
const assertValidUploadMimeType = (mimeType) => {
|
|
26
|
+
if (isRejectedUploadMimeType(mimeType)) throw new CalmLensError(API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
27
|
+
message: getRejectedUploadMimeTypeError(mimeType),
|
|
28
|
+
details: { mimeType }
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
const isImageMimeType = (mimeType) => mimeType.toLowerCase().startsWith("image/");
|
|
32
|
+
const invalidPayload = (message) => new CalmLensError(API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
33
|
+
/** Checks upload options before anything is sent. */
|
|
34
|
+
const prepareSubmitAssetOptions = (options) => {
|
|
35
|
+
if (!options || typeof options !== "object") throw invalidArgument("Asset options are required");
|
|
36
|
+
if (!("content" in options) && !("url" in options) && !("part" in options)) throw invalidPayload("Either file content, URL, or multipart part is required");
|
|
37
|
+
if (!options.name) throw invalidPayload("Asset name is required");
|
|
38
|
+
if (!("part" in options) && "isFinal" in options) throw invalidPayload("isFinal is only valid for multipart uploads");
|
|
39
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw new CalmLensError(API_ERROR_CODES.USE_UPLOAD_PART, { message: "Use uploadPart for multipart upload parts after the first part" });
|
|
40
|
+
if ("content" in options) {
|
|
41
|
+
assertValidUploadMimeType(options.content.type);
|
|
42
|
+
if (!("plainText" in options.content)) {
|
|
43
|
+
const validation = validateAssetPayload(options.content);
|
|
44
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if ("part" in options) {
|
|
48
|
+
assertValidUploadMimeType(options.part.type);
|
|
49
|
+
const partSizeInBytes = options.part.sizeInBytes ?? getAssetPayloadByteLength(options.part);
|
|
50
|
+
if (isImageMimeType(options.part.type) && partSizeInBytes !== void 0) {
|
|
51
|
+
if (partSizeInBytes > MAX_IMAGE_UPLOAD_BYTES) throw new CalmLensError(API_ERROR_CODES.MULTIPART_IMAGE_TOO_LARGE, { message: "Multipart image uploads cannot exceed 20MB." });
|
|
52
|
+
if (options.part.isFinal && partSizeInBytes < MIN_MULTIPART_IMAGE_UPLOAD_BYTES) throw new CalmLensError(API_ERROR_CODES.MULTIPART_IMAGE_TOO_SMALL, { message: "Multipart image uploads must total at least 10MB." });
|
|
53
|
+
}
|
|
54
|
+
const validation = validateAssetPayload(options.part, {
|
|
55
|
+
multipart: true,
|
|
56
|
+
isFinal: options.part.isFinal
|
|
57
|
+
});
|
|
58
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
59
|
+
}
|
|
60
|
+
return options;
|
|
61
|
+
};
|
|
62
|
+
const newIdempotencyKey = () => randomUuid();
|
|
63
|
+
/** Text content is small, so it keeps the API deadline; binary files may be arbitrarily large. */
|
|
64
|
+
const isTextPayload = (payload) => "plainText" in payload;
|
|
65
|
+
const isAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id && typeof data.status === "string";
|
|
66
|
+
const isUploadedAsset = (data) => !!data && typeof data === "object" && typeof data.id === "string" && !!data.id;
|
|
67
|
+
const expectUploadedAsset = (data) => {
|
|
68
|
+
if (!isUploadedAsset(data)) throw new CalmLensError(SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
69
|
+
message: "Expected an uploaded asset response",
|
|
70
|
+
status: 200
|
|
71
|
+
});
|
|
72
|
+
return data;
|
|
73
|
+
};
|
|
74
|
+
const expectAsset = (data) => {
|
|
75
|
+
if (!isAsset(data)) throw new CalmLensError(SDK_ERROR_CODES.INVALID_RESPONSE, {
|
|
76
|
+
message: "Expected an asset response",
|
|
77
|
+
status: 200
|
|
78
|
+
});
|
|
79
|
+
return data;
|
|
80
|
+
};
|
|
81
|
+
/** Fills in the default API base URL and drops trailing slashes. */
|
|
82
|
+
const resolveConnectionOptions = (options) => ({
|
|
83
|
+
...options,
|
|
84
|
+
baseUrl: (options.baseUrl ?? `https://api.calmlens.com/v1`).replace(/\/+$/, "")
|
|
85
|
+
});
|
|
86
|
+
/** One client's transport, base URL and deadlines, and the requests both clients make. */
|
|
87
|
+
var CalmLensConnection = class {
|
|
88
|
+
constructor(options, apiKey) {
|
|
89
|
+
this.options = options;
|
|
90
|
+
const resolveFetch = () => {
|
|
91
|
+
const fetchImpl = this.options.fetch ?? globalThis.fetch;
|
|
92
|
+
if (typeof fetchImpl !== "function") throw new CalmLensError(SDK_ERROR_CODES.NO_FETCH, { message: "No fetch implementation is available; pass one as the fetch option" });
|
|
93
|
+
return fetchImpl;
|
|
94
|
+
};
|
|
95
|
+
this.transport = new CalmLensTransport({
|
|
96
|
+
apiKey,
|
|
97
|
+
fetch: ((input, init) => resolveFetch()(input, init)),
|
|
98
|
+
signal: options.signal,
|
|
99
|
+
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES,
|
|
100
|
+
maxResponseBytes: options.maxResponseBytes ?? DEFAULT_MAX_RESPONSE_BYTES
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
get timeoutMs() {
|
|
104
|
+
return this.options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
105
|
+
}
|
|
106
|
+
url(path, query) {
|
|
107
|
+
const search = query ? stringifyQuery(query) : "";
|
|
108
|
+
return `${this.options.baseUrl}${path}${search ? `?${search}` : ""}`;
|
|
109
|
+
}
|
|
110
|
+
/** A JSON request whose deadline covers reading the response. */
|
|
111
|
+
json(method, path, { query, body, headers, idempotencyKey } = {}) {
|
|
112
|
+
return this.transport.json({
|
|
113
|
+
method,
|
|
114
|
+
url: this.url(path, query),
|
|
115
|
+
headers: body === void 0 ? headers : {
|
|
116
|
+
...headers,
|
|
117
|
+
"Content-Type": "application/json"
|
|
118
|
+
},
|
|
119
|
+
body: body === void 0 ? void 0 : JSON.stringify(body),
|
|
120
|
+
idempotencyKey,
|
|
121
|
+
timeoutMs: this.timeoutMs,
|
|
122
|
+
deadline: "body"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
upload(url, payload, headers, idempotencyKey) {
|
|
126
|
+
return this.transport.json({
|
|
127
|
+
method: "POST",
|
|
128
|
+
url,
|
|
129
|
+
headers,
|
|
130
|
+
body: toRawUploadBody(payload),
|
|
131
|
+
idempotencyKey,
|
|
132
|
+
timeoutMs: isTextPayload(payload) ? this.timeoutMs : this.options.uploadTimeoutMs,
|
|
133
|
+
deadline: "body",
|
|
134
|
+
fetch: this.options.uploadFetch
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Uploads file content or a first multipart part to `uploadsPath` (`.../assets`), as raw
|
|
139
|
+
* bytes with the options in the query.
|
|
140
|
+
*/
|
|
141
|
+
submitRawUpload(uploadsPath, options, queue, authHeaders = {}) {
|
|
142
|
+
const query = new URLSearchParams();
|
|
143
|
+
query.set("name", options.name);
|
|
144
|
+
query.set("queue", String(queue));
|
|
145
|
+
setOptionalQueryValue(query, "description", options.description);
|
|
146
|
+
setOptionalQueryValue(query, "processingOptions", options.processingOptions);
|
|
147
|
+
setOptionalQueryValue(query, "visibility", options.visibility);
|
|
148
|
+
setOptionalQueryValue(query, "metadata", options.metadata);
|
|
149
|
+
setOptionalQueryValue(query, "externalId", options.externalId);
|
|
150
|
+
setOptionalQueryValue(query, "externalUrl", options.externalUrl);
|
|
151
|
+
setOptionalQueryValue(query, "folderId", options.folderId);
|
|
152
|
+
setOptionalQueryValue(query, "uploadPresetId", options.uploadPresetId);
|
|
153
|
+
const isMultipart = "part" in options;
|
|
154
|
+
const payload = isMultipart ? options.part : options.content;
|
|
155
|
+
query.set("contentType", payload.type);
|
|
156
|
+
setOptionalQueryValue(query, "sizeInBytes", payload.sizeInBytes ?? getAssetPayloadByteLength(payload));
|
|
157
|
+
if (isMultipart) {
|
|
158
|
+
const part = payload;
|
|
159
|
+
const multipartOptions = options;
|
|
160
|
+
query.set("partNumber", String(part.partNumber ?? 1));
|
|
161
|
+
query.set("isFinal", String(part.isFinal ?? false));
|
|
162
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? multipartOptions.sizeInBytes);
|
|
163
|
+
setOptionalQueryValue(query, "totalSizeInBytes", multipartOptions.totalSizeInBytes);
|
|
164
|
+
}
|
|
165
|
+
return this.upload(this.url(`${uploadsPath}?${query}`), payload, {
|
|
166
|
+
...authHeaders,
|
|
167
|
+
"Content-Type": payload.type,
|
|
168
|
+
"X-CalmLens-Upload": isMultipart ? "part" : "content"
|
|
169
|
+
}, options.idempotencyKey ?? newIdempotencyKey());
|
|
170
|
+
}
|
|
171
|
+
/** Uploads the next part of a multipart upload to `partPath` (`.../assets/{id}/part`). */
|
|
172
|
+
uploadPart(partPath, { part, queue, idempotencyKey }, authHeaders = {}) {
|
|
173
|
+
if (!part) throw invalidArgument("Part options are required");
|
|
174
|
+
const validation = validateAssetPayload(part, {
|
|
175
|
+
multipart: true,
|
|
176
|
+
isFinal: part.isFinal
|
|
177
|
+
});
|
|
178
|
+
if (!validation.ok) throw invalidPayload(validation.error);
|
|
179
|
+
const query = new URLSearchParams({
|
|
180
|
+
partNumber: String(part.partNumber),
|
|
181
|
+
isFinal: String(part.isFinal ?? false)
|
|
182
|
+
});
|
|
183
|
+
if (queue !== void 0) query.set("queue", String(queue));
|
|
184
|
+
setOptionalQueryValue(query, "sizeInBytes", part.sizeInBytes ?? getAssetPayloadByteLength(part));
|
|
185
|
+
return this.upload(this.url(`${partPath}?${query}`), part, {
|
|
186
|
+
...authHeaders,
|
|
187
|
+
"X-CalmLens-Upload": "part"
|
|
188
|
+
}, idempotencyKey ?? newIdempotencyKey());
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
//#endregion
|
|
192
|
+
export { newIdempotencyKey as a, invalidPayload as i, expectAsset as n, prepareSubmitAssetOptions as o, expectUploadedAsset as r, resolveConnectionOptions as s, CalmLensConnection as t };
|