payload-guard-filter 1.3.1 → 1.4.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 CHANGED
@@ -1,10 +1,11 @@
1
1
  # payload-guard
2
+
2
3
  > Part of the [Professional Node.js Backend Toolkit](https://github.com/sannuk79/PROJECTS-AND-NPM-PACKAGES-)
4
+
3
5
  <p align="center">
4
6
  <strong>🛡️ Lightweight, zero-dependency shape-based payload filtering and sanitization</strong>
5
7
  </p>
6
8
 
7
-
8
9
  <p align="center">
9
10
  <img src="https://img.shields.io/badge/bundle%20size-%3C8KB-brightgreen" alt="Bundle Size">
10
11
  <img src="https://img.shields.io/badge/dependencies-0-blue" alt="Zero Dependencies">
@@ -45,10 +46,6 @@ graph LR
45
46
 
46
47
  ```bash
47
48
  npm install payload-guard
48
- # or
49
- yarn add payload-guard
50
- # or
51
- pnpm add payload-guard
52
49
  ```
53
50
 
54
51
  ---
@@ -80,318 +77,67 @@ const safeData = userShape(rawData);
80
77
  // Result: { id: 1, name: 'John Doe', email: 'john@example.com' }
81
78
  ```
82
79
 
83
- ### Express Middleware
80
+ ### Advanced Validation (v1.4+)
84
81
 
85
82
  ```typescript
86
- import express from 'express';
87
- import { guard, guardMiddleware } from 'payload-guard';
88
-
89
- const app = express();
90
- app.use(express.json());
91
-
92
- // Apply middleware
93
- app.use(guardMiddleware({
94
- sanitizeBody: true,
95
- sensitiveFields: ['password', 'token'],
96
- devMode: process.env.NODE_ENV === 'development',
97
- }));
98
-
99
83
  const userShape = guard.shape({
100
- id: 'number',
101
- name: 'string',
102
- email: 'string',
103
- });
104
-
105
- app.post('/users', (req, res) => {
106
- const user = createUser(req.body);
107
- res.guardJson(userShape, user); // ✅ Filtered response
84
+ email: guard.string().email().toLowerCase().trim(),
85
+ age: guard.number().min(18).max(100).default(18),
86
+ tags: guard.array(guard.string().min(2)),
87
+ role: guard.string().validate(v => ['admin', 'user'].includes(v)).default('user'),
88
+ bio: guard.string().max(200).optional(),
108
89
  });
109
-
110
- app.listen(3000);
111
- ```
112
-
113
- ### Frontend Usage
114
-
115
- ```typescript
116
- import { validateShape } from 'payload-guard/client';
117
-
118
- const userShape = { id: 'number', name: 'string', email: 'string' };
119
- const validateUser = validateShape(userShape, { devMode: true });
120
-
121
- // Fetch and validate
122
- const user = await fetch('/api/user')
123
- .then(r => r.json())
124
- .then(validateUser);
125
-
126
- // Dev mode warnings:
127
- // ⚠️ Unexpected field "createdAt" in response
128
- // ⚠️ Missing field "email" in response
129
90
  ```
130
91
 
131
92
  ---
132
93
 
133
94
  ## 📖 API Reference
134
95
 
135
- ### `guard.shape(descriptor, options?)`
136
-
137
- Create a filter function from a shape descriptor.
138
-
139
- ```typescript
140
- const userShape = guard.shape({
141
- id: 'number',
142
- name: 'string',
143
- email: 'string',
144
- role: { type: 'string', default: 'user' },
145
- });
146
- ```
147
-
148
- **Supported types:**
149
- - `'string'` String values (auto-trimmed)
150
- - `'number'` — Numeric values
151
- - `'boolean'`Boolean values
152
- - `'any'`Any value (no transformation)
153
-
154
- **Field config options:**
155
- ```typescript
156
- {
157
- type: 'string',
158
- required: false, // Optional field
159
- default: 'value', // Default value if missing
160
- }
161
- ```
162
-
163
- ### `guard.array(itemShape)`
164
-
165
- Create an array filter.
166
-
167
- ```typescript
168
- const postsShape = guard.shape({
169
- posts: guard.array({
170
- id: 'number',
171
- title: 'string',
172
- }),
173
- });
174
- ```
175
-
176
- ### `guardMiddleware(options)`
177
-
178
- Express middleware for automatic sanitization.
179
-
180
- ```typescript
181
- app.use(guardMiddleware({
182
- sanitizeBody: true, // Filter req.body
183
- requestShape: userShape, // Shape for request body
184
- filterResponse: true, // Auto-filter all res.json()
185
- sensitiveFields: [], // Extra sensitive field names
186
- devMode: false, // Enable dev warnings
187
- }));
188
- ```
189
-
190
- ### `res.guardJson(shape, data)`
191
-
192
- Added by middleware — send filtered JSON response.
193
-
194
- ```typescript
195
- app.get('/user', (req, res) => {
196
- res.guardJson(userShape, userData);
197
- });
198
- ```
199
-
200
- ### `validateShape(shape, options?)` (Client)
201
-
202
- Create a validator for frontend use.
203
-
204
- ```typescript
205
- import { validateShape } from 'payload-guard/client';
206
-
207
- const validate = validateShape(userShape, {
208
- devMode: true, // Log warnings
209
- strict: false, // Throw on errors
210
- });
211
- ```
212
-
213
- ---
214
-
215
- ## 🔒 Security
216
-
217
- ### Default Sensitive Fields
218
-
219
- These fields are **automatically removed** from all outputs:
220
-
221
- - `password`, `password_hash`, `password_reset_token`, `pwd`
222
- - `token`, `access_token`, `refresh_token`, `auth_token`
223
- - `secret`, `api_key`, `private_key`, `encryption_key`
224
- - `authorization`, `auth`, `session_id`
225
- - `ssn`, `credit_card`, `cvv`, `card_number`
226
-
227
- ### Add Custom Sensitive Fields
228
-
229
- ```typescript
230
- guard.config({
231
- sensitiveFields: ['internal_id', 'admin_notes', 'salary'],
232
- });
233
- ```
234
-
235
- ---
236
-
237
- ## 🏗️ Nested Objects & Arrays
238
-
239
- ```typescript
240
- const postShape = guard.shape({
241
- id: 'number',
242
- title: 'string',
243
- author: guard.shape({
244
- id: 'number',
245
- name: 'string',
246
- // author.password, author.token auto-removed!
247
- }),
248
- tags: guard.array({
249
- id: 'number',
250
- name: 'string',
251
- }),
252
- comments: guard.array(
253
- guard.shape({
254
- id: 'number',
255
- text: 'string',
256
- user: guard.shape({
257
- id: 'number',
258
- name: 'string',
259
- }),
260
- })
261
- ),
262
- });
263
- ```
96
+ ### `guard.string()`
97
+ Creates a string builder with chained constraints:
98
+ - `.min(length)` Minimum character length
99
+ - `.max(length)` — Maximum character length
100
+ - `.email()` — Basic email validation
101
+ - `.regex(pattern)` Regex pattern match
102
+ - `.trim()` — Auto-trim whitespace (Transformation)
103
+ - `.toLowerCase()` / `.toUpperCase()` — Case transformation
104
+
105
+ ### `guard.number()`
106
+ - `.min(value)` / `.max(value)` — Range validation
107
+ - `.integer()` — Ensure number is an integer
108
+ - `.positive()` — Shortcut for `.min(0)`
109
+
110
+ ### Common Builder Methods
111
+ - `.required()` / `.optional()` — Toggle requirement
112
+ - `.default(value)` — Value to use if field is missing or invalid
113
+ - `.transform(fn)` — Custom transformation function
114
+ - `.validate(fn)` — Custom validation function (return `false` to fail)
264
115
 
265
116
  ---
266
117
 
267
118
  ## ⚡ Performance
268
119
 
269
- | Operation | payload-guard | Zod | JOI |
270
- |-----------|---------------|-----|-----|
271
- | Simple filter | 0.05ms | 0.8ms | 1.2ms |
272
- | Nested (3 levels) | 0.15ms | 2.5ms | 3.8ms |
273
- | Array (100 items) | 1.2ms | 15ms | 22ms |
274
- | Bundle size | <8KB | 50KB+ | 70KB+ |
275
- | Dependencies | 0 | 5+ | 10+ |
276
-
277
- ---
278
-
279
- ## 🏢 Enterprise Features (v1.2.0+)
120
+ | Benchmark | ops/sec | avg (ms) |
121
+ |-----------|---------|----------|
122
+ | **Small payload** (5 fields) | 449,365 | **0.0022ms** |
123
+ | **Medium payload** (50 posts) | 7,791 | **0.1284ms** |
124
+ | **Large payload** (1000 users) | 246 | **4.0724ms** |
280
125
 
281
- Designed for high-performance production systems, Payload Guard includes enterprise-grade features for security, observability, and performance.
282
-
283
- ### 🛡️ 1. Header Sanitization
284
- Automatically remove sensitive headers like `Authorization` or `Cookie` before your business logic handles the request.
285
- ```ts
286
- app.use(guardMiddleware({
287
- sanitizeHeaders: true,
288
- sensitiveHeaders: ['x-api-key', 'session-id'] // optional extras
289
- }));
290
- ```
291
-
292
- ### 🧠 2. Memory Safety (`maxArrayLength`)
293
- Prevent DoS attacks via extremely large arrays by automatically truncating them to a safe limit.
294
- ```ts
295
- const shape = guard.shape({ items: guard.array('string') }, { maxArrayLength: 1000 });
296
- ```
297
-
298
- ### ⚡ 3. Async Safety (`maxPayloadSize`)
299
- Avoid processing massive JSON payloads that could block the event loop.
300
- ```ts
301
- app.use(guardMiddleware({
302
- maxPayloadSize: 1024 * 512, // 512KB
303
- skipLargePayload: true // Skips filtering if too large
304
- }));
305
- ```
306
-
307
- ### ⏱️ 4. Middleware Timing Stats
308
- Track exactly how much time Payload Guard adds to your request cycle.
309
- ```
310
- [payload-guard] [POST] /api/data: reduced 15KB -> 4KB (73%) in 0.12ms
311
- ```
312
-
313
- ### 📊 5. Human-Readable Metrics
314
- Visibility into bandwidth savings with formatted byte sizes and percentages.
315
-
316
- ### 🛠️ 6. CLI Type Sync
317
- Generate frontend TypeScript types from your backend shapes with one command.
318
- ```bash
319
- npx payload-guard sync
320
- ```
321
-
322
- ### ⚠️ 7. Route-Aware Dev Warnings
323
- Dev mode warnings now include the full method, path, and nested field locations (e.g., `user.profile.password`) for faster debugging.
324
-
325
- ### 🛣️ 8. Ignore Routes
326
- Skip processing for high-volume or incompatible routes like file uploads or health checks.
327
- ```ts
328
- app.use(guardMiddleware({ ignoreRoutes: ['/health', '/upload'] }));
329
- ```
330
-
331
- ### 🏗️ 9. Shared Schemas & Examples
332
- Built-in support for reusable schemas across your monorepo and a `examples/real-world` project for reference.
333
-
334
- ### 🚀 10. Performance Benchmarks
335
- A dedicated benchmark suite to verify sub-millisecond overhead. `npm run benchmark`.
126
+ > **Memory Usage**: ~121 MB Heap Used (Stable)
336
127
 
337
128
  ---
338
129
 
339
- ## 🏗️ Production Hardening
340
-
341
- ### 🛡️ Fail-Safe Mode (`failOpen`)
342
- In production, we prioritize availability. If filtering fails for any reason, `failOpen: true` (default) ensures the original data is sent instead of breaking the request.
130
+ ## 🛡️ Fail-Safe Design
343
131
 
344
- ### 🚫 Non-Serializable Protection
345
- Payload Guard automatically detects and skips `Buffer`, `Stream`, `File`, and `Blob` objects to prevent crashes and performance bottlenecks.
346
-
347
- ### 🔍 Detailed Debug Logs
348
- Enable `logRemovedFields: true` to see exactly which fields were stripped from your payloads:
349
- `[payload-guard] /api/user Removed fields: password, token, internal_id`
132
+ Built for production reliability:
133
+ - **Isolation**: Monitoring failures **never** break your API responses. If a storage adapter or plugin crashes, the error is caught and logged, while the user's request continues normally.
134
+ - **Async-Only**: All processing is non-blocking to ensure zero impact on event loop latency.
350
135
 
351
136
  ---
352
-
353
- ## 🌐 Multi-Framework Support
354
-
355
- The core is zero-dependency and framework-agnostic. Use it anywhere:
356
-
357
- ### Hono / Cloudflare Workers
358
- ```ts
359
- app.post('/user', async (c) => {
360
- const body = await c.req.json();
361
- return c.json(userShape(body));
362
- });
363
- ```
364
-
365
- ### Fastify
366
- ```ts
367
- fastify.post('/user', (req, reply) => {
368
- reply.send(userShape(req.body));
369
- });
370
- ```
371
-
372
- ---
373
-
374
- ## 🛑 When NOT to use
375
-
376
- Payload Guard is optimized for JSON APIs. It is **not** suitable for:
377
- 1. **Binary Data**: PDFs, Images, or raw Buffers.
378
- 2. **File Streams**: Use `multer` or similar for multipart data.
379
- 3. **Heavy Computation**: Don't use it on payloads > 10MB without adjusting `maxPayloadSize`.
380
-
381
- ---
382
-
383
- ## 🛠️ CLI
384
-
385
- ```bash
386
- npx payload-guard init
387
- ```
388
-
389
- Creates example files in your project:
390
- - `shapes.ts` — Example shape definitions
391
- - `server-example.ts` — Express middleware setup
137
+ ### ⛑️ Maintained actively.
138
+ **Bug fixes usually within 24–48 hours.**
392
139
 
393
140
  ---
394
-
395
141
  ## 📄 License
396
142
 
397
143
  MIT
@@ -20,6 +20,55 @@ function compile(shape, opts = {}) {
20
20
  if (isShapeFunction(shape)) {
21
21
  return shape.compile;
22
22
  }
23
+ // Field config or Builder
24
+ if (isFieldConfig(shape)) {
25
+ const config = shape.__isBuilder ? shape.config : shape;
26
+ const primType = config.type;
27
+ const defaultVal = config.default;
28
+ return (value) => {
29
+ let val = value == null ? (defaultVal ?? value) : (0, sanitizer_1.coercePrimitive)(primType, value);
30
+ // Perform validation and transformation if value is not null
31
+ if (val != null) {
32
+ // String validations
33
+ if (primType === 'string' && typeof val === 'string') {
34
+ val = (0, sanitizer_1.trimString)(val);
35
+ if (config.min !== undefined && val.length < config.min)
36
+ return defaultVal ?? undefined;
37
+ if (config.max !== undefined && val.length > config.max)
38
+ return defaultVal ?? undefined;
39
+ if (config.regex && !config.regex.test(val))
40
+ return defaultVal ?? undefined;
41
+ if (config.email) {
42
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
43
+ if (!emailRegex.test(val))
44
+ return defaultVal ?? undefined;
45
+ }
46
+ }
47
+ // Number validations
48
+ if (primType === 'number' && typeof val === 'number') {
49
+ if (config.min !== undefined && val < config.min)
50
+ return defaultVal ?? undefined;
51
+ if (config.max !== undefined && val > config.max)
52
+ return defaultVal ?? undefined;
53
+ }
54
+ // Custom validation
55
+ if (config.validate && !config.validate(val)) {
56
+ return defaultVal ?? undefined;
57
+ }
58
+ // Transformation
59
+ if (config.transform) {
60
+ try {
61
+ val = config.transform(val);
62
+ }
63
+ catch (e) {
64
+ if (opts.dev)
65
+ (opts.logger ?? console.warn)('[payload-guard] transform error: ' + String(e));
66
+ }
67
+ }
68
+ }
69
+ return val;
70
+ };
71
+ }
23
72
  // If already a regular function, wrap it
24
73
  if (typeof shape === 'function') {
25
74
  return (value) => shape(value);
@@ -44,17 +93,6 @@ function compile(shape, opts = {}) {
44
93
  return primType === 'string' ? (0, sanitizer_1.trimString)(coerced) : coerced;
45
94
  };
46
95
  }
47
- // Field config with type
48
- if (isFieldConfig(shape)) {
49
- const primType = shape.type;
50
- const defaultVal = shape.default;
51
- return (value) => {
52
- if (value == null)
53
- return defaultVal ?? value;
54
- const coerced = (0, sanitizer_1.coercePrimitive)(primType, value);
55
- return primType === 'string' ? (0, sanitizer_1.trimString)(coerced) : coerced;
56
- };
57
- }
58
96
  // Array shape
59
97
  if (isArrayShape(shape)) {
60
98
  const itemFn = compile(shape.item, { ...opts, _parentPath: (opts._parentPath || '') + '[]' });
@@ -154,8 +192,11 @@ function isArrayShape(value) {
154
192
  * Type guard for FieldConfig
155
193
  */
156
194
  function isFieldConfig(value) {
157
- return (value !== null &&
158
- typeof value === 'object' &&
195
+ if (value === null)
196
+ return false;
197
+ if (typeof value === 'function' && value.__isBuilder)
198
+ return true;
199
+ return (typeof value === 'object' &&
159
200
  !isShapeFunction(value) &&
160
201
  'type' in value &&
161
202
  typeof value.type === 'string');
@@ -1,4 +1,4 @@
1
- import { ShapeDescriptor, ShapeFunction, GuardConfig } from './types';
1
+ import { ShapeDescriptor, ShapeFunction, GuardConfig, StringBuilder, NumberBuilder, BooleanBuilder, AnyBuilder, ArrayShapeDescriptor } from './types';
2
2
  import { defaultSensitive } from './security';
3
3
  /**
4
4
  * Configure global guard settings
@@ -10,39 +10,18 @@ export declare function config(opts: GuardConfig): void;
10
10
  export declare function getConfig(): GuardConfig;
11
11
  /**
12
12
  * Build a shape filter function from a shape descriptor
13
- * Returns a callable function that filters objects to match the shape
14
13
  */
15
14
  export declare function buildShape<S extends ShapeDescriptor>(shape: S, opts?: GuardConfig): ShapeFunction<S>;
16
- /**
17
- * Main guard API object
18
- */
19
15
  export declare const guard: {
20
- /**
21
- * Create a shape filter from a descriptor
22
- * @example
23
- * const userShape = guard.shape({ id: 'number', name: 'string' });
24
- * const filtered = userShape(userData);
25
- */
26
- shape: <S extends ShapeDescriptor>(descriptor: S, opts?: GuardConfig) => ShapeFunction<S>;
27
- /** Compile a descriptor into a fast filter function directly */
28
- compile: <S extends ShapeDescriptor>(descriptor: S, opts?: GuardConfig) => import("./types").CompiledFilter;
29
- /**
30
- * Create an array shape descriptor
31
- * @example
32
- * const usersShape = guard.shape({ users: guard.array({ id: 'number', name: 'string' }) });
33
- */
34
- array: (item: ShapeDescriptor) => import("./types").ArrayShapeDescriptor;
35
- /**
36
- * Configure global settings
37
- */
38
16
  config: typeof config;
39
- /**
40
- * Get current configuration
41
- */
42
17
  getConfig: typeof getConfig;
43
- /**
44
- * Get default sensitive fields list
45
- */
46
18
  defaultSensitive: typeof defaultSensitive;
19
+ shape: typeof buildShape;
20
+ array: (item: ShapeDescriptor) => ArrayShapeDescriptor;
21
+ string: () => StringBuilder;
22
+ number: () => NumberBuilder;
23
+ boolean: () => BooleanBuilder;
24
+ any: () => AnyBuilder;
25
+ compile: (descriptor: ShapeDescriptor, opts?: GuardConfig) => import("./types").CompiledFilter;
47
26
  };
48
27
  export default guard;
@@ -38,7 +38,6 @@ function getConfig() {
38
38
  }
39
39
  /**
40
40
  * Build a shape filter function from a shape descriptor
41
- * Returns a callable function that filters objects to match the shape
42
41
  */
43
42
  function buildShape(shape, opts) {
44
43
  const mergedOpts = {
@@ -53,59 +52,81 @@ function buildShape(shape, opts) {
53
52
  logRemovedFields: opts?.logRemovedFields ?? globalConfig.logRemovedFields,
54
53
  };
55
54
  const compiledFn = (0, compiler_1.compile)(shape, mergedOpts);
56
- // Create callable wrapper with compile method attached
57
55
  const wrapper = ((value) => compiledFn(value));
58
56
  wrapper.compile = compiledFn;
59
- // attach extend to allow partial shape merges
60
57
  wrapper.extend = function (extra) {
61
- // shallow merge for object shapes
62
58
  if (typeof shape === 'object' && typeof extra === 'object') {
63
59
  const merged = { ...shape, ...extra };
64
60
  return buildShape(merged, opts);
65
61
  }
66
- // fallback: return a wrapper that composes both
67
62
  return buildShape({ _v: shape, _e: extra }, opts);
68
63
  };
69
64
  return wrapper;
70
65
  }
71
- /**
72
- * Main guard API object
73
- */
66
+ function createBuilder(type) {
67
+ const config = { type };
68
+ const builder = (value) => {
69
+ // real logic is in compile()
70
+ return value;
71
+ };
72
+ const proto = {
73
+ __isBuilder: true,
74
+ config,
75
+ required() { config.required = true; return builder; },
76
+ optional() { config.required = false; return builder; },
77
+ default(val) { config.default = val; return builder; },
78
+ transform(fn) { config.transform = fn; return builder; },
79
+ validate(fn) { config.validate = fn; return builder; },
80
+ };
81
+ Object.setPrototypeOf(builder, proto);
82
+ if (type === 'string') {
83
+ builder.min = (n) => { config.min = n; return builder; };
84
+ builder.max = (n) => { config.max = n; return builder; };
85
+ builder.email = () => { config.email = true; return builder; };
86
+ builder.regex = (re) => { config.regex = re; return builder; };
87
+ builder.trim = () => {
88
+ const prev = config.transform;
89
+ config.transform = (v) => {
90
+ let val = typeof v === 'string' ? v.trim() : v;
91
+ return prev ? prev(val) : val;
92
+ };
93
+ return builder;
94
+ };
95
+ builder.toLowerCase = () => {
96
+ const prev = config.transform;
97
+ config.transform = (v) => {
98
+ let val = typeof v === 'string' ? v.toLowerCase() : v;
99
+ return prev ? prev(val) : val;
100
+ };
101
+ return builder;
102
+ };
103
+ builder.toUpperCase = () => {
104
+ const prev = config.transform;
105
+ config.transform = (v) => {
106
+ let val = typeof v === 'string' ? v.toUpperCase() : v;
107
+ return prev ? prev(val) : val;
108
+ };
109
+ return builder;
110
+ };
111
+ }
112
+ if (type === 'number') {
113
+ builder.min = (n) => { config.min = n; return builder; };
114
+ builder.max = (n) => { config.max = n; return builder; };
115
+ builder.integer = () => { config.validate = (v) => Number.isInteger(v); return builder; };
116
+ builder.positive = () => { config.min = 0; return builder; };
117
+ }
118
+ return builder;
119
+ }
74
120
  exports.guard = {
75
- /**
76
- * Create a shape filter from a descriptor
77
- * @example
78
- * const userShape = guard.shape({ id: 'number', name: 'string' });
79
- * const filtered = userShape(userData);
80
- */
81
- shape: (descriptor, opts) => buildShape(descriptor, opts),
82
- /** Compile a descriptor into a fast filter function directly */
83
- compile: (descriptor, opts) => (0, compiler_1.compile)(descriptor, {
84
- ...opts,
85
- sensitive: [...(opts?.sensitiveFields || []), ...(globalConfig.sensitiveFields || [])],
86
- dev: opts?.devMode ?? globalConfig.devMode,
87
- strict: opts?.strict ?? globalConfig.strict,
88
- redact: opts?.redact ?? globalConfig.redact,
89
- logger: opts?.logger ?? globalConfig.logger,
90
- maxArrayLength: opts?.maxArrayLength ?? globalConfig.maxArrayLength,
91
- }),
92
- /**
93
- * Create an array shape descriptor
94
- * @example
95
- * const usersShape = guard.shape({ users: guard.array({ id: 'number', name: 'string' }) });
96
- */
97
- array: (item) => (0, compiler_1.array)(item),
98
- /**
99
- * Configure global settings
100
- */
101
121
  config,
102
- /**
103
- * Get current configuration
104
- */
105
122
  getConfig,
106
- /**
107
- * Get default sensitive fields list
108
- */
109
123
  defaultSensitive: security_1.defaultSensitive,
124
+ shape: buildShape,
125
+ array: (item) => (0, compiler_1.array)(item),
126
+ string: () => createBuilder('string'),
127
+ number: () => createBuilder('number'),
128
+ boolean: () => createBuilder('boolean'),
129
+ any: () => createBuilder('any'),
130
+ compile: (descriptor, opts) => (0, compiler_1.compile)(descriptor, opts),
110
131
  };
111
132
  exports.default = exports.guard;
@@ -7,11 +7,47 @@ export interface FieldConfig<T extends PrimitiveType = PrimitiveType> {
7
7
  type: T;
8
8
  required?: boolean;
9
9
  default?: InferPrimitive<T>;
10
+ min?: number;
11
+ max?: number;
12
+ regex?: RegExp;
13
+ email?: boolean;
14
+ transform?: (v: any) => any;
15
+ /** Custom validation function: return false if invalid */
16
+ validate?: (v: any) => boolean;
17
+ }
18
+ export interface ShapeBuilder<T extends PrimitiveType> {
19
+ (value: unknown): InferPrimitive<T>;
20
+ readonly __isBuilder: true;
21
+ readonly config: FieldConfig<T>;
22
+ required(): ShapeBuilder<T>;
23
+ optional(): ShapeBuilder<T>;
24
+ default(val: InferPrimitive<T>): ShapeBuilder<T>;
25
+ transform(fn: (v: InferPrimitive<T>) => any): ShapeBuilder<T>;
26
+ validate(fn: (v: InferPrimitive<T>) => boolean): ShapeBuilder<T>;
27
+ }
28
+ export interface StringBuilder extends ShapeBuilder<'string'> {
29
+ min(len: number): StringBuilder;
30
+ max(len: number): StringBuilder;
31
+ email(): StringBuilder;
32
+ regex(pattern: RegExp): StringBuilder;
33
+ trim(): StringBuilder;
34
+ toLowerCase(): StringBuilder;
35
+ toUpperCase(): StringBuilder;
36
+ }
37
+ export interface NumberBuilder extends ShapeBuilder<'number'> {
38
+ min(val: number): NumberBuilder;
39
+ max(val: number): NumberBuilder;
40
+ integer(): NumberBuilder;
41
+ positive(): NumberBuilder;
42
+ }
43
+ export interface BooleanBuilder extends ShapeBuilder<'boolean'> {
44
+ }
45
+ export interface AnyBuilder extends ShapeBuilder<'any'> {
10
46
  }
11
47
  export type InferPrimitive<T extends PrimitiveType> = T extends 'string' ? string : T extends 'number' ? number : T extends 'boolean' ? boolean : T extends 'any' ? unknown : never;
12
48
  export type ShapeDescriptor = PrimitiveType | FieldConfig | {
13
49
  [key: string]: ShapeDescriptor;
14
- } | ArrayShapeDescriptor | CompiledFilter;
50
+ } | ArrayShapeDescriptor | CompiledFilter | ShapeBuilder<any>;
15
51
  export interface ArrayShapeDescriptor {
16
52
  readonly __isArray: true;
17
53
  readonly item: ShapeDescriptor;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-guard-filter",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "Lightweight, zero-dependency shape-based payload filtering and sanitization for Node.js and browser",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",