adorn-api 1.0.34 → 1.0.36

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adorn-api",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "Decorator-first web framework with OpenAPI 3.1 schema generation.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "express": "^4.19.2",
18
- "metal-orm": "^1.0.94"
18
+ "metal-orm": "^1.0.104"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@electric-sql/pglite": "^0.3.15",
@@ -1,10 +0,0 @@
1
- import type { SchemaSource } from "./schema";
2
- import { ValidationError } from "./validation-errors";
3
- /**
4
- * Validates a value against a schema source.
5
- * @param value Value to validate
6
- * @param schema Schema source (schema node or DTO constructor)
7
- * @param path Optional field path for error reporting
8
- * @returns Array of validation errors
9
- */
10
- export declare function validate(value: unknown, schema: SchemaSource, path?: string): ValidationError[];
@@ -1,533 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validate = validate;
4
- const metadata_1 = require("./metadata");
5
- /**
6
- * Validates a value against a schema source.
7
- * @param value Value to validate
8
- * @param schema Schema source (schema node or DTO constructor)
9
- * @param path Optional field path for error reporting
10
- * @returns Array of validation errors
11
- */
12
- function validate(value, schema, path = "") {
13
- if (typeof schema === "function") {
14
- return validateDto(value, schema, path);
15
- }
16
- return validateSchemaNode(value, schema, path);
17
- }
18
- /**
19
- * Validates a value against a DTO schema.
20
- * @param value Value to validate
21
- * @param dto DTO constructor
22
- * @param path Optional field path for error reporting
23
- * @returns Array of validation errors
24
- */
25
- function validateDto(value, dto, path) {
26
- const dtoMeta = (0, metadata_1.getDtoMeta)(dto);
27
- if (!dtoMeta) {
28
- throw new Error(`DTO "${dto.name}" is missing @Dto decorator.`);
29
- }
30
- const errors = [];
31
- // Check if value is an object
32
- if (value === null || value === undefined) {
33
- errors.push({
34
- field: path,
35
- message: "must be an object",
36
- value
37
- });
38
- return errors;
39
- }
40
- if (typeof value !== "object") {
41
- errors.push({
42
- field: path,
43
- message: "must be an object",
44
- value
45
- });
46
- return errors;
47
- }
48
- // Validate each field
49
- for (const [fieldName, fieldMeta] of Object.entries(dtoMeta.fields)) {
50
- const fieldPath = path ? `${path}.${fieldName}` : fieldName;
51
- const fieldValue = value[fieldName];
52
- const isOptional = fieldMeta.optional ?? fieldMeta.schema.optional ?? false;
53
- // Check required field
54
- if (!isOptional && fieldValue === undefined) {
55
- errors.push({
56
- field: fieldPath,
57
- message: "is required",
58
- value: fieldValue
59
- });
60
- continue;
61
- }
62
- // Validate field value if present
63
- if (fieldValue !== undefined && fieldValue !== null) {
64
- const fieldErrors = validateSchemaNode(fieldValue, fieldMeta.schema, fieldPath);
65
- errors.push(...fieldErrors);
66
- }
67
- }
68
- // Check additional properties
69
- if (!dtoMeta.additionalProperties) {
70
- const valueKeys = Object.keys(value);
71
- const dtoKeys = Object.keys(dtoMeta.fields);
72
- const additionalKeys = valueKeys.filter(key => !dtoKeys.includes(key));
73
- for (const key of additionalKeys) {
74
- errors.push({
75
- field: path ? `${path}.${key}` : key,
76
- message: "is not a valid field",
77
- value: value[key]
78
- });
79
- }
80
- }
81
- return errors;
82
- }
83
- /**
84
- * Validates a value against a schema node.
85
- * @param value Value to validate
86
- * @param schema Schema node to validate against
87
- * @param path Optional field path for error reporting
88
- * @returns Array of validation errors
89
- */
90
- function validateSchemaNode(value, schema, path) {
91
- const errors = [];
92
- // Handle optional fields
93
- if (schema.optional && value === undefined) {
94
- return errors;
95
- }
96
- // Handle nullable fields
97
- if (schema.nullable && value === null) {
98
- return errors;
99
- }
100
- // Validate based on schema kind
101
- switch (schema.kind) {
102
- case "string":
103
- errors.push(...validateString(value, schema, path));
104
- break;
105
- case "number":
106
- case "integer":
107
- errors.push(...validateNumber(value, schema, path));
108
- break;
109
- case "boolean":
110
- errors.push(...validateBoolean(value, schema, path));
111
- break;
112
- case "array":
113
- errors.push(...validateArray(value, schema, path));
114
- break;
115
- case "object":
116
- errors.push(...validateObject(value, schema, path));
117
- break;
118
- case "enum":
119
- errors.push(...validateEnum(value, schema, path));
120
- break;
121
- case "literal":
122
- errors.push(...validateLiteral(value, schema, path));
123
- break;
124
- case "union":
125
- errors.push(...validateUnion(value, schema, path));
126
- break;
127
- case "record":
128
- errors.push(...validateRecord(value, schema, path));
129
- break;
130
- case "ref":
131
- errors.push(...validate(value, schema.dto, path));
132
- break;
133
- case "any":
134
- // Any type accepts all values
135
- break;
136
- case "null":
137
- errors.push(...validateNull(value, schema, path));
138
- break;
139
- case "file":
140
- // File validation is handled separately
141
- break;
142
- default:
143
- break;
144
- }
145
- return errors;
146
- }
147
- /**
148
- * Validates a string value.
149
- */
150
- function validateString(value, schema, path) {
151
- const errors = [];
152
- if (typeof value !== "string") {
153
- errors.push({
154
- field: path,
155
- message: "must be a string",
156
- value
157
- });
158
- return errors;
159
- }
160
- if (schema.minLength !== undefined && value.length < schema.minLength) {
161
- errors.push({
162
- field: path,
163
- message: `must be at least ${schema.minLength} characters long`,
164
- value
165
- });
166
- }
167
- if (schema.maxLength !== undefined && value.length > schema.maxLength) {
168
- errors.push({
169
- field: path,
170
- message: `must be at most ${schema.maxLength} characters long`,
171
- value
172
- });
173
- }
174
- if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
175
- errors.push({
176
- field: path,
177
- message: `must match pattern ${schema.pattern}`,
178
- value
179
- });
180
- }
181
- if (schema.format) {
182
- const formatErrors = validateFormat(value, schema.format, path);
183
- errors.push(...formatErrors);
184
- }
185
- return errors;
186
- }
187
- /**
188
- * Validates string format.
189
- */
190
- function validateFormat(value, format, path) {
191
- const errors = [];
192
- switch (format) {
193
- case "uuid":
194
- const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
195
- if (!uuidPattern.test(value)) {
196
- errors.push({
197
- field: path,
198
- message: "must be a valid UUID",
199
- value
200
- });
201
- }
202
- break;
203
- case "date-time":
204
- const date = new Date(value);
205
- if (isNaN(date.getTime())) {
206
- errors.push({
207
- field: path,
208
- message: "must be a valid date-time",
209
- value
210
- });
211
- }
212
- break;
213
- }
214
- return errors;
215
- }
216
- /**
217
- * Validates a number value.
218
- */
219
- function validateNumber(value, schema, path) {
220
- const errors = [];
221
- if (typeof value !== "number") {
222
- errors.push({
223
- field: path,
224
- message: `must be a ${schema.kind}`,
225
- value
226
- });
227
- return errors;
228
- }
229
- if (schema.kind === "integer" && !Number.isInteger(value)) {
230
- errors.push({
231
- field: path,
232
- message: "must be an integer",
233
- value
234
- });
235
- }
236
- if (schema.minimum !== undefined && value < schema.minimum) {
237
- errors.push({
238
- field: path,
239
- message: `must be at least ${schema.minimum}`,
240
- value
241
- });
242
- }
243
- if (schema.maximum !== undefined && value > schema.maximum) {
244
- errors.push({
245
- field: path,
246
- message: `must be at most ${schema.maximum}`,
247
- value
248
- });
249
- }
250
- if (schema.exclusiveMinimum !== undefined && value <= schema.exclusiveMinimum) {
251
- errors.push({
252
- field: path,
253
- message: `must be greater than ${schema.exclusiveMinimum}`,
254
- value
255
- });
256
- }
257
- if (schema.exclusiveMaximum !== undefined && value >= schema.exclusiveMaximum) {
258
- errors.push({
259
- field: path,
260
- message: `must be less than ${schema.exclusiveMaximum}`,
261
- value
262
- });
263
- }
264
- if (schema.multipleOf !== undefined && value % schema.multipleOf !== 0) {
265
- errors.push({
266
- field: path,
267
- message: `must be a multiple of ${schema.multipleOf}`,
268
- value
269
- });
270
- }
271
- return errors;
272
- }
273
- /**
274
- * Validates a boolean value.
275
- */
276
- function validateBoolean(value, schema, path) {
277
- const errors = [];
278
- if (typeof value !== "boolean") {
279
- errors.push({
280
- field: path,
281
- message: "must be a boolean",
282
- value
283
- });
284
- }
285
- return errors;
286
- }
287
- /**
288
- * Validates an array value.
289
- */
290
- function validateArray(value, schema, path) {
291
- const errors = [];
292
- if (!Array.isArray(value)) {
293
- errors.push({
294
- field: path,
295
- message: "must be an array",
296
- value
297
- });
298
- return errors;
299
- }
300
- if (schema.minItems !== undefined && value.length < schema.minItems) {
301
- errors.push({
302
- field: path,
303
- message: `must have at least ${schema.minItems} items`,
304
- value
305
- });
306
- }
307
- if (schema.maxItems !== undefined && value.length > schema.maxItems) {
308
- errors.push({
309
- field: path,
310
- message: `must have at most ${schema.maxItems} items`,
311
- value
312
- });
313
- }
314
- if (schema.uniqueItems && hasDuplicates(value)) {
315
- errors.push({
316
- field: path,
317
- message: "must contain unique items",
318
- value
319
- });
320
- }
321
- // Validate each item
322
- for (let i = 0; i < value.length; i++) {
323
- const itemErrors = validateSchemaNode(value[i], schema.items, `${path}[${i}]`);
324
- errors.push(...itemErrors);
325
- }
326
- return errors;
327
- }
328
- function hasDuplicates(arr) {
329
- const seen = new Set();
330
- for (const item of arr) {
331
- const serialized = JSON.stringify(item);
332
- if (seen.has(serialized)) {
333
- return true;
334
- }
335
- seen.add(serialized);
336
- }
337
- return false;
338
- }
339
- /**
340
- * Validates an object value.
341
- */
342
- function validateObject(value, schema, path) {
343
- const errors = [];
344
- if (value === null || value === undefined) {
345
- errors.push({
346
- field: path,
347
- message: "must be an object",
348
- value
349
- });
350
- return errors;
351
- }
352
- if (typeof value !== "object") {
353
- errors.push({
354
- field: path,
355
- message: "must be an object",
356
- value
357
- });
358
- return errors;
359
- }
360
- if (schema.properties) {
361
- // Validate properties
362
- for (const [propName, propSchema] of Object.entries(schema.properties)) {
363
- const propPath = path ? `${path}.${propName}` : propName;
364
- const propValue = value[propName];
365
- const isRequired = schema.required?.includes(propName) ?? false;
366
- if (isRequired && propValue === undefined) {
367
- errors.push({
368
- field: propPath,
369
- message: "is required",
370
- value: propValue
371
- });
372
- continue;
373
- }
374
- if (propValue !== undefined && propValue !== null) {
375
- const propErrors = validateSchemaNode(propValue, propSchema, propPath);
376
- errors.push(...propErrors);
377
- }
378
- }
379
- }
380
- // Validate additional properties
381
- if (schema.additionalProperties === false) {
382
- const valueKeys = Object.keys(value);
383
- const schemaKeys = schema.properties ? Object.keys(schema.properties) : [];
384
- const additionalKeys = valueKeys.filter(key => !schemaKeys.includes(key));
385
- for (const key of additionalKeys) {
386
- errors.push({
387
- field: path ? `${path}.${key}` : key,
388
- message: "is not a valid field",
389
- value: value[key]
390
- });
391
- }
392
- }
393
- else if (typeof schema.additionalProperties === "object") {
394
- const valueKeys = Object.keys(value);
395
- const schemaKeys = schema.properties ? Object.keys(schema.properties) : [];
396
- const additionalKeys = valueKeys.filter(key => !schemaKeys.includes(key));
397
- for (const key of additionalKeys) {
398
- const additionalPath = path ? `${path}.${key}` : key;
399
- const additionalValue = value[key];
400
- const additionalErrors = validateSchemaNode(additionalValue, schema.additionalProperties, additionalPath);
401
- errors.push(...additionalErrors);
402
- }
403
- }
404
- if (schema.minProperties !== undefined && Object.keys(value).length < schema.minProperties) {
405
- errors.push({
406
- field: path,
407
- message: `must have at least ${schema.minProperties} properties`,
408
- value
409
- });
410
- }
411
- if (schema.maxProperties !== undefined && Object.keys(value).length > schema.maxProperties) {
412
- errors.push({
413
- field: path,
414
- message: `must have at most ${schema.maxProperties} properties`,
415
- value
416
- });
417
- }
418
- return errors;
419
- }
420
- /**
421
- * Validates an enum value.
422
- */
423
- function validateEnum(value, schema, path) {
424
- const errors = [];
425
- if (!schema.values.some(v => isEqual(v, value))) {
426
- errors.push({
427
- field: path,
428
- message: `must be one of: ${schema.values.map(v => JSON.stringify(v)).join(", ")}`,
429
- value
430
- });
431
- }
432
- return errors;
433
- }
434
- /**
435
- * Validates a literal value.
436
- */
437
- function validateLiteral(value, schema, path) {
438
- const errors = [];
439
- if (!isEqual(schema.value, value)) {
440
- errors.push({
441
- field: path,
442
- message: `must be ${JSON.stringify(schema.value)}`,
443
- value
444
- });
445
- }
446
- return errors;
447
- }
448
- /**
449
- * Validates a union value.
450
- */
451
- function validateUnion(value, schema, path) {
452
- // For union types, value must validate against at least one schema
453
- const validSchemas = schema.anyOf.filter(s => {
454
- const errors = validateSchemaNode(value, s, path);
455
- return errors.length === 0;
456
- });
457
- if (validSchemas.length === 0) {
458
- return [
459
- {
460
- field: path,
461
- message: "must match one of the allowed types",
462
- value
463
- }
464
- ];
465
- }
466
- return [];
467
- }
468
- /**
469
- * Validates a record value.
470
- */
471
- function validateRecord(value, schema, path) {
472
- const errors = [];
473
- if (value === null || value === undefined) {
474
- errors.push({
475
- field: path,
476
- message: "must be an object",
477
- value
478
- });
479
- return errors;
480
- }
481
- if (typeof value !== "object") {
482
- errors.push({
483
- field: path,
484
- message: "must be an object",
485
- value
486
- });
487
- return errors;
488
- }
489
- // Validate each value in the record
490
- for (const [key, recordValue] of Object.entries(value)) {
491
- const recordPath = path ? `${path}.${key}` : key;
492
- const recordErrors = validateSchemaNode(recordValue, schema.values, recordPath);
493
- errors.push(...recordErrors);
494
- }
495
- return errors;
496
- }
497
- /**
498
- * Validates a null value.
499
- */
500
- function validateNull(value, schema, path) {
501
- const errors = [];
502
- if (value !== null) {
503
- errors.push({
504
- field: path,
505
- message: "must be null",
506
- value
507
- });
508
- }
509
- return errors;
510
- }
511
- /**
512
- * Deep equality check for validation.
513
- */
514
- function isEqual(a, b) {
515
- if (a === b)
516
- return true;
517
- if (typeof a !== typeof b)
518
- return false;
519
- if (typeof a === "object") {
520
- if (a === null || b === null)
521
- return false;
522
- const aKeys = Object.keys(a);
523
- const bKeys = Object.keys(b);
524
- if (aKeys.length !== bKeys.length)
525
- return false;
526
- for (const key of aKeys) {
527
- if (!isEqual(a[key], b[key]))
528
- return false;
529
- }
530
- return true;
531
- }
532
- return false;
533
- }