@dwtechs/antity 0.18.3 → 0.19.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 +51 -30
- package/dist/antity.d.ts +40 -79
- package/dist/antity.js +68 -49
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
[](https://opensource.org/licenses/MIT)
|
|
3
3
|
[](https://www.npmjs.com/package/@dwtechs/antity)
|
|
4
4
|
[](https://www.npmjs.com/package/@dwtechs/antity)
|
|
5
|
-

|
|
6
6
|
|
|
7
7
|
- [Synopsis](#synopsis)
|
|
8
8
|
- [Support](#support)
|
|
@@ -45,7 +45,7 @@ $ npm i @dwtechs/antity
|
|
|
45
45
|
import { Entity } from "@dwtechs/antity";
|
|
46
46
|
import { normalizeName, normalizeNickname } from "@dwtechs/checkard";
|
|
47
47
|
|
|
48
|
-
const entity = new Entity("
|
|
48
|
+
const entity = new Entity("users", [
|
|
49
49
|
{
|
|
50
50
|
key: "id",
|
|
51
51
|
type: "integer",
|
|
@@ -89,7 +89,7 @@ const entity = new Entity("consumers", [
|
|
|
89
89
|
max: 255,
|
|
90
90
|
isTypeChecked: true,
|
|
91
91
|
requiredFor: ["PUT"],
|
|
92
|
-
isPrivate:
|
|
92
|
+
isPrivate: false,
|
|
93
93
|
sanitizer: null,
|
|
94
94
|
normalizer: normalizeNickname,
|
|
95
95
|
validator: null,
|
|
@@ -146,14 +146,15 @@ type Method = "PATCH" | "PUT" | "POST";
|
|
|
146
146
|
class Property {
|
|
147
147
|
key: string;
|
|
148
148
|
type: Type;
|
|
149
|
-
min: number | Date
|
|
150
|
-
max: number | Date
|
|
149
|
+
min: number | Date;
|
|
150
|
+
max: number | Date;
|
|
151
151
|
requiredFor: Method[];
|
|
152
152
|
isPrivate: boolean;
|
|
153
153
|
isTypeChecked: boolean;
|
|
154
154
|
sanitizer: Function | null;
|
|
155
155
|
normalizer: Function | null;
|
|
156
156
|
validator: Function | null;
|
|
157
|
+
readOnly: boolean;
|
|
157
158
|
};
|
|
158
159
|
|
|
159
160
|
class Entity {
|
|
@@ -208,6 +209,28 @@ class Entity {
|
|
|
208
209
|
* ```
|
|
209
210
|
*/
|
|
210
211
|
getPropsByMethod(method: Method): Property[];
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Builds a single Property instance from a plain field-definition object.
|
|
215
|
+
* Called once per entry of the `properties` array passed to the constructor.
|
|
216
|
+
*
|
|
217
|
+
* - Not meant to be called directly; override it in a subclass to build
|
|
218
|
+
* your own `Property` subclass (e.g. a library adding its own fields)
|
|
219
|
+
* while still reusing `Entity`'s constructor bookkeeping.
|
|
220
|
+
*
|
|
221
|
+
* @param {Record<string, unknown>} p - Plain field-definition object
|
|
222
|
+
* @returns {Property} The constructed Property instance
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```typescript
|
|
226
|
+
* class MyEntity extends Entity {
|
|
227
|
+
* protected createProperty(p: Record<string, unknown>): Property {
|
|
228
|
+
* return new MyProperty(p.key, p.type, ..., p.myCustomField);
|
|
229
|
+
* }
|
|
230
|
+
* }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
protected createProperty(p: Record<string, unknown>): Property;
|
|
211
234
|
|
|
212
235
|
/**
|
|
213
236
|
* Normalizes an array of records by applying sanitization and normalization
|
|
@@ -219,7 +242,7 @@ class Entity {
|
|
|
219
242
|
* - Calls next(error) on failure, next() on success
|
|
220
243
|
*
|
|
221
244
|
* @param {Request} req - Express request object containing rows
|
|
222
|
-
* @param {Response}
|
|
245
|
+
* @param {Response} res - Express response object. Set `res.locals.allowReadOnly = true` beforehand to allow writing `readOnly` properties through this call.
|
|
223
246
|
* @param {NextFunction} next - Express next function
|
|
224
247
|
*
|
|
225
248
|
* @returns {void}
|
|
@@ -240,7 +263,7 @@ class Entity {
|
|
|
240
263
|
* });
|
|
241
264
|
* ```
|
|
242
265
|
*/
|
|
243
|
-
normalizeArray: (req: Request,
|
|
266
|
+
normalizeArray: (req: Request, res: Response, next: NextFunction) => void;
|
|
244
267
|
|
|
245
268
|
/**
|
|
246
269
|
* Normalizes a single record by applying sanitization and normalization
|
|
@@ -252,7 +275,7 @@ class Entity {
|
|
|
252
275
|
* - Calls next(error) on failure, next() on success
|
|
253
276
|
*
|
|
254
277
|
* @param {Request} req - Express request object containing a single record
|
|
255
|
-
* @param {Response}
|
|
278
|
+
* @param {Response} res - Express response object. Set `res.locals.allowReadOnly = true` beforehand to allow writing `readOnly` properties through this call.
|
|
256
279
|
* @param {NextFunction} next - Express next function
|
|
257
280
|
*
|
|
258
281
|
* @returns {void}
|
|
@@ -273,7 +296,7 @@ class Entity {
|
|
|
273
296
|
* });
|
|
274
297
|
* ```
|
|
275
298
|
*/
|
|
276
|
-
normalizeOne: (req: Request,
|
|
299
|
+
normalizeOne: (req: Request, res: Response, next: NextFunction) => void;
|
|
277
300
|
|
|
278
301
|
/**
|
|
279
302
|
* Validates an array of rows according to property config and HTTP method.
|
|
@@ -282,7 +305,7 @@ class Entity {
|
|
|
282
305
|
* - Calls next(error) on failure, next() on success
|
|
283
306
|
*
|
|
284
307
|
* @param {Request} req - Express request object containing rows
|
|
285
|
-
* @param {Response}
|
|
308
|
+
* @param {Response} res - Express response object. Set `res.locals.allowReadOnly = true` beforehand to allow writing `readOnly` properties through this call.
|
|
286
309
|
* @param {NextFunction} next - Express next function
|
|
287
310
|
*
|
|
288
311
|
* @returns {void}
|
|
@@ -302,7 +325,7 @@ class Entity {
|
|
|
302
325
|
* });
|
|
303
326
|
* ```
|
|
304
327
|
*/
|
|
305
|
-
validateArray: (req: Request,
|
|
328
|
+
validateArray: (req: Request, res: Response, next: NextFunction) => void;
|
|
306
329
|
|
|
307
330
|
/**
|
|
308
331
|
* Validates a single record according to property config and HTTP method.
|
|
@@ -311,7 +334,7 @@ class Entity {
|
|
|
311
334
|
* - Calls next(error) on failure, next() on success
|
|
312
335
|
*
|
|
313
336
|
* @param {Request} req - Express request object containing a single record
|
|
314
|
-
* @param {Response}
|
|
337
|
+
* @param {Response} res - Express response object. Set `res.locals.allowReadOnly = true` beforehand to allow writing `readOnly` properties through this call.
|
|
315
338
|
* @param {NextFunction} next - Express next function
|
|
316
339
|
*
|
|
317
340
|
* @returns {void}
|
|
@@ -331,7 +354,7 @@ class Entity {
|
|
|
331
354
|
* });
|
|
332
355
|
* ```
|
|
333
356
|
*/
|
|
334
|
-
validateOne: (req: Request,
|
|
357
|
+
validateOne: (req: Request, res: Response, next: NextFunction) => void;
|
|
335
358
|
|
|
336
359
|
```
|
|
337
360
|
**normalizeArray()**, **normalizeOne()**, **validateArray()**, and **validateOne()** methods are made to be used as Express.js middlewares.
|
|
@@ -371,23 +394,21 @@ Properties **min** and **max** of the password properties will override default
|
|
|
371
394
|
|
|
372
395
|
### Available options for a property
|
|
373
396
|
|
|
374
|
-
Any of these can be passed into the options object for each function.
|
|
375
|
-
|
|
376
|
-
| Name | Type |
|
|
377
|
-
| :-------------- | :----------------------- |
|
|
378
|
-
| key | string |
|
|
379
|
-
| type | Type |
|
|
380
|
-
| min | number \| Date |
|
|
381
|
-
| max | number \| Date |
|
|
382
|
-
| requiredFor | Methods[] |
|
|
383
|
-
| isPrivate | boolean |
|
|
384
|
-
| isTypeChecked | boolean |
|
|
385
|
-
|
|
|
386
|
-
|
|
|
387
|
-
|
|
|
388
|
-
|
|
389
|
-
* *Min and max parameters are not used for boolean type*
|
|
390
|
-
* *TypeCheck Parameter is not used for boolean, string and array types*
|
|
397
|
+
Any of these can be passed into the options object for each function. **Behavior** describes exactly what `normalizeArray`/`normalizeOne`/`validateArray`/`validateOne` do with the property at runtime, depending on its value.
|
|
398
|
+
|
|
399
|
+
| Name | Type | Default value | Behavior |
|
|
400
|
+
| :-------------- | :----------------------- | :-------------- | :------- |
|
|
401
|
+
| key | string | | Read/write key on each record. No behavior of its own. |
|
|
402
|
+
| type | Type | | When a value is present, selects the built-in type validator run during `validate()` — skipped entirely if `validator` is set. |
|
|
403
|
+
| min | number \| Date | 0 \| 1900-01-01 | Passed to the `type` validator as a lower bound during `validate()`. Not used for `boolean`. |
|
|
404
|
+
| max | number \| Date | 999999999 \| 2200-12-31 | Passed to the `type` validator as an upper bound during `validate()`. Not used for `boolean`. |
|
|
405
|
+
| requiredFor | Methods[] | [ ] | If the current HTTP method is in this list, `validate()` rejects the record with a 400 when the value is `null`/`undefined`. **Ignored when `readOnly` is `true`** (see below) — a `readOnly` field is never required. |
|
|
406
|
+
| isPrivate | boolean | false | Not enforced by `normalize()`/`validate()`. When `true`, the key is added to `entity.privateProps` — your own response code is responsible for stripping it from output; antity.js never removes it itself. |
|
|
407
|
+
| isTypeChecked | boolean | false | Passed to the `type` validator to toggle strict vs. lenient checking; the exact effect depends on `type` (e.g. a stricter locale/timezone allow-list). Not used for `boolean`, `string` or `array` types. |
|
|
408
|
+
| readOnly | boolean | false | If `true`: `normalize()` **deletes** the key from the record before sanitizing/normalizing, and `validate()` **skips** it entirely (never required, never checked) — a client can never set it or trigger validation on it, whatever `requiredFor` says. Bypassed for one request by setting `res.locals.allowReadOnly = true` beforehand (trusted server-side writes only). If `false`, treated like any other property. |
|
|
409
|
+
| sanitizer | ((v:any) => any) \| null | null | If set, `normalize()` calls it instead of the default sanitizer for any present (truthy) value. If `null`, the default trims strings — recursively for a plain object's string properties, per-element for an array. |
|
|
410
|
+
| normalizer | ((v:any) => any) \| null | null | If set, `normalize()` calls it right after sanitizing, for any present (truthy) value. If `null`, no normalization step runs. |
|
|
411
|
+
| validator | ((v:any) => boolean) \| null | null | If set, `validate()` calls it instead of the built-in `type` validator for any present value: return `false` or throw to fail (a thrown error's message is included in the 400 response). If `null`, the built-in `type`/`min`/`max`/`isTypeChecked` validator runs. |
|
|
391
412
|
|
|
392
413
|
|
|
393
414
|
## Contributors
|
package/dist/antity.d.ts
CHANGED
|
@@ -24,88 +24,49 @@ SOFTWARE.
|
|
|
24
24
|
https://github.com/DWTechs/Antity.js
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import
|
|
28
|
-
declare const Methods: readonly ["PATCH", "PUT", "POST"];
|
|
29
|
-
type Method = typeof Methods[number];
|
|
30
|
-
type Type =
|
|
31
|
-
"boolean" |
|
|
32
|
-
"string" |
|
|
33
|
-
"number" |
|
|
34
|
-
"integer" |
|
|
35
|
-
"float" |
|
|
36
|
-
"even" |
|
|
37
|
-
"odd" |
|
|
38
|
-
"positive" |
|
|
39
|
-
"negative" |
|
|
40
|
-
"powerOfTwo" |
|
|
41
|
-
"ascii" |
|
|
42
|
-
"array" |
|
|
43
|
-
"jwt" |
|
|
44
|
-
"symbol" |
|
|
45
|
-
"password" |
|
|
46
|
-
"email" |
|
|
47
|
-
"regex" |
|
|
48
|
-
"json" |
|
|
49
|
-
"ipAddress" |
|
|
50
|
-
"slug" |
|
|
51
|
-
"hexadecimal" |
|
|
52
|
-
"date" |
|
|
53
|
-
"timestamp" |
|
|
54
|
-
"function" |
|
|
55
|
-
"htmlElement" |
|
|
56
|
-
"htmlEventAttribute" |
|
|
57
|
-
"node" |
|
|
58
|
-
"object" |
|
|
59
|
-
"ansiEscapeCode" |
|
|
60
|
-
"locale" |
|
|
61
|
-
"timeZone";
|
|
27
|
+
import { Request, Response, NextFunction } from 'express';
|
|
62
28
|
|
|
63
|
-
declare
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
constructor(name: string, properties: Property[]);
|
|
68
|
-
get name(): string;
|
|
69
|
-
get privateProps(): string[];
|
|
70
|
-
get properties(): Property[];
|
|
71
|
-
set name(name: string);
|
|
72
|
-
getProp(key: string): Property | undefined;
|
|
73
|
-
getPropsByMethod(method: Method): Property[];
|
|
74
|
-
normalizeArray: (req: Request, _res: Response, next: NextFunction) => void;
|
|
75
|
-
normalizeOne: (req: Request, _res: Response, next: NextFunction) => void;
|
|
76
|
-
validateArray: (req: Request, _res: Response, next: NextFunction) => void;
|
|
77
|
-
validateOne: (req: Request, _res: Response, next: NextFunction) => void;
|
|
78
|
-
}
|
|
29
|
+
declare const METHODS: string[];
|
|
30
|
+
|
|
31
|
+
type Type = "boolean" | "string" | "number" | "integer" | "float" | "even" | "odd" | "positive" | "negative" | "powerOfTwo" | "ascii" | "array" | "jwt" | "symbol" | "password" | "email" | "regex" | "json" | "ipAddress" | "slug" | "hexadecimal" | "date" | "timestamp" | "function" | "htmlElement" | "htmlEventAttribute" | "node" | "object" | "ansiEscapeCode" | "locale" | "timeZone";
|
|
32
|
+
type Method = typeof METHODS[number];
|
|
79
33
|
|
|
80
34
|
declare class Property {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
key: string,
|
|
94
|
-
|
|
95
|
-
min: number | Date | null,
|
|
96
|
-
max: number | Date | null,
|
|
97
|
-
isPrivate: boolean,
|
|
98
|
-
requiredFor: Method[],
|
|
99
|
-
isTypeChecked: boolean,
|
|
100
|
-
sanitizer: ((v: any) => any) | null,
|
|
101
|
-
normalizer: ((v: any) => any) | null,
|
|
102
|
-
validator: ((v: any) => any) | null
|
|
103
|
-
);
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
key: string;
|
|
37
|
+
type: Type;
|
|
38
|
+
min: number | Date;
|
|
39
|
+
max: number | Date;
|
|
40
|
+
isPrivate: boolean;
|
|
41
|
+
requiredFor: Method[];
|
|
42
|
+
isTypeChecked: boolean;
|
|
43
|
+
readOnly: boolean;
|
|
44
|
+
sanitizer: ((v: any) => any) | null;
|
|
45
|
+
normalizer: ((v: any) => any) | null;
|
|
46
|
+
validator: ((v: any) => boolean) | null;
|
|
47
|
+
constructor(key: string, type: Type, min: number | Date | null, max: number | Date | null, isPrivate: boolean, requiredFor: Method[], isTypeChecked: boolean, readOnly: boolean, sanitizer: ((v: any) => any) | null, normalizer: ((v: any) => any) | null, validator: ((v: any) => boolean) | null);
|
|
48
|
+
private interval;
|
|
104
49
|
}
|
|
105
50
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
51
|
+
declare const STANDARD_PROP_KEYS: ReadonlySet<string>;
|
|
52
|
+
declare class Entity {
|
|
53
|
+
private _name;
|
|
54
|
+
private _privateProps;
|
|
55
|
+
private _properties;
|
|
56
|
+
private _propsByMethod;
|
|
57
|
+
constructor(name: string, properties: Property[]);
|
|
58
|
+
protected createProperty(p: Record<string, unknown>): Property;
|
|
59
|
+
get name(): string;
|
|
60
|
+
get privateProps(): string[];
|
|
61
|
+
get properties(): Property[];
|
|
62
|
+
set name(name: string);
|
|
63
|
+
getProp(key: string): Property | undefined;
|
|
64
|
+
getPropsByMethod(method: Method): Property[];
|
|
65
|
+
normalizeArray: (req: Request, res: Response, next: NextFunction) => void;
|
|
66
|
+
normalizeOne: (req: Request, res: Response, next: NextFunction) => void;
|
|
67
|
+
validateArray: (req: Request, res: Response, next: NextFunction) => void;
|
|
68
|
+
validateOne: (req: Request, res: Response, next: NextFunction) => void;
|
|
69
|
+
}
|
|
111
70
|
|
|
71
|
+
export { Entity, Property, STANDARD_PROP_KEYS };
|
|
72
|
+
export type { Method, Type };
|
package/dist/antity.js
CHANGED
|
@@ -24,7 +24,7 @@ SOFTWARE.
|
|
|
24
24
|
https://github.com/DWTechs/Antity.js
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import { isBoolean, isStringOfLength, isValidNumber, isValidInteger, isValidFloat, isEven, isOdd, isPositive, isNegative, isPowerOfTwo, isAscii, isArrayOfLength, isEmail, isRegex, isJson, isJWT, isSymbol, isIpAddress, isSlug, isHexadecimal, isValidDate, isValidTimestamp, isFunction, isHtmlElement, isHtmlEventAttribute, isNode, isObject, isAnsiEscapeCode, isLocale, isTimeZone, isString, isProperty, isArray, isIn, isDate, isNumber,
|
|
27
|
+
import { isBoolean, isStringOfLength, isValidNumber, isValidInteger, isValidFloat, isEven, isOdd, isPositive, isNegative, isPowerOfTwo, isAscii, isArrayOfLength, isEmail, isRegex, isJson, isJWT, isSymbol, isIpAddress, isSlug, isHexadecimal, isValidDate, isValidTimestamp, isFunction, isHtmlElement, isHtmlEventAttribute, isNode, isObject, isAnsiEscapeCode, isLocale, isTimeZone, isString, isProperty, isArray, isIn, isDate, isNumber, isNil } from '@dwtechs/checkard';
|
|
28
28
|
import { log } from '@dwtechs/winstan';
|
|
29
29
|
import { isValidPassword } from '@dwtechs/passken';
|
|
30
30
|
|
|
@@ -46,34 +46,34 @@ const Types = {
|
|
|
46
46
|
validate: (v, min, max, _typeCheck) => isStringOfLength(v, min, max, true)
|
|
47
47
|
},
|
|
48
48
|
number: {
|
|
49
|
-
validate: (v, min, max, typeCheck) => isValidNumber(v, min
|
|
49
|
+
validate: (v, min, max, typeCheck) => isValidNumber(v, min, max, typeCheck, true)
|
|
50
50
|
},
|
|
51
51
|
integer: {
|
|
52
|
-
validate: (v, min, max, typeCheck) => isValidInteger(v, min
|
|
52
|
+
validate: (v, min, max, typeCheck) => isValidInteger(v, min, max, typeCheck, true)
|
|
53
53
|
},
|
|
54
54
|
float: {
|
|
55
|
-
validate: (v, min, max, typeCheck) => isValidFloat(v, min
|
|
55
|
+
validate: (v, min, max, typeCheck) => isValidFloat(v, min, max, typeCheck, true)
|
|
56
56
|
},
|
|
57
57
|
even: {
|
|
58
|
-
validate: (v, _min, _max, typeCheck) => isEven(v, typeCheck
|
|
58
|
+
validate: (v, _min, _max, typeCheck) => isEven(v, typeCheck, true)
|
|
59
59
|
},
|
|
60
60
|
odd: {
|
|
61
|
-
validate: (v, _min, _max, typeCheck) => isOdd(v, typeCheck
|
|
61
|
+
validate: (v, _min, _max, typeCheck) => isOdd(v, typeCheck, true)
|
|
62
62
|
},
|
|
63
63
|
positive: {
|
|
64
|
-
validate: (v, _min, _max, typeCheck) => isPositive(v, typeCheck
|
|
64
|
+
validate: (v, _min, _max, typeCheck) => isPositive(v, typeCheck, true)
|
|
65
65
|
},
|
|
66
66
|
negative: {
|
|
67
|
-
validate: (v, _min, _max, typeCheck) => isNegative(v, typeCheck
|
|
67
|
+
validate: (v, _min, _max, typeCheck) => isNegative(v, typeCheck, true)
|
|
68
68
|
},
|
|
69
69
|
powerOfTwo: {
|
|
70
|
-
validate: (v, _min, _max, typeCheck) => isPowerOfTwo(v, typeCheck
|
|
70
|
+
validate: (v, _min, _max, typeCheck) => isPowerOfTwo(v, typeCheck, true)
|
|
71
71
|
},
|
|
72
72
|
ascii: {
|
|
73
|
-
validate: (v, _min, _max, typeCheck) => isAscii(v, typeCheck
|
|
73
|
+
validate: (v, _min, _max, typeCheck) => isAscii(v, typeCheck, true)
|
|
74
74
|
},
|
|
75
75
|
array: {
|
|
76
|
-
validate: (v, min, max, _typeCheck) => isArrayOfLength(v, min
|
|
76
|
+
validate: (v, min, max, _typeCheck) => isArrayOfLength(v, min, max, true)
|
|
77
77
|
},
|
|
78
78
|
password: {
|
|
79
79
|
validate: (v, min, max, _typeCheck) => {
|
|
@@ -92,7 +92,7 @@ const Types = {
|
|
|
92
92
|
validate: (v, _min, _max, _typeCheck) => isEmail(v, true)
|
|
93
93
|
},
|
|
94
94
|
regex: {
|
|
95
|
-
validate: (v, _min, _max, typeCheck) => isRegex(v, typeCheck
|
|
95
|
+
validate: (v, _min, _max, typeCheck) => isRegex(v, typeCheck, true)
|
|
96
96
|
},
|
|
97
97
|
json: {
|
|
98
98
|
validate: (v, _min, _max, _typeCheck) => isJson(v, true)
|
|
@@ -113,10 +113,10 @@ const Types = {
|
|
|
113
113
|
validate: (v, _min, _max, _typeCheck) => isHexadecimal(v, true)
|
|
114
114
|
},
|
|
115
115
|
date: {
|
|
116
|
-
validate: (v, min, max, _typeCheck) => isValidDate(v, min
|
|
116
|
+
validate: (v, min, max, _typeCheck) => isValidDate(v, min, max, true)
|
|
117
117
|
},
|
|
118
118
|
timestamp: {
|
|
119
|
-
validate: (v, min, max, typeCheck) => isValidTimestamp(v, min
|
|
119
|
+
validate: (v, min, max, typeCheck) => isValidTimestamp(v, min, max, typeCheck, true)
|
|
120
120
|
},
|
|
121
121
|
function: {
|
|
122
122
|
validate: (v, _min, _max, _typeCheck) => isFunction(v, true)
|
|
@@ -131,13 +131,13 @@ const Types = {
|
|
|
131
131
|
validate: (v, _min, _max, _typeCheck) => isNode(v, true)
|
|
132
132
|
},
|
|
133
133
|
object: {
|
|
134
|
-
validate: (v, _min, _max, _typeCheck) => isObject(v, true)
|
|
134
|
+
validate: (v, _min, _max, _typeCheck) => isObject(v, false, true)
|
|
135
135
|
},
|
|
136
136
|
ansiEscapeCode: {
|
|
137
137
|
validate: (v, _min, _max, _typeCheck) => isAnsiEscapeCode(v, true)
|
|
138
138
|
},
|
|
139
139
|
locale: {
|
|
140
|
-
validate: (v, _min, _max, typeCheck) => isLocale(v, typeCheck
|
|
140
|
+
validate: (v, _min, _max, typeCheck) => isLocale(v, typeCheck, true)
|
|
141
141
|
},
|
|
142
142
|
timeZone: {
|
|
143
143
|
validate: (v, _min, _max, _typeCheck) => isTimeZone(v, true)
|
|
@@ -152,10 +152,11 @@ class Property {
|
|
|
152
152
|
isPrivate;
|
|
153
153
|
requiredFor;
|
|
154
154
|
isTypeChecked;
|
|
155
|
+
readOnly;
|
|
155
156
|
sanitizer;
|
|
156
157
|
normalizer;
|
|
157
158
|
validator;
|
|
158
|
-
constructor(key, type, min, max, isPrivate, requiredFor, isTypeChecked, sanitizer, normalizer, validator) {
|
|
159
|
+
constructor(key, type, min, max, isPrivate, requiredFor, isTypeChecked, readOnly, sanitizer, normalizer, validator) {
|
|
159
160
|
try {
|
|
160
161
|
isString(key, "!0", null, true);
|
|
161
162
|
}
|
|
@@ -168,16 +169,14 @@ class Property {
|
|
|
168
169
|
catch (err) {
|
|
169
170
|
throw new Error(`${LOGS_PREFIX}Property "type" must be a valid type - caused by: ${err.message}`);
|
|
170
171
|
}
|
|
171
|
-
if (isArray(requiredFor))
|
|
172
|
-
for (const m of requiredFor)
|
|
172
|
+
if (isArray(requiredFor))
|
|
173
|
+
for (const m of requiredFor)
|
|
173
174
|
try {
|
|
174
175
|
isIn(METHODS, m, 0, true);
|
|
175
176
|
}
|
|
176
177
|
catch (err) {
|
|
177
178
|
throw new Error(`${LOGS_PREFIX}Property "requiredFor" must be an array of REST methods - caused by: ${err.message}`);
|
|
178
179
|
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
180
|
this.key = key;
|
|
182
181
|
this.type = type;
|
|
183
182
|
this.min = this.interval(min, type, 0, "1900-01-01T00:00:00Z");
|
|
@@ -185,6 +184,7 @@ class Property {
|
|
|
185
184
|
this.requiredFor = isArray(requiredFor) ? requiredFor : [];
|
|
186
185
|
this.isPrivate = isBoolean(isPrivate) ? isPrivate : false;
|
|
187
186
|
this.isTypeChecked = isBoolean(isTypeChecked) ? isTypeChecked : false;
|
|
187
|
+
this.readOnly = isBoolean(readOnly) ? readOnly : false;
|
|
188
188
|
this.sanitizer = isFunction(sanitizer) ? sanitizer : null;
|
|
189
189
|
this.normalizer = isFunction(normalizer) ? normalizer : null;
|
|
190
190
|
this.validator = isFunction(validator) ? validator : null;
|
|
@@ -192,7 +192,7 @@ class Property {
|
|
|
192
192
|
interval(val, type, integerDefault, dateDefault) {
|
|
193
193
|
if (type === "date")
|
|
194
194
|
return isDate(val) ? val : new Date(dateDefault);
|
|
195
|
-
return
|
|
195
|
+
return isNumber(val, true) ? val : integerDefault;
|
|
196
196
|
}
|
|
197
197
|
}
|
|
198
198
|
|
|
@@ -221,14 +221,22 @@ function trim(v) {
|
|
|
221
221
|
return v;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
function
|
|
225
|
-
|
|
224
|
+
function logSafe(v) {
|
|
225
|
+
return typeof v === 'string' ? v.replace(/[\r\n\t]/g, '') : v;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function normalize(record, properties, allowReadOnly = false) {
|
|
229
|
+
for (const { key, type, sanitizer, normalizer, readOnly, } of properties) {
|
|
230
|
+
if (readOnly && !allowReadOnly) {
|
|
231
|
+
delete record[key];
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
226
234
|
let v = record[key];
|
|
227
|
-
if (v) {
|
|
228
|
-
log.debug(`sanitize ${key}: ${type} = ${v}`);
|
|
235
|
+
if (!isNil(v)) {
|
|
236
|
+
log.debug(`sanitize ${key}: ${type} = ${logSafe(v)}`);
|
|
229
237
|
v = sanitize(v, sanitizer);
|
|
230
238
|
if (normalizer) {
|
|
231
|
-
log.debug(`normalize ${key}: ${type} = ${v}`);
|
|
239
|
+
log.debug(`normalize ${key}: ${type} = ${logSafe(v)}`);
|
|
232
240
|
v = normalizer(v);
|
|
233
241
|
}
|
|
234
242
|
record[key] = v;
|
|
@@ -237,11 +245,12 @@ function normalize(record, properties) {
|
|
|
237
245
|
}
|
|
238
246
|
|
|
239
247
|
function control(v, key, type, min, max, typeCheck, cb) {
|
|
240
|
-
log.debug(`control ${key}: ${type} = ${v}`);
|
|
248
|
+
log.debug(`control ${key}: ${type} = ${logSafe(v)}`);
|
|
241
249
|
let errorMessage = "";
|
|
242
250
|
if (cb)
|
|
243
251
|
try {
|
|
244
|
-
cb(v)
|
|
252
|
+
if (cb(v) === false)
|
|
253
|
+
errorMessage = `Custom validator callback failed for "${key}"`;
|
|
245
254
|
}
|
|
246
255
|
catch (err) {
|
|
247
256
|
errorMessage = `Custom validator callback failed for "${key}" - caused by: ${err.message}`;
|
|
@@ -259,19 +268,21 @@ function control(v, key, type, min, max, typeCheck, cb) {
|
|
|
259
268
|
}
|
|
260
269
|
|
|
261
270
|
function require(v, key, type) {
|
|
262
|
-
log.debug(`require ${key}: ${type} = ${v}`);
|
|
271
|
+
log.debug(`require ${key}: ${type} = ${logSafe(v)}`);
|
|
263
272
|
return isNil(v) ? { statusCode: 400, message: `${LOGS_PREFIX}Missing ${key} of type ${type}` } : null;
|
|
264
273
|
}
|
|
265
274
|
|
|
266
|
-
function validate(record, properties, method) {
|
|
267
|
-
for (const { key, type, min, max, requiredFor, isTypeChecked, validator } of properties) {
|
|
275
|
+
function validate(record, properties, method, allowReadOnly = false) {
|
|
276
|
+
for (const { key, type, min, max, requiredFor, isTypeChecked, readOnly, validator, } of properties) {
|
|
277
|
+
if (readOnly && !allowReadOnly)
|
|
278
|
+
continue;
|
|
268
279
|
const v = record[key];
|
|
269
280
|
if (requiredFor.includes(method)) {
|
|
270
281
|
const rq = require(v, key, type);
|
|
271
282
|
if (rq)
|
|
272
283
|
return rq;
|
|
273
284
|
}
|
|
274
|
-
if (v) {
|
|
285
|
+
if (!isNil(v)) {
|
|
275
286
|
const ct = control(v, key, type, min, max, isTypeChecked, validator);
|
|
276
287
|
if (ct)
|
|
277
288
|
return ct;
|
|
@@ -280,7 +291,10 @@ function validate(record, properties, method) {
|
|
|
280
291
|
return null;
|
|
281
292
|
}
|
|
282
293
|
|
|
283
|
-
const STANDARD_PROP_KEYS = new Set([
|
|
294
|
+
const STANDARD_PROP_KEYS = new Set([
|
|
295
|
+
'key', 'type', 'min', 'max', 'isPrivate', 'requiredFor',
|
|
296
|
+
'isTypeChecked', 'readOnly', 'sanitizer', 'normalizer', 'validator',
|
|
297
|
+
]);
|
|
284
298
|
class Entity {
|
|
285
299
|
_name;
|
|
286
300
|
_privateProps;
|
|
@@ -292,10 +306,7 @@ class Entity {
|
|
|
292
306
|
this._privateProps = [];
|
|
293
307
|
this._propsByMethod = new Map(METHODS.map(m => [m, []]));
|
|
294
308
|
for (const p of properties) {
|
|
295
|
-
const prop =
|
|
296
|
-
for (const k of Object.keys(p))
|
|
297
|
-
if (!STANDARD_PROP_KEYS.has(k))
|
|
298
|
-
prop[k] = p[k];
|
|
309
|
+
const prop = this.createProperty(p);
|
|
299
310
|
this._properties.push(prop);
|
|
300
311
|
if (prop.isPrivate)
|
|
301
312
|
this._privateProps.push(prop.key);
|
|
@@ -303,6 +314,13 @@ class Entity {
|
|
|
303
314
|
this._propsByMethod.get(m)?.push(prop);
|
|
304
315
|
}
|
|
305
316
|
}
|
|
317
|
+
createProperty(p) {
|
|
318
|
+
const prop = new Property(p.key, p.type, p.min, p.max, p.isPrivate, p.requiredFor, p.isTypeChecked, p.readOnly, p.sanitizer, p.normalizer, p.validator);
|
|
319
|
+
for (const k of Object.keys(p))
|
|
320
|
+
if (!STANDARD_PROP_KEYS.has(k))
|
|
321
|
+
prop[k] = p[k];
|
|
322
|
+
return prop;
|
|
323
|
+
}
|
|
306
324
|
get name() {
|
|
307
325
|
return this._name;
|
|
308
326
|
}
|
|
@@ -323,29 +341,29 @@ class Entity {
|
|
|
323
341
|
getPropsByMethod(method) {
|
|
324
342
|
return this._propsByMethod.get(method) ?? [];
|
|
325
343
|
}
|
|
326
|
-
normalizeArray = (req,
|
|
344
|
+
normalizeArray = (req, res, next) => {
|
|
327
345
|
log.debug(`normalizeArray ${this.name}`);
|
|
328
346
|
const rows = req.body?.rows;
|
|
329
347
|
if (!isArray(rows, ">", 0)) {
|
|
330
348
|
next({ statusCode: 400, message: `${LOGS_PREFIX}Normalize: no rows found in request body` });
|
|
331
349
|
return;
|
|
332
350
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
351
|
+
const allowReadOnly = res?.locals?.allowReadOnly === true;
|
|
352
|
+
for (const r of rows)
|
|
353
|
+
normalize(r, this._properties, allowReadOnly);
|
|
336
354
|
next();
|
|
337
355
|
};
|
|
338
|
-
normalizeOne = (req,
|
|
356
|
+
normalizeOne = (req, res, next) => {
|
|
339
357
|
log.debug(`normalizeOne ${this.name}`);
|
|
340
358
|
const r = req.body;
|
|
341
359
|
if (!isObject(r, true)) {
|
|
342
360
|
next({ statusCode: 400, message: `${LOGS_PREFIX}Normalize: no data found in request body` });
|
|
343
361
|
return;
|
|
344
362
|
}
|
|
345
|
-
normalize(r, this._properties);
|
|
363
|
+
normalize(r, this._properties, res?.locals?.allowReadOnly === true);
|
|
346
364
|
next();
|
|
347
365
|
};
|
|
348
|
-
validateArray = (req,
|
|
366
|
+
validateArray = (req, res, next) => {
|
|
349
367
|
log.debug(`validateArray ${this.name}`);
|
|
350
368
|
const rows = req.body?.rows;
|
|
351
369
|
const method = req.method;
|
|
@@ -357,8 +375,9 @@ class Entity {
|
|
|
357
375
|
next({ statusCode: 400, message: `${LOGS_PREFIX}Invalid REST method. Received: ${method}. Must be one of: ${METHODS.toString()}` });
|
|
358
376
|
return;
|
|
359
377
|
}
|
|
378
|
+
const allowReadOnly = res?.locals?.allowReadOnly === true;
|
|
360
379
|
for (const r of rows) {
|
|
361
|
-
const error = validate(r, this._properties, method);
|
|
380
|
+
const error = validate(r, this._properties, method, allowReadOnly);
|
|
362
381
|
if (error) {
|
|
363
382
|
next(error);
|
|
364
383
|
return;
|
|
@@ -366,7 +385,7 @@ class Entity {
|
|
|
366
385
|
}
|
|
367
386
|
next();
|
|
368
387
|
};
|
|
369
|
-
validateOne = (req,
|
|
388
|
+
validateOne = (req, res, next) => {
|
|
370
389
|
log.debug(`validateOne ${this.name}`);
|
|
371
390
|
const record = req.body;
|
|
372
391
|
const method = req.method;
|
|
@@ -378,7 +397,7 @@ class Entity {
|
|
|
378
397
|
next({ statusCode: 400, message: `${LOGS_PREFIX}Invalid REST method. Received: ${method}. Must be one of: ${METHODS.toString()}` });
|
|
379
398
|
return;
|
|
380
399
|
}
|
|
381
|
-
const error = validate(record, this._properties, method);
|
|
400
|
+
const error = validate(record, this._properties, method, res?.locals?.allowReadOnly === true);
|
|
382
401
|
if (error) {
|
|
383
402
|
next(error);
|
|
384
403
|
return;
|
|
@@ -387,4 +406,4 @@ class Entity {
|
|
|
387
406
|
};
|
|
388
407
|
}
|
|
389
408
|
|
|
390
|
-
export { Entity };
|
|
409
|
+
export { Entity, Property, STANDARD_PROP_KEYS };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dwtechs/antity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Open source library for easy entity management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entities"
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"dist/"
|
|
33
33
|
],
|
|
34
|
-
|
|
34
|
+
"engines": {
|
|
35
35
|
"node": ">= 22"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@dwtechs/checkard": "3.
|
|
38
|
+
"@dwtechs/checkard": "3.7.0",
|
|
39
39
|
"@dwtechs/passken": "0.6.1",
|
|
40
40
|
"@dwtechs/sparray": "0.2.1",
|
|
41
41
|
"@dwtechs/winstan": "0.7.1"
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"core-js": "3.33.0",
|
|
51
51
|
"jest": "29.7.0",
|
|
52
52
|
"rollup": "4.24.0",
|
|
53
|
+
"rollup-plugin-dts": "6.5.1",
|
|
53
54
|
"typescript": "6.0.3"
|
|
54
55
|
}
|
|
55
56
|
}
|