@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.
Files changed (35) hide show
  1. package/README.md +14 -1
  2. package/cjs/CalmLensConnection-DzRDCMOP.js +233 -0
  3. package/cjs/CalmLensUtils-vX5H_vMn.js +699 -0
  4. package/cjs/{CalmLensError-CKwMKV0S.js → CalmLensVersion-DjhsJ1gj.js} +48 -0
  5. package/cjs/{CalmLensWebhooks-BTPxKvcD.js → CalmLensWebhooks-hVquX5L1.js} +9 -9
  6. package/cjs/{WebhookPayload-Cgb32aSH.d.ts → WebhookPayload-B7CgSmI5.d.ts} +4996 -3255
  7. package/cjs/app.d.ts +3 -3
  8. package/cjs/app.js +10 -10
  9. package/cjs/{index-BgmWj3KL.d.ts → index-BywTQbRq.d.ts} +3 -1
  10. package/cjs/index.d.ts +3 -3
  11. package/cjs/index.js +4 -5
  12. package/cjs/server.d.ts +12 -4
  13. package/cjs/server.js +31 -18
  14. package/cjs/testing.d.ts +6 -1
  15. package/cjs/testing.js +91 -73
  16. package/esm/CalmLensConnection-D8VsTdsA.js +192 -0
  17. package/esm/{CalmLensUtils-Cx32j2zt.js → CalmLensUtils-B4i3Z2He.js} +316 -2
  18. package/esm/{CalmLensError-CihGG26K.js → CalmLensVersion-DK4BMEST.js} +25 -1
  19. package/esm/{CalmLensWebhooks-DShmAal4.js → CalmLensWebhooks-DyCoEvpf.js} +2 -2
  20. package/esm/{WebhookPayload-Cgb32aSH.d.ts → WebhookPayload-B7CgSmI5.d.ts} +4996 -3255
  21. package/esm/app.d.ts +3 -3
  22. package/esm/app.js +4 -4
  23. package/esm/{index-BgmWj3KL.d.ts → index-BywTQbRq.d.ts} +3 -1
  24. package/esm/index.d.ts +3 -3
  25. package/esm/index.js +1 -3
  26. package/esm/server.d.ts +12 -4
  27. package/esm/server.js +20 -7
  28. package/esm/testing.d.ts +6 -1
  29. package/esm/testing.js +59 -41
  30. package/package.json +1 -1
  31. package/cjs/CalmLensConnection-CYtBP-vu.js +0 -516
  32. package/cjs/CalmLensUtils-Dg3s4loH.js +0 -367
  33. package/cjs/src-Dsc8Pn6v.js +0 -45
  34. package/esm/CalmLensConnection-wLNy0OIP.js +0 -475
  35. 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 require_CalmLensError = require("./CalmLensError-CKwMKV0S.js");
25
- const require_CalmLensWebhooks = require("./CalmLensWebhooks-BTPxKvcD.js");
26
- const require_CalmLensUtils = require("./CalmLensUtils-Dg3s4loH.js");
24
+ const require_CalmLensVersion = require("./CalmLensVersion-DjhsJ1gj.js");
25
+ const require_CalmLensWebhooks = require("./CalmLensWebhooks-hVquX5L1.js");
26
+ const require_CalmLensUtils = require("./CalmLensUtils-vX5H_vMn.js");
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-sexual-language",
74
+ "sexual-explicit-language",
75
75
  "sexual-solicitation",
76
- "physical-attack",
77
- "violent-threat",
78
- "violent-preparation",
79
- "violent-encouragement",
80
- "injury",
81
- "death",
82
- "graphic",
76
+ "sexual-historical",
77
+ "sexual-documentary",
78
+ "sexual-fictional",
79
+ "sexual-support-prevention",
80
+ "violence-attack",
81
+ "violence-threat",
82
+ "violence-preparation",
83
+ "violence-encouragement",
84
+ "violence-injury",
85
+ "violence-death",
86
+ "violence-graphic",
87
+ "violence-historical",
88
+ "violence-documentary",
89
+ "violence-fictional",
90
+ "violence-support-prevention",
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
- "personal-abuse",
89
- "targeted-intimidation",
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-harassment",
102
+ "harassment-sexual",
92
103
  "harassment-encouragement",
93
- "identity-abuse",
94
- "dehumanization",
95
- "identity-inferiority",
96
- "discriminatory-exclusion",
104
+ "harassment-fictional",
105
+ "hate-abuse",
106
+ "hate-dehumanization",
107
+ "hate-inferiority",
108
+ "hate-exclusion",
97
109
  "hate-threat",
98
- "criminal-demonstration",
99
- "criminal-preparation",
100
- "criminal-request",
101
- "criminal-instructions",
102
- "criminal-promotion",
103
- "historical",
104
- "documentary",
105
- "fictional",
106
- "support-prevention"
110
+ "hate-historical",
111
+ "hate-fictional",
112
+ "criminal-activity-demonstration",
113
+ "criminal-activity-preparation",
114
+ "criminal-activity-request",
115
+ "criminal-activity-instructions",
116
+ "criminal-activity-promotion",
117
+ "criminal-activity-substances",
118
+ "criminal-activity-fictional"
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(["not-supported", "unavailable"]);
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
- "member",
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 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");
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) => require_CalmLensError.invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
631
+ const unsupportedFilter = (expression) => require_CalmLensVersion.invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
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 require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TRANSPORT_ERROR, {
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 require_CalmLensError.CalmLensError(require_CalmLensError.SDK_ERROR_CODES.TIMEOUT, {
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 = require_CalmLensError.API_ERROR_DEFINITIONS[code];
780
+ const definition = require_CalmLensVersion.API_ERROR_DEFINITIONS[code];
769
781
  const requestId = `mock-request-${++this.requestCount}`;
770
- return new require_CalmLensError.CalmLensError(code, {
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(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
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(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
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(require_CalmLensError.API_ERROR_CODES.ASSET_NOT_FOUND);
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(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
904
+ if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
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(require_CalmLensError.API_ERROR_CODES.INVALID_EVENT_CURSOR);
912
+ if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_EVENT_CURSOR);
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 require_CalmLensError.invalidArgument("Part options are required");
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(require_CalmLensError.API_ERROR_CODES.UPLOAD_NOT_OPEN);
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(require_CalmLensError.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
1050
+ if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
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 require_CalmLensError.invalidArgument("Specify either a lens or a transform, not both");
1066
+ if (options.lens && options.transform) throw require_CalmLensVersion.invalidArgument("Specify either a lens or a transform, not both");
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(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_FOUND);
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(require_CalmLensError.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
1081
- if (asset.storageDeletedAt) throw this.apiError(require_CalmLensError.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
1092
+ if (asset.status !== "error") throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
1093
+ if (asset.storageDeletedAt) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
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(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
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(require_CalmLensError.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
1193
+ if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
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 require_CalmLensError.invalidArgument("Lens identifier is required");
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(require_CalmLensError.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
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 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");
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(require_CalmLensError.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
1253
+ if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
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(require_CalmLensError.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
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 require_CalmLensError.invalidArgument("Asset options are required");
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(require_CalmLensError.API_ERROR_CODES.USE_UPLOAD_PART);
1276
+ if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.USE_UPLOAD_PART);
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(require_CalmLensError.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
1293
+ if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
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(require_CalmLensError.API_ERROR_CODES.FOLDER_NOT_FOUND);
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(require_CalmLensError.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
1337
+ if ((this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).find((f) => f.id !== excludeFolderId && f.parentId === (parentId ?? null) && f.name === name)) throw this.apiError(require_CalmLensVersion.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
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 };