@devshub198211/devguard 2.0.1 → 2.0.3

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/dx.cjs DELETED
@@ -1,747 +0,0 @@
1
- 'use strict';
2
-
3
- var fs = require('fs');
4
- var path = require('path');
5
- var https = require('https');
6
- var http = require('http');
7
- var os = require('os');
8
-
9
- function _interopNamespace(e) {
10
- if (e && e.__esModule) return e;
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n.default = e;
24
- return Object.freeze(n);
25
- }
26
-
27
- var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
28
- var path__namespace = /*#__PURE__*/_interopNamespace(path);
29
- var https__namespace = /*#__PURE__*/_interopNamespace(https);
30
- var http__namespace = /*#__PURE__*/_interopNamespace(http);
31
- var os__namespace = /*#__PURE__*/_interopNamespace(os);
32
-
33
- // src/dx/env-safe.ts
34
- function parseDotenv(content) {
35
- const result = /* @__PURE__ */ Object.create(null);
36
- if (typeof content !== "string") return result;
37
- const lines = content.split(/\r?\n/);
38
- for (const raw of lines) {
39
- const line = raw.trim();
40
- if (!line || line.startsWith("#")) continue;
41
- const eqIdx = line.indexOf("=");
42
- if (eqIdx === -1) continue;
43
- const key = line.slice(0, eqIdx).trim();
44
- let val = line.slice(eqIdx + 1).trim();
45
- if (!key || key === "__proto__" || key === "constructor" || key === "prototype") continue;
46
- if (!/^[a-zA-Z_][a-zA-Z0-9_.]*$/.test(key)) continue;
47
- if (!val.startsWith('"') && !val.startsWith("'")) {
48
- val = val.split(/\s+#/)[0].trim();
49
- }
50
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
51
- val = val.slice(1, -1).replace(/\\n/g, "\n").replace(/\\t/g, " ");
52
- }
53
- result[key] = val;
54
- }
55
- return result;
56
- }
57
- function loadDotenvFile(filePath) {
58
- const resolved = path__namespace.resolve(filePath);
59
- if (!fs__namespace.existsSync(resolved)) return /* @__PURE__ */ Object.create(null);
60
- try {
61
- const stat = fs__namespace.statSync(resolved);
62
- if (!stat.isFile() || stat.size > 1 * 1024 * 1024) return /* @__PURE__ */ Object.create(null);
63
- return parseDotenv(fs__namespace.readFileSync(resolved, "utf-8"));
64
- } catch {
65
- return /* @__PURE__ */ Object.create(null);
66
- }
67
- }
68
- function coerce(key, raw, spec) {
69
- const val = raw.trim();
70
- if (spec.type === "string") {
71
- if (spec.minLength !== void 0 && val.length < spec.minLength) throw new Error(`${key}: too short (min ${spec.minLength} chars)`);
72
- if (spec.maxLength !== void 0 && val.length > spec.maxLength) throw new Error(`${key}: too long (max ${spec.maxLength} chars)`);
73
- if (spec.enum && !spec.enum.includes(val)) throw new Error(`${key}: must be one of [${spec.enum.join(", ")}]`);
74
- if (spec.regex && !spec.regex.test(val)) throw new Error(`${key}: does not match expected pattern`);
75
- return val;
76
- }
77
- if (spec.type === "number" || spec.type === "integer") {
78
- const n = spec.type === "integer" ? parseInt(val, 10) : parseFloat(val);
79
- if (isNaN(n)) throw new Error(`${key}: expected ${spec.type}`);
80
- if (!isFinite(n)) throw new Error(`${key}: value must be finite`);
81
- if (spec.min !== void 0 && n < spec.min) throw new Error(`${key}: too small (min ${spec.min})`);
82
- if (spec.max !== void 0 && n > spec.max) throw new Error(`${key}: too large (max ${spec.max})`);
83
- return n;
84
- }
85
- if (spec.type === "boolean") {
86
- if (["true", "1", "yes", "on"].includes(val.toLowerCase())) return true;
87
- if (["false", "0", "no", "off"].includes(val.toLowerCase())) return false;
88
- throw new Error(`${key}: expected boolean (true/false/1/0/yes/no)`);
89
- }
90
- if (spec.type === "url") {
91
- try {
92
- new URL(val);
93
- return val;
94
- } catch {
95
- throw new Error(`${key}: invalid URL`);
96
- }
97
- }
98
- if (spec.type === "email") {
99
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) throw new Error(`${key}: invalid email`);
100
- return val;
101
- }
102
- if (spec.type === "json") {
103
- try {
104
- return JSON.parse(val);
105
- } catch {
106
- throw new Error(`${key}: invalid JSON`);
107
- }
108
- }
109
- if (spec.type === "csv") {
110
- return val.split(",").map((s) => s.trim()).filter(Boolean);
111
- }
112
- return val;
113
- }
114
- function parseEnv(schema, source = process.env) {
115
- const errors = [];
116
- const result = /* @__PURE__ */ Object.create(null);
117
- for (const [key, spec] of Object.entries(schema)) {
118
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
119
- const raw = source[key] ?? spec.default;
120
- const isRequired = spec.required !== false && spec.default === void 0;
121
- if (raw === void 0 || raw === "") {
122
- if (isRequired) {
123
- const hint = spec.example ? ` (example: ${spec.example})` : "";
124
- const desc = spec.description ? ` \u2014 ${spec.description}` : "";
125
- errors.push(` \u2717 ${key}: required but missing${desc}${hint}`);
126
- }
127
- continue;
128
- }
129
- try {
130
- result[key] = coerce(key, raw, spec);
131
- } catch (e) {
132
- const errorMsg = e.message ?? "validation failed";
133
- if (spec.secret) {
134
- errors.push(` \u2717 ${key}: ${errorMsg}`);
135
- } else {
136
- errors.push(` \u2717 ${errorMsg} (got: "${raw}")`);
137
- }
138
- }
139
- }
140
- if (errors.length > 0) {
141
- const msg = `
142
- \u{1F6A8} env-safe: ${errors.length} environment variable error(s):
143
-
144
- ${errors.join("\n")}
145
-
146
- Fix these in your .env file before starting.
147
- `;
148
- throw new Error(msg);
149
- }
150
- return result;
151
- }
152
- function loadEnv(schema, opts) {
153
- const { envFiles = [".env", ".env.local"], override = false } = opts ?? {};
154
- const merged = /* @__PURE__ */ Object.create(null);
155
- for (const file of envFiles) {
156
- if (typeof file !== "string") continue;
157
- const fullPath = path__namespace.resolve(process.cwd(), file);
158
- const parsed = loadDotenvFile(fullPath);
159
- for (const [k, v] of Object.entries(parsed)) {
160
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
161
- if (override || !(k in merged)) merged[k] = v;
162
- }
163
- }
164
- const combined = { ...merged };
165
- for (const [k, v] of Object.entries(process.env)) {
166
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
167
- if (override || !(k in merged)) combined[k] = v;
168
- }
169
- return parseEnv(schema, combined);
170
- }
171
- function generateExample(schema) {
172
- const lines = ["# Generated by devguard env-safe", ""];
173
- for (const [key, spec] of Object.entries(schema)) {
174
- if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
175
- if (spec.description) lines.push(`# ${spec.description}`);
176
- const hint = spec.example ?? (spec.enum ? spec.enum[0] : spec.type.toUpperCase());
177
- const req = spec.required !== false && !spec.default ? " (required)" : spec.default ? ` (default: ${spec.default})` : "";
178
- lines.push(`# Type: ${spec.type}${req}`);
179
- lines.push(`${key}=${hint}`);
180
- lines.push("");
181
- }
182
- return lines.join("\n");
183
- }
184
- var LEVEL_NUM = { trace: 10, debug: 20, info: 30, warn: 40, error: 50, fatal: 60 };
185
- var LEVEL_COLOR = { trace: "\x1B[90m", debug: "\x1B[36m", info: "\x1B[32m", warn: "\x1B[33m", error: "\x1B[31m", fatal: "\x1B[35m" };
186
- var RESET = "\x1B[0m";
187
- var PROTECTED_FIELDS = /* @__PURE__ */ new Set(["level", "levelNum", "msg", "time", "pid", "hostname"]);
188
- function redactValue(val) {
189
- if (typeof val === "string" && val.length > 4) return val.slice(0, 2) + "****";
190
- return "****";
191
- }
192
- function safeStringify(obj) {
193
- const seen = /* @__PURE__ */ new WeakSet();
194
- try {
195
- return JSON.stringify(obj, (_key, value) => {
196
- if (typeof value === "object" && value !== null) {
197
- if (seen.has(value)) return "[Circular]";
198
- seen.add(value);
199
- }
200
- if (typeof value === "bigint") return value.toString();
201
- return value;
202
- });
203
- } catch {
204
- return '"[Unserializable]"';
205
- }
206
- }
207
- var OTLPExporter = class {
208
- constructor(cfg) {
209
- this.batch = [];
210
- this._warnCount = 0;
211
- this._isFlushing = false;
212
- this.timer = null;
213
- this.cfg = { headers: {}, batchSize: 100, flushIntervalMs: 5e3, ...cfg };
214
- try {
215
- new URL(this.cfg.endpoint);
216
- } catch {
217
- throw new Error(`Invalid OTLP endpoint URL: ${cfg.endpoint}`);
218
- }
219
- this.timer = setInterval(() => this.flush(), this.cfg.flushIntervalMs);
220
- if (this.timer.unref) this.timer.unref();
221
- }
222
- push(record) {
223
- this.batch.push(record);
224
- if (this.batch.length >= this.cfg.batchSize) this.flush();
225
- }
226
- flush() {
227
- if (this.batch.length === 0) return;
228
- if (this._isFlushing) return;
229
- this._isFlushing = true;
230
- const records = this.batch.splice(0);
231
- try {
232
- const body = JSON.stringify({ resourceLogs: [{ resource: {}, scopeLogs: [{ logRecords: records.map((r) => ({
233
- timeUnixNano: String(Date.parse(r.time) * 1e6),
234
- severityNumber: r.levelNum,
235
- severityText: r.level.toUpperCase(),
236
- body: { stringValue: r.msg },
237
- attributes: Object.entries(r).filter(([k]) => !PROTECTED_FIELDS.has(k)).map(([k, v]) => ({ key: k, value: { stringValue: String(v) } }))
238
- })) }] }] });
239
- const url = new URL(this.cfg.endpoint);
240
- const lib = url.protocol === "https:" ? https__namespace : http__namespace;
241
- const req = lib.request({
242
- hostname: url.hostname,
243
- port: url.port,
244
- path: url.pathname,
245
- method: "POST",
246
- headers: { "Content-Type": "application/json", ...this.cfg.headers }
247
- });
248
- req.on("error", (e) => {
249
- if (this._warnCount++ < 3) {
250
- process.stderr.write(`[devguard/log-otlp] OTLP export failed: ${e.message} (further errors suppressed)
251
- `);
252
- }
253
- });
254
- req.write(body);
255
- req.end();
256
- } finally {
257
- this._isFlushing = false;
258
- }
259
- }
260
- destroy() {
261
- if (this.timer) clearInterval(this.timer);
262
- this.timer = null;
263
- this.flush();
264
- }
265
- };
266
- var _hostname = os__namespace.hostname();
267
- var Logger = class _Logger {
268
- constructor(opts, bindings = {}) {
269
- this.opts = opts;
270
- this.minLevel = LEVEL_NUM[opts.level ?? "info"];
271
- this.redactSet = new Set(opts.redact ?? []);
272
- this.exporter = opts.otlp ? new OTLPExporter(opts.otlp) : null;
273
- this.dest = opts.destination ?? process.stdout;
274
- this.bindings = bindings;
275
- }
276
- log(level, msg, data) {
277
- if (LEVEL_NUM[level] < this.minLevel) return;
278
- if (this.opts.output === "silent") return;
279
- let traceCtx = {};
280
- try {
281
- traceCtx = this.opts.traceContext?.() ?? {};
282
- } catch {
283
- }
284
- const record = {
285
- level,
286
- levelNum: LEVEL_NUM[level],
287
- msg,
288
- time: (/* @__PURE__ */ new Date()).toISOString(),
289
- pid: process.pid,
290
- hostname: _hostname,
291
- ...this.bindings,
292
- ...traceCtx,
293
- ...this.opts.service ? { service: this.opts.service } : {}
294
- };
295
- if (data) {
296
- for (const [k, v] of Object.entries(data)) {
297
- if (PROTECTED_FIELDS.has(k)) continue;
298
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
299
- record[k] = this.redactSet.has(k) ? redactValue(v) : v;
300
- }
301
- }
302
- const output = this.opts.output ?? (process.env.NODE_ENV === "development" || process.stdout.isTTY ? "pretty" : "json");
303
- if (output === "pretty") {
304
- const color = LEVEL_COLOR[level];
305
- const ts = record.time.slice(11, 23);
306
- const extras = Object.entries(record).filter(([k]) => !PROTECTED_FIELDS.has(k));
307
- const extraStr = extras.length ? " " + extras.map(([k, v]) => `${k}=${safeStringify(v)}`).join(" ") : "";
308
- this.dest.write(`${color}${ts} ${level.toUpperCase().padEnd(5)}${RESET} ${msg}${extraStr}
309
- `);
310
- } else {
311
- this.dest.write(safeStringify(record) + "\n");
312
- }
313
- this.exporter?.push(record);
314
- }
315
- trace(msg, data) {
316
- this.log("trace", msg, data);
317
- }
318
- debug(msg, data) {
319
- this.log("debug", msg, data);
320
- }
321
- info(msg, data) {
322
- this.log("info", msg, data);
323
- }
324
- warn(msg, data) {
325
- this.log("warn", msg, data);
326
- }
327
- error(msg, data) {
328
- this.log("error", msg, data);
329
- }
330
- fatal(msg, data) {
331
- this.log("fatal", msg, data);
332
- }
333
- /** Create a child logger with additional bound fields */
334
- child(bindings) {
335
- return new _Logger(this.opts, { ...this.bindings, ...bindings });
336
- }
337
- /** Attach trace context for automatic injection */
338
- withTrace(ctx) {
339
- return new _Logger({ ...this.opts, traceContext: () => ctx }, this.bindings);
340
- }
341
- /** Flush OTLP buffer before process exit */
342
- flush() {
343
- this.exporter?.flush();
344
- }
345
- destroy() {
346
- this.exporter?.destroy();
347
- }
348
- /** Middleware: log all HTTP requests */
349
- requestMiddleware() {
350
- const log = this;
351
- return function logRequest(req, res, next) {
352
- const start = Date.now();
353
- res.on("finish", () => {
354
- log.info("http request", {
355
- method: req.method,
356
- url: req.url,
357
- status: res.statusCode,
358
- durationMs: Date.now() - start,
359
- ip: req.ip ?? req.connection?.remoteAddress,
360
- userAgent: req.headers["user-agent"]
361
- });
362
- });
363
- next();
364
- };
365
- }
366
- };
367
- function createLogger(opts = {}) {
368
- return new Logger(opts);
369
- }
370
-
371
- // src/dx/api-contract.ts
372
- var Schema = class {
373
- constructor() {
374
- /** @internal */
375
- this._isOptional = false;
376
- }
377
- safeParse(val) {
378
- try {
379
- let result = this._parse(val === void 0 ? this._default : val, "");
380
- if (this._transform) result = this._transform(result);
381
- return { success: true, data: result, errors: [] };
382
- } catch (e) {
383
- return { success: false, errors: parseErrors(e) };
384
- }
385
- }
386
- parse(val) {
387
- const r = this.safeParse(val);
388
- if (!r.success) throw new Error(r.errors.map((e) => (e.path ? `${e.path}: ` : "") + e.message).join("; "));
389
- return r.data;
390
- }
391
- optional() {
392
- return new OptionalSchema(this);
393
- }
394
- nullable() {
395
- return new NullableSchema(this);
396
- }
397
- default(val) {
398
- const c2 = Object.create(this);
399
- c2._default = val;
400
- return c2;
401
- }
402
- describe(desc) {
403
- const c2 = Object.create(this);
404
- c2._description = desc;
405
- return c2;
406
- }
407
- transform(fn) {
408
- const c2 = Object.create(this);
409
- c2._transform = fn;
410
- return c2;
411
- }
412
- toJSONSchema() {
413
- return {};
414
- }
415
- };
416
- function parseErrors(e) {
417
- if (e instanceof ValidationException) return e.errors;
418
- return [{ path: "", message: e?.message ?? "Unknown error" }];
419
- }
420
- var ValidationException = class extends Error {
421
- constructor(errors) {
422
- super(errors.map((e) => e.message).join("; "));
423
- this.errors = errors;
424
- }
425
- };
426
- function fail(path2, message) {
427
- throw new ValidationException([{ path: path2, message }]);
428
- }
429
- var OptionalSchema = class extends Schema {
430
- constructor(inner) {
431
- super();
432
- this.inner = inner;
433
- }
434
- _parse(val, path2) {
435
- if (val === void 0 || val === null) return void 0;
436
- return this.inner._parse(val, path2);
437
- }
438
- };
439
- var NullableSchema = class extends Schema {
440
- constructor(inner) {
441
- super();
442
- this.inner = inner;
443
- }
444
- _parse(val, path2) {
445
- if (val === null || val === void 0) return null;
446
- return this.inner._parse(val, path2);
447
- }
448
- };
449
- var StringSchema = class extends Schema {
450
- constructor() {
451
- super(...arguments);
452
- this._email = false;
453
- this._url = false;
454
- this._trim = false;
455
- }
456
- _parse(val, path2) {
457
- if (typeof val !== "string") fail(path2, `Expected string, got ${typeof val}`);
458
- let s = val;
459
- if (this._trim) s = s.trim();
460
- if (this._min !== void 0 && s.length < this._min) fail(path2, `Too short (min ${this._min})`);
461
- if (this._max !== void 0 && s.length > this._max) fail(path2, `Too long (max ${this._max})`);
462
- if (this._enum && !this._enum.includes(s)) fail(path2, `Must be one of: ${this._enum.join(", ")}`);
463
- if (this._regex && !this._regex.test(s)) fail(path2, `Does not match ${this._regex}`);
464
- if (this._email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s)) fail(path2, "Invalid email address");
465
- if (this._url) {
466
- try {
467
- new URL(s);
468
- } catch {
469
- fail(path2, "Invalid URL");
470
- }
471
- }
472
- return s;
473
- }
474
- min(n) {
475
- const c2 = Object.create(this);
476
- c2._min = n;
477
- return c2;
478
- }
479
- max(n) {
480
- const c2 = Object.create(this);
481
- c2._max = n;
482
- return c2;
483
- }
484
- length(n) {
485
- return this.min(n).max(n);
486
- }
487
- enum(vals) {
488
- const c2 = Object.create(this);
489
- c2._enum = vals;
490
- return c2;
491
- }
492
- regex(r) {
493
- const c2 = Object.create(this);
494
- c2._regex = r;
495
- return c2;
496
- }
497
- email() {
498
- const c2 = Object.create(this);
499
- c2._email = true;
500
- return c2;
501
- }
502
- url() {
503
- const c2 = Object.create(this);
504
- c2._url = true;
505
- return c2;
506
- }
507
- trim() {
508
- const c2 = Object.create(this);
509
- c2._trim = true;
510
- return c2;
511
- }
512
- toJSONSchema() {
513
- return { type: "string", ...this._min ? { minLength: this._min } : {}, ...this._max ? { maxLength: this._max } : {}, ...this._enum ? { enum: this._enum } : {}, ...this._email ? { format: "email" } : {}, ...this._url ? { format: "uri" } : {} };
514
- }
515
- };
516
- var NumberSchema = class extends Schema {
517
- constructor() {
518
- super(...arguments);
519
- this._int = false;
520
- this._positive = false;
521
- }
522
- _parse(val, path2) {
523
- const n = typeof val === "string" ? parseFloat(val) : val;
524
- if (typeof n !== "number" || isNaN(n)) fail(path2, `Expected number, got ${typeof val}`);
525
- if (this._int && !Number.isInteger(n)) fail(path2, "Expected integer");
526
- if (this._positive && n <= 0) fail(path2, "Must be positive");
527
- if (this._min !== void 0 && n < this._min) fail(path2, `Too small (min ${this._min})`);
528
- if (this._max !== void 0 && n > this._max) fail(path2, `Too large (max ${this._max})`);
529
- return n;
530
- }
531
- min(n) {
532
- const c2 = Object.create(this);
533
- c2._min = n;
534
- return c2;
535
- }
536
- max(n) {
537
- const c2 = Object.create(this);
538
- c2._max = n;
539
- return c2;
540
- }
541
- int() {
542
- const c2 = Object.create(this);
543
- c2._int = true;
544
- return c2;
545
- }
546
- positive() {
547
- const c2 = Object.create(this);
548
- c2._positive = true;
549
- return c2;
550
- }
551
- toJSONSchema() {
552
- return { type: this._int ? "integer" : "number", ...this._min !== void 0 ? { minimum: this._min } : {}, ...this._max !== void 0 ? { maximum: this._max } : {} };
553
- }
554
- };
555
- var BooleanSchema = class extends Schema {
556
- _parse(val, path2) {
557
- if (val === "true" || val === 1 || val === "1") return true;
558
- if (val === "false" || val === 0 || val === "0") return false;
559
- if (typeof val !== "boolean") fail(path2, `Expected boolean, got ${typeof val}`);
560
- return val;
561
- }
562
- toJSONSchema() {
563
- return { type: "boolean" };
564
- }
565
- };
566
- var ObjectSchema = class _ObjectSchema extends Schema {
567
- constructor(shape, isStrict = false) {
568
- super();
569
- this.shape = shape;
570
- this._isStrict = isStrict;
571
- }
572
- _parse(val, path2) {
573
- if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path2, `Expected object`);
574
- const obj = val;
575
- const result = {};
576
- const errors = [];
577
- for (const [key, schema] of Object.entries(this.shape)) {
578
- try {
579
- result[key] = schema._parse(obj[key], path2 ? `${path2}.${key}` : key);
580
- } catch (e) {
581
- errors.push(...parseErrors(e));
582
- }
583
- }
584
- if (this._isStrict) {
585
- const knownKeys = new Set(Object.keys(this.shape));
586
- for (const k of Object.keys(obj)) if (!knownKeys.has(k)) errors.push({ path: path2 ? `${path2}.${k}` : k, message: "Unknown field" });
587
- }
588
- if (errors.length) throw new ValidationException(errors);
589
- return result;
590
- }
591
- extend(extra) {
592
- return new _ObjectSchema({ ...this.shape, ...extra });
593
- }
594
- pick(keys) {
595
- const s = {};
596
- keys.forEach((k) => s[k] = this.shape[k]);
597
- return new _ObjectSchema(s);
598
- }
599
- omit(keys) {
600
- const s = { ...this.shape };
601
- keys.forEach((k) => delete s[k]);
602
- return new _ObjectSchema(s);
603
- }
604
- strict() {
605
- return new _ObjectSchema(this.shape, true);
606
- }
607
- toJSONSchema() {
608
- const props = {};
609
- const required = [];
610
- for (const [k, v] of Object.entries(this.shape)) {
611
- props[k] = v.toJSONSchema?.() ?? {};
612
- if (!(v instanceof OptionalSchema) && !(v instanceof NullableSchema)) required.push(k);
613
- }
614
- return { type: "object", properties: props, ...required.length ? { required } : {} };
615
- }
616
- };
617
- var ArraySchema = class extends Schema {
618
- constructor(item) {
619
- super();
620
- this.item = item;
621
- }
622
- _parse(val, path2) {
623
- if (!Array.isArray(val)) fail(path2, `Expected array`);
624
- if (this._min !== void 0 && val.length < this._min) fail(path2, `Too few items (min ${this._min})`);
625
- if (this._max !== void 0 && val.length > this._max) fail(path2, `Too many items (max ${this._max})`);
626
- const errors = [];
627
- const result = [];
628
- val.forEach((item, i) => {
629
- try {
630
- result.push(this.item._parse(item, `${path2}[${i}]`));
631
- } catch (e) {
632
- errors.push(...parseErrors(e));
633
- }
634
- });
635
- if (errors.length) throw new ValidationException(errors);
636
- return result;
637
- }
638
- min(n) {
639
- const c2 = Object.create(this);
640
- c2._min = n;
641
- return c2;
642
- }
643
- max(n) {
644
- const c2 = Object.create(this);
645
- c2._max = n;
646
- return c2;
647
- }
648
- nonempty() {
649
- return this.min(1);
650
- }
651
- toJSONSchema() {
652
- return { type: "array", items: this.item.toJSONSchema?.() ?? {} };
653
- }
654
- };
655
- var RecordSchema = class extends Schema {
656
- constructor(valueSchema) {
657
- super();
658
- this.valueSchema = valueSchema;
659
- }
660
- _parse(val, path2) {
661
- if (typeof val !== "object" || val === null || Array.isArray(val)) fail(path2, "Expected object");
662
- const result = {};
663
- for (const [k, v] of Object.entries(val)) {
664
- if (k === "__proto__" || k === "constructor" || k === "prototype") continue;
665
- result[k] = this.valueSchema._parse(v, `${path2}.${k}`);
666
- }
667
- return result;
668
- }
669
- };
670
- var LiteralSchema = class extends Schema {
671
- constructor(literal) {
672
- super();
673
- this.literal = literal;
674
- }
675
- _parse(val, path2) {
676
- if (val !== this.literal) fail(path2, `Expected ${JSON.stringify(this.literal)}, got ${JSON.stringify(val)}`);
677
- return val;
678
- }
679
- };
680
- var UnionSchema = class extends Schema {
681
- constructor(options) {
682
- super();
683
- this.options = options;
684
- }
685
- _parse(val, path2) {
686
- for (const opt of this.options) {
687
- try {
688
- return opt._parse(val, path2);
689
- } catch {
690
- continue;
691
- }
692
- }
693
- fail(path2, `Value does not match any union variant`);
694
- }
695
- };
696
- var c = {
697
- string: () => new StringSchema(),
698
- number: () => new NumberSchema(),
699
- boolean: () => new BooleanSchema(),
700
- object: (shape) => new ObjectSchema(shape),
701
- array: (item) => new ArraySchema(item),
702
- record: (val) => new RecordSchema(val),
703
- literal: (v) => new LiteralSchema(v),
704
- union: (opts) => new UnionSchema(opts),
705
- any: () => new class extends Schema {
706
- _parse(v) {
707
- return v;
708
- }
709
- }()
710
- };
711
- function validateBody(schema) {
712
- return function(req, res, next) {
713
- const result = schema.safeParse(req.body);
714
- if (!result.success) return res.status(400).json({ error: "Validation failed", details: result.errors });
715
- req.body = result.data;
716
- next();
717
- };
718
- }
719
- function validateQuery(schema) {
720
- return function(req, res, next) {
721
- const result = schema.safeParse(req.query);
722
- if (!result.success) return res.status(400).json({ error: "Invalid query parameters", details: result.errors });
723
- req.query = result.data;
724
- next();
725
- };
726
- }
727
- async function typedFetch(url, schema, init) {
728
- if (typeof fetch === "undefined") throw new Error("[devguard] typedFetch requires Node.js >= 18 or a global fetch polyfill. See: https://nodejs.org/en/blog/announcements/v18-release-announce");
729
- const response = await fetch(url, init);
730
- if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
731
- const json = await response.json();
732
- const data = schema.parse(json);
733
- return { data, response };
734
- }
735
-
736
- exports.Logger = Logger;
737
- exports.Schema = Schema;
738
- exports.c = c;
739
- exports.createLogger = createLogger;
740
- exports.generateExample = generateExample;
741
- exports.loadDotenvFile = loadDotenvFile;
742
- exports.loadEnv = loadEnv;
743
- exports.parseDotenv = parseDotenv;
744
- exports.parseEnv = parseEnv;
745
- exports.typedFetch = typedFetch;
746
- exports.validateBody = validateBody;
747
- exports.validateQuery = validateQuery;