@vettly/shared 0.1.9

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/dist/index.js ADDED
@@ -0,0 +1,299 @@
1
+ // src/types.ts
2
+ import { z } from "zod";
3
+ var ContentTypeSchema = z.enum(["text", "image", "video"]);
4
+ var UseCaseTypeSchema = z.enum([
5
+ "social_post",
6
+ "comment",
7
+ "profile",
8
+ "message",
9
+ "review",
10
+ "listing",
11
+ "bio",
12
+ "other"
13
+ ]);
14
+ var CategorySchema = z.enum([
15
+ "hate_speech",
16
+ "harassment",
17
+ "violence",
18
+ "self_harm",
19
+ "sexual",
20
+ "spam",
21
+ "profanity",
22
+ "scam",
23
+ "illegal"
24
+ ]);
25
+ var ProviderNameSchema = z.enum([
26
+ "openai",
27
+ "perspective",
28
+ "hive",
29
+ "azure",
30
+ "bot_detection",
31
+ "gemini_vision",
32
+ "mock",
33
+ "fallback"
34
+ ]);
35
+ var ActionSchema = z.enum(["block", "warn", "flag", "allow"]);
36
+ var RuleSchema = z.object({
37
+ category: CategorySchema,
38
+ threshold: z.number().min(0).max(1),
39
+ provider: ProviderNameSchema,
40
+ action: ActionSchema,
41
+ priority: z.number().optional().default(0),
42
+ // Custom prompt-based moderation (Pro+ tier only)
43
+ // When set, uses Gemini Vision for semantic image analysis
44
+ customPrompt: z.string().min(1).max(500).optional(),
45
+ customCategory: z.string().min(1).max(100).optional()
46
+ }).refine(
47
+ (data) => {
48
+ const hasPrompt = !!data.customPrompt;
49
+ const hasCategory = !!data.customCategory;
50
+ return hasPrompt === hasCategory;
51
+ },
52
+ {
53
+ message: "customPrompt and customCategory must both be set, or both be omitted",
54
+ path: ["customPrompt"]
55
+ }
56
+ );
57
+ var OverrideSchema = z.object({
58
+ locale: z.string().optional(),
59
+ region: z.string().optional(),
60
+ data_residency: z.string().optional(),
61
+ provider: ProviderNameSchema.optional()
62
+ });
63
+ var FallbackConfigSchema = z.object({
64
+ provider: ProviderNameSchema,
65
+ on_timeout: z.boolean().optional().default(true),
66
+ timeout_ms: z.number().optional().default(5e3)
67
+ });
68
+ var PolicySchema = z.object({
69
+ name: z.string(),
70
+ version: z.string(),
71
+ rules: z.array(RuleSchema),
72
+ overrides: z.array(OverrideSchema).optional(),
73
+ fallback: FallbackConfigSchema.optional()
74
+ });
75
+ var DecisionSchema = z.object({
76
+ id: z.string().uuid(),
77
+ content: z.string(),
78
+ contentHash: z.string(),
79
+ contentType: ContentTypeSchema,
80
+ policy: z.object({
81
+ id: z.string(),
82
+ version: z.string()
83
+ }),
84
+ result: z.object({
85
+ safe: z.boolean(),
86
+ flagged: z.boolean(),
87
+ action: ActionSchema,
88
+ categories: z.array(
89
+ z.object({
90
+ category: CategorySchema,
91
+ score: z.number(),
92
+ threshold: z.number(),
93
+ triggered: z.boolean()
94
+ })
95
+ )
96
+ }),
97
+ provider: z.object({
98
+ name: ProviderNameSchema,
99
+ latency: z.number(),
100
+ cost: z.number()
101
+ }),
102
+ metadata: z.record(z.unknown()).optional(),
103
+ timestamp: z.string().datetime(),
104
+ requestId: z.string().optional()
105
+ });
106
+ var MultiModalCheckRequestSchema = z.object({
107
+ // Multi-modal content inputs
108
+ text: z.string().max(1e5, "Text exceeds maximum size of 100KB").optional(),
109
+ images: z.array(
110
+ z.string().refine(
111
+ (val) => {
112
+ return val.startsWith("http://") || val.startsWith("https://") || val.startsWith("data:image/");
113
+ },
114
+ { message: "Images must be URLs or base64 data URIs" }
115
+ )
116
+ ).max(10, "Maximum 10 images per request").optional(),
117
+ video: z.string().url("Video must be a valid URL").optional(),
118
+ // Moderation context
119
+ context: z.object({
120
+ useCase: UseCaseTypeSchema.default("other"),
121
+ userId: z.string().optional(),
122
+ userReputation: z.number().min(0).max(1).optional(),
123
+ // 0-1 score
124
+ locale: z.string().optional(),
125
+ region: z.string().optional()
126
+ }).optional(),
127
+ // Policy and metadata
128
+ policyId: z.string(),
129
+ metadata: z.record(z.unknown()).optional(),
130
+ requestId: z.string().optional()
131
+ // For idempotency
132
+ }).refine(
133
+ (data) => {
134
+ return data.text || data.images?.length || data.video;
135
+ },
136
+ { message: "At least one of text, images, or video must be provided" }
137
+ );
138
+ var CheckRequestSchema = z.object({
139
+ content: z.string().min(1).max(1e5, "Content exceeds maximum size of 100KB"),
140
+ policyId: z.string(),
141
+ contentType: ContentTypeSchema.optional().default("text"),
142
+ metadata: z.record(z.unknown()).optional(),
143
+ requestId: z.string().optional()
144
+ // For idempotency
145
+ });
146
+ var ContentItemResultSchema = z.object({
147
+ contentType: ContentTypeSchema,
148
+ contentRef: z.string().optional(),
149
+ // URL or identifier for images/video
150
+ contentItemId: z.string().uuid().optional(),
151
+ // Database ID for linking evidence
152
+ safe: z.boolean(),
153
+ flagged: z.boolean(),
154
+ action: ActionSchema,
155
+ categories: z.array(
156
+ z.object({
157
+ category: CategorySchema,
158
+ score: z.number(),
159
+ triggered: z.boolean()
160
+ })
161
+ ),
162
+ provider: ProviderNameSchema,
163
+ latency: z.number(),
164
+ cost: z.number(),
165
+ evidence: z.object({
166
+ url: z.string().url().optional(),
167
+ // Signed URL to evidence (screenshot, frame)
168
+ expiresAt: z.string().datetime().optional()
169
+ }).optional()
170
+ });
171
+ var MultiModalCheckResponseSchema = z.object({
172
+ decisionId: z.string().uuid(),
173
+ safe: z.boolean(),
174
+ // Overall safe if ALL content items are safe
175
+ flagged: z.boolean(),
176
+ // Overall flagged if ANY content item is flagged
177
+ action: ActionSchema,
178
+ // Most severe action across all content
179
+ results: z.array(ContentItemResultSchema),
180
+ // Per-content-type results
181
+ totalLatency: z.number(),
182
+ totalCost: z.number(),
183
+ requestId: z.string().optional()
184
+ });
185
+ var CheckResponseSchema = z.object({
186
+ decisionId: z.string().uuid(),
187
+ safe: z.boolean(),
188
+ flagged: z.boolean(),
189
+ action: ActionSchema,
190
+ categories: z.array(
191
+ z.object({
192
+ category: CategorySchema,
193
+ score: z.number(),
194
+ triggered: z.boolean()
195
+ })
196
+ ),
197
+ provider: ProviderNameSchema,
198
+ latency: z.number(),
199
+ cost: z.number(),
200
+ requestId: z.string().optional()
201
+ });
202
+ var ReplayRequestSchema = z.object({
203
+ decisionId: z.string().uuid(),
204
+ policyId: z.string()
205
+ });
206
+ var ModerationError = class extends Error {
207
+ constructor(message, code, statusCode = 500, details) {
208
+ super(message);
209
+ this.code = code;
210
+ this.statusCode = statusCode;
211
+ this.details = details;
212
+ this.name = "ModerationError";
213
+ }
214
+ };
215
+ var PolicyValidationError = class extends ModerationError {
216
+ constructor(message, details) {
217
+ super(message, "POLICY_VALIDATION_ERROR", 400, details);
218
+ this.name = "PolicyValidationError";
219
+ }
220
+ };
221
+ var ProviderError = class extends ModerationError {
222
+ constructor(message, provider, details) {
223
+ super(message, "PROVIDER_ERROR", 502, { provider, ...details });
224
+ this.name = "ProviderError";
225
+ }
226
+ };
227
+ var WebhookEventTypeSchema = z.enum([
228
+ "decision.created",
229
+ "decision.flagged",
230
+ "decision.blocked",
231
+ "policy.created",
232
+ "policy.updated"
233
+ ]);
234
+ var WebhookEndpointSchema = z.object({
235
+ url: z.string().url().refine(
236
+ (url) => {
237
+ if (process.env.NODE_ENV === "production") {
238
+ return !url.includes("localhost") && !url.includes("127.0.0.1");
239
+ }
240
+ return true;
241
+ },
242
+ "Localhost webhooks are not allowed in production"
243
+ ),
244
+ events: z.array(WebhookEventTypeSchema),
245
+ description: z.string().max(500).optional()
246
+ });
247
+
248
+ // src/utils.ts
249
+ import crypto from "crypto";
250
+ function hashContent(content) {
251
+ return crypto.createHash("sha256").update(content).digest("hex");
252
+ }
253
+ function generateUUID() {
254
+ return crypto.randomUUID();
255
+ }
256
+ function generateRequestId() {
257
+ return `req_${Date.now()}_${Math.random().toString(36).substring(7)}`;
258
+ }
259
+ function calculatePolicyVersion(yaml) {
260
+ return hashContent(yaml).substring(0, 16);
261
+ }
262
+ function formatCost(cost) {
263
+ return `$${cost.toFixed(6)}`;
264
+ }
265
+ function formatLatency(ms) {
266
+ if (ms < 1e3) {
267
+ return `${ms.toFixed(0)}ms`;
268
+ }
269
+ return `${(ms / 1e3).toFixed(2)}s`;
270
+ }
271
+ export {
272
+ ActionSchema,
273
+ CategorySchema,
274
+ CheckRequestSchema,
275
+ CheckResponseSchema,
276
+ ContentItemResultSchema,
277
+ ContentTypeSchema,
278
+ DecisionSchema,
279
+ FallbackConfigSchema,
280
+ ModerationError,
281
+ MultiModalCheckRequestSchema,
282
+ MultiModalCheckResponseSchema,
283
+ OverrideSchema,
284
+ PolicySchema,
285
+ PolicyValidationError,
286
+ ProviderError,
287
+ ProviderNameSchema,
288
+ ReplayRequestSchema,
289
+ RuleSchema,
290
+ UseCaseTypeSchema,
291
+ WebhookEndpointSchema,
292
+ WebhookEventTypeSchema,
293
+ calculatePolicyVersion,
294
+ formatCost,
295
+ formatLatency,
296
+ generateRequestId,
297
+ generateUUID,
298
+ hashContent
299
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@vettly/shared",
3
+ "version": "0.1.9",
4
+ "description": "Shared TypeScript types for Vettly protection SDK",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
21
+ "test": "bun test",
22
+ "prepublishOnly": "bun run build"
23
+ },
24
+ "keywords": [
25
+ "vettly",
26
+ "moderation",
27
+ "protection",
28
+ "types",
29
+ "typescript"
30
+ ],
31
+ "author": "Vettly",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/brian-nextaura/vettly-docs.git",
36
+ "directory": "packages/shared"
37
+ },
38
+ "homepage": "https://vettly.dev",
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "name": "@nextauralabs/vettly-shared"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/brian-nextaura/vettly-docs/issues"
45
+ },
46
+ "dependencies": {
47
+ "zod": "^3.22.4"
48
+ },
49
+ "devDependencies": {
50
+ "@types/bun": "latest",
51
+ "tsup": "^8.0.0",
52
+ "typescript": "^5"
53
+ }
54
+ }