progressive-zod 1.2.0 → 1.2.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/.claude/commands/instrument.md +12 -1
- package/README.md +11 -1
- package/dist/cli/index.js +98 -47
- package/dist/client.cjs +42 -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 +42 -73
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +129 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +129 -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
|
@@ -32,17 +32,37 @@ var init_amplitude = __esm({
|
|
|
32
32
|
}
|
|
33
33
|
addViolation(_name, _violation) {
|
|
34
34
|
}
|
|
35
|
-
incrConform(name) {
|
|
35
|
+
incrConform(name, _sample) {
|
|
36
36
|
this.client.track(
|
|
37
37
|
"pzod:type_checked",
|
|
38
38
|
{ type_name: name, result: "conform" },
|
|
39
39
|
{ device_id: this.deviceId }
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
|
-
incrViolate(name) {
|
|
42
|
+
incrViolate(name, sample, errors) {
|
|
43
|
+
const properties = {
|
|
44
|
+
type_name: name,
|
|
45
|
+
result: "violate"
|
|
46
|
+
};
|
|
47
|
+
if (sample) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(sample);
|
|
50
|
+
properties.sample_type = typeof parsed;
|
|
51
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
52
|
+
properties.field_count = Object.keys(parsed).length;
|
|
53
|
+
}
|
|
54
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
55
|
+
} catch {
|
|
56
|
+
properties.sample_type = "unknown";
|
|
57
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (errors) {
|
|
61
|
+
properties.validation_errors = errors.slice(0, 1024);
|
|
62
|
+
}
|
|
43
63
|
this.client.track(
|
|
44
64
|
"pzod:type_checked",
|
|
45
|
-
|
|
65
|
+
properties,
|
|
46
66
|
{ device_id: this.deviceId }
|
|
47
67
|
);
|
|
48
68
|
}
|
|
@@ -169,22 +189,14 @@ var init_redis = __esm({
|
|
|
169
189
|
import { Command } from "commander";
|
|
170
190
|
|
|
171
191
|
// src/storage/memory.ts
|
|
172
|
-
import * as fs from "fs";
|
|
173
|
-
import * as path from "path";
|
|
174
192
|
var MemoryStorage = class {
|
|
175
193
|
names = /* @__PURE__ */ new Set();
|
|
176
194
|
data = /* @__PURE__ */ new Map();
|
|
177
195
|
maxSamples;
|
|
178
196
|
maxViolations;
|
|
179
|
-
dataDir;
|
|
180
|
-
flushTimer;
|
|
181
197
|
constructor(config = {}) {
|
|
182
198
|
this.maxSamples = config.maxSamples ?? 1e3;
|
|
183
199
|
this.maxViolations = config.maxViolations ?? 1e3;
|
|
184
|
-
this.dataDir = config.dataDir;
|
|
185
|
-
if (this.dataDir) {
|
|
186
|
-
this.loadFromDisk();
|
|
187
|
-
}
|
|
188
200
|
}
|
|
189
201
|
getOrCreate(name) {
|
|
190
202
|
let entry = this.data.get(name);
|
|
@@ -196,7 +208,6 @@ var MemoryStorage = class {
|
|
|
196
208
|
}
|
|
197
209
|
addName(name) {
|
|
198
210
|
this.names.add(name);
|
|
199
|
-
this.schedulePersist();
|
|
200
211
|
}
|
|
201
212
|
addSample(name, sample) {
|
|
202
213
|
const entry = this.getOrCreate(name);
|
|
@@ -204,7 +215,6 @@ var MemoryStorage = class {
|
|
|
204
215
|
if (entry.samples.length > this.maxSamples) {
|
|
205
216
|
entry.samples.length = this.maxSamples;
|
|
206
217
|
}
|
|
207
|
-
this.schedulePersist();
|
|
208
218
|
}
|
|
209
219
|
addViolation(name, violation) {
|
|
210
220
|
const entry = this.getOrCreate(name);
|
|
@@ -212,15 +222,12 @@ var MemoryStorage = class {
|
|
|
212
222
|
if (entry.violations.length > this.maxViolations) {
|
|
213
223
|
entry.violations.length = this.maxViolations;
|
|
214
224
|
}
|
|
215
|
-
this.schedulePersist();
|
|
216
225
|
}
|
|
217
226
|
incrConform(name) {
|
|
218
227
|
this.getOrCreate(name).conform++;
|
|
219
|
-
this.schedulePersist();
|
|
220
228
|
}
|
|
221
229
|
incrViolate(name) {
|
|
222
230
|
this.getOrCreate(name).violate++;
|
|
223
|
-
this.schedulePersist();
|
|
224
231
|
}
|
|
225
232
|
getNames() {
|
|
226
233
|
return [...this.names].sort();
|
|
@@ -235,6 +242,79 @@ var MemoryStorage = class {
|
|
|
235
242
|
const entry = this.getOrCreate(name);
|
|
236
243
|
return { conform: entry.conform, violate: entry.violate };
|
|
237
244
|
}
|
|
245
|
+
disconnect() {
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// src/storage/resolve.ts
|
|
250
|
+
var currentConfig = {};
|
|
251
|
+
var currentStorage = null;
|
|
252
|
+
var storageFactory = async (config) => new MemoryStorage(config);
|
|
253
|
+
function _setStorageFactory(factory) {
|
|
254
|
+
storageFactory = factory;
|
|
255
|
+
}
|
|
256
|
+
function env(key) {
|
|
257
|
+
if (typeof process !== "undefined" && process.env) {
|
|
258
|
+
return process.env[key];
|
|
259
|
+
}
|
|
260
|
+
return void 0;
|
|
261
|
+
}
|
|
262
|
+
function getConfig() {
|
|
263
|
+
return {
|
|
264
|
+
storage: currentConfig.storage ?? env("PROGRESSIVE_ZOD_STORAGE") ?? "memory",
|
|
265
|
+
redisUrl: currentConfig.redisUrl ?? env("PROGRESSIVE_ZOD_REDIS_URL"),
|
|
266
|
+
keyPrefix: currentConfig.keyPrefix ?? env("PROGRESSIVE_ZOD_KEY_PREFIX") ?? "pzod:",
|
|
267
|
+
maxViolations: currentConfig.maxViolations ?? parseInt(env("PROGRESSIVE_ZOD_MAX_VIOLATIONS") ?? "1000", 10),
|
|
268
|
+
maxSamples: currentConfig.maxSamples ?? parseInt(env("PROGRESSIVE_ZOD_MAX_SAMPLES") ?? "1000", 10),
|
|
269
|
+
dataDir: currentConfig.dataDir ?? env("PROGRESSIVE_ZOD_DATA_DIR")
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function getStorage() {
|
|
273
|
+
if (currentStorage) return currentStorage;
|
|
274
|
+
const config = getConfig();
|
|
275
|
+
currentStorage = await storageFactory(config, currentConfig);
|
|
276
|
+
return currentStorage;
|
|
277
|
+
}
|
|
278
|
+
async function disconnectStorage() {
|
|
279
|
+
if (currentStorage) {
|
|
280
|
+
await currentStorage.disconnect();
|
|
281
|
+
currentStorage = null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/storage/memory-server.ts
|
|
286
|
+
import * as fs from "fs";
|
|
287
|
+
import * as path from "path";
|
|
288
|
+
var PersistentMemoryStorage = class extends MemoryStorage {
|
|
289
|
+
dataDir;
|
|
290
|
+
flushTimer;
|
|
291
|
+
constructor(config = {}) {
|
|
292
|
+
super(config);
|
|
293
|
+
this.dataDir = config.dataDir;
|
|
294
|
+
if (this.dataDir) {
|
|
295
|
+
this.loadFromDisk();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
addName(name) {
|
|
299
|
+
super.addName(name);
|
|
300
|
+
this.schedulePersist();
|
|
301
|
+
}
|
|
302
|
+
addSample(name, sample) {
|
|
303
|
+
super.addSample(name, sample);
|
|
304
|
+
this.schedulePersist();
|
|
305
|
+
}
|
|
306
|
+
addViolation(name, violation) {
|
|
307
|
+
super.addViolation(name, violation);
|
|
308
|
+
this.schedulePersist();
|
|
309
|
+
}
|
|
310
|
+
incrConform(name) {
|
|
311
|
+
super.incrConform(name);
|
|
312
|
+
this.schedulePersist();
|
|
313
|
+
}
|
|
314
|
+
incrViolate(name) {
|
|
315
|
+
super.incrViolate(name);
|
|
316
|
+
this.schedulePersist();
|
|
317
|
+
}
|
|
238
318
|
disconnect() {
|
|
239
319
|
if (this.flushTimer) {
|
|
240
320
|
clearTimeout(this.flushTimer);
|
|
@@ -287,36 +367,6 @@ var MemoryStorage = class {
|
|
|
287
367
|
}
|
|
288
368
|
};
|
|
289
369
|
|
|
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
370
|
// src/storage/index.ts
|
|
321
371
|
_setStorageFactory(async (config, userConfig) => {
|
|
322
372
|
if (config.storage === "amplitude") {
|
|
@@ -331,7 +381,8 @@ _setStorageFactory(async (config, userConfig) => {
|
|
|
331
381
|
const { RedisStorage: RedisStorage2 } = await Promise.resolve().then(() => (init_redis(), redis_exports));
|
|
332
382
|
return new RedisStorage2(config);
|
|
333
383
|
}
|
|
334
|
-
|
|
384
|
+
const serverConfig = { ...config, dataDir: config.dataDir ?? ".progressive-zod" };
|
|
385
|
+
return new PersistentMemoryStorage(serverConfig);
|
|
335
386
|
});
|
|
336
387
|
|
|
337
388
|
// 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
|
|
@@ -52,17 +42,37 @@ var init_amplitude = __esm({
|
|
|
52
42
|
}
|
|
53
43
|
addViolation(_name, _violation) {
|
|
54
44
|
}
|
|
55
|
-
incrConform(name) {
|
|
45
|
+
incrConform(name, _sample) {
|
|
56
46
|
this.client.track(
|
|
57
47
|
"pzod:type_checked",
|
|
58
48
|
{ type_name: name, result: "conform" },
|
|
59
49
|
{ device_id: this.deviceId }
|
|
60
50
|
);
|
|
61
51
|
}
|
|
62
|
-
incrViolate(name) {
|
|
52
|
+
incrViolate(name, sample, errors) {
|
|
53
|
+
const properties = {
|
|
54
|
+
type_name: name,
|
|
55
|
+
result: "violate"
|
|
56
|
+
};
|
|
57
|
+
if (sample) {
|
|
58
|
+
try {
|
|
59
|
+
const parsed = JSON.parse(sample);
|
|
60
|
+
properties.sample_type = typeof parsed;
|
|
61
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
62
|
+
properties.field_count = Object.keys(parsed).length;
|
|
63
|
+
}
|
|
64
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
65
|
+
} catch {
|
|
66
|
+
properties.sample_type = "unknown";
|
|
67
|
+
properties.sample_preview = sample.slice(0, 256);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (errors) {
|
|
71
|
+
properties.validation_errors = errors.slice(0, 1024);
|
|
72
|
+
}
|
|
63
73
|
this.client.track(
|
|
64
74
|
"pzod:type_checked",
|
|
65
|
-
|
|
75
|
+
properties,
|
|
66
76
|
{ device_id: this.deviceId }
|
|
67
77
|
);
|
|
68
78
|
}
|
|
@@ -99,22 +109,14 @@ __export(client_exports, {
|
|
|
99
109
|
module.exports = __toCommonJS(client_exports);
|
|
100
110
|
|
|
101
111
|
// src/storage/memory.ts
|
|
102
|
-
var fs = __toESM(require("fs"), 1);
|
|
103
|
-
var path = __toESM(require("path"), 1);
|
|
104
112
|
var MemoryStorage = class {
|
|
105
113
|
names = /* @__PURE__ */ new Set();
|
|
106
114
|
data = /* @__PURE__ */ new Map();
|
|
107
115
|
maxSamples;
|
|
108
116
|
maxViolations;
|
|
109
|
-
dataDir;
|
|
110
|
-
flushTimer;
|
|
111
117
|
constructor(config = {}) {
|
|
112
118
|
this.maxSamples = config.maxSamples ?? 1e3;
|
|
113
119
|
this.maxViolations = config.maxViolations ?? 1e3;
|
|
114
|
-
this.dataDir = config.dataDir;
|
|
115
|
-
if (this.dataDir) {
|
|
116
|
-
this.loadFromDisk();
|
|
117
|
-
}
|
|
118
120
|
}
|
|
119
121
|
getOrCreate(name) {
|
|
120
122
|
let entry = this.data.get(name);
|
|
@@ -126,7 +128,6 @@ var MemoryStorage = class {
|
|
|
126
128
|
}
|
|
127
129
|
addName(name) {
|
|
128
130
|
this.names.add(name);
|
|
129
|
-
this.schedulePersist();
|
|
130
131
|
}
|
|
131
132
|
addSample(name, sample) {
|
|
132
133
|
const entry = this.getOrCreate(name);
|
|
@@ -134,7 +135,6 @@ var MemoryStorage = class {
|
|
|
134
135
|
if (entry.samples.length > this.maxSamples) {
|
|
135
136
|
entry.samples.length = this.maxSamples;
|
|
136
137
|
}
|
|
137
|
-
this.schedulePersist();
|
|
138
138
|
}
|
|
139
139
|
addViolation(name, violation) {
|
|
140
140
|
const entry = this.getOrCreate(name);
|
|
@@ -142,15 +142,12 @@ var MemoryStorage = class {
|
|
|
142
142
|
if (entry.violations.length > this.maxViolations) {
|
|
143
143
|
entry.violations.length = this.maxViolations;
|
|
144
144
|
}
|
|
145
|
-
this.schedulePersist();
|
|
146
145
|
}
|
|
147
146
|
incrConform(name) {
|
|
148
147
|
this.getOrCreate(name).conform++;
|
|
149
|
-
this.schedulePersist();
|
|
150
148
|
}
|
|
151
149
|
incrViolate(name) {
|
|
152
150
|
this.getOrCreate(name).violate++;
|
|
153
|
-
this.schedulePersist();
|
|
154
151
|
}
|
|
155
152
|
getNames() {
|
|
156
153
|
return [...this.names].sort();
|
|
@@ -166,54 +163,6 @@ var MemoryStorage = class {
|
|
|
166
163
|
return { conform: entry.conform, violate: entry.violate };
|
|
167
164
|
}
|
|
168
165
|
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
166
|
}
|
|
218
167
|
};
|
|
219
168
|
|
|
@@ -231,14 +180,20 @@ function configure(config) {
|
|
|
231
180
|
currentStorage = null;
|
|
232
181
|
}
|
|
233
182
|
}
|
|
183
|
+
function env(key) {
|
|
184
|
+
if (typeof process !== "undefined" && process.env) {
|
|
185
|
+
return process.env[key];
|
|
186
|
+
}
|
|
187
|
+
return void 0;
|
|
188
|
+
}
|
|
234
189
|
function getConfig() {
|
|
235
190
|
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 ??
|
|
191
|
+
storage: currentConfig.storage ?? env("PROGRESSIVE_ZOD_STORAGE") ?? "memory",
|
|
192
|
+
redisUrl: currentConfig.redisUrl ?? env("PROGRESSIVE_ZOD_REDIS_URL"),
|
|
193
|
+
keyPrefix: currentConfig.keyPrefix ?? env("PROGRESSIVE_ZOD_KEY_PREFIX") ?? "pzod:",
|
|
194
|
+
maxViolations: currentConfig.maxViolations ?? parseInt(env("PROGRESSIVE_ZOD_MAX_VIOLATIONS") ?? "1000", 10),
|
|
195
|
+
maxSamples: currentConfig.maxSamples ?? parseInt(env("PROGRESSIVE_ZOD_MAX_SAMPLES") ?? "1000", 10),
|
|
196
|
+
dataDir: currentConfig.dataDir ?? env("PROGRESSIVE_ZOD_DATA_DIR")
|
|
242
197
|
};
|
|
243
198
|
}
|
|
244
199
|
async function getStorage() {
|
|
@@ -360,13 +315,17 @@ var BatchProcessor = class {
|
|
|
360
315
|
if (obs.schema) {
|
|
361
316
|
const result = obs.schema.safeParse(JSON.parse(obs.serialized));
|
|
362
317
|
if (result.success) {
|
|
363
|
-
await storage.incrConform(obs.name);
|
|
318
|
+
await storage.incrConform(obs.name, obs.serialized);
|
|
364
319
|
} else {
|
|
365
|
-
|
|
320
|
+
const errors = result.error.issues.map((i) => {
|
|
321
|
+
const path = i.path.length > 0 ? i.path.join(".") : "(root)";
|
|
322
|
+
return `${path}: ${i.message}`;
|
|
323
|
+
}).join("; ");
|
|
324
|
+
await storage.incrViolate(obs.name, obs.serialized, errors);
|
|
366
325
|
await storage.addViolation(obs.name, obs.serialized);
|
|
367
326
|
}
|
|
368
327
|
} else {
|
|
369
|
-
await storage.incrViolate(obs.name);
|
|
328
|
+
await storage.incrViolate(obs.name, obs.serialized, "no schema defined");
|
|
370
329
|
await storage.addViolation(obs.name, obs.serialized);
|
|
371
330
|
}
|
|
372
331
|
}
|