@calmlens/sdk 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -16
- package/cjs/CalmLensConnection-DzRDCMOP.js +233 -0
- package/cjs/CalmLensUtils-vX5H_vMn.js +699 -0
- package/cjs/CalmLensVersion-DjhsJ1gj.js +410 -0
- package/cjs/CalmLensWebhooks-hVquX5L1.js +201 -0
- package/cjs/WebhookPayload-B7CgSmI5.d.ts +11928 -0
- package/cjs/app.d.ts +43 -0
- package/cjs/app.js +91 -0
- package/cjs/index-BywTQbRq.d.ts +352 -0
- package/cjs/index.d.ts +3 -382
- package/cjs/index.js +4 -816
- package/cjs/server.d.ts +210 -0
- package/cjs/server.js +384 -0
- package/cjs/testing.d.ts +59 -18
- package/cjs/testing.js +245 -99
- package/esm/CalmLensConnection-D8VsTdsA.js +192 -0
- package/esm/CalmLensUtils-B4i3Z2He.js +574 -0
- package/esm/CalmLensVersion-DK4BMEST.js +345 -0
- package/esm/CalmLensWebhooks-DyCoEvpf.js +148 -0
- package/esm/WebhookPayload-B7CgSmI5.d.ts +11928 -0
- package/esm/app.d.ts +43 -0
- package/esm/app.js +87 -0
- package/esm/index-BywTQbRq.d.ts +352 -0
- package/esm/index.d.ts +3 -382
- package/esm/index.js +1 -807
- package/esm/server.d.ts +210 -0
- package/esm/server.js +374 -0
- package/esm/testing.d.ts +59 -18
- package/esm/testing.js +195 -49
- package/package.json +23 -1
- package/cjs/CalmLensTypes-BZ-nxhr_.d.ts +0 -8613
- package/cjs/CalmLensWebhooks-CH2PaPs0.js +0 -784
- package/esm/CalmLensTypes-BZ-nxhr_.d.ts +0 -8613
- package/esm/CalmLensWebhooks-3Otbubyf.js +0 -593
package/esm/testing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as SearchAssetsOptions, A as DeleteAssetContentOptions, At as AssetBinaryPayloadInput, B as Lens, C as CdnAssetUrlOptions, D as CreateAssetSignedUrlOptions, G as ModerateTextOptions, H as ListAssetChildrenOptions, K as MoveAssetOptions, M as EnsureLensOptions, N as EnsureLensesOptions, O as CreateFolderOptions, P as FolderIdOptions, Q as RetryAssetOptions, R as IterateAssetChildrenOptions, S as CalmLensServerClientOptions, U as ListAssetsOptions, W as ListEventsOptions, Z as RetrieveAssetOptions, at as UpdateFolderOptions, d as Asset, et as SignUploadTokenOptions, g as AssetSearchResponse, gt as Event, h as AssetPage, ht as Folder, it as UpdateAssetOptions, j as DeleteAssetOptions, mt as PostAssetSignedUrlResponse, ot as UploadAssetPartOptions, p as AssetChildrenPage, pt as GetEventsResponse, q as MoveFolderOptions, r as ClassificationCategory, rt as SubmitAssetOptions, t as WebhookEventPayload, tt as SignedUploadToken, ut as WaitForAssetDecisionOptions, v as AssetStatus, vt as TypedEvent, w as CdnServeOptions, z as IterateAssetsOptions } from "./WebhookPayload-B7CgSmI5.js";
|
|
2
2
|
import * as zod from "zod";
|
|
3
3
|
//#region ../shared/src/TestTriggers.d.ts
|
|
4
4
|
type ClassificationTestSignal = ClassificationCategory | "sexual.involving-minors" | "violence.graphic";
|
|
@@ -32,8 +32,12 @@ declare const GRANT_SCHEMA: zod.ZodObject<{
|
|
|
32
32
|
"metadata:read": "metadata:read";
|
|
33
33
|
"metadata:write": "metadata:write";
|
|
34
34
|
"project:manage": "project:manage";
|
|
35
|
+
"reviews:read": "reviews:read";
|
|
36
|
+
"reviews:write": "reviews:write";
|
|
35
37
|
"settings:read": "settings:read";
|
|
36
38
|
"settings:write": "settings:write";
|
|
39
|
+
"upload-presets:read": "upload-presets:read";
|
|
40
|
+
"upload-presets:write": "upload-presets:write";
|
|
37
41
|
}>>;
|
|
38
42
|
folderId: zod.ZodNullable<zod.ZodString>;
|
|
39
43
|
includeSubfolders: zod.ZodBoolean;
|
|
@@ -102,8 +106,8 @@ export declare class InMemoryMockDatabaseStorage implements MockDatabaseStorage
|
|
|
102
106
|
deleteFolder(folderId: string): Promise<void>;
|
|
103
107
|
}
|
|
104
108
|
//#endregion
|
|
105
|
-
//#region src/testing/
|
|
106
|
-
interface
|
|
109
|
+
//#region src/testing/MockCalmLensServerClient.d.ts
|
|
110
|
+
interface MockCalmLensServerClientOptions extends CalmLensServerClientOptions {
|
|
107
111
|
mockOptions?: {
|
|
108
112
|
databaseStorage?: MockDatabaseStorage;
|
|
109
113
|
imageStorage?: MockImageStorage;
|
|
@@ -116,13 +120,45 @@ interface MockCalmLensClientOptions extends CalmLensClientOptions {
|
|
|
116
120
|
signedUrlBaseUrl?: string;
|
|
117
121
|
};
|
|
118
122
|
}
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
123
|
+
/** The options each `MockCalmLensServerClient` method was given, by operation name. */
|
|
124
|
+
interface MockCalmLensOperationOptions {
|
|
125
|
+
"assets.list": ListAssetsOptions;
|
|
126
|
+
"assets.search": SearchAssetsOptions;
|
|
127
|
+
"assets.retrieve": RetrieveAssetOptions;
|
|
128
|
+
"assets.update": UpdateAssetOptions;
|
|
129
|
+
"assets.listChildren": ListAssetChildrenOptions;
|
|
130
|
+
"assets.create": SubmitAssetOptions;
|
|
131
|
+
"assets.moderate": SubmitAssetOptions;
|
|
132
|
+
"assets.uploadPart": UploadAssetPartOptions;
|
|
133
|
+
"assets.createSignedUrl": CreateAssetSignedUrlOptions;
|
|
134
|
+
"assets.move": MoveAssetOptions;
|
|
135
|
+
"assets.retry": RetryAssetOptions;
|
|
136
|
+
"assets.waitForDecision": WaitForAssetDecisionOptions;
|
|
137
|
+
"assets.deleteContent": DeleteAssetContentOptions;
|
|
138
|
+
"assets.delete": DeleteAssetOptions;
|
|
139
|
+
"text.moderate": ModerateTextOptions;
|
|
140
|
+
"cdn.serve": CdnServeOptions;
|
|
141
|
+
"events.list": ListEventsOptions;
|
|
142
|
+
"folders.list": undefined;
|
|
143
|
+
"folders.retrieve": FolderIdOptions;
|
|
144
|
+
"folders.create": CreateFolderOptions;
|
|
145
|
+
"folders.update": UpdateFolderOptions;
|
|
146
|
+
"folders.delete": FolderIdOptions;
|
|
147
|
+
"folders.move": MoveFolderOptions;
|
|
148
|
+
ensureLens: EnsureLensOptions;
|
|
149
|
+
ensureLenses: EnsureLensesOptions;
|
|
150
|
+
"uploadTokens.sign": SignUploadTokenOptions;
|
|
125
151
|
}
|
|
152
|
+
/** A `MockCalmLensServerClient` method, named like its path on the client. */
|
|
153
|
+
type MockCalmLensOperation = keyof MockCalmLensOperationOptions;
|
|
154
|
+
/**
|
|
155
|
+
* One recorded call: the method and the options object it was given, exactly as passed. Narrow
|
|
156
|
+
* on `operation` (or use `callsTo`) to get its options type.
|
|
157
|
+
*/
|
|
158
|
+
type MockCalmLensCall<TOperation extends MockCalmLensOperation = MockCalmLensOperation> = { [Operation in TOperation]: {
|
|
159
|
+
operation: Operation;
|
|
160
|
+
options: MockCalmLensOperationOptions[Operation];
|
|
161
|
+
}; }[TOperation];
|
|
126
162
|
/**
|
|
127
163
|
* What a simulated request failure throws: `"transport-error"` and `"timeout"` throw the same
|
|
128
164
|
* `CalmLensError` the real client throws for a network failure (`sdk/transport-error`) or a
|
|
@@ -134,11 +170,11 @@ type MockEventInput = Partial<Omit<TypedEvent, "type">> & {
|
|
|
134
170
|
type: TypedEvent["type"];
|
|
135
171
|
};
|
|
136
172
|
/**
|
|
137
|
-
* An in-memory stand-in for `
|
|
173
|
+
* An in-memory stand-in for `CalmLensServerClient` with the same methods, validation and error
|
|
138
174
|
* codes. API errors carry the HTTP status, a `requestId` and response headers like the real
|
|
139
175
|
* client's, so error handling can be tested without a network.
|
|
140
176
|
*/
|
|
141
|
-
export declare class
|
|
177
|
+
export declare class MockCalmLensServerClient {
|
|
142
178
|
readonly databaseStorage: MockDatabaseStorage;
|
|
143
179
|
readonly imageStorage: MockImageStorage;
|
|
144
180
|
private readonly createId;
|
|
@@ -149,6 +185,7 @@ export declare class MockCalmLensClient {
|
|
|
149
185
|
private readonly signedUrlBaseUrl;
|
|
150
186
|
private readonly cdnBaseUrl;
|
|
151
187
|
private readonly projectId;
|
|
188
|
+
private readonly apiKey;
|
|
152
189
|
private readonly lenses;
|
|
153
190
|
private readonly eventFeed;
|
|
154
191
|
/** The queue mode each open multipart upload was started with, as the API saves it. */
|
|
@@ -158,8 +195,8 @@ export declare class MockCalmLensClient {
|
|
|
158
195
|
private readonly pendingFailures;
|
|
159
196
|
/** Every call made through this client, oldest first. */
|
|
160
197
|
readonly calls: MockCalmLensCall[];
|
|
161
|
-
/** Accepts the same options as `
|
|
162
|
-
constructor(options:
|
|
198
|
+
/** Accepts the same options as `CalmLensServerClient` (validated the same way), plus `mockOptions`. */
|
|
199
|
+
constructor(options: MockCalmLensServerClientOptions);
|
|
163
200
|
/**
|
|
164
201
|
* Makes the next call (or the next call to `operation`) throw `failure` instead of running,
|
|
165
202
|
* as if the request never completed. Queue several to fail several calls in a row.
|
|
@@ -168,7 +205,7 @@ export declare class MockCalmLensClient {
|
|
|
168
205
|
operation?: MockCalmLensOperation;
|
|
169
206
|
}): void;
|
|
170
207
|
/** Calls recorded for one operation, oldest first. */
|
|
171
|
-
callsTo(operation:
|
|
208
|
+
callsTo<TOperation extends MockCalmLensOperation>(operation: TOperation): MockCalmLensCall<TOperation>[];
|
|
172
209
|
/**
|
|
173
210
|
* Adds an event to the feed `events.list` reads, as if CalmLens had recorded it. Returns the
|
|
174
211
|
* stored event; pass it to `toWebhookEventPayload` to build a matching webhook body.
|
|
@@ -211,9 +248,7 @@ export declare class MockCalmLensClient {
|
|
|
211
248
|
* description and tags, best match first. Mock scores are 1 for an exact match and 0.5
|
|
212
249
|
* otherwise; `mode` is accepted but there is no semantic search.
|
|
213
250
|
*/
|
|
214
|
-
search: (options:
|
|
215
|
-
query: string;
|
|
216
|
-
}) => Promise<AssetSearchResponse>;
|
|
251
|
+
search: (options: SearchAssetsOptions) => Promise<AssetSearchResponse>;
|
|
217
252
|
/** Lists assets with the real client's paging, `ordering`, `folderId` and `filter`. */
|
|
218
253
|
list: (options?: ListAssetsOptions) => Promise<AssetPage>;
|
|
219
254
|
iterate: (options?: IterateAssetsOptions) => AsyncGenerator<Asset, void, undefined>;
|
|
@@ -228,9 +263,15 @@ export declare class MockCalmLensClient {
|
|
|
228
263
|
createSignedUrl: (signedUrlOptions: CreateAssetSignedUrlOptions) => Promise<PostAssetSignedUrlResponse>;
|
|
229
264
|
move: (options: MoveAssetOptions) => Promise<Asset>;
|
|
230
265
|
retry: (options: RetryAssetOptions) => Promise<Asset>;
|
|
266
|
+
/** Waits like the real client, checking the mock's storage for a decision. */
|
|
267
|
+
waitForDecision: (options: WaitForAssetDecisionOptions) => Promise<Asset>;
|
|
231
268
|
deleteContent: (options: DeleteAssetContentOptions) => Promise<Asset>;
|
|
232
269
|
delete: (options: DeleteAssetOptions) => Promise<void>;
|
|
233
270
|
};
|
|
271
|
+
/** Signs real upload tokens with the mock's API key, like the real client, without a request. */
|
|
272
|
+
get uploadTokens(): {
|
|
273
|
+
sign: (options: SignUploadTokenOptions) => Promise<SignedUploadToken>;
|
|
274
|
+
};
|
|
234
275
|
get folders(): {
|
|
235
276
|
list: () => Promise<Folder[]>;
|
|
236
277
|
retrieve: (options: FolderIdOptions) => Promise<Folder>;
|
|
@@ -265,4 +306,4 @@ export declare const createWebhookHeaders: (options: {
|
|
|
265
306
|
timestampSeconds?: number;
|
|
266
307
|
}) => Promise<Record<string, string>>;
|
|
267
308
|
//#endregion
|
|
268
|
-
export type { MockCalmLensCall,
|
|
309
|
+
export type { MockCalmLensCall, MockCalmLensOperation, MockCalmLensOperationOptions, MockCalmLensServerClientOptions, MockDatabaseStorage, MockEventInput, MockImageStorage, MockRequestFailure, MockStoredContent };
|
package/esm/testing.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as invalidArgument, d as API_ERROR_DEFINITIONS, o as CalmLensError, s as SDK_ERROR_CODES, u as API_ERROR_CODES } from "./CalmLensVersion-DK4BMEST.js";
|
|
2
|
+
import { c as hmacSha256Hex, i as WEBHOOK_TIMESTAMP_HEADER, l as stringifyJsonCanonical, n as WEBHOOK_SIGNATURE_HEADER, s as signUploadTokenWithApiKey, t as WEBHOOK_DELIVERY_HEADER } from "./CalmLensWebhooks-DyCoEvpf.js";
|
|
3
|
+
import { b as getRejectedUploadMimeTypeError, f as sleep, g as validateAssetPayload, h as getAssetPayloadByteLength, m as assetToData, n as encodePathSegment, o as toTextAssetOptions, p as assetDataToUint8Array, r as pollForDecision, s as transformSelector, t as appendCdnSelector, u as validateServerClientOptions, x as isRejectedUploadMimeType } from "./CalmLensUtils-B4i3Z2He.js";
|
|
2
4
|
import * as zod from "zod";
|
|
3
5
|
import "zod/v4";
|
|
4
6
|
//#region ../shared/src/ModerationCategoryDefinitions.ts
|
|
@@ -34,60 +36,66 @@ const MODERATION_CATEGORY_DEFINITIONS = {
|
|
|
34
36
|
docsHref: "/docs/guides/moderation-categories#hate-and-identity-abuse"
|
|
35
37
|
},
|
|
36
38
|
"criminal-activity": {
|
|
37
|
-
summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act.",
|
|
38
|
-
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.",
|
|
39
|
-
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.",
|
|
39
|
+
summary: "Planning, assistance, instructions, encouragement or facilitation of a concrete criminal or seriously dangerous act, and illegal drugs or contraband.",
|
|
40
|
+
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.",
|
|
41
|
+
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.",
|
|
40
42
|
docsHref: "/docs/guides/moderation-categories#criminal-and-dangerous-activity"
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
//#endregion
|
|
44
46
|
//#region ../shared/src/ModerationPolicyDimensions.ts
|
|
45
47
|
const MODERATION_QUALIFIERS = [
|
|
46
|
-
"nudity",
|
|
48
|
+
"sexual-nudity",
|
|
47
49
|
"sexual-activity",
|
|
48
|
-
"explicit-
|
|
50
|
+
"sexual-explicit-language",
|
|
49
51
|
"sexual-solicitation",
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
52
|
+
"sexual-historical",
|
|
53
|
+
"sexual-documentary",
|
|
54
|
+
"sexual-fictional",
|
|
55
|
+
"sexual-support-prevention",
|
|
56
|
+
"violence-attack",
|
|
57
|
+
"violence-threat",
|
|
58
|
+
"violence-preparation",
|
|
59
|
+
"violence-encouragement",
|
|
60
|
+
"violence-injury",
|
|
61
|
+
"violence-death",
|
|
62
|
+
"violence-graphic",
|
|
63
|
+
"violence-historical",
|
|
64
|
+
"violence-documentary",
|
|
65
|
+
"violence-fictional",
|
|
66
|
+
"violence-support-prevention",
|
|
57
67
|
"self-harm-thoughts",
|
|
58
68
|
"self-harm-preparation",
|
|
59
69
|
"self-harm-act",
|
|
60
70
|
"self-harm-methods",
|
|
61
71
|
"self-harm-encouragement",
|
|
62
|
-
"
|
|
63
|
-
"
|
|
72
|
+
"self-harm-documentary",
|
|
73
|
+
"self-harm-fictional",
|
|
74
|
+
"self-harm-support-prevention",
|
|
75
|
+
"harassment-abuse",
|
|
76
|
+
"harassment-intimidation",
|
|
64
77
|
"harassment-threat",
|
|
65
|
-
"sexual
|
|
78
|
+
"harassment-sexual",
|
|
66
79
|
"harassment-encouragement",
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
80
|
+
"harassment-fictional",
|
|
81
|
+
"hate-abuse",
|
|
82
|
+
"hate-dehumanization",
|
|
83
|
+
"hate-inferiority",
|
|
84
|
+
"hate-exclusion",
|
|
71
85
|
"hate-threat",
|
|
72
|
-
"
|
|
73
|
-
"
|
|
74
|
-
"criminal-
|
|
75
|
-
"criminal-
|
|
76
|
-
"criminal-
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
86
|
+
"hate-historical",
|
|
87
|
+
"hate-fictional",
|
|
88
|
+
"criminal-activity-demonstration",
|
|
89
|
+
"criminal-activity-preparation",
|
|
90
|
+
"criminal-activity-request",
|
|
91
|
+
"criminal-activity-instructions",
|
|
92
|
+
"criminal-activity-promotion",
|
|
93
|
+
"criminal-activity-substances",
|
|
94
|
+
"criminal-activity-fictional"
|
|
81
95
|
];
|
|
82
|
-
const SHARED_QUALIFIERS = [
|
|
83
|
-
"historical",
|
|
84
|
-
"documentary",
|
|
85
|
-
"fictional",
|
|
86
|
-
"support-prevention"
|
|
87
|
-
];
|
|
88
|
-
[...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS], [...SHARED_QUALIFIERS];
|
|
89
96
|
//#endregion
|
|
90
97
|
//#region ../../node_modules/zod-meta/esm/index.js
|
|
98
|
+
const ZOD_META_KEY = "__zod_meta__";
|
|
91
99
|
const createMetaType = (type) => {
|
|
92
100
|
return Object.assign((value) => {
|
|
93
101
|
return {
|
|
@@ -96,7 +104,117 @@ const createMetaType = (type) => {
|
|
|
96
104
|
};
|
|
97
105
|
}, type);
|
|
98
106
|
};
|
|
99
|
-
|
|
107
|
+
const metaStore = (meta) => {
|
|
108
|
+
const metaMap = meta.reduce((acc, meta) => {
|
|
109
|
+
if (meta) acc[meta.type.id] = meta;
|
|
110
|
+
return acc;
|
|
111
|
+
}, {});
|
|
112
|
+
return { [ZOD_META_KEY]: {
|
|
113
|
+
itemMap: metaMap,
|
|
114
|
+
get itemList() {
|
|
115
|
+
return Object.values(metaMap);
|
|
116
|
+
}
|
|
117
|
+
} };
|
|
118
|
+
};
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region ../shared/src/DocMetaTypes.ts
|
|
121
|
+
const docPropertyInfo = createMetaType({ id: "docPropertyInfo" });
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region ../shared/src/ProcessingFeatures.ts
|
|
124
|
+
/**
|
|
125
|
+
* Optional analyses a request can turn on with `processingOptions.features.<id>`. The catalog is
|
|
126
|
+
* the single source for the request and result schemas, cache keys and metering. Features are
|
|
127
|
+
* chosen per request only; there is no project-level default.
|
|
128
|
+
*/
|
|
129
|
+
/** How mature a feature is. Beta results may change shape. */
|
|
130
|
+
const PROCESSING_FEATURE_STAGES = ["beta", "ga"];
|
|
131
|
+
const createFeatureOptionsSchemas = (booleanSchema) => {
|
|
132
|
+
const enabled = booleanSchema.meta(metaStore([docPropertyInfo({ description: "Whether to run the feature. Every feature is off unless this is true." })]));
|
|
133
|
+
return {
|
|
134
|
+
ocr: zod.object({ enabled }).strict().meta(metaStore([docPropertyInfo({
|
|
135
|
+
description: "Extract image text into a child asset and run normal text moderation. Applies to images, including those extracted from multimedia documents and websites.",
|
|
136
|
+
namedType: "OcrFeatureOptions"
|
|
137
|
+
})])),
|
|
138
|
+
personDetection: zod.object({ enabled }).strict().meta(metaStore([docPropertyInfo({
|
|
139
|
+
description: "Beta. Locate people in images, including people whose faces are not visible. Results are informational and never change the moderation decision.",
|
|
140
|
+
namedType: "PersonDetectionFeatureOptions"
|
|
141
|
+
})])),
|
|
142
|
+
faceDetection: zod.object({
|
|
143
|
+
enabled,
|
|
144
|
+
ageEstimation: booleanSchema.optional().meta(metaStore([docPropertyInfo({
|
|
145
|
+
description: "Also estimate an age range for each face",
|
|
146
|
+
defaultValue: false
|
|
147
|
+
})]))
|
|
148
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
149
|
+
description: "Beta. Locate faces in images and optionally estimate an age range for each. Faces are not identified or stored as biometric templates. Results are informational and never change the moderation decision.",
|
|
150
|
+
namedType: "FaceDetectionFeatureOptions"
|
|
151
|
+
})]))
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
const createProcessingFeaturesSchema = (booleanSchema) => {
|
|
155
|
+
const options = createFeatureOptionsSchemas(booleanSchema);
|
|
156
|
+
return zod.object({
|
|
157
|
+
ocr: options.ocr.nullish(),
|
|
158
|
+
personDetection: options.personDetection.nullish(),
|
|
159
|
+
faceDetection: options.faceDetection.nullish()
|
|
160
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
161
|
+
description: "Optional analyses to run on this asset, keyed by feature. Unknown features are rejected.",
|
|
162
|
+
namedType: "ProcessingFeatures"
|
|
163
|
+
})]));
|
|
164
|
+
};
|
|
165
|
+
createProcessingFeaturesSchema(zod.boolean());
|
|
166
|
+
/** Position and size as fractions of the image width and height, from 0 to 1. */
|
|
167
|
+
const BOUNDING_BOX_SCHEMA = zod.object({
|
|
168
|
+
left: zod.number(),
|
|
169
|
+
top: zod.number(),
|
|
170
|
+
width: zod.number().min(0),
|
|
171
|
+
height: zod.number().min(0)
|
|
172
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
173
|
+
description: "Position and size as fractions of the image width and height, from 0 to 1. `left` and `top` can be slightly negative when the subject extends past the edge.",
|
|
174
|
+
namedType: "BoundingBox"
|
|
175
|
+
})]));
|
|
176
|
+
const DETECTION_CONFIDENCE_SCHEMA = zod.number().min(0).max(1).describe("Detection confidence from 0 to 1");
|
|
177
|
+
const AGE_RANGE_SCHEMA = zod.object({
|
|
178
|
+
low: zod.number().int().nonnegative(),
|
|
179
|
+
high: zod.number().int().nonnegative()
|
|
180
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
181
|
+
description: "Estimated age range in years. Estimates can be off by several years.",
|
|
182
|
+
namedType: "AgeRange"
|
|
183
|
+
})]));
|
|
184
|
+
const DETECTED_PERSON_SCHEMA = zod.object({
|
|
185
|
+
box: BOUNDING_BOX_SCHEMA,
|
|
186
|
+
confidence: DETECTION_CONFIDENCE_SCHEMA
|
|
187
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
188
|
+
description: "A detected person",
|
|
189
|
+
namedType: "DetectedPerson"
|
|
190
|
+
})]));
|
|
191
|
+
const DETECTED_FACE_SCHEMA = zod.object({
|
|
192
|
+
box: BOUNDING_BOX_SCHEMA,
|
|
193
|
+
confidence: DETECTION_CONFIDENCE_SCHEMA,
|
|
194
|
+
ageRange: AGE_RANGE_SCHEMA.optional()
|
|
195
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
196
|
+
description: "A detected face",
|
|
197
|
+
namedType: "DetectedFace"
|
|
198
|
+
})]));
|
|
199
|
+
const NOT_EVALUATED_FEATURE_RESULT_SCHEMA = zod.object({
|
|
200
|
+
status: zod.literal("not-evaluated"),
|
|
201
|
+
stage: zod.enum(PROCESSING_FEATURE_STAGES),
|
|
202
|
+
reason: zod.enum(["not-supported", "unavailable"])
|
|
203
|
+
}).strict();
|
|
204
|
+
const createFeatureResultSchema = (shape) => zod.discriminatedUnion("status", [zod.object({
|
|
205
|
+
status: zod.literal("evaluated"),
|
|
206
|
+
stage: zod.enum(PROCESSING_FEATURE_STAGES),
|
|
207
|
+
...shape
|
|
208
|
+
}).strict(), NOT_EVALUATED_FEATURE_RESULT_SCHEMA]);
|
|
209
|
+
const PERSON_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ people: zod.array(DETECTED_PERSON_SCHEMA) });
|
|
210
|
+
const FACE_DETECTION_RESULT_SCHEMA = createFeatureResultSchema({ faces: zod.array(DETECTED_FACE_SCHEMA) });
|
|
211
|
+
const PROCESSING_FEATURE_RESULTS_SCHEMA = zod.object({
|
|
212
|
+
personDetection: PERSON_DETECTION_RESULT_SCHEMA.optional(),
|
|
213
|
+
faceDetection: FACE_DETECTION_RESULT_SCHEMA.optional()
|
|
214
|
+
}).strict().meta(metaStore([docPropertyInfo({
|
|
215
|
+
description: "Results of the optional features requested in `processingOptions.features`, keyed by feature",
|
|
216
|
+
namedType: "ProcessingFeatureResults"
|
|
217
|
+
})]));
|
|
100
218
|
//#endregion
|
|
101
219
|
//#region ../shared/src/ZodUtils.ts
|
|
102
220
|
const SIZE_LIMITS = {
|
|
@@ -139,7 +257,11 @@ const LIKELIHOOD_SCHEMA = zod.enum([
|
|
|
139
257
|
"likely",
|
|
140
258
|
"very-likely"
|
|
141
259
|
]);
|
|
142
|
-
const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum([
|
|
260
|
+
const CLASSIFICATION_NOT_EVALUATED_REASON_SCHEMA = zod.enum([
|
|
261
|
+
"not-supported",
|
|
262
|
+
"unavailable",
|
|
263
|
+
"not-requested"
|
|
264
|
+
]);
|
|
143
265
|
const CLASSIFICATION_QUALIFIER_RESULTS_SCHEMA = zod.partialRecord(zod.enum(MODERATION_QUALIFIERS), zod.discriminatedUnion("status", [zod.object({
|
|
144
266
|
status: zod.literal("evaluated"),
|
|
145
267
|
score: zod.number().min(0).max(1),
|
|
@@ -209,7 +331,8 @@ zod.object({
|
|
|
209
331
|
categories: CATEGORIES_SCHEMA
|
|
210
332
|
}).strict())
|
|
211
333
|
}).strict().optional(),
|
|
212
|
-
labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish()
|
|
334
|
+
labels: zod.array(CLASSIFICATION_LABEL_SCHEMA).nullish(),
|
|
335
|
+
features: PROCESSING_FEATURE_RESULTS_SCHEMA.optional()
|
|
213
336
|
}).strict();
|
|
214
337
|
//#endregion
|
|
215
338
|
//#region ../shared/src/MediaTestTriggers.ts
|
|
@@ -264,6 +387,10 @@ const ACTION_SCHEMA = zod.enum([
|
|
|
264
387
|
"metadata:write",
|
|
265
388
|
"lenses:read",
|
|
266
389
|
"lenses:write",
|
|
390
|
+
"upload-presets:read",
|
|
391
|
+
"upload-presets:write",
|
|
392
|
+
"reviews:read",
|
|
393
|
+
"reviews:write",
|
|
267
394
|
"project:manage"
|
|
268
395
|
]);
|
|
269
396
|
const PROJECT_ACTIONS = [...[
|
|
@@ -272,7 +399,9 @@ const PROJECT_ACTIONS = [...[
|
|
|
272
399
|
"api-keys",
|
|
273
400
|
"connectors",
|
|
274
401
|
"metadata",
|
|
275
|
-
"lenses"
|
|
402
|
+
"lenses",
|
|
403
|
+
"upload-presets",
|
|
404
|
+
"reviews"
|
|
276
405
|
].flatMap((feature) => [`${feature}:read`, `${feature}:write`]), "project:manage"];
|
|
277
406
|
const isProjectAction = (action) => PROJECT_ACTIONS.includes(action);
|
|
278
407
|
/** The actions that include `action`: its feature's write action and project:manage. */
|
|
@@ -285,7 +414,7 @@ const GRANT_SCHEMA = zod.object({
|
|
|
285
414
|
const GRANTS_SCHEMA = zod.array(GRANT_SCHEMA).max(100);
|
|
286
415
|
zod.enum([
|
|
287
416
|
"admin",
|
|
288
|
-
"
|
|
417
|
+
"editor",
|
|
289
418
|
"viewer",
|
|
290
419
|
"custom"
|
|
291
420
|
]);
|
|
@@ -475,7 +604,7 @@ const parseFilterValue = (raw) => {
|
|
|
475
604
|
return value;
|
|
476
605
|
};
|
|
477
606
|
const readField = (item, path) => path.split(".").reduce((value, key) => value && typeof value === "object" ? value[key] : void 0, item);
|
|
478
|
-
const unsupportedFilter = (expression) => invalidArgument(`
|
|
607
|
+
const unsupportedFilter = (expression) => invalidArgument(`MockCalmLensServerClient cannot evaluate the filter "${expression}". It supports ==, !=, <, <=, >, >=, =in= and =out=, joined by ; (and) or , (or), without grouping parentheses.`);
|
|
479
608
|
const matchesComparison = (item, comparison) => {
|
|
480
609
|
const match = COMPARISON.exec(comparison.trim());
|
|
481
610
|
if (!match) throw unsupportedFilter(comparison);
|
|
@@ -532,7 +661,7 @@ const filterAssets = (items, filter) => {
|
|
|
532
661
|
return items.filter((item) => alternatives.some((conditions) => conditions.every((condition) => matchesComparison(item, condition))));
|
|
533
662
|
};
|
|
534
663
|
//#endregion
|
|
535
|
-
//#region src/testing/
|
|
664
|
+
//#region src/testing/MockCalmLensServerClient.ts
|
|
536
665
|
const DEFAULT_EVENTS_LIMIT = 50;
|
|
537
666
|
/** Stands in for binary payloads when comparing idempotent requests, like the API's size check. */
|
|
538
667
|
const describeBinary = (value) => {
|
|
@@ -558,12 +687,12 @@ const toFailureError = (failure) => {
|
|
|
558
687
|
return failure;
|
|
559
688
|
};
|
|
560
689
|
/**
|
|
561
|
-
* An in-memory stand-in for `
|
|
690
|
+
* An in-memory stand-in for `CalmLensServerClient` with the same methods, validation and error
|
|
562
691
|
* codes. API errors carry the HTTP status, a `requestId` and response headers like the real
|
|
563
692
|
* client's, so error handling can be tested without a network.
|
|
564
693
|
*/
|
|
565
|
-
var
|
|
566
|
-
/** Accepts the same options as `
|
|
694
|
+
var MockCalmLensServerClient = class {
|
|
695
|
+
/** Accepts the same options as `CalmLensServerClient` (validated the same way), plus `mockOptions`. */
|
|
567
696
|
constructor(options) {
|
|
568
697
|
this.lenses = /* @__PURE__ */ new Map();
|
|
569
698
|
this.eventFeed = [];
|
|
@@ -572,8 +701,9 @@ var MockCalmLensClient = class {
|
|
|
572
701
|
this.requestCount = 0;
|
|
573
702
|
this.pendingFailures = [];
|
|
574
703
|
this.calls = [];
|
|
575
|
-
|
|
704
|
+
validateServerClientOptions(options);
|
|
576
705
|
this.projectId = options.projectId;
|
|
706
|
+
this.apiKey = options.apiKey;
|
|
577
707
|
this.databaseStorage = options.mockOptions?.databaseStorage ?? new InMemoryMockDatabaseStorage();
|
|
578
708
|
this.imageStorage = options.mockOptions?.imageStorage ?? new InMemoryMockImageStorage();
|
|
579
709
|
this.createId = options.mockOptions?.createId ?? crypto.randomUUID.bind(crypto);
|
|
@@ -586,7 +716,7 @@ var MockCalmLensClient = class {
|
|
|
586
716
|
}
|
|
587
717
|
/** Keeps the API key out of `console.log` and `util.inspect`. */
|
|
588
718
|
[Symbol.for("nodejs.util.inspect.custom")]() {
|
|
589
|
-
return `
|
|
719
|
+
return `MockCalmLensServerClient { projectId: ${JSON.stringify(this.projectId)} }`;
|
|
590
720
|
}
|
|
591
721
|
/**
|
|
592
722
|
* Makes the next call (or the next call to `operation`) throw `failure` instead of running,
|
|
@@ -943,6 +1073,12 @@ var MockCalmLensClient = class {
|
|
|
943
1073
|
updatedAt: this.now()
|
|
944
1074
|
});
|
|
945
1075
|
},
|
|
1076
|
+
/** Waits like the real client, checking the mock's storage for a decision. */
|
|
1077
|
+
waitForDecision: async (options) => {
|
|
1078
|
+
this.begin("assets.waitForDecision", options);
|
|
1079
|
+
const { assetId, ...waitOptions } = options;
|
|
1080
|
+
return pollForDecision(() => this.findOwnAsset(assetId), waitOptions, sleep);
|
|
1081
|
+
},
|
|
946
1082
|
deleteContent: async (options) => {
|
|
947
1083
|
this.begin("assets.deleteContent", options);
|
|
948
1084
|
const asset = await this.findOwnAsset(options?.assetId);
|
|
@@ -962,6 +1098,16 @@ var MockCalmLensClient = class {
|
|
|
962
1098
|
}
|
|
963
1099
|
};
|
|
964
1100
|
}
|
|
1101
|
+
/** Signs real upload tokens with the mock's API key, like the real client, without a request. */
|
|
1102
|
+
get uploadTokens() {
|
|
1103
|
+
return { sign: async (options) => {
|
|
1104
|
+
this.begin("uploadTokens.sign", options);
|
|
1105
|
+
return signUploadTokenWithApiKey(this.apiKey, options, {
|
|
1106
|
+
now: this.now(),
|
|
1107
|
+
nonce: this.createId()
|
|
1108
|
+
});
|
|
1109
|
+
} };
|
|
1110
|
+
}
|
|
965
1111
|
get folders() {
|
|
966
1112
|
return {
|
|
967
1113
|
list: async () => {
|
|
@@ -1204,4 +1350,4 @@ const createWebhookHeaders = async (options) => {
|
|
|
1204
1350
|
};
|
|
1205
1351
|
};
|
|
1206
1352
|
//#endregion
|
|
1207
|
-
export { InMemoryMockDatabaseStorage, InMemoryMockImageStorage, MEDIA_TEST_SIGNALS, MOCK_API_KEY_PREFIX,
|
|
1353
|
+
export { InMemoryMockDatabaseStorage, InMemoryMockImageStorage, MEDIA_TEST_SIGNALS, MOCK_API_KEY_PREFIX, MockCalmLensServerClient, createMockApiKey, createWebhookHeaders, createWebhookSignatureHeader, generateMediaTestCode, toWebhookEventPayload };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@calmlens/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Official CalmLens SDK for JavaScript and TypeScript: content moderation, asset storage and CDN delivery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://calmlens.com/docs/sdk/javascript",
|
|
@@ -72,6 +72,28 @@
|
|
|
72
72
|
},
|
|
73
73
|
"default": "./esm/index.js"
|
|
74
74
|
},
|
|
75
|
+
"./server": {
|
|
76
|
+
"import": {
|
|
77
|
+
"types": "./esm/server.d.ts",
|
|
78
|
+
"default": "./esm/server.js"
|
|
79
|
+
},
|
|
80
|
+
"require": {
|
|
81
|
+
"types": "./cjs/server.d.ts",
|
|
82
|
+
"default": "./cjs/server.js"
|
|
83
|
+
},
|
|
84
|
+
"default": "./esm/server.js"
|
|
85
|
+
},
|
|
86
|
+
"./app": {
|
|
87
|
+
"import": {
|
|
88
|
+
"types": "./esm/app.d.ts",
|
|
89
|
+
"default": "./esm/app.js"
|
|
90
|
+
},
|
|
91
|
+
"require": {
|
|
92
|
+
"types": "./cjs/app.d.ts",
|
|
93
|
+
"default": "./cjs/app.js"
|
|
94
|
+
},
|
|
95
|
+
"default": "./esm/app.js"
|
|
96
|
+
},
|
|
75
97
|
"./testing": {
|
|
76
98
|
"import": {
|
|
77
99
|
"types": "./esm/testing.d.ts",
|