@calmlens/sdk 0.9.1 → 0.10.1
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/LICENSE +21 -0
- package/README.md +156 -60
- package/cjs/CalmLensTypes-DjYFaTyh.d.ts +8608 -0
- package/cjs/CalmLensWebhooks-DJnaLpRy.js +741 -0
- package/cjs/index.d.ts +276 -7525
- package/cjs/index.js +672 -1185
- package/cjs/testing.d.ts +83 -8
- package/cjs/testing.js +991 -596
- package/esm/CalmLensTypes-DjYFaTyh.d.ts +8608 -0
- package/esm/CalmLensWebhooks-DLUozliX.js +556 -0
- package/esm/index.d.ts +276 -7525
- package/esm/index.js +658 -1169
- package/esm/testing.d.ts +83 -8
- package/esm/testing.js +960 -592
- package/package.json +24 -18
- package/cjs/CalmLensTypes-CZqyni4p.d.ts +0 -3633
- package/cjs/CalmLensUtils-B6vwJijv.js +0 -657
- package/esm/CalmLensTypes-CZqyni4p.d.ts +0 -3633
- package/esm/CalmLensUtils-O1xk_Ts0.js +0 -492
package/cjs/testing.js
CHANGED
|
@@ -1,7 +1,269 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
13
|
+
get: ((k) => from[k]).bind(null, key),
|
|
14
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
20
|
+
value: mod,
|
|
21
|
+
enumerable: true
|
|
22
|
+
}) : target, mod));
|
|
23
|
+
//#endregion
|
|
24
|
+
const require_CalmLensWebhooks = require("./CalmLensWebhooks-DJnaLpRy.js");
|
|
3
25
|
let zod = require("zod");
|
|
4
|
-
zod =
|
|
26
|
+
zod = __toESM(zod);
|
|
27
|
+
require("zod/v4");
|
|
28
|
+
//#region ../shared/src/ModerationCategoryDefinitions.ts
|
|
29
|
+
const MODERATION_CATEGORY_DEFINITIONS = {
|
|
30
|
+
sexual: {
|
|
31
|
+
summary: "Sexual acts, explicit sexual anatomy, sexual services, sexual assault depictions and clearly erotic material.",
|
|
32
|
+
definition: "covers sexual acts, explicit sexual anatomy, sexual services and clearly erotic material. Descriptions of sexual assault can qualify without erotic intent. Fictional, illustrated and generated depictions can also qualify.",
|
|
33
|
+
exclusions: "Ordinary affection, swimwear, fitness, fashion, non-erotic medical diagrams and general sexual-health education do not qualify on their own. A person accepting money or wearing revealing clothing does not establish sexual content without a qualifying act, display or offer.",
|
|
34
|
+
docsHref: "/docs/guides/moderation-categories#sexual-content"
|
|
35
|
+
},
|
|
36
|
+
violence: {
|
|
37
|
+
summary: "Physical attacks, injury, death, threats against others, and planning or encouraging physical violence.",
|
|
38
|
+
definition: "covers physical attacks, bodily injury, death and threats against others. Accidental or medical injuries and natural death also count; an attacker or intent to harm is not required. It also includes planning, requesting, encouraging or praising physical violence. A depicted or described act can qualify in fiction or news reporting even when the author condemns it.",
|
|
39
|
+
exclusions: "Solely intentional self-injury or suicide belongs under self-harm. Property damage alone, figurative language, abstract discussion of violence, injury or death and merely possessing a weapon do not establish violence. Consensual sport or training without injury or abuse is excluded.",
|
|
40
|
+
docsHref: "/docs/guides/moderation-categories#violence"
|
|
41
|
+
},
|
|
42
|
+
"self-harm": {
|
|
43
|
+
summary: "Intentional self-injury, suicide, preparation, methods or encouragement; text also includes current or recurring self-harm thoughts.",
|
|
44
|
+
definition: "covers intentional self-injury, suicide, current self-harm risk, preparation, methods and encouragement. For text, it also includes current or recurring self-harm thoughts and an explicitly expressed desire to die. Asking for support does not cancel a separate disclosure of current risk or behavior.",
|
|
45
|
+
exclusions: "For images, require evidence of a self-directed act, preparation, harmful methods or encouragement. An injury or covered body does not establish that harm was self-inflicted. General sadness, neutral statistics, prevention, recovery and healed scars alone are excluded.",
|
|
46
|
+
docsHref: "/docs/guides/moderation-categories#self-harm"
|
|
47
|
+
},
|
|
48
|
+
harassment: {
|
|
49
|
+
summary: "Targeted personal abuse, humiliation, bullying or serious intimidation; ordinary criticism and neutral reporting are excluded.",
|
|
50
|
+
definition: "covers personal abuse, humiliation, bullying and serious intimidation directed at a person or identifiable group. The target can be named, implied or defined by an activity or affiliation. A protected identity is not required.",
|
|
51
|
+
exclusions: "Disagreement, ordinary criticism of conduct or competence, untargeted profanity and neutral reporting of abuse do not qualify without a separate personal attack. Criticism can still qualify when it also contains personal degradation.",
|
|
52
|
+
docsHref: "/docs/guides/moderation-categories#harassment-and-bullying"
|
|
53
|
+
},
|
|
54
|
+
hate: {
|
|
55
|
+
summary: "Identity-based abuse, dehumanization, discriminatory exclusion or threats against people because of protected characteristics.",
|
|
56
|
+
definition: "covers attacks on people because of race, ethnicity, nationality, religion, caste, sex, gender identity, sexual orientation or disability. It includes dehumanization, asserted inferiority, discriminatory exclusion, identity-based abuse and threats.",
|
|
57
|
+
exclusions: "Both the protected target and the identity-based attack must be supported. Identity mentions, cultural clothing, religious imagery, criticism of beliefs or an unrelated threat do not establish hate. Neutral reporting, condemnation and opposition to hate are excluded unless a separate attack is expressed or endorsed.",
|
|
58
|
+
docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
|
|
59
|
+
},
|
|
60
|
+
"criminal-activity": {
|
|
61
|
+
summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act.",
|
|
62
|
+
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.",
|
|
63
|
+
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.",
|
|
64
|
+
docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region ../shared/src/ModerationPolicyDimensions.ts
|
|
69
|
+
const MODERATION_QUALIFIERS = [
|
|
70
|
+
"nudity",
|
|
71
|
+
"sexual-activity",
|
|
72
|
+
"explicit-sexual-language",
|
|
73
|
+
"sexual-solicitation",
|
|
74
|
+
"physical-attack",
|
|
75
|
+
"violent-threat",
|
|
76
|
+
"violent-preparation",
|
|
77
|
+
"violent-encouragement",
|
|
78
|
+
"injury",
|
|
79
|
+
"death",
|
|
80
|
+
"graphic",
|
|
81
|
+
"self-harm-thoughts",
|
|
82
|
+
"self-harm-preparation",
|
|
83
|
+
"self-harm-act",
|
|
84
|
+
"self-harm-methods",
|
|
85
|
+
"self-harm-encouragement",
|
|
86
|
+
"personal-abuse",
|
|
87
|
+
"targeted-intimidation",
|
|
88
|
+
"harassment-threat",
|
|
89
|
+
"sexual-harassment",
|
|
90
|
+
"harassment-encouragement",
|
|
91
|
+
"identity-abuse",
|
|
92
|
+
"dehumanization",
|
|
93
|
+
"identity-inferiority",
|
|
94
|
+
"discriminatory-exclusion",
|
|
95
|
+
"hate-threat",
|
|
96
|
+
"criminal-demonstration",
|
|
97
|
+
"criminal-preparation",
|
|
98
|
+
"criminal-request",
|
|
99
|
+
"criminal-instructions",
|
|
100
|
+
"criminal-promotion",
|
|
101
|
+
"historical",
|
|
102
|
+
"documentary",
|
|
103
|
+
"fictional",
|
|
104
|
+
"support-prevention"
|
|
105
|
+
];
|
|
106
|
+
const SHARED_QUALIFIERS = [
|
|
107
|
+
"historical",
|
|
108
|
+
"documentary",
|
|
109
|
+
"fictional",
|
|
110
|
+
"support-prevention"
|
|
111
|
+
];
|
|
112
|
+
[...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region ../../node_modules/zod-meta/esm/index.js
|
|
115
|
+
const createMetaType = (type) => {
|
|
116
|
+
return Object.assign((value) => {
|
|
117
|
+
return {
|
|
118
|
+
type,
|
|
119
|
+
data: value ?? {}
|
|
120
|
+
};
|
|
121
|
+
}, type);
|
|
122
|
+
};
|
|
123
|
+
createMetaType({ id: "docPropertyInfo" });
|
|
124
|
+
//#endregion
|
|
125
|
+
//#region ../shared/src/ZodUtils.ts
|
|
126
|
+
const SIZE_LIMITS = {
|
|
127
|
+
small: 64,
|
|
128
|
+
medium: 200,
|
|
129
|
+
large: 1500,
|
|
130
|
+
xlarge: 1e4,
|
|
131
|
+
token: 1e4
|
|
132
|
+
};
|
|
133
|
+
const saneStringField = (options) => {
|
|
134
|
+
return zod.string().transform((value) => value.trim()).refine((value) => {
|
|
135
|
+
if (options?.minWidth && value.length < options.minWidth) return false;
|
|
136
|
+
return true;
|
|
137
|
+
}, "Not long enough").refine((value) => {
|
|
138
|
+
const sizeLimit = options?.maxLength ?? SIZE_LIMITS[options?.type ?? "medium"];
|
|
139
|
+
return typeof value === "string" && value.length < sizeLimit;
|
|
140
|
+
}, "Too long");
|
|
141
|
+
};
|
|
142
|
+
const CLASSIFICATION_INPUT_TYPE_SCHEMA = zod.enum(["text", "image"]);
|
|
143
|
+
const CLASSIFICATION_CATEGORIES = [
|
|
144
|
+
"sexual",
|
|
145
|
+
"violence",
|
|
146
|
+
"self-harm",
|
|
147
|
+
"harassment",
|
|
148
|
+
"hate",
|
|
149
|
+
"criminal-activity"
|
|
150
|
+
];
|
|
151
|
+
const CLASSIFICATION_CATEGORY_SCHEMA = zod.enum(CLASSIFICATION_CATEGORIES);
|
|
152
|
+
zod.object({
|
|
153
|
+
category: CLASSIFICATION_CATEGORY_SCHEMA,
|
|
154
|
+
displayName: saneStringField(),
|
|
155
|
+
description: saneStringField(),
|
|
156
|
+
supportedInputTypes: zod.array(CLASSIFICATION_INPUT_TYPE_SCHEMA).min(1)
|
|
157
|
+
});
|
|
158
|
+
MODERATION_CATEGORY_DEFINITIONS.sexual.summary, MODERATION_CATEGORY_DEFINITIONS.violence.summary, MODERATION_CATEGORY_DEFINITIONS["self-harm"].summary, MODERATION_CATEGORY_DEFINITIONS.harassment.summary, MODERATION_CATEGORY_DEFINITIONS.hate.summary, MODERATION_CATEGORY_DEFINITIONS["criminal-activity"].summary;
|
|
159
|
+
const LIKELIHOOD_SCHEMA = zod.enum([
|
|
160
|
+
"very-unlikely",
|
|
161
|
+
"unlikely",
|
|
162
|
+
"possible",
|
|
163
|
+
"likely",
|
|
164
|
+
"very-likely"
|
|
165
|
+
]);
|
|
166
|
+
const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum(["not-supported", "unavailable"]);
|
|
167
|
+
const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
|
|
168
|
+
status: zod.literal("evaluated"),
|
|
169
|
+
score: zod.number().min(0).max(1),
|
|
170
|
+
scopeScore: zod.number().min(0).max(1).optional(),
|
|
171
|
+
likelihood: LIKELIHOOD_SCHEMA
|
|
172
|
+
}).strict().refine(({ score, scopeScore }) => scopeScore === void 0 || scopeScore <= score, {
|
|
173
|
+
message: "Only confidence cannot exceed Any confidence",
|
|
174
|
+
path: ["scopeScore"]
|
|
175
|
+
}), zod.object({
|
|
176
|
+
status: zod.literal("not-evaluated"),
|
|
177
|
+
reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA
|
|
178
|
+
}).strict()]));
|
|
179
|
+
const CLASSIFICATION_CATEGORY_REPORT_SCHEMA = zod.discriminatedUnion("status", [
|
|
180
|
+
zod.object({
|
|
181
|
+
status: zod.literal("evaluated"),
|
|
182
|
+
likelihood: LIKELIHOOD_SCHEMA,
|
|
183
|
+
score: zod.number().min(0).max(1),
|
|
184
|
+
qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
|
|
185
|
+
}).strict(),
|
|
186
|
+
zod.object({
|
|
187
|
+
status: zod.literal("partial"),
|
|
188
|
+
likelihood: LIKELIHOOD_SCHEMA,
|
|
189
|
+
score: zod.number().min(0).max(1),
|
|
190
|
+
qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
|
|
191
|
+
}).strict(),
|
|
192
|
+
zod.object({
|
|
193
|
+
status: zod.literal("not-evaluated"),
|
|
194
|
+
reason: CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA,
|
|
195
|
+
qualifiers: CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA.optional()
|
|
196
|
+
}).strict()
|
|
197
|
+
]);
|
|
198
|
+
const CATEGORIES_SCHEMA = zod.object({
|
|
199
|
+
sexual: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
|
|
200
|
+
violence: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
|
|
201
|
+
"self-harm": CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
|
|
202
|
+
harassment: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
|
|
203
|
+
hate: CLASSIFICATION_CATEGORY_REPORT_SCHEMA,
|
|
204
|
+
"criminal-activity": CLASSIFICATION_CATEGORY_REPORT_SCHEMA
|
|
205
|
+
}).strict();
|
|
206
|
+
const CLASSIFICATION_LABEL_SCHEMA = zod.object({
|
|
207
|
+
description: saneStringField(),
|
|
208
|
+
score: zod.number(),
|
|
209
|
+
topicalityScore: zod.number().nullish(),
|
|
210
|
+
likelihood: LIKELIHOOD_SCHEMA,
|
|
211
|
+
knowledgeBaseId: saneStringField().nullish()
|
|
212
|
+
});
|
|
213
|
+
zod.object({
|
|
214
|
+
processedAt: zod.number(),
|
|
215
|
+
categories: CATEGORIES_SCHEMA,
|
|
216
|
+
flagged: zod.boolean(),
|
|
217
|
+
score: zod.number(),
|
|
218
|
+
notes: saneStringField().nullish(),
|
|
219
|
+
media: zod.object({
|
|
220
|
+
durationSeconds: zod.number().positive(),
|
|
221
|
+
frameIntervalSeconds: zod.number().positive().optional(),
|
|
222
|
+
sampledFrames: zod.number().int().nonnegative(),
|
|
223
|
+
audioTranscribed: zod.boolean(),
|
|
224
|
+
segments: zod.array(zod.object({
|
|
225
|
+
source: zod.enum([
|
|
226
|
+
"frame",
|
|
227
|
+
"speech",
|
|
228
|
+
"frame-text",
|
|
229
|
+
"clip"
|
|
230
|
+
]),
|
|
231
|
+
startSeconds: zod.number().nonnegative(),
|
|
232
|
+
endSeconds: zod.number().nonnegative(),
|
|
233
|
+
categories: CATEGORIES_SCHEMA
|
|
234
|
+
}).strict())
|
|
235
|
+
}).strict().optional(),
|
|
236
|
+
labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
|
|
237
|
+
}).strict();
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region ../shared/src/MediaTestTriggers.ts
|
|
240
|
+
const MEDIA_TEST_SIGNALS = [...CLASSIFICATION_CATEGORIES, "violence.graphic"];
|
|
241
|
+
const HEX_WORDS = [
|
|
242
|
+
"zero",
|
|
243
|
+
"one",
|
|
244
|
+
"two",
|
|
245
|
+
"three",
|
|
246
|
+
"four",
|
|
247
|
+
"five",
|
|
248
|
+
"six",
|
|
249
|
+
"seven",
|
|
250
|
+
"eight",
|
|
251
|
+
"nine",
|
|
252
|
+
"alpha",
|
|
253
|
+
"bravo",
|
|
254
|
+
"charlie",
|
|
255
|
+
"delta",
|
|
256
|
+
"echo",
|
|
257
|
+
"foxtrot"
|
|
258
|
+
];
|
|
259
|
+
/** Read this aloud, or render it as large, high-contrast text in a video frame. */
|
|
260
|
+
function generateMediaTestCode(signal, projectId) {
|
|
261
|
+
if (!MEDIA_TEST_SIGNALS.includes(signal)) throw new Error("Unsupported media test signal");
|
|
262
|
+
const suffix = projectId.slice(-8).toLowerCase();
|
|
263
|
+
if (!/^[a-f0-9]{8}$/.test(suffix)) throw new Error("Expected a project ID with an eight-character hexadecimal suffix");
|
|
264
|
+
return `Calm Lens test ${[...suffix].map((character) => HEX_WORDS[Number.parseInt(character, 16)]).join(" ")} ${signal.replace(/[.-]/g, " ")} end test`;
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
5
267
|
//#region ../shared/src/Permissions.ts
|
|
6
268
|
const ACTION_SCHEMA = zod.enum([
|
|
7
269
|
"assets:read",
|
|
@@ -57,11 +319,10 @@ const permits = (grants, action, folderId, folders) => grants.some((grant) => {
|
|
|
57
319
|
let current = folderId;
|
|
58
320
|
const visited = /* @__PURE__ */ new Set();
|
|
59
321
|
while (current && !visited.has(current)) {
|
|
60
|
-
var _folders$find$parentI, _folders$find;
|
|
61
322
|
if (current === grant.folderId) return true;
|
|
62
323
|
if (!grant.includeSubfolders) return false;
|
|
63
324
|
visited.add(current);
|
|
64
|
-
current =
|
|
325
|
+
current = folders.find((folder) => folder.id === current)?.parentId ?? null;
|
|
65
326
|
}
|
|
66
327
|
return false;
|
|
67
328
|
});
|
|
@@ -85,7 +346,7 @@ const getMockApiKeyGrants = (apiKey) => {
|
|
|
85
346
|
if (!apiKey.startsWith("sk_test_")) return [];
|
|
86
347
|
try {
|
|
87
348
|
return GRANTS_SCHEMA.parse(JSON.parse(fromBase64Url(apiKey.slice(8))));
|
|
88
|
-
} catch
|
|
349
|
+
} catch {
|
|
89
350
|
return [];
|
|
90
351
|
}
|
|
91
352
|
};
|
|
@@ -95,50 +356,37 @@ var InMemoryMockImageStorage = class {
|
|
|
95
356
|
constructor() {
|
|
96
357
|
this.content = /* @__PURE__ */ new Map();
|
|
97
358
|
}
|
|
98
|
-
putContent(assetId, content) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
type: content.type
|
|
108
|
-
});
|
|
109
|
-
})();
|
|
359
|
+
async putContent(assetId, content) {
|
|
360
|
+
const validation = require_CalmLensWebhooks.validateAssetPayload(content);
|
|
361
|
+
if (!validation.ok) throw new Error(validation.error);
|
|
362
|
+
const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(content));
|
|
363
|
+
this.content.set(assetId, {
|
|
364
|
+
assetId,
|
|
365
|
+
parts: /* @__PURE__ */ new Map([[1, bytes]]),
|
|
366
|
+
type: content.type
|
|
367
|
+
});
|
|
110
368
|
}
|
|
111
|
-
putPart(assetId, part) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
var _this3 = this;
|
|
133
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
134
|
-
return _this3.content.get(assetId);
|
|
135
|
-
})();
|
|
136
|
-
}
|
|
137
|
-
deleteContent(assetId) {
|
|
138
|
-
var _this4 = this;
|
|
139
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
140
|
-
_this4.content.delete(assetId);
|
|
141
|
-
})();
|
|
369
|
+
async putPart(assetId, part) {
|
|
370
|
+
const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
|
|
371
|
+
multipart: true,
|
|
372
|
+
isFinal: part.isFinal
|
|
373
|
+
});
|
|
374
|
+
if (!validation.ok) throw new Error(validation.error);
|
|
375
|
+
const bytes = await require_CalmLensWebhooks.assetDataToUint8Array(require_CalmLensWebhooks.assetToData(part));
|
|
376
|
+
const existing = this.content.get(assetId) ?? {
|
|
377
|
+
assetId,
|
|
378
|
+
parts: /* @__PURE__ */ new Map(),
|
|
379
|
+
type: part.type
|
|
380
|
+
};
|
|
381
|
+
existing.parts.set(part.partNumber ?? 1, bytes);
|
|
382
|
+
existing.type = part.type ?? existing.type;
|
|
383
|
+
this.content.set(assetId, existing);
|
|
384
|
+
}
|
|
385
|
+
async getContent(assetId) {
|
|
386
|
+
return this.content.get(assetId);
|
|
387
|
+
}
|
|
388
|
+
async deleteContent(assetId) {
|
|
389
|
+
this.content.delete(assetId);
|
|
142
390
|
}
|
|
143
391
|
};
|
|
144
392
|
var InMemoryMockDatabaseStorage = class {
|
|
@@ -146,69 +394,39 @@ var InMemoryMockDatabaseStorage = class {
|
|
|
146
394
|
this.assets = /* @__PURE__ */ new Map();
|
|
147
395
|
this.folders = /* @__PURE__ */ new Map();
|
|
148
396
|
}
|
|
149
|
-
createAsset(asset) {
|
|
150
|
-
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
var _this10 = this;
|
|
183
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
184
|
-
_this10.folders.set(folder.id, folder);
|
|
185
|
-
return folder;
|
|
186
|
-
})();
|
|
187
|
-
}
|
|
188
|
-
updateFolder(folder) {
|
|
189
|
-
var _this11 = this;
|
|
190
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
191
|
-
_this11.folders.set(folder.id, folder);
|
|
192
|
-
return folder;
|
|
193
|
-
})();
|
|
194
|
-
}
|
|
195
|
-
getFolder(folderId) {
|
|
196
|
-
var _this12 = this;
|
|
197
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
198
|
-
return _this12.folders.get(folderId);
|
|
199
|
-
})();
|
|
200
|
-
}
|
|
201
|
-
listFolders(projectId) {
|
|
202
|
-
var _this13 = this;
|
|
203
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
204
|
-
return [..._this13.folders.values()].filter((folder) => folder.projectId === projectId).sort((a, b) => a.name.localeCompare(b.name));
|
|
205
|
-
})();
|
|
206
|
-
}
|
|
207
|
-
deleteFolder(folderId) {
|
|
208
|
-
var _this14 = this;
|
|
209
|
-
return require_CalmLensUtils._asyncToGenerator(function* () {
|
|
210
|
-
_this14.folders.delete(folderId);
|
|
211
|
-
})();
|
|
397
|
+
async createAsset(asset) {
|
|
398
|
+
this.assets.set(asset.id, asset);
|
|
399
|
+
return asset;
|
|
400
|
+
}
|
|
401
|
+
async updateAsset(asset) {
|
|
402
|
+
this.assets.set(asset.id, asset);
|
|
403
|
+
return asset;
|
|
404
|
+
}
|
|
405
|
+
async getAsset(assetId) {
|
|
406
|
+
return this.assets.get(assetId);
|
|
407
|
+
}
|
|
408
|
+
async listAssets(projectId) {
|
|
409
|
+
return [...this.assets.values()].filter((asset) => asset.projectId === projectId);
|
|
410
|
+
}
|
|
411
|
+
async deleteAsset(assetId) {
|
|
412
|
+
this.assets.delete(assetId);
|
|
413
|
+
}
|
|
414
|
+
async createFolder(folder) {
|
|
415
|
+
this.folders.set(folder.id, folder);
|
|
416
|
+
return folder;
|
|
417
|
+
}
|
|
418
|
+
async updateFolder(folder) {
|
|
419
|
+
this.folders.set(folder.id, folder);
|
|
420
|
+
return folder;
|
|
421
|
+
}
|
|
422
|
+
async getFolder(folderId) {
|
|
423
|
+
return this.folders.get(folderId);
|
|
424
|
+
}
|
|
425
|
+
async listFolders(projectId) {
|
|
426
|
+
return [...this.folders.values()].filter((folder) => folder.projectId === projectId).sort((a, b) => a.name.localeCompare(b.name));
|
|
427
|
+
}
|
|
428
|
+
async deleteFolder(folderId) {
|
|
429
|
+
this.folders.delete(folderId);
|
|
212
430
|
}
|
|
213
431
|
};
|
|
214
432
|
//#endregion
|
|
@@ -235,62 +453,164 @@ const inferAssetType = (options) => {
|
|
|
235
453
|
return "file";
|
|
236
454
|
};
|
|
237
455
|
const createPage = (items, query) => {
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
456
|
+
const pageIndex = query.pageIndex ?? 0;
|
|
457
|
+
const pageSize = query.pageSize ?? 10;
|
|
458
|
+
if (!Number.isInteger(pageIndex) || pageIndex < 0) throw require_CalmLensWebhooks.invalidArgument("pageIndex must be a non-negative integer");
|
|
459
|
+
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) throw require_CalmLensWebhooks.invalidArgument("pageSize must be an integer from 1 to 100");
|
|
241
460
|
const start = pageIndex * pageSize;
|
|
242
461
|
return {
|
|
243
462
|
items: items.slice(start, start + pageSize),
|
|
244
463
|
total: items.length,
|
|
245
464
|
pageIndex,
|
|
246
465
|
pageSize,
|
|
247
|
-
ordering: [],
|
|
466
|
+
ordering: query.ordering ?? [],
|
|
248
467
|
hasMore: start + pageSize < items.length
|
|
249
468
|
};
|
|
250
469
|
};
|
|
470
|
+
const compareValues = (left, right) => {
|
|
471
|
+
if (left === right) return 0;
|
|
472
|
+
if (left === void 0 || left === null) return -1;
|
|
473
|
+
if (right === void 0 || right === null) return 1;
|
|
474
|
+
if (typeof left === "number" && typeof right === "number") return left - right;
|
|
475
|
+
return String(left).localeCompare(String(right));
|
|
476
|
+
};
|
|
477
|
+
/** Sorts like the API's `ordering`; without one, newest first. */
|
|
478
|
+
const orderItems = (items, ordering) => {
|
|
479
|
+
const rules = ordering?.length ? ordering : [{
|
|
480
|
+
field: "createdAt",
|
|
481
|
+
direction: "DESC"
|
|
482
|
+
}];
|
|
483
|
+
return [...items].sort((a, b) => {
|
|
484
|
+
for (const rule of rules) {
|
|
485
|
+
const result = compareValues(a[rule.field], b[rule.field]);
|
|
486
|
+
if (result !== 0) return rule.direction === "DESC" ? -result : result;
|
|
487
|
+
}
|
|
488
|
+
return 0;
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
const COMPARISON = /^([A-Za-z][\w.]*)(==|!=|=in=|=out=|>=|<=|=gt=|=ge=|=lt=|=le=|>|<)(.+)$/;
|
|
492
|
+
const parseFilterValue = (raw) => {
|
|
493
|
+
const value = raw.trim();
|
|
494
|
+
if (/^".*"$/.test(value) || /^'.*'$/.test(value)) return value.slice(1, -1);
|
|
495
|
+
if (value === "null") return null;
|
|
496
|
+
if (value === "true") return true;
|
|
497
|
+
if (value === "false") return false;
|
|
498
|
+
if (value !== "" && !Number.isNaN(Number(value))) return Number(value);
|
|
499
|
+
return value;
|
|
500
|
+
};
|
|
501
|
+
const readField = (item, path) => path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, item);
|
|
502
|
+
const unsupportedFilter = (expression) => require_CalmLensWebhooks.invalidArgument(`MockCalmLensClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
|
|
503
|
+
const matchesComparison = (item, comparison) => {
|
|
504
|
+
const match = COMPARISON.exec(comparison.trim());
|
|
505
|
+
if (!match) throw unsupportedFilter(comparison);
|
|
506
|
+
const [, field, operator, rawValue] = match;
|
|
507
|
+
const actual = readField(item, field);
|
|
508
|
+
const isList = operator === "=in=" || operator === "=out=";
|
|
509
|
+
if (isList && !/^\(.*\)$/.test(rawValue.trim())) throw unsupportedFilter(comparison);
|
|
510
|
+
const values = isList ? rawValue.trim().slice(1, -1).split(",").map(parseFilterValue) : [parseFilterValue(rawValue)];
|
|
511
|
+
const equals = (expected) => Array.isArray(actual) ? actual.includes(expected) : expected === null ? actual === null || actual === void 0 : actual === expected;
|
|
512
|
+
switch (operator) {
|
|
513
|
+
case "==": return equals(values[0]);
|
|
514
|
+
case "!=": return !equals(values[0]);
|
|
515
|
+
case "=in=": return values.some(equals);
|
|
516
|
+
case "=out=": return !values.some(equals);
|
|
517
|
+
case ">":
|
|
518
|
+
case "=gt=": return compareValues(actual, values[0]) > 0;
|
|
519
|
+
case ">=":
|
|
520
|
+
case "=ge=": return compareValues(actual, values[0]) >= 0;
|
|
521
|
+
case "<":
|
|
522
|
+
case "=lt=": return compareValues(actual, values[0]) < 0;
|
|
523
|
+
default: return compareValues(actual, values[0]) <= 0;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
/** Splits on `separator` outside quotes and parentheses; grouping parentheses are refused. */
|
|
527
|
+
const splitTopLevel = (text, separator) => {
|
|
528
|
+
const parts = [];
|
|
529
|
+
let depth = 0;
|
|
530
|
+
let quote = null;
|
|
531
|
+
let current = "";
|
|
532
|
+
for (const char of text) {
|
|
533
|
+
if (quote) {
|
|
534
|
+
if (char === quote) quote = null;
|
|
535
|
+
} else if (char === "\"" || char === "'") quote = char;
|
|
536
|
+
else if (char === "(") depth++;
|
|
537
|
+
else if (char === ")") depth--;
|
|
538
|
+
else if (char === separator && depth === 0) {
|
|
539
|
+
parts.push(current);
|
|
540
|
+
current = "";
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
current += char;
|
|
544
|
+
}
|
|
545
|
+
parts.push(current);
|
|
546
|
+
return parts;
|
|
547
|
+
};
|
|
548
|
+
/**
|
|
549
|
+
* Applies an RSQL `filter` like the API for simple expressions: comparisons joined by `;`
|
|
550
|
+
* (and) and `,` (or). Anything it can't evaluate throws instead of returning wrong results.
|
|
551
|
+
*/
|
|
552
|
+
const filterAssets = (items, filter) => {
|
|
553
|
+
if (!filter) return items;
|
|
554
|
+
const alternatives = splitTopLevel(filter, ",").map((group) => splitTopLevel(group, ";"));
|
|
555
|
+
for (const condition of alternatives.flat()) if (condition.trim().startsWith("(")) throw unsupportedFilter(condition);
|
|
556
|
+
return items.filter((item) => alternatives.some((conditions) => conditions.every((condition) => matchesComparison(item, condition))));
|
|
557
|
+
};
|
|
251
558
|
//#endregion
|
|
252
559
|
//#region src/testing/MockCalmLensClient.ts
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
if (require_CalmLensUtils.isRejectedUploadMimeType(mimeType)) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
261
|
-
message: require_CalmLensUtils.getRejectedUploadMimeTypeError(mimeType),
|
|
262
|
-
details: { mimeType }
|
|
263
|
-
});
|
|
560
|
+
const DEFAULT_EVENTS_LIMIT = 50;
|
|
561
|
+
/** Stands in for binary payloads when comparing idempotent requests, like the API's size check. */
|
|
562
|
+
const describeBinary = (value) => {
|
|
563
|
+
if (value instanceof Blob) return `[blob:${value.size}]`;
|
|
564
|
+
if (value instanceof ArrayBuffer) return `[bytes:${value.byteLength}]`;
|
|
565
|
+
if (ArrayBuffer.isView(value)) return `[bytes:${value.byteLength}]`;
|
|
566
|
+
return value;
|
|
264
567
|
};
|
|
568
|
+
const MAX_EVENTS_LIMIT = 100;
|
|
265
569
|
const toFailureError = (failure) => {
|
|
266
|
-
if (failure === "transport-error") return
|
|
570
|
+
if (failure === "transport-error") return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TRANSPORT_ERROR, {
|
|
267
571
|
message: "Request failed",
|
|
268
572
|
cause: /* @__PURE__ */ new TypeError("Simulated network failure")
|
|
269
573
|
});
|
|
270
|
-
if (failure === "timeout")
|
|
271
|
-
|
|
272
|
-
cause
|
|
273
|
-
|
|
574
|
+
if (failure === "timeout") {
|
|
575
|
+
const cause = /* @__PURE__ */ new Error("The operation timed out.");
|
|
576
|
+
cause.name = "TimeoutError";
|
|
577
|
+
return new require_CalmLensWebhooks.CalmLensError(require_CalmLensWebhooks.SDK_ERROR_CODES.TIMEOUT, {
|
|
578
|
+
message: "Request timed out",
|
|
579
|
+
cause
|
|
580
|
+
});
|
|
581
|
+
}
|
|
274
582
|
return failure;
|
|
275
583
|
};
|
|
584
|
+
/**
|
|
585
|
+
* An in-memory stand-in for `CalmLensClient` with the same methods, validation and error
|
|
586
|
+
* codes. API errors carry the HTTP status, a `requestId` and response headers like the real
|
|
587
|
+
* client's, so error handling can be tested without a network.
|
|
588
|
+
*/
|
|
276
589
|
var MockCalmLensClient = class {
|
|
277
590
|
/** Accepts the same options as `CalmLensClient` (validated the same way), plus `mockOptions`. */
|
|
278
591
|
constructor(options) {
|
|
279
|
-
var _options$mockOptions$, _options$mockOptions, _options$mockOptions$2, _options$mockOptions2, _options$mockOptions$3, _options$mockOptions3, _options$mockOptions$4, _options$mockOptions4, _options$mockOptions$5, _options$mockOptions5, _options$mockOptions$6, _options$mockOptions6, _options$mockOptions$7, _options$mockOptions7, _options$cdnBaseUrl;
|
|
280
|
-
this.options = options;
|
|
281
592
|
this.lenses = /* @__PURE__ */ new Map();
|
|
593
|
+
this.eventFeed = [];
|
|
594
|
+
this.uploadQueues = /* @__PURE__ */ new Map();
|
|
595
|
+
this.idempotentResults = /* @__PURE__ */ new Map();
|
|
596
|
+
this.requestCount = 0;
|
|
282
597
|
this.pendingFailures = [];
|
|
283
598
|
this.calls = [];
|
|
284
|
-
|
|
285
|
-
this.
|
|
286
|
-
this.
|
|
287
|
-
this.
|
|
288
|
-
this.
|
|
289
|
-
this.
|
|
290
|
-
this.
|
|
599
|
+
require_CalmLensWebhooks.validateClientOptions(options);
|
|
600
|
+
this.projectId = options.projectId;
|
|
601
|
+
this.databaseStorage = options.mockOptions?.databaseStorage ?? new InMemoryMockDatabaseStorage();
|
|
602
|
+
this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
|
|
603
|
+
this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
|
|
604
|
+
this.now = options.mockOptions?.now ?? Date.now;
|
|
605
|
+
this.queuedStatus = options.mockOptions?.queuedStatus ?? "pending";
|
|
606
|
+
this.analyzedStatus = options.mockOptions?.analyzedStatus ?? "approved";
|
|
291
607
|
this.apiKeyGrants = getMockApiKeyGrants(options.apiKey);
|
|
292
|
-
this.signedUrlBaseUrl =
|
|
293
|
-
this.cdnBaseUrl = (
|
|
608
|
+
this.signedUrlBaseUrl = options.mockOptions?.signedUrlBaseUrl ?? "mock://calmlens";
|
|
609
|
+
this.cdnBaseUrl = (options.cdnBaseUrl ?? "mock://calmlens/v1").replace(/\/+$/, "");
|
|
610
|
+
}
|
|
611
|
+
/** Keeps the API key out of `console.log` and `util.inspect`. */
|
|
612
|
+
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
613
|
+
return `MockCalmLensClient { projectId: ${JSON.stringify(this.projectId)} }`;
|
|
294
614
|
}
|
|
295
615
|
/**
|
|
296
616
|
* Makes the next call (or the next call to `operation`) throw `failure` instead of running,
|
|
@@ -306,14 +626,54 @@ var MockCalmLensClient = class {
|
|
|
306
626
|
callsTo(operation) {
|
|
307
627
|
return this.calls.filter((call) => call.operation === operation);
|
|
308
628
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
629
|
+
/**
|
|
630
|
+
* Adds an event to the feed `events.list` reads, as if CalmLens had recorded it. Returns the
|
|
631
|
+
* stored event; pass it to `toWebhookEventPayload` to build a matching webhook body.
|
|
632
|
+
*/
|
|
633
|
+
enqueueEvent(input) {
|
|
634
|
+
const now = this.now();
|
|
635
|
+
const event = {
|
|
636
|
+
id: this.createId(),
|
|
637
|
+
projectId: this.projectId,
|
|
638
|
+
severity: "low",
|
|
639
|
+
title: input.type,
|
|
640
|
+
facts: [],
|
|
641
|
+
createdAt: now,
|
|
642
|
+
updatedAt: now,
|
|
643
|
+
...input
|
|
644
|
+
};
|
|
645
|
+
this.eventFeed.push(event);
|
|
646
|
+
return event;
|
|
647
|
+
}
|
|
648
|
+
/** An API error as the real client throws it: status, default message and request ID. */
|
|
649
|
+
apiError(code, options = {}) {
|
|
650
|
+
const definition = require_CalmLensWebhooks.API_ERROR_DEFINITIONS[code];
|
|
651
|
+
const requestId = `mock-request-${++this.requestCount}`;
|
|
652
|
+
return new require_CalmLensWebhooks.CalmLensError(code, {
|
|
653
|
+
message: options.message ?? definition.message,
|
|
654
|
+
status: definition.status,
|
|
655
|
+
details: options.details,
|
|
656
|
+
requestId,
|
|
657
|
+
headers: new Headers({
|
|
658
|
+
"content-type": "application/json",
|
|
659
|
+
"x-request-id": requestId
|
|
660
|
+
})
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
assertValidUploadMimeType(mimeType) {
|
|
664
|
+
if (require_CalmLensWebhooks.isRejectedUploadMimeType(mimeType)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_MIME_TYPE, {
|
|
665
|
+
message: require_CalmLensWebhooks.getRejectedUploadMimeTypeError(mimeType),
|
|
666
|
+
details: { mimeType }
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
invalidPayload(message) {
|
|
670
|
+
return this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message });
|
|
671
|
+
}
|
|
672
|
+
async findOwnAsset(assetId) {
|
|
673
|
+
require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
|
|
674
|
+
const asset = await this.databaseStorage.getAsset(assetId);
|
|
675
|
+
if (!asset || asset.projectId !== this.projectId) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_NOT_FOUND);
|
|
676
|
+
return asset;
|
|
317
677
|
}
|
|
318
678
|
begin(operation, options) {
|
|
319
679
|
this.calls.push({
|
|
@@ -326,519 +686,554 @@ var MockCalmLensClient = class {
|
|
|
326
686
|
throw toFailureError(pending.failure);
|
|
327
687
|
}
|
|
328
688
|
}
|
|
689
|
+
jsonResponse(status, body, url) {
|
|
690
|
+
const requestId = `mock-request-${++this.requestCount}`;
|
|
691
|
+
const response = new Response(JSON.stringify(body), {
|
|
692
|
+
status,
|
|
693
|
+
headers: {
|
|
694
|
+
"content-type": "application/json",
|
|
695
|
+
"x-request-id": requestId
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
Object.defineProperty(response, "url", { value: url });
|
|
699
|
+
return response;
|
|
700
|
+
}
|
|
329
701
|
get cdn() {
|
|
330
|
-
var _this2 = this;
|
|
331
702
|
return {
|
|
332
|
-
getAssetUrl: (
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const resource = (_options$resource = options.resource) !== null && _options$resource !== void 0 ? _options$resource : "content";
|
|
337
|
-
const url = `${this.cdnBaseUrl.replace(/\/$/, "")}/assets/${encodeURIComponent(this.options.projectId)}/${require_CalmLensUtils.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
|
|
338
|
-
return require_CalmLensUtils.appendCdnSelector(url, options);
|
|
703
|
+
getAssetUrl: ({ assetIdentifier, ...options }) => {
|
|
704
|
+
const resource = options.resource ?? "content";
|
|
705
|
+
const url = `${this.cdnBaseUrl}/assets/${encodeURIComponent(this.projectId)}/${require_CalmLensWebhooks.encodePathSegment(assetIdentifier, "Asset identifier")}/${resource}`;
|
|
706
|
+
return require_CalmLensWebhooks.appendCdnSelector(url, options);
|
|
339
707
|
},
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
708
|
+
/**
|
|
709
|
+
* Serves stored content like the CDN: `200` with `Content-Type`, `Content-Length`,
|
|
710
|
+
* `ETag` and `X-Request-Id`, or a JSON error body with `404`/`403`. Never throws for
|
|
711
|
+
* HTTP errors, like the real client.
|
|
712
|
+
*/
|
|
713
|
+
serve: async (serveOptions) => {
|
|
714
|
+
this.begin("cdn.serve", serveOptions);
|
|
715
|
+
const { assetIdentifier, init, ...options } = serveOptions;
|
|
716
|
+
const url = this.cdn.getAssetUrl({
|
|
717
|
+
assetIdentifier,
|
|
718
|
+
...options
|
|
719
|
+
});
|
|
720
|
+
const asset = (await this.databaseStorage.listAssets(this.projectId)).find((asset) => asset.id === assetIdentifier || asset.externalId === assetIdentifier);
|
|
721
|
+
const notFound = () => this.jsonResponse(404, {
|
|
722
|
+
code: "asset/not-found",
|
|
723
|
+
message: "Asset not found"
|
|
724
|
+
}, url);
|
|
725
|
+
if (!asset) return notFound();
|
|
726
|
+
if (asset.visibility !== "public" && !permits(this.apiKeyGrants, "assets:download", asset.folderId ?? null, await this.databaseStorage.listFolders?.(this.projectId) ?? [])) return this.jsonResponse(403, {
|
|
727
|
+
code: "asset/api-key-missing-permission",
|
|
728
|
+
message: "This API key needs assets:download permission for this asset’s folder."
|
|
729
|
+
}, url);
|
|
730
|
+
const storedContent = await this.imageStorage.getContent(asset.id);
|
|
731
|
+
if (!storedContent) return notFound();
|
|
732
|
+
const parts = [...storedContent.parts.entries()].sort(([left], [right]) => left - right).map(([, value]) => value);
|
|
733
|
+
const byteLength = parts.reduce((sum, value) => sum + value.byteLength, 0);
|
|
734
|
+
const bytes = new Uint8Array(byteLength);
|
|
735
|
+
let offset = 0;
|
|
736
|
+
for (const part of parts) {
|
|
737
|
+
bytes.set(part, offset);
|
|
738
|
+
offset += part.byteLength;
|
|
739
|
+
}
|
|
740
|
+
const requestId = `mock-request-${++this.requestCount}`;
|
|
741
|
+
const isHead = init?.method?.toUpperCase() === "HEAD";
|
|
742
|
+
const response = new Response(isHead ? null : bytes, {
|
|
743
|
+
status: 200,
|
|
744
|
+
headers: {
|
|
745
|
+
"Content-Type": storedContent.type ?? "application/octet-stream",
|
|
746
|
+
"Content-Length": String(byteLength),
|
|
747
|
+
"Cache-Control": asset.visibility === "public" ? "public, max-age=31536000" : "private, no-store",
|
|
748
|
+
ETag: `"${asset.id}-${asset.updatedAt}"`,
|
|
749
|
+
"X-Request-Id": requestId,
|
|
750
|
+
"X-Mock-CalmLens-CDN-URL": url
|
|
364
751
|
}
|
|
365
|
-
return new Response(bytes, {
|
|
366
|
-
status: 200,
|
|
367
|
-
headers: {
|
|
368
|
-
"Content-Type": (_storedContent$type = storedContent.type) !== null && _storedContent$type !== void 0 ? _storedContent$type : "application/octet-stream",
|
|
369
|
-
"X-Mock-CalmLens-CDN-URL": _this2.cdn.getAssetUrl(require_CalmLensUtils._objectSpread2({ assetIdentifier }, options))
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
752
|
});
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}()
|
|
753
|
+
Object.defineProperty(response, "url", { value: url });
|
|
754
|
+
return response;
|
|
755
|
+
}
|
|
377
756
|
};
|
|
378
757
|
}
|
|
379
758
|
get events() {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
759
|
+
return {
|
|
760
|
+
/**
|
|
761
|
+
* Reads the mock feed like the API: without `after`, no events and a cursor for "now";
|
|
762
|
+
* with `after`, up to `limit` events added since, oldest first. Add events with
|
|
763
|
+
* `enqueueEvent`.
|
|
764
|
+
*/
|
|
765
|
+
list: async (options = {}) => {
|
|
766
|
+
this.begin("events.list", options);
|
|
767
|
+
return this.listEvents(options);
|
|
768
|
+
},
|
|
769
|
+
pages: (options = {}) => this.iterateEventPages(options)
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
listEvents(options) {
|
|
773
|
+
const limit = options.limit ?? DEFAULT_EVENTS_LIMIT;
|
|
774
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > MAX_EVENTS_LIMIT) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: `limit must be an integer from 1 to ${MAX_EVENTS_LIMIT}` });
|
|
775
|
+
if (!options.after) return {
|
|
776
|
+
events: [],
|
|
777
|
+
cursor: `mock_${this.eventFeed.length}`,
|
|
778
|
+
hasMore: false
|
|
779
|
+
};
|
|
780
|
+
const match = /^mock_(\d+)$/.exec(options.after);
|
|
781
|
+
const position = match ? Number(match[1]) : NaN;
|
|
782
|
+
if (!Number.isSafeInteger(position) || position > this.eventFeed.length) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_EVENT_CURSOR);
|
|
783
|
+
const events = this.eventFeed.slice(position, position + limit);
|
|
784
|
+
const next = position + events.length;
|
|
785
|
+
return {
|
|
786
|
+
events,
|
|
787
|
+
cursor: `mock_${next}`,
|
|
788
|
+
hasMore: next < this.eventFeed.length
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
async *iterateEventPages(options) {
|
|
792
|
+
let after = options.after;
|
|
793
|
+
for (;;) {
|
|
794
|
+
const page = await this.events.list({
|
|
795
|
+
...options,
|
|
796
|
+
after
|
|
392
797
|
});
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
798
|
+
yield page;
|
|
799
|
+
if (!page.hasMore || page.cursor === after) return;
|
|
800
|
+
after = page.cursor;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
async *iteratePages(listPage) {
|
|
804
|
+
for (let pageIndex = 0;; pageIndex++) {
|
|
805
|
+
const page = await listPage(pageIndex);
|
|
806
|
+
yield* page.items;
|
|
807
|
+
if (!page.hasMore || page.items.length === 0) return;
|
|
808
|
+
}
|
|
397
809
|
}
|
|
398
810
|
get text() {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
return _this4.moderateAsset(require_CalmLensUtils.toTextAssetOptions(options));
|
|
404
|
-
});
|
|
405
|
-
return function moderate(_x2) {
|
|
406
|
-
return _ref4.apply(this, arguments);
|
|
407
|
-
};
|
|
408
|
-
}() };
|
|
811
|
+
return { moderate: async (options) => {
|
|
812
|
+
this.begin("text.moderate", options);
|
|
813
|
+
return this.moderateAsset(require_CalmLensWebhooks.toTextAssetOptions(options), "text.moderate");
|
|
814
|
+
} };
|
|
409
815
|
}
|
|
410
816
|
get assets() {
|
|
411
|
-
var _this5 = this;
|
|
412
817
|
return {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const assets = yield _this5.databaseStorage.listAssets(_this5.options.projectId);
|
|
444
|
-
return createPage(assets.filter((asset) => asset.parentId === assetId), require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, options), {}, { pageIndex: (_options$pageIndex2 = options.pageIndex) !== null && _options$pageIndex2 !== void 0 ? _options$pageIndex2 : 0 }));
|
|
445
|
-
});
|
|
446
|
-
return function listChildren() {
|
|
447
|
-
return _ref7.apply(this, arguments);
|
|
818
|
+
/**
|
|
819
|
+
* Matches `query` (case-insensitively) against each asset's name, ID, external ID,
|
|
820
|
+
* description and tags, best match first. Mock scores are 1 for an exact match and 0.5
|
|
821
|
+
* otherwise; `mode` is accepted but there is no semantic search.
|
|
822
|
+
*/
|
|
823
|
+
search: async (options) => {
|
|
824
|
+
this.begin("assets.search", options);
|
|
825
|
+
const query = typeof options?.query === "string" ? options.query.trim() : "";
|
|
826
|
+
if (!query || query.length > 200) throw this.invalidPayload("query must be 1 to 200 characters");
|
|
827
|
+
const limit = options.limit ?? 10;
|
|
828
|
+
const needle = query.toLowerCase();
|
|
829
|
+
const fields = [
|
|
830
|
+
"name",
|
|
831
|
+
"id",
|
|
832
|
+
"externalId",
|
|
833
|
+
"description"
|
|
834
|
+
];
|
|
835
|
+
return {
|
|
836
|
+
query,
|
|
837
|
+
items: (await this.databaseStorage.listAssets(this.projectId)).map((asset) => {
|
|
838
|
+
const matchedFields = [];
|
|
839
|
+
for (const field of fields) if (String(asset[field] ?? "").toLowerCase().includes(needle)) matchedFields.push(field);
|
|
840
|
+
if (asset.tags?.some((tag) => tag.toLowerCase().includes(needle))) matchedFields.push("tags");
|
|
841
|
+
return {
|
|
842
|
+
asset,
|
|
843
|
+
score: fields.some((field) => String(asset[field] ?? "").toLowerCase() === needle) ? 1 : .5,
|
|
844
|
+
matchedFields
|
|
845
|
+
};
|
|
846
|
+
}).filter((result) => result.matchedFields.length > 0).sort((a, b) => b.score - a.score).slice(0, limit),
|
|
847
|
+
semanticStatus: "disabled"
|
|
448
848
|
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
849
|
+
},
|
|
850
|
+
/** Lists assets with the real client's paging, `ordering`, `folderId` and `filter`. */
|
|
851
|
+
list: async (options = {}) => {
|
|
852
|
+
this.begin("assets.list", options);
|
|
853
|
+
let assets = await this.databaseStorage.listAssets(this.projectId);
|
|
854
|
+
if (options.folderId !== void 0) assets = assets.filter((asset) => (asset.folderId ?? null) === options.folderId);
|
|
855
|
+
assets = filterAssets(assets, options.filter);
|
|
856
|
+
return createPage(orderItems(assets, options.ordering), {
|
|
857
|
+
...options,
|
|
858
|
+
pageIndex: options.pageIndex ?? 0
|
|
459
859
|
});
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
860
|
+
},
|
|
861
|
+
iterate: (options = {}) => this.iteratePages((pageIndex) => this.assets.list({
|
|
862
|
+
...options,
|
|
863
|
+
pageIndex
|
|
864
|
+
})),
|
|
865
|
+
retrieve: async (options) => {
|
|
866
|
+
this.begin("assets.retrieve", options);
|
|
867
|
+
return this.findOwnAsset(options?.assetId);
|
|
868
|
+
},
|
|
869
|
+
listChildren: async (childrenOptions = {}) => {
|
|
870
|
+
this.begin("assets.listChildren", childrenOptions);
|
|
871
|
+
const { assetId, ...options } = childrenOptions;
|
|
872
|
+
require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
|
|
873
|
+
const assets = await this.databaseStorage.listAssets(this.projectId);
|
|
874
|
+
return createPage(orderItems(assets.filter((asset) => asset.parentId === assetId), options.ordering), {
|
|
875
|
+
...options,
|
|
876
|
+
pageIndex: options.pageIndex ?? 0
|
|
468
877
|
});
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
878
|
+
},
|
|
879
|
+
iterateChildren: (options) => {
|
|
880
|
+
require_CalmLensWebhooks.encodePathSegment(options?.assetId, "Asset ID");
|
|
881
|
+
return this.iteratePages((pageIndex) => this.assets.listChildren({
|
|
882
|
+
...options,
|
|
883
|
+
pageIndex
|
|
884
|
+
}));
|
|
885
|
+
},
|
|
886
|
+
update: async (options) => {
|
|
887
|
+
this.begin("assets.update", options);
|
|
888
|
+
const { assetId, ...body } = options;
|
|
889
|
+
const asset = await this.findOwnAsset(assetId);
|
|
890
|
+
const changes = Object.fromEntries(Object.entries(body).filter(([, value]) => value !== void 0));
|
|
891
|
+
return this.databaseStorage.updateAsset({
|
|
892
|
+
...asset,
|
|
893
|
+
...changes,
|
|
894
|
+
updatedAt: this.now()
|
|
477
895
|
});
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
896
|
+
},
|
|
897
|
+
create: async (options) => {
|
|
898
|
+
this.begin("assets.create", options);
|
|
899
|
+
return this.withIdempotency("assets.create", options, () => this.createAsset(options, true));
|
|
900
|
+
},
|
|
901
|
+
moderate: async (options) => {
|
|
902
|
+
this.begin("assets.moderate", options);
|
|
903
|
+
return this.moderateAsset(options, "assets.moderate");
|
|
904
|
+
},
|
|
905
|
+
/** Continues a multipart upload: parts must arrive in order while it is `uploading`. */
|
|
906
|
+
uploadPart: async (options) => {
|
|
907
|
+
this.begin("assets.uploadPart", options);
|
|
908
|
+
const { assetId, part } = options;
|
|
909
|
+
require_CalmLensWebhooks.encodePathSegment(assetId, "Asset ID");
|
|
910
|
+
if (!part) throw require_CalmLensWebhooks.invalidArgument("Part options are required");
|
|
911
|
+
return this.withIdempotency("assets.uploadPart", options, async () => {
|
|
912
|
+
const asset = await this.findOwnAsset(assetId);
|
|
913
|
+
const validation = require_CalmLensWebhooks.validateAssetPayload(part, {
|
|
492
914
|
multipart: true,
|
|
493
915
|
isFinal: part.isFinal
|
|
494
916
|
});
|
|
495
|
-
if (!validation.ok) throw
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}(),
|
|
508
|
-
createSignedUrl: function() {
|
|
509
|
-
var _ref12 = require_CalmLensUtils._asyncToGenerator(function* (signedUrlOptions) {
|
|
510
|
-
var _options$expiresInSec, _options$resource2;
|
|
511
|
-
_this5.begin("assets.createSignedUrl", signedUrlOptions);
|
|
512
|
-
const { assetId } = signedUrlOptions, options = require_CalmLensUtils._objectWithoutProperties(signedUrlOptions, _excluded5);
|
|
513
|
-
if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
|
|
514
|
-
if (!(yield _this5.databaseStorage.getAsset(assetId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
|
|
515
|
-
const expiresInSeconds = (_options$expiresInSec = options.expiresInSeconds) !== null && _options$expiresInSec !== void 0 ? _options$expiresInSec : 900;
|
|
516
|
-
const resource = (_options$resource2 = options.resource) !== null && _options$resource2 !== void 0 ? _options$resource2 : "content";
|
|
517
|
-
if (options.lens && options.transform) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Specify either a lens or a transform, not both" });
|
|
518
|
-
const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : options.transform ? `&transform=${encodeURIComponent(options.transform)}` : "";
|
|
519
|
-
return {
|
|
520
|
-
url: `${_this5.signedUrlBaseUrl}/assets/${assetId}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
|
|
521
|
-
expiresAt: _this5.now() + expiresInSeconds * 1e3
|
|
522
|
-
};
|
|
523
|
-
});
|
|
524
|
-
return function createSignedUrl(_x8) {
|
|
525
|
-
return _ref12.apply(this, arguments);
|
|
526
|
-
};
|
|
527
|
-
}(),
|
|
528
|
-
move: function() {
|
|
529
|
-
var _ref13 = require_CalmLensUtils._asyncToGenerator(function* (options) {
|
|
530
|
-
_this5.begin("assets.move", options);
|
|
531
|
-
const { assetId, folderId } = options;
|
|
532
|
-
if (!assetId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Asset ID is required" });
|
|
533
|
-
const asset = yield _this5.databaseStorage.getAsset(assetId);
|
|
534
|
-
if (!asset || asset.projectId !== _this5.options.projectId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.ASSET_NOT_FOUND);
|
|
535
|
-
if (folderId !== null && folderId !== void 0) {
|
|
536
|
-
if (!(yield _this5.resolveFolder(folderId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
537
|
-
}
|
|
538
|
-
const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
|
|
539
|
-
folderId: folderId !== null && folderId !== void 0 ? folderId : null,
|
|
540
|
-
updatedAt: _this5.now()
|
|
917
|
+
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
918
|
+
if (asset.status !== "uploading") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.UPLOAD_NOT_OPEN);
|
|
919
|
+
const expectedPartNumber = (asset.uploadPartsReceived ?? 1) + 1;
|
|
920
|
+
if (part.partNumber !== expectedPartNumber) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_UPLOAD_PART_NUMBER, { details: { expectedPartNumber } });
|
|
921
|
+
await this.imageStorage.putPart(assetId, part);
|
|
922
|
+
const queue = options.queue ?? this.uploadQueues.get(assetId) ?? false;
|
|
923
|
+
if (part.isFinal) this.uploadQueues.delete(assetId);
|
|
924
|
+
return this.databaseStorage.updateAsset({
|
|
925
|
+
...asset,
|
|
926
|
+
status: part.isFinal ? queue ? this.queuedStatus : this.analyzedStatus : "uploading",
|
|
927
|
+
uploadPartsReceived: part.partNumber,
|
|
928
|
+
updatedAt: this.now()
|
|
541
929
|
});
|
|
542
|
-
return _this5.databaseStorage.updateAsset(updated);
|
|
543
930
|
});
|
|
544
|
-
|
|
545
|
-
|
|
931
|
+
},
|
|
932
|
+
createSignedUrl: async (signedUrlOptions) => {
|
|
933
|
+
this.begin("assets.createSignedUrl", signedUrlOptions);
|
|
934
|
+
const { assetId, ...options } = signedUrlOptions;
|
|
935
|
+
const asset = await this.findOwnAsset(assetId);
|
|
936
|
+
if (options.lens && options.transform) throw require_CalmLensWebhooks.invalidArgument("Specify either a lens or a transform, not both");
|
|
937
|
+
const expiresInSeconds = options.expiresInSeconds ?? 900;
|
|
938
|
+
const resource = options.resource ?? "content";
|
|
939
|
+
const selector = options.lens ? `&lens=${encodeURIComponent(options.lens)}` : options.transform ? `&transform=${encodeURIComponent(options.transform)}` : "";
|
|
940
|
+
return {
|
|
941
|
+
url: `${this.signedUrlBaseUrl}/assets/${asset.id}/${resource}?expiresInSeconds=${expiresInSeconds}${selector}`,
|
|
942
|
+
expiresAt: this.now() + expiresInSeconds * 1e3
|
|
546
943
|
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}));
|
|
944
|
+
},
|
|
945
|
+
move: async (options) => {
|
|
946
|
+
this.begin("assets.move", options);
|
|
947
|
+
const { assetId, folderId } = options;
|
|
948
|
+
const asset = await this.findOwnAsset(assetId);
|
|
949
|
+
if (folderId !== null && folderId !== void 0) {
|
|
950
|
+
if (!await this.resolveFolder(folderId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
951
|
+
}
|
|
952
|
+
return this.databaseStorage.updateAsset({
|
|
953
|
+
...asset,
|
|
954
|
+
folderId: folderId ?? null,
|
|
955
|
+
updatedAt: this.now()
|
|
560
956
|
});
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
return _this5.databaseStorage.updateAsset(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, asset), {}, {
|
|
572
|
-
extractedText: null,
|
|
573
|
-
storageDeletedAt: (_asset$storageDeleted = asset.storageDeletedAt) !== null && _asset$storageDeleted !== void 0 ? _asset$storageDeleted : _this5.now(),
|
|
574
|
-
updatedAt: _this5.now()
|
|
575
|
-
}));
|
|
957
|
+
},
|
|
958
|
+
retry: async (options) => {
|
|
959
|
+
this.begin("assets.retry", options);
|
|
960
|
+
const asset = await this.findOwnAsset(options?.assetId);
|
|
961
|
+
if (asset.status !== "error") throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_NOT_RETRYABLE);
|
|
962
|
+
if (asset.storageDeletedAt) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.ASSET_CONTENT_UNAVAILABLE);
|
|
963
|
+
return this.databaseStorage.updateAsset({
|
|
964
|
+
...asset,
|
|
965
|
+
status: "pending",
|
|
966
|
+
updatedAt: this.now()
|
|
576
967
|
});
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
968
|
+
},
|
|
969
|
+
deleteContent: async (options) => {
|
|
970
|
+
this.begin("assets.deleteContent", options);
|
|
971
|
+
const asset = await this.findOwnAsset(options?.assetId);
|
|
972
|
+
await this.imageStorage.deleteContent(asset.id);
|
|
973
|
+
return this.databaseStorage.updateAsset({
|
|
974
|
+
...asset,
|
|
975
|
+
extractedText: null,
|
|
976
|
+
storageDeletedAt: asset.storageDeletedAt ?? this.now(),
|
|
977
|
+
updatedAt: this.now()
|
|
587
978
|
});
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
979
|
+
},
|
|
980
|
+
delete: async (options) => {
|
|
981
|
+
this.begin("assets.delete", options);
|
|
982
|
+
const asset = await this.findOwnAsset(options?.assetId);
|
|
983
|
+
await this.imageStorage.deleteContent(asset.id);
|
|
984
|
+
await this.databaseStorage.deleteAsset(asset.id);
|
|
985
|
+
}
|
|
592
986
|
};
|
|
593
987
|
}
|
|
594
988
|
get folders() {
|
|
595
|
-
var _this6 = this;
|
|
596
989
|
return {
|
|
597
|
-
list:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
};
|
|
618
|
-
}(),
|
|
619
|
-
create: function() {
|
|
620
|
-
var _ref19 = require_CalmLensUtils._asyncToGenerator(function* (options) {
|
|
621
|
-
var _options$parentId, _options$parentId2;
|
|
622
|
-
_this6.begin("folders.create", options);
|
|
623
|
-
if (!options.name) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder name is required" });
|
|
624
|
-
if (options.parentId) {
|
|
625
|
-
if (!(yield _this6.resolveFolder(options.parentId))) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
626
|
-
}
|
|
627
|
-
yield _this6.assertUniqueFolderName(options.name, (_options$parentId = options.parentId) !== null && _options$parentId !== void 0 ? _options$parentId : null);
|
|
628
|
-
const folder = {
|
|
629
|
-
id: _this6.createId(),
|
|
630
|
-
projectId: _this6.options.projectId,
|
|
631
|
-
parentId: (_options$parentId2 = options.parentId) !== null && _options$parentId2 !== void 0 ? _options$parentId2 : null,
|
|
632
|
-
name: options.name,
|
|
633
|
-
createdAt: _this6.now(),
|
|
634
|
-
updatedAt: _this6.now()
|
|
635
|
-
};
|
|
636
|
-
if (_this6.databaseStorage.createFolder) return yield _this6.databaseStorage.createFolder(folder);
|
|
637
|
-
return folder;
|
|
638
|
-
});
|
|
639
|
-
return function create(_x14) {
|
|
640
|
-
return _ref19.apply(this, arguments);
|
|
641
|
-
};
|
|
642
|
-
}(),
|
|
643
|
-
update: function() {
|
|
644
|
-
var _ref20 = require_CalmLensUtils._asyncToGenerator(function* (options) {
|
|
645
|
-
_this6.begin("folders.update", options);
|
|
646
|
-
const { folderId } = options, body = require_CalmLensUtils._objectWithoutProperties(options, _excluded6);
|
|
647
|
-
if (!folderId) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.INVALID_UPLOAD_PAYLOAD, { message: "Folder ID is required" });
|
|
648
|
-
const folder = yield _this6.resolveFolder(folderId);
|
|
649
|
-
if (!folder) throw require_CalmLensUtils.calmLensError(require_CalmLensUtils.PUBLIC_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
650
|
-
if (body.name !== void 0 && body.name !== folder.name) {
|
|
651
|
-
var _folder$parentId;
|
|
652
|
-
yield _this6.assertUniqueFolderName(body.name, (_folder$parentId = folder.parentId) !== null && _folder$parentId !== void 0 ? _folder$parentId : null, folderId);
|
|
653
|
-
}
|
|
654
|
-
const updated = require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2(require_CalmLensUtils._objectSpread2({}, folder), body.name !== void 0 ? { name: body.name } : {}), {}, { updatedAt: _this6.now() });
|
|
655
|
-
if (_this6.databaseStorage.updateFolder) return yield _this6.databaseStorage.updateFolder(updated);
|
|
656
|
-
return updated;
|
|
657
|
-
});
|
|
658
|
-
return function update(_x15) {
|
|
659
|
-
return _ref20.apply(this, arguments);
|
|
990
|
+
list: async () => {
|
|
991
|
+
this.begin("folders.list", void 0);
|
|
992
|
+
return [...this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []].sort((a, b) => a.name.localeCompare(b.name));
|
|
993
|
+
},
|
|
994
|
+
retrieve: async (options) => {
|
|
995
|
+
this.begin("folders.retrieve", options);
|
|
996
|
+
return this.findOwnFolder(options?.folderId);
|
|
997
|
+
},
|
|
998
|
+
create: async (options) => {
|
|
999
|
+
this.begin("folders.create", options);
|
|
1000
|
+
if (!options?.name) throw this.invalidPayload("Folder name is required");
|
|
1001
|
+
if (options.parentId) await this.findOwnFolder(options.parentId);
|
|
1002
|
+
await this.assertUniqueFolderName(options.name, options.parentId ?? null);
|
|
1003
|
+
const folder = {
|
|
1004
|
+
id: this.createId(),
|
|
1005
|
+
projectId: this.projectId,
|
|
1006
|
+
parentId: options.parentId ?? null,
|
|
1007
|
+
name: options.name,
|
|
1008
|
+
createdAt: this.now(),
|
|
1009
|
+
updatedAt: this.now()
|
|
660
1010
|
};
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
hasChildren
|
|
673
|
-
} });
|
|
674
|
-
if (_this6.databaseStorage.deleteFolder) yield _this6.databaseStorage.deleteFolder(folderId);
|
|
675
|
-
});
|
|
676
|
-
return function _delete(_x16) {
|
|
677
|
-
return _ref21.apply(this, arguments);
|
|
1011
|
+
return this.databaseStorage.createFolder ? await this.databaseStorage.createFolder(folder) : folder;
|
|
1012
|
+
},
|
|
1013
|
+
update: async (options) => {
|
|
1014
|
+
this.begin("folders.update", options);
|
|
1015
|
+
const { folderId, ...body } = options;
|
|
1016
|
+
const folder = await this.findOwnFolder(folderId);
|
|
1017
|
+
if (body.name !== void 0 && body.name !== folder.name) await this.assertUniqueFolderName(body.name, folder.parentId ?? null, folderId);
|
|
1018
|
+
const updated = {
|
|
1019
|
+
...folder,
|
|
1020
|
+
...body.name !== void 0 ? { name: body.name } : {},
|
|
1021
|
+
updatedAt: this.now()
|
|
678
1022
|
};
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
1023
|
+
return this.databaseStorage.updateFolder ? await this.databaseStorage.updateFolder(updated) : updated;
|
|
1024
|
+
},
|
|
1025
|
+
delete: async (options) => {
|
|
1026
|
+
this.begin("folders.delete", options);
|
|
1027
|
+
const { folderId } = options;
|
|
1028
|
+
await this.findOwnFolder(folderId);
|
|
1029
|
+
const hasAssets = (await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.folderId === folderId);
|
|
1030
|
+
const hasChildren = (this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).some((f) => f.parentId === folderId);
|
|
1031
|
+
if (hasAssets || hasChildren) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_EMPTY, { details: {
|
|
1032
|
+
hasAssets,
|
|
1033
|
+
hasChildren
|
|
1034
|
+
} });
|
|
1035
|
+
await this.databaseStorage.deleteFolder?.(folderId);
|
|
1036
|
+
},
|
|
1037
|
+
move: async (options) => {
|
|
1038
|
+
this.begin("folders.move", options);
|
|
1039
|
+
const { folderId, parentId } = options;
|
|
1040
|
+
const folder = await this.findOwnFolder(folderId);
|
|
1041
|
+
const targetParentId = parentId ?? null;
|
|
1042
|
+
if (targetParentId) {
|
|
1043
|
+
let candidate = await this.findOwnFolder(targetParentId);
|
|
1044
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1045
|
+
while (candidate) {
|
|
1046
|
+
if (candidate.id === folderId || visited.has(candidate.id)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_PARENT_CYCLE);
|
|
1047
|
+
visited.add(candidate.id);
|
|
1048
|
+
candidate = candidate.parentId ? await this.resolveFolder(candidate.parentId) : void 0;
|
|
698
1049
|
}
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return updated;
|
|
706
|
-
});
|
|
707
|
-
return function move(_x17) {
|
|
708
|
-
return _ref22.apply(this, arguments);
|
|
1050
|
+
}
|
|
1051
|
+
if (targetParentId !== folder.parentId) await this.assertUniqueFolderName(folder.name, targetParentId, folderId);
|
|
1052
|
+
const updated = {
|
|
1053
|
+
...folder,
|
|
1054
|
+
parentId: targetParentId,
|
|
1055
|
+
updatedAt: this.now()
|
|
709
1056
|
};
|
|
710
|
-
|
|
1057
|
+
return this.databaseStorage.updateFolder ? await this.databaseStorage.updateFolder(updated) : updated;
|
|
1058
|
+
}
|
|
711
1059
|
};
|
|
712
1060
|
}
|
|
713
|
-
ensureLens(options) {
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
return _this7.ensureLensDefinition(options);
|
|
718
|
-
})();
|
|
1061
|
+
async ensureLens(options) {
|
|
1062
|
+
this.begin("ensureLens", options);
|
|
1063
|
+
if (!options?.identifier) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
|
|
1064
|
+
return this.ensureLensDefinition(options);
|
|
719
1065
|
}
|
|
720
1066
|
ensureLensDefinition(options) {
|
|
721
|
-
var _existing$description, _options$description, _existing$transforms, _options$transforms;
|
|
722
1067
|
const existing = this.lenses.get(options.identifier);
|
|
723
1068
|
if (!existing) {
|
|
724
|
-
const lens =
|
|
1069
|
+
const lens = {
|
|
725
1070
|
id: this.createId(),
|
|
726
|
-
projectId: this.
|
|
727
|
-
userId: null
|
|
728
|
-
|
|
1071
|
+
projectId: this.projectId,
|
|
1072
|
+
userId: null,
|
|
1073
|
+
...options,
|
|
729
1074
|
createdAt: this.now(),
|
|
730
1075
|
updatedAt: this.now()
|
|
731
|
-
}
|
|
1076
|
+
};
|
|
732
1077
|
this.lenses.set(lens.identifier, lens);
|
|
733
1078
|
return lens;
|
|
734
1079
|
}
|
|
735
|
-
if (existing.name === options.name && (
|
|
1080
|
+
if (existing.name === options.name && (existing.description ?? null) === (options.description ?? null) && existing.type === options.type && existing.active === options.active && require_CalmLensWebhooks.stringifyJsonCanonical(existing.transforms ?? null) === require_CalmLensWebhooks.stringifyJsonCanonical(options.transforms ?? null) && existing.version === options.version) return existing;
|
|
736
1081
|
if (options.version > existing.version) {
|
|
737
|
-
const lens =
|
|
1082
|
+
const lens = {
|
|
1083
|
+
...existing,
|
|
1084
|
+
...options,
|
|
1085
|
+
updatedAt: this.now()
|
|
1086
|
+
};
|
|
738
1087
|
this.lenses.set(lens.identifier, lens);
|
|
739
1088
|
return lens;
|
|
740
1089
|
}
|
|
741
|
-
throw
|
|
1090
|
+
throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.LENS_VERSION_CONFLICT, { details: {
|
|
742
1091
|
currentVersion: existing.version,
|
|
743
1092
|
minimumVersion: existing.version + 1,
|
|
744
1093
|
requestedVersion: options.version
|
|
745
1094
|
} });
|
|
746
1095
|
}
|
|
747
|
-
ensureLenses(options) {
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1096
|
+
async ensureLenses(options) {
|
|
1097
|
+
this.begin("ensureLenses", options);
|
|
1098
|
+
if (!Array.isArray(options?.lenses) || options.lenses.length === 0) throw require_CalmLensWebhooks.invalidArgument("At least one lens is required");
|
|
1099
|
+
if (options.lenses.some((lens) => !lens?.identifier)) throw require_CalmLensWebhooks.invalidArgument("Lens identifier is required");
|
|
1100
|
+
return options.lenses.map((option) => this.ensureLensDefinition(option));
|
|
1101
|
+
}
|
|
1102
|
+
/** Replays the first result for a reused idempotency key, like the API. */
|
|
1103
|
+
async withIdempotency(operation, options, run) {
|
|
1104
|
+
const key = options?.idempotencyKey;
|
|
1105
|
+
if (key === void 0) return run();
|
|
1106
|
+
if (!key || key.length > 255 || !/^[\x21-\x7e]+$/.test(key)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.INVALID_IDEMPOTENCY_KEY);
|
|
1107
|
+
const fingerprint = `${operation}:${require_CalmLensWebhooks.stringifyJsonCanonical(JSON.parse(JSON.stringify(options, (_key, value) => describeBinary(value))))}`;
|
|
1108
|
+
const saved = this.idempotentResults.get(key);
|
|
1109
|
+
if (saved) {
|
|
1110
|
+
if (saved.fingerprint !== fingerprint) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.IDEMPOTENCY_KEY_REUSED);
|
|
1111
|
+
return saved.asset;
|
|
1112
|
+
}
|
|
1113
|
+
const asset = await run();
|
|
1114
|
+
this.idempotentResults.set(key, {
|
|
1115
|
+
fingerprint,
|
|
1116
|
+
asset
|
|
1117
|
+
});
|
|
1118
|
+
return asset;
|
|
1119
|
+
}
|
|
1120
|
+
async moderateAsset(options, operation) {
|
|
1121
|
+
if (options && "content" in options && "plainText" in options.content && (typeof options.content.plainText !== "string" || !options.content.plainText.trim())) throw this.invalidPayload("Text to moderate must be a non-empty string");
|
|
1122
|
+
return this.withIdempotency(operation, options, () => this.createAsset(options, false));
|
|
1123
|
+
}
|
|
1124
|
+
async createAsset(options, queue) {
|
|
1125
|
+
if (!options || typeof options !== "object") throw require_CalmLensWebhooks.invalidArgument("Asset options are required");
|
|
1126
|
+
if (!("content" in options) && !("url" in options) && !("part" in options)) throw this.invalidPayload("Either file content, URL, or multipart part is required");
|
|
1127
|
+
if (!options.name) throw this.invalidPayload("Asset name is required");
|
|
1128
|
+
if (!("part" in options) && "isFinal" in options) throw this.invalidPayload("isFinal is only valid for multipart uploads");
|
|
1129
|
+
if ("part" in options && options.part.partNumber && options.part.partNumber > 1) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.USE_UPLOAD_PART);
|
|
1130
|
+
if ("content" in options) {
|
|
1131
|
+
this.assertValidUploadMimeType(options.content.type);
|
|
1132
|
+
if (!("plainText" in options.content)) {
|
|
1133
|
+
const validation = require_CalmLensWebhooks.validateAssetPayload(options.content);
|
|
1134
|
+
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
784
1135
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
const
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
return
|
|
825
|
-
|
|
826
|
-
|
|
1136
|
+
}
|
|
1137
|
+
if ("part" in options) {
|
|
1138
|
+
this.assertValidUploadMimeType(options.part.type);
|
|
1139
|
+
const validation = require_CalmLensWebhooks.validateAssetPayload(options.part, {
|
|
1140
|
+
multipart: true,
|
|
1141
|
+
isFinal: options.part.isFinal
|
|
1142
|
+
});
|
|
1143
|
+
if (!validation.ok) throw this.invalidPayload(validation.error);
|
|
1144
|
+
}
|
|
1145
|
+
if (options.externalId) {
|
|
1146
|
+
if ((await this.databaseStorage.listAssets(this.projectId)).some((asset) => asset.externalId === options.externalId)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.DUPLICATE_EXTERNAL_ID);
|
|
1147
|
+
}
|
|
1148
|
+
const id = this.createId();
|
|
1149
|
+
const now = this.now();
|
|
1150
|
+
const isOpenUpload = "part" in options && !options.part.isFinal;
|
|
1151
|
+
const asset = {
|
|
1152
|
+
id,
|
|
1153
|
+
projectId: this.projectId,
|
|
1154
|
+
folderId: options.folderId ?? null,
|
|
1155
|
+
createdAt: now,
|
|
1156
|
+
updatedAt: now,
|
|
1157
|
+
name: options.name,
|
|
1158
|
+
description: options.description,
|
|
1159
|
+
externalId: options.externalId,
|
|
1160
|
+
externalUrl: "url" in options ? options.url : "content" in options ? options.externalUrl : void 0,
|
|
1161
|
+
type: inferAssetType(options),
|
|
1162
|
+
metadata: options.metadata,
|
|
1163
|
+
status: isOpenUpload ? "uploading" : queue ? this.queuedStatus : this.analyzedStatus,
|
|
1164
|
+
processingOptions: options.processingOptions,
|
|
1165
|
+
visibility: options.visibility ?? "private",
|
|
1166
|
+
sizeInBytes: this.getSizeInBytes(options),
|
|
1167
|
+
uploadPartsReceived: "part" in options ? options.part.partNumber ?? 1 : void 0
|
|
1168
|
+
};
|
|
1169
|
+
if ("content" in options) {
|
|
1170
|
+
if (!("plainText" in options.content)) await this.imageStorage.putContent(id, options.content);
|
|
1171
|
+
} else if ("part" in options) {
|
|
1172
|
+
await this.imageStorage.putPart(id, options.part);
|
|
1173
|
+
if (isOpenUpload) this.uploadQueues.set(id, queue);
|
|
1174
|
+
}
|
|
1175
|
+
return this.databaseStorage.createAsset(asset);
|
|
1176
|
+
}
|
|
1177
|
+
async resolveFolder(folderId) {
|
|
1178
|
+
if (!this.databaseStorage.getFolder) return void 0;
|
|
1179
|
+
const folder = await this.databaseStorage.getFolder(folderId);
|
|
1180
|
+
if (!folder || folder.projectId !== this.projectId) return;
|
|
1181
|
+
return folder;
|
|
1182
|
+
}
|
|
1183
|
+
async findOwnFolder(folderId) {
|
|
1184
|
+
require_CalmLensWebhooks.encodePathSegment(folderId, "Folder ID");
|
|
1185
|
+
const folder = await this.resolveFolder(folderId);
|
|
1186
|
+
if (!folder) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.FOLDER_NOT_FOUND);
|
|
1187
|
+
return folder;
|
|
1188
|
+
}
|
|
1189
|
+
async assertUniqueFolderName(name, parentId, excludeFolderId) {
|
|
1190
|
+
if ((this.databaseStorage.listFolders ? await this.databaseStorage.listFolders(this.projectId) : []).find((f) => f.id !== excludeFolderId && f.parentId === (parentId ?? null) && f.name === name)) throw this.apiError(require_CalmLensWebhooks.API_ERROR_CODES.DUPLICATE_FOLDER_NAME, { message: `A folder named "${name}" already exists in this location.` });
|
|
827
1191
|
}
|
|
828
1192
|
getSizeInBytes(options) {
|
|
829
1193
|
if ("content" in options) {
|
|
830
1194
|
if ("plainText" in options.content) return new TextEncoder().encode(options.content.plainText).byteLength;
|
|
831
|
-
return
|
|
832
|
-
}
|
|
833
|
-
if ("part" in options) {
|
|
834
|
-
var _options$sizeInBytes;
|
|
835
|
-
return (_options$sizeInBytes = options.sizeInBytes) !== null && _options$sizeInBytes !== void 0 ? _options$sizeInBytes : require_CalmLensUtils.getAssetPayloadByteLength(options.part);
|
|
1195
|
+
return require_CalmLensWebhooks.getAssetPayloadByteLength(options.content);
|
|
836
1196
|
}
|
|
1197
|
+
if ("part" in options) return options.part.sizeInBytes ?? options.sizeInBytes ?? require_CalmLensWebhooks.getAssetPayloadByteLength(options.part);
|
|
1198
|
+
}
|
|
1199
|
+
};
|
|
1200
|
+
//#endregion
|
|
1201
|
+
//#region src/testing/MockWebhooks.ts
|
|
1202
|
+
/** The JSON body of a webhook delivery for a stored event (or an event from `events.list`). */
|
|
1203
|
+
const toWebhookEventPayload = (event) => ({
|
|
1204
|
+
id: event.id,
|
|
1205
|
+
type: event.type,
|
|
1206
|
+
createdAt: event.createdAt,
|
|
1207
|
+
projectId: event.projectId,
|
|
1208
|
+
data: {
|
|
1209
|
+
severity: event.severity,
|
|
1210
|
+
title: event.title,
|
|
1211
|
+
description: event.description ?? null,
|
|
1212
|
+
assetId: event.assetId ?? null,
|
|
1213
|
+
asset: event.asset ?? null,
|
|
1214
|
+
facts: event.facts
|
|
837
1215
|
}
|
|
1216
|
+
});
|
|
1217
|
+
/** `v1=<hex HMAC-SHA256 of "<timestamp>.<body>">`, the `x-calmlens-signature` header value. */
|
|
1218
|
+
const createWebhookSignatureHeader = async (secret, timestamp, body) => `v1=${await require_CalmLensWebhooks.hmacSha256Hex(secret, `${timestamp}.${body}`)}`;
|
|
1219
|
+
/** Headers for one signed delivery of `body`, as CalmLens sends them. */
|
|
1220
|
+
const createWebhookHeaders = async (options) => {
|
|
1221
|
+
const timestamp = options.timestampSeconds ?? Math.floor(Date.now() / 1e3);
|
|
1222
|
+
return {
|
|
1223
|
+
"content-type": "application/json",
|
|
1224
|
+
[require_CalmLensWebhooks.WEBHOOK_DELIVERY_HEADER]: options.deliveryId,
|
|
1225
|
+
[require_CalmLensWebhooks.WEBHOOK_TIMESTAMP_HEADER]: String(timestamp),
|
|
1226
|
+
[require_CalmLensWebhooks.WEBHOOK_SIGNATURE_HEADER]: await createWebhookSignatureHeader(options.secret, timestamp, options.body)
|
|
1227
|
+
};
|
|
838
1228
|
};
|
|
839
1229
|
//#endregion
|
|
840
1230
|
exports.InMemoryMockDatabaseStorage = InMemoryMockDatabaseStorage;
|
|
841
1231
|
exports.InMemoryMockImageStorage = InMemoryMockImageStorage;
|
|
1232
|
+
exports.MEDIA_TEST_SIGNALS = MEDIA_TEST_SIGNALS;
|
|
842
1233
|
exports.MOCK_API_KEY_PREFIX = MOCK_API_KEY_PREFIX;
|
|
843
1234
|
exports.MockCalmLensClient = MockCalmLensClient;
|
|
844
1235
|
exports.createMockApiKey = createMockApiKey;
|
|
1236
|
+
exports.createWebhookHeaders = createWebhookHeaders;
|
|
1237
|
+
exports.createWebhookSignatureHeader = createWebhookSignatureHeader;
|
|
1238
|
+
exports.generateMediaTestCode = generateMediaTestCode;
|
|
1239
|
+
exports.toWebhookEventPayload = toWebhookEventPayload;
|