progressive-zod 1.2.0 → 1.3.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/.claude/commands/instrument.md +12 -1
- package/README.md +11 -1
- package/dist/cli/index.js +105 -47
- package/dist/client.cjs +49 -83
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -2
- package/dist/client.d.ts +2 -2
- package/dist/client.js +49 -73
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +136 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +136 -74
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -27,8 +27,12 @@ Search for code patterns that handle data without runtime validation:
|
|
|
27
27
|
For each boundary found, add a `progressive()` call. Use descriptive names that identify the boundary.
|
|
28
28
|
|
|
29
29
|
```typescript
|
|
30
|
+
// Server-side code (Node.js, Express, etc.)
|
|
30
31
|
import { progressive } from "progressive-zod";
|
|
31
32
|
|
|
33
|
+
// Frontend code (Vite, Next.js client, etc.)
|
|
34
|
+
import { progressive } from "progressive-zod/client";
|
|
35
|
+
|
|
32
36
|
// Before: untyped
|
|
33
37
|
const data = req.body;
|
|
34
38
|
|
|
@@ -44,11 +48,15 @@ const data = UserCreate.parse(req.body);
|
|
|
44
48
|
|
|
45
49
|
### 3. Ensure progressive-zod is configured
|
|
46
50
|
|
|
47
|
-
If there's no existing setup, add
|
|
51
|
+
If there's no existing setup, add configuration to the app's entry point. Use the right import path based on the environment:
|
|
48
52
|
|
|
49
53
|
```typescript
|
|
54
|
+
// Server-side (Node.js, Express, etc.)
|
|
50
55
|
import { configure } from "progressive-zod";
|
|
51
56
|
|
|
57
|
+
// Frontend (Vite, Next.js client, etc.) — avoids bundling ioredis
|
|
58
|
+
import { configure } from "progressive-zod/client";
|
|
59
|
+
|
|
52
60
|
// For localhost development — no Redis needed
|
|
53
61
|
configure({
|
|
54
62
|
storage: "memory",
|
|
@@ -56,6 +64,8 @@ configure({
|
|
|
56
64
|
});
|
|
57
65
|
```
|
|
58
66
|
|
|
67
|
+
**Important:** In frontend/browser code, always use `progressive-zod/client` to avoid bundler errors from server-only dependencies (ioredis). The client entry supports memory and Amplitude storage but not Redis.
|
|
68
|
+
|
|
59
69
|
Add `.progressive-zod/` to `.gitignore` if not already there.
|
|
60
70
|
|
|
61
71
|
### 4. Show the user what you did
|
|
@@ -75,6 +85,7 @@ After instrumenting, summarize:
|
|
|
75
85
|
- **progressive() never throws** — it always returns input unchanged, so it's safe to add anywhere
|
|
76
86
|
- **Be conservative** — only instrument clear untyped boundaries, don't wrap already-typed internal code
|
|
77
87
|
- **One progressive() per boundary** — don't double-wrap
|
|
88
|
+
- **Use the right import path** — use `progressive-zod/client` in frontend/browser code (Vite, Next.js client components, etc.) and `progressive-zod` in server-side code. The client entry excludes Redis to avoid bundler errors.
|
|
78
89
|
- If the user already has Zod schemas for some boundaries, pass them as the second argument:
|
|
79
90
|
```typescript
|
|
80
91
|
const UserCreate = progressive("UserCreate", existingUserSchema);
|
package/README.md
CHANGED
|
@@ -28,6 +28,16 @@ app.post("/users", (req, res) => {
|
|
|
28
28
|
});
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
### Using in a frontend (Vite, Next.js, etc.)
|
|
32
|
+
|
|
33
|
+
If you're bundling with Vite or another frontend bundler, import from the client entry point to avoid pulling in server-only dependencies like `ioredis`:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { progressive } from "progressive-zod/client";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The client entry supports memory and Amplitude storage. If you need Redis, use the server entry (`progressive-zod`).
|
|
40
|
+
|
|
31
41
|
Run your app, hit some endpoints, then:
|
|
32
42
|
|
|
33
43
|
```bash
|
|
@@ -113,7 +123,7 @@ This package includes a `/instrument` skill for [Claude Code](https://docs.anthr
|
|
|
113
123
|
|
|
114
124
|
| Option | Env var | Default | Description |
|
|
115
125
|
|--------|---------|---------|-------------|
|
|
116
|
-
| `storage` | `PROGRESSIVE_ZOD_STORAGE` | `"memory"` | `"memory"` or `"
|
|
126
|
+
| `storage` | `PROGRESSIVE_ZOD_STORAGE` | `"memory"` | `"memory"`, `"redis"`, or `"amplitude"` |
|
|
117
127
|
| `redisUrl` | `PROGRESSIVE_ZOD_REDIS_URL` | `redis://localhost:6379` | Redis URL (only for redis storage) |
|
|
118
128
|
| `keyPrefix` | `PROGRESSIVE_ZOD_KEY_PREFIX` | `pzod:` | Key namespace prefix |
|
|
119
129
|
| `maxSamples` | `PROGRESSIVE_ZOD_MAX_SAMPLES` | `1000` | Max samples per type |
|
package/dist/cli/index.js
CHANGED
|
@@ -15,6 +15,13 @@ var amplitude_exports = {};
|
|
|
15
15
|
__export(amplitude_exports, {
|
|
16
16
|
AmplitudeStorage: () => AmplitudeStorage
|
|
17
17
|
});
|
|
18
|
+
function flattenToString(value) {
|
|
19
|
+
if (value === null || value === void 0) return String(value);
|
|
20
|
+
if (typeof value !== "object") return String(value);
|
|
21
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
22
|
+
const entries = Object.entries(value);
|
|
23
|
+
return entries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join("; ");
|
|
24
|
+
}
|
|
18
25
|
var AmplitudeStorage;
|
|
19
26
|
var init_amplitude = __esm({
|
|
20
27
|
"src/storage/amplitude.ts"() {
|
|
@@ -32,17 +39,37 @@ var init_amplitude = __esm({
|
|
|
32
39
|
}
|
|
33
40
|
addViolation(_name, _violation) {
|
|
34
41
|
}
|
|
35
|
-
incrConform(name) {
|
|
42
|
+
incrConform(name, _sample) {
|
|
36
43
|
this.client.track(
|
|
37
44
|
"pzod:type_checked",
|
|
38
45
|
{ type_name: name, result: "conform" },
|
|
39
46
|
{ device_id: this.deviceId }
|
|
40
47
|
);
|
|
41
48
|
}
|
|
42
|
-
incrViolate(name) {
|
|
49
|
+
incrViolate(name, sample, errors) {
|
|
50
|
+
const properties = {
|
|
51
|
+
type_name: name,
|
|
52
|
+
result: "violate"
|
|
53
|
+
};
|
|
54
|
+
if (sample) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(sample);
|
|
57
|
+
properties.sample_type = typeof parsed;
|
|
58
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
59
|
+
properties.field_count = Object.keys(parsed).length;
|
|
60
|
+
}
|
|
61
|
+
properties.sample_preview = flattenToString(parsed).slice(0, 256);
|
|
62
|
+
} catch {
|
|
63
|
+
properties.sample_type = "unknown";
|
|
64
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (errors) {
|
|
68
|
+
properties.validation_errors = errors.slice(0, 1024);
|
|
69
|
+
}
|
|
43
70
|
this.client.track(
|
|
44
71
|
"pzod:type_checked",
|
|
45
|
-
|
|
72
|
+
properties,
|
|
46
73
|
{ device_id: this.deviceId }
|
|
47
74
|
);
|
|
48
75
|
}
|
|
@@ -169,22 +196,14 @@ var init_redis = __esm({
|
|
|
169
196
|
import { Command } from "commander";
|
|
170
197
|
|
|
171
198
|
// src/storage/memory.ts
|
|
172
|
-
import * as fs from "fs";
|
|
173
|
-
import * as path from "path";
|
|
174
199
|
var MemoryStorage = class {
|
|
175
200
|
names = /* @__PURE__ */ new Set();
|
|
176
201
|
data = /* @__PURE__ */ new Map();
|
|
177
202
|
maxSamples;
|
|
178
203
|
maxViolations;
|
|
179
|
-
dataDir;
|
|
180
|
-
flushTimer;
|
|
181
204
|
constructor(config = {}) {
|
|
182
205
|
this.maxSamples = config.maxSamples ?? 1e3;
|
|
183
206
|
this.maxViolations = config.maxViolations ?? 1e3;
|
|
184
|
-
this.dataDir = config.dataDir;
|
|
185
|
-
if (this.dataDir) {
|
|
186
|
-
this.loadFromDisk();
|
|
187
|
-
}
|
|
188
207
|
}
|
|
189
208
|
getOrCreate(name) {
|
|
190
209
|
let entry = this.data.get(name);
|
|
@@ -196,7 +215,6 @@ var MemoryStorage = class {
|
|
|
196
215
|
}
|
|
197
216
|
addName(name) {
|
|
198
217
|
this.names.add(name);
|
|
199
|
-
this.schedulePersist();
|
|
200
218
|
}
|
|
201
219
|
addSample(name, sample) {
|
|
202
220
|
const entry = this.getOrCreate(name);
|
|
@@ -204,7 +222,6 @@ var MemoryStorage = class {
|
|
|
204
222
|
if (entry.samples.length > this.maxSamples) {
|
|
205
223
|
entry.samples.length = this.maxSamples;
|
|
206
224
|
}
|
|
207
|
-
this.schedulePersist();
|
|
208
225
|
}
|
|
209
226
|
addViolation(name, violation) {
|
|
210
227
|
const entry = this.getOrCreate(name);
|
|
@@ -212,15 +229,12 @@ var MemoryStorage = class {
|
|
|
212
229
|
if (entry.violations.length > this.maxViolations) {
|
|
213
230
|
entry.violations.length = this.maxViolations;
|
|
214
231
|
}
|
|
215
|
-
this.schedulePersist();
|
|
216
232
|
}
|
|
217
233
|
incrConform(name) {
|
|
218
234
|
this.getOrCreate(name).conform++;
|
|
219
|
-
this.schedulePersist();
|
|
220
235
|
}
|
|
221
236
|
incrViolate(name) {
|
|
222
237
|
this.getOrCreate(name).violate++;
|
|
223
|
-
this.schedulePersist();
|
|
224
238
|
}
|
|
225
239
|
getNames() {
|
|
226
240
|
return [...this.names].sort();
|
|
@@ -235,6 +249,79 @@ var MemoryStorage = class {
|
|
|
235
249
|
const entry = this.getOrCreate(name);
|
|
236
250
|
return { conform: entry.conform, violate: entry.violate };
|
|
237
251
|
}
|
|
252
|
+
disconnect() {
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/storage/resolve.ts
|
|
257
|
+
var currentConfig = {};
|
|
258
|
+
var currentStorage = null;
|
|
259
|
+
var storageFactory = async (config) => new MemoryStorage(config);
|
|
260
|
+
function _setStorageFactory(factory) {
|
|
261
|
+
storageFactory = factory;
|
|
262
|
+
}
|
|
263
|
+
function env(key) {
|
|
264
|
+
if (typeof process !== "undefined" && process.env) {
|
|
265
|
+
return process.env[key];
|
|
266
|
+
}
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
function getConfig() {
|
|
270
|
+
return {
|
|
271
|
+
storage: currentConfig.storage ?? env("PROGRESSIVE_ZOD_STORAGE") ?? "memory",
|
|
272
|
+
redisUrl: currentConfig.redisUrl ?? env("PROGRESSIVE_ZOD_REDIS_URL"),
|
|
273
|
+
keyPrefix: currentConfig.keyPrefix ?? env("PROGRESSIVE_ZOD_KEY_PREFIX") ?? "pzod:",
|
|
274
|
+
maxViolations: currentConfig.maxViolations ?? parseInt(env("PROGRESSIVE_ZOD_MAX_VIOLATIONS") ?? "1000", 10),
|
|
275
|
+
maxSamples: currentConfig.maxSamples ?? parseInt(env("PROGRESSIVE_ZOD_MAX_SAMPLES") ?? "1000", 10),
|
|
276
|
+
dataDir: currentConfig.dataDir ?? env("PROGRESSIVE_ZOD_DATA_DIR")
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
async function getStorage() {
|
|
280
|
+
if (currentStorage) return currentStorage;
|
|
281
|
+
const config = getConfig();
|
|
282
|
+
currentStorage = await storageFactory(config, currentConfig);
|
|
283
|
+
return currentStorage;
|
|
284
|
+
}
|
|
285
|
+
async function disconnectStorage() {
|
|
286
|
+
if (currentStorage) {
|
|
287
|
+
await currentStorage.disconnect();
|
|
288
|
+
currentStorage = null;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/storage/memory-server.ts
|
|
293
|
+
import * as fs from "fs";
|
|
294
|
+
import * as path from "path";
|
|
295
|
+
var PersistentMemoryStorage = class extends MemoryStorage {
|
|
296
|
+
dataDir;
|
|
297
|
+
flushTimer;
|
|
298
|
+
constructor(config = {}) {
|
|
299
|
+
super(config);
|
|
300
|
+
this.dataDir = config.dataDir;
|
|
301
|
+
if (this.dataDir) {
|
|
302
|
+
this.loadFromDisk();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
addName(name) {
|
|
306
|
+
super.addName(name);
|
|
307
|
+
this.schedulePersist();
|
|
308
|
+
}
|
|
309
|
+
addSample(name, sample) {
|
|
310
|
+
super.addSample(name, sample);
|
|
311
|
+
this.schedulePersist();
|
|
312
|
+
}
|
|
313
|
+
addViolation(name, violation) {
|
|
314
|
+
super.addViolation(name, violation);
|
|
315
|
+
this.schedulePersist();
|
|
316
|
+
}
|
|
317
|
+
incrConform(name) {
|
|
318
|
+
super.incrConform(name);
|
|
319
|
+
this.schedulePersist();
|
|
320
|
+
}
|
|
321
|
+
incrViolate(name) {
|
|
322
|
+
super.incrViolate(name);
|
|
323
|
+
this.schedulePersist();
|
|
324
|
+
}
|
|
238
325
|
disconnect() {
|
|
239
326
|
if (this.flushTimer) {
|
|
240
327
|
clearTimeout(this.flushTimer);
|
|
@@ -287,36 +374,6 @@ var MemoryStorage = class {
|
|
|
287
374
|
}
|
|
288
375
|
};
|
|
289
376
|
|
|
290
|
-
// src/storage/resolve.ts
|
|
291
|
-
var currentConfig = {};
|
|
292
|
-
var currentStorage = null;
|
|
293
|
-
var storageFactory = async (config) => new MemoryStorage(config);
|
|
294
|
-
function _setStorageFactory(factory) {
|
|
295
|
-
storageFactory = factory;
|
|
296
|
-
}
|
|
297
|
-
function getConfig() {
|
|
298
|
-
return {
|
|
299
|
-
storage: currentConfig.storage ?? process.env.PROGRESSIVE_ZOD_STORAGE ?? "memory",
|
|
300
|
-
redisUrl: currentConfig.redisUrl ?? process.env.PROGRESSIVE_ZOD_REDIS_URL,
|
|
301
|
-
keyPrefix: currentConfig.keyPrefix ?? process.env.PROGRESSIVE_ZOD_KEY_PREFIX ?? "pzod:",
|
|
302
|
-
maxViolations: currentConfig.maxViolations ?? parseInt(process.env.PROGRESSIVE_ZOD_MAX_VIOLATIONS ?? "1000", 10),
|
|
303
|
-
maxSamples: currentConfig.maxSamples ?? parseInt(process.env.PROGRESSIVE_ZOD_MAX_SAMPLES ?? "1000", 10),
|
|
304
|
-
dataDir: currentConfig.dataDir ?? process.env.PROGRESSIVE_ZOD_DATA_DIR ?? ".progressive-zod"
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
async function getStorage() {
|
|
308
|
-
if (currentStorage) return currentStorage;
|
|
309
|
-
const config = getConfig();
|
|
310
|
-
currentStorage = await storageFactory(config, currentConfig);
|
|
311
|
-
return currentStorage;
|
|
312
|
-
}
|
|
313
|
-
async function disconnectStorage() {
|
|
314
|
-
if (currentStorage) {
|
|
315
|
-
await currentStorage.disconnect();
|
|
316
|
-
currentStorage = null;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
377
|
// src/storage/index.ts
|
|
321
378
|
_setStorageFactory(async (config, userConfig) => {
|
|
322
379
|
if (config.storage === "amplitude") {
|
|
@@ -331,7 +388,8 @@ _setStorageFactory(async (config, userConfig) => {
|
|
|
331
388
|
const { RedisStorage: RedisStorage2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
|
|
332
389
|
return new RedisStorage2(config);
|
|
333
390
|
}
|
|
334
|
-
|
|
391
|
+
const serverConfig = { ...config, dataDir: config.dataDir ?? ".progressive-zod" };
|
|
392
|
+
return new PersistentMemoryStorage(serverConfig);
|
|
335
393
|
});
|
|
336
394
|
|
|
337
395
|
// src/cli/commands/list.ts
|
package/dist/client.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __esm = (fn, res) => function __init() {
|
|
9
7
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
@@ -20,14 +18,6 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
20
18
|
}
|
|
21
19
|
return to;
|
|
22
20
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
-
mod
|
|
30
|
-
));
|
|
31
21
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
22
|
|
|
33
23
|
// src/storage/amplitude.ts
|
|
@@ -35,6 +25,13 @@ var amplitude_exports = {};
|
|
|
35
25
|
__export(amplitude_exports, {
|
|
36
26
|
AmplitudeStorage: () => AmplitudeStorage
|
|
37
27
|
});
|
|
28
|
+
function flattenToString(value) {
|
|
29
|
+
if (value === null || value === void 0) return String(value);
|
|
30
|
+
if (typeof value !== "object") return String(value);
|
|
31
|
+
if (Array.isArray(value)) return value.map(String).join(", ");
|
|
32
|
+
const entries = Object.entries(value);
|
|
33
|
+
return entries.map(([k, v]) => `${k}=${JSON.stringify(v)}`).join("; ");
|
|
34
|
+
}
|
|
38
35
|
var AmplitudeStorage;
|
|
39
36
|
var init_amplitude = __esm({
|
|
40
37
|
"src/storage/amplitude.ts"() {
|
|
@@ -52,17 +49,37 @@ var init_amplitude = __esm({
|
|
|
52
49
|
}
|
|
53
50
|
addViolation(_name, _violation) {
|
|
54
51
|
}
|
|
55
|
-
incrConform(name) {
|
|
52
|
+
incrConform(name, _sample) {
|
|
56
53
|
this.client.track(
|
|
57
54
|
"pzod:type_checked",
|
|
58
55
|
{ type_name: name, result: "conform" },
|
|
59
56
|
{ device_id: this.deviceId }
|
|
60
57
|
);
|
|
61
58
|
}
|
|
62
|
-
incrViolate(name) {
|
|
59
|
+
incrViolate(name, sample, errors) {
|
|
60
|
+
const properties = {
|
|
61
|
+
type_name: name,
|
|
62
|
+
result: "violate"
|
|
63
|
+
};
|
|
64
|
+
if (sample) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = JSON.parse(sample);
|
|
67
|
+
properties.sample_type = typeof parsed;
|
|
68
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
69
|
+
properties.field_count = Object.keys(parsed).length;
|
|
70
|
+
}
|
|
71
|
+
properties.sample_preview = flattenToString(parsed).slice(0, 256);
|
|
72
|
+
} catch {
|
|
73
|
+
properties.sample_type = "unknown";
|
|
74
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (errors) {
|
|
78
|
+
properties.validation_errors = errors.slice(0, 1024);
|
|
79
|
+
}
|
|
63
80
|
this.client.track(
|
|
64
81
|
"pzod:type_checked",
|
|
65
|
-
|
|
82
|
+
properties,
|
|
66
83
|
{ device_id: this.deviceId }
|
|
67
84
|
);
|
|
68
85
|
}
|
|
@@ -99,22 +116,14 @@ __export(client_exports, {
|
|
|
99
116
|
module.exports = __toCommonJS(client_exports);
|
|
100
117
|
|
|
101
118
|
// src/storage/memory.ts
|
|
102
|
-
var fs = __toESM(require("fs"), 1);
|
|
103
|
-
var path = __toESM(require("path"), 1);
|
|
104
119
|
var MemoryStorage = class {
|
|
105
120
|
names = /* @__PURE__ */ new Set();
|
|
106
121
|
data = /* @__PURE__ */ new Map();
|
|
107
122
|
maxSamples;
|
|
108
123
|
maxViolations;
|
|
109
|
-
dataDir;
|
|
110
|
-
flushTimer;
|
|
111
124
|
constructor(config = {}) {
|
|
112
125
|
this.maxSamples = config.maxSamples ?? 1e3;
|
|
113
126
|
this.maxViolations = config.maxViolations ?? 1e3;
|
|
114
|
-
this.dataDir = config.dataDir;
|
|
115
|
-
if (this.dataDir) {
|
|
116
|
-
this.loadFromDisk();
|
|
117
|
-
}
|
|
118
127
|
}
|
|
119
128
|
getOrCreate(name) {
|
|
120
129
|
let entry = this.data.get(name);
|
|
@@ -126,7 +135,6 @@ var MemoryStorage = class {
|
|
|
126
135
|
}
|
|
127
136
|
addName(name) {
|
|
128
137
|
this.names.add(name);
|
|
129
|
-
this.schedulePersist();
|
|
130
138
|
}
|
|
131
139
|
addSample(name, sample) {
|
|
132
140
|
const entry = this.getOrCreate(name);
|
|
@@ -134,7 +142,6 @@ var MemoryStorage = class {
|
|
|
134
142
|
if (entry.samples.length > this.maxSamples) {
|
|
135
143
|
entry.samples.length = this.maxSamples;
|
|
136
144
|
}
|
|
137
|
-
this.schedulePersist();
|
|
138
145
|
}
|
|
139
146
|
addViolation(name, violation) {
|
|
140
147
|
const entry = this.getOrCreate(name);
|
|
@@ -142,15 +149,12 @@ var MemoryStorage = class {
|
|
|
142
149
|
if (entry.violations.length > this.maxViolations) {
|
|
143
150
|
entry.violations.length = this.maxViolations;
|
|
144
151
|
}
|
|
145
|
-
this.schedulePersist();
|
|
146
152
|
}
|
|
147
153
|
incrConform(name) {
|
|
148
154
|
this.getOrCreate(name).conform++;
|
|
149
|
-
this.schedulePersist();
|
|
150
155
|
}
|
|
151
156
|
incrViolate(name) {
|
|
152
157
|
this.getOrCreate(name).violate++;
|
|
153
|
-
this.schedulePersist();
|
|
154
158
|
}
|
|
155
159
|
getNames() {
|
|
156
160
|
return [...this.names].sort();
|
|
@@ -166,54 +170,6 @@ var MemoryStorage = class {
|
|
|
166
170
|
return { conform: entry.conform, violate: entry.violate };
|
|
167
171
|
}
|
|
168
172
|
disconnect() {
|
|
169
|
-
if (this.flushTimer) {
|
|
170
|
-
clearTimeout(this.flushTimer);
|
|
171
|
-
this.flushTimer = void 0;
|
|
172
|
-
}
|
|
173
|
-
if (this.dataDir) {
|
|
174
|
-
this.persistToDisk();
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
schedulePersist() {
|
|
178
|
-
if (!this.dataDir || this.flushTimer) return;
|
|
179
|
-
this.flushTimer = setTimeout(() => {
|
|
180
|
-
this.flushTimer = void 0;
|
|
181
|
-
this.persistToDisk();
|
|
182
|
-
}, 500);
|
|
183
|
-
}
|
|
184
|
-
persistToDisk() {
|
|
185
|
-
if (!this.dataDir) return;
|
|
186
|
-
try {
|
|
187
|
-
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
188
|
-
const snapshot = {
|
|
189
|
-
names: [...this.names],
|
|
190
|
-
types: Object.fromEntries(this.data)
|
|
191
|
-
};
|
|
192
|
-
fs.writeFileSync(
|
|
193
|
-
path.join(this.dataDir, "data.json"),
|
|
194
|
-
JSON.stringify(snapshot, null, 2)
|
|
195
|
-
);
|
|
196
|
-
} catch {
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
loadFromDisk() {
|
|
200
|
-
if (!this.dataDir) return;
|
|
201
|
-
try {
|
|
202
|
-
const raw = fs.readFileSync(
|
|
203
|
-
path.join(this.dataDir, "data.json"),
|
|
204
|
-
"utf-8"
|
|
205
|
-
);
|
|
206
|
-
const snapshot = JSON.parse(raw);
|
|
207
|
-
if (Array.isArray(snapshot.names)) {
|
|
208
|
-
for (const n of snapshot.names) this.names.add(n);
|
|
209
|
-
}
|
|
210
|
-
if (snapshot.types && typeof snapshot.types === "object") {
|
|
211
|
-
for (const [key, val] of Object.entries(snapshot.types)) {
|
|
212
|
-
this.data.set(key, val);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
} catch {
|
|
216
|
-
}
|
|
217
173
|
}
|
|
218
174
|
};
|
|
219
175
|
|
|
@@ -231,14 +187,20 @@ function configure(config) {
|
|
|
231
187
|
currentStorage = null;
|
|
232
188
|
}
|
|
233
189
|
}
|
|
190
|
+
function env(key) {
|
|
191
|
+
if (typeof process !== "undefined" && process.env) {
|
|
192
|
+
return process.env[key];
|
|
193
|
+
}
|
|
194
|
+
return void 0;
|
|
195
|
+
}
|
|
234
196
|
function getConfig() {
|
|
235
197
|
return {
|
|
236
|
-
storage: currentConfig.storage ??
|
|
237
|
-
redisUrl: currentConfig.redisUrl ??
|
|
238
|
-
keyPrefix: currentConfig.keyPrefix ??
|
|
239
|
-
maxViolations: currentConfig.maxViolations ?? parseInt(
|
|
240
|
-
maxSamples: currentConfig.maxSamples ?? parseInt(
|
|
241
|
-
dataDir: currentConfig.dataDir ??
|
|
198
|
+
storage: currentConfig.storage ?? env("PROGRESSIVE_ZOD_STORAGE") ?? "memory",
|
|
199
|
+
redisUrl: currentConfig.redisUrl ?? env("PROGRESSIVE_ZOD_REDIS_URL"),
|
|
200
|
+
keyPrefix: currentConfig.keyPrefix ?? env("PROGRESSIVE_ZOD_KEY_PREFIX") ?? "pzod:",
|
|
201
|
+
maxViolations: currentConfig.maxViolations ?? parseInt(env("PROGRESSIVE_ZOD_MAX_VIOLATIONS") ?? "1000", 10),
|
|
202
|
+
maxSamples: currentConfig.maxSamples ?? parseInt(env("PROGRESSIVE_ZOD_MAX_SAMPLES") ?? "1000", 10),
|
|
203
|
+
dataDir: currentConfig.dataDir ?? env("PROGRESSIVE_ZOD_DATA_DIR")
|
|
242
204
|
};
|
|
243
205
|
}
|
|
244
206
|
async function getStorage() {
|
|
@@ -360,13 +322,17 @@ var BatchProcessor = class {
|
|
|
360
322
|
if (obs.schema) {
|
|
361
323
|
const result = obs.schema.safeParse(JSON.parse(obs.serialized));
|
|
362
324
|
if (result.success) {
|
|
363
|
-
await storage.incrConform(obs.name);
|
|
325
|
+
await storage.incrConform(obs.name, obs.serialized);
|
|
364
326
|
} else {
|
|
365
|
-
|
|
327
|
+
const errors = result.error.issues.map((i) => {
|
|
328
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
329
|
+
return `${path}: ${i.message}`;
|
|
330
|
+
}).join("; ");
|
|
331
|
+
await storage.incrViolate(obs.name, obs.serialized, errors);
|
|
366
332
|
await storage.addViolation(obs.name, obs.serialized);
|
|
367
333
|
}
|
|
368
334
|
} else {
|
|
369
|
-
await storage.incrViolate(obs.name);
|
|
335
|
+
await storage.incrViolate(obs.name, obs.serialized, "no schema defined");
|
|
370
336
|
await storage.addViolation(obs.name, obs.serialized);
|
|
371
337
|
}
|
|
372
338
|
}
|