json-rest-api 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +16 -0
- package/LICENSE +674 -0
- package/README.md +655 -309
- package/RELATIONSHIPS.md +1013 -0
- package/TODO_rest-api-plugin-extras.js +142 -0
- package/connectors_ideas/express-plugin.js +120 -0
- package/connectors_ideas/fastify.js +219 -0
- package/connectors_ideas/http-plugin.js +138 -0
- package/connectors_ideas/koa-plugin.js +113 -0
- package/docs/fileUploads.md +722 -0
- package/docs/mini-guides/file-uploads-complete.md +159 -0
- package/examples/.gitkeep +0 -0
- package/examples/express-example.js +262 -0
- package/examples/express-plugin.js +366 -0
- package/examples/file-upload-example.js +205 -0
- package/examples/filename-strategies.js +140 -0
- package/examples/knex-basic.js +66 -0
- package/index.js +31 -56
- package/lib/payload-validators.js +685 -0
- package/lib/rest-api-errors.js +58 -0
- package/package.json +41 -14
- package/payloadValdators.js +686 -0
- package/plugins/core/connectors/express-plugin.js +449 -0
- package/plugins/core/connectors/http-plugin.js +450 -0
- package/plugins/core/file-handling-plugin.js +338 -0
- package/plugins/core/lib/busboy-detector.js +167 -0
- package/plugins/core/lib/formidable-detector.js +130 -0
- package/plugins/core/rest-api-knex-plugin.js +200 -0
- package/plugins/core/rest-api-plugin.js +1419 -0
- package/plugins/storage/local-storage.js +218 -0
- package/plugins/storage/s3-storage.js +125 -0
- package/test-examples/package-lock.json +906 -0
- package/test-examples/package.json +15 -0
- package/test-examples/test-basic-api.js +11 -0
- package/test-examples/test-books-resource.js +116 -0
- package/test-examples/test-complete-file-upload.js +140 -0
- package/test-examples/test-debug.js +28 -0
- package/test-examples/test-express-plugin.js +177 -0
- package/test-examples/test-express-simple.js +28 -0
- package/test-examples/test-express-with-import.js +71 -0
- package/test-examples/test-file-upload.js +132 -0
- package/test-examples/test-http-plugin.js +159 -0
- package/test-examples/test-programmatic-api.js +188 -0
- package/test-examples/test-rest-api-plugin.js +13 -0
- package/TODO.todo +0 -113
- package/api.js +0 -628
- package/docs/API.md +0 -1306
- package/docs/FIXES_APPLIED.md +0 -59
- package/docs/GUIDES.md +0 -1792
- package/docs/RESOURCES_IN_EXPRESS.md +0 -456
- package/docs/example-versioning.js +0 -168
- package/docs/example.js +0 -195
- package/plugins/API guide.md +0 -364
- package/plugins/api-registry.js +0 -142
- package/plugins/api-versioning.md +0 -135
- package/plugins/http.js +0 -273
- package/plugins/logging.js +0 -255
- package/plugins/memory.js +0 -182
- package/plugins/mysql.js +0 -753
- package/plugins/openapi.js +0 -401
- package/plugins/positioning.js +0 -220
- package/plugins/security.js +0 -261
- package/plugins/validation.js +0 -173
- package/plugins/versioning.js +0 -288
- package/resource-helper.js +0 -79
- package/schema.js +0 -481
- package/test-basic.js +0 -107
package/schema.js
DELETED
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
import { stringify as serialize, parse as deserialize } from 'circular-json-es6';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Schema class for validating and transforming data with plugin support
|
|
5
|
-
*/
|
|
6
|
-
export class Schema {
|
|
7
|
-
constructor(structure = {}, options = {}) {
|
|
8
|
-
this.structure = structure;
|
|
9
|
-
this.options = options;
|
|
10
|
-
this.plugins = [];
|
|
11
|
-
this.types = new Map();
|
|
12
|
-
this.params = new Map();
|
|
13
|
-
this._initializeBuiltins();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Add a plugin to the schema
|
|
18
|
-
*/
|
|
19
|
-
use(plugin, options = {}) {
|
|
20
|
-
if (typeof plugin.install === 'function') {
|
|
21
|
-
plugin.install(this, options);
|
|
22
|
-
}
|
|
23
|
-
this.plugins.push({ plugin, options });
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Register a custom type handler
|
|
29
|
-
*/
|
|
30
|
-
registerType(name, handler) {
|
|
31
|
-
this.types.set(name, handler);
|
|
32
|
-
return this;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Register a custom parameter handler
|
|
37
|
-
*/
|
|
38
|
-
registerParam(name, handler) {
|
|
39
|
-
this.params.set(name, handler);
|
|
40
|
-
return this;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Initialize built-in types and parameters
|
|
45
|
-
*/
|
|
46
|
-
_initializeBuiltins() {
|
|
47
|
-
// Built-in types
|
|
48
|
-
this.registerType('none', this._noneType);
|
|
49
|
-
this.registerType('string', this._stringType);
|
|
50
|
-
this.registerType('blob', this._blobType);
|
|
51
|
-
this.registerType('number', this._numberType);
|
|
52
|
-
this.registerType('timestamp', this._timestampType);
|
|
53
|
-
this.registerType('dateTime', this._dateTimeType);
|
|
54
|
-
this.registerType('date', this._dateType);
|
|
55
|
-
this.registerType('array', this._arrayType);
|
|
56
|
-
this.registerType('object', this._objectType);
|
|
57
|
-
this.registerType('serialize', this._serializeType);
|
|
58
|
-
this.registerType('boolean', this._booleanType);
|
|
59
|
-
this.registerType('id', this._idType);
|
|
60
|
-
|
|
61
|
-
// Built-in parameters
|
|
62
|
-
this.registerParam('required', this._requiredParam);
|
|
63
|
-
this.registerParam('min', this._minParam);
|
|
64
|
-
this.registerParam('max', this._maxParam);
|
|
65
|
-
this.registerParam('validator', this._validatorParam);
|
|
66
|
-
this.registerParam('uppercase', this._uppercaseParam);
|
|
67
|
-
this.registerParam('lowercase', this._lowercaseParam);
|
|
68
|
-
this.registerParam('trim', this._trimParam);
|
|
69
|
-
this.registerParam('length', this._lengthParam);
|
|
70
|
-
this.registerParam('default', this._defaultParam);
|
|
71
|
-
this.registerParam('notEmpty', this._notEmptyParam);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Validate an object against the schema
|
|
76
|
-
*/
|
|
77
|
-
async validate(object, options = {}) {
|
|
78
|
-
const errors = [];
|
|
79
|
-
const validatedObject = { ...object };
|
|
80
|
-
|
|
81
|
-
// Check for unknown fields unless we're only validating object values
|
|
82
|
-
if (!options.onlyObjectValues) {
|
|
83
|
-
for (const fieldName in object) {
|
|
84
|
-
if (!this.structure[fieldName]) {
|
|
85
|
-
errors.push({
|
|
86
|
-
field: fieldName,
|
|
87
|
-
message: 'Field not allowed',
|
|
88
|
-
code: 'FIELD_NOT_ALLOWED'
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Determine target object for validation
|
|
95
|
-
const targetObject = options.onlyObjectValues ? object : this.structure;
|
|
96
|
-
|
|
97
|
-
for (const fieldName in targetObject) {
|
|
98
|
-
const definition = this.structure[fieldName];
|
|
99
|
-
if (!definition) continue;
|
|
100
|
-
|
|
101
|
-
let skipCast = false;
|
|
102
|
-
let skipParams = false;
|
|
103
|
-
|
|
104
|
-
// Get field options
|
|
105
|
-
const canBeNull = this._getCanBeNull(definition, options);
|
|
106
|
-
const emptyAsNull = this._getEmptyAsNull(definition, options);
|
|
107
|
-
|
|
108
|
-
// Handle required fields
|
|
109
|
-
if (definition.required && object[fieldName] === undefined) {
|
|
110
|
-
if (!this._skipParam('required', options.skipParams, fieldName)) {
|
|
111
|
-
const errorMessage = definition.requiredErrorMessage ||
|
|
112
|
-
definition.errorMessage ||
|
|
113
|
-
'Field required';
|
|
114
|
-
errors.push({
|
|
115
|
-
field: fieldName,
|
|
116
|
-
message: errorMessage,
|
|
117
|
-
code: 'FIELD_REQUIRED'
|
|
118
|
-
});
|
|
119
|
-
skipParams = true;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Skip casting if not required and undefined
|
|
124
|
-
if (!definition.required && object[fieldName] === undefined) {
|
|
125
|
-
skipCast = true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Handle empty strings
|
|
129
|
-
if (String(object[fieldName]) === '' && emptyAsNull) {
|
|
130
|
-
validatedObject[fieldName] = null;
|
|
131
|
-
skipParams = true;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Handle null values
|
|
135
|
-
if (object[fieldName] === null) {
|
|
136
|
-
if (!canBeNull) {
|
|
137
|
-
const errorMessage = definition.nullErrorMessage ||
|
|
138
|
-
definition.errorMessage ||
|
|
139
|
-
'Field cannot be null';
|
|
140
|
-
errors.push({
|
|
141
|
-
field: fieldName,
|
|
142
|
-
message: errorMessage,
|
|
143
|
-
code: 'FIELD_CANNOT_BE_NULL'
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
skipParams = true;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Type casting
|
|
150
|
-
if (!skipCast && !skipParams && this.types.has(definition.type)) {
|
|
151
|
-
try {
|
|
152
|
-
const typeHandler = this.types.get(definition.type);
|
|
153
|
-
const result = await typeHandler.call(this, {
|
|
154
|
-
definition,
|
|
155
|
-
value: object[fieldName],
|
|
156
|
-
fieldName,
|
|
157
|
-
object: validatedObject,
|
|
158
|
-
objectBeforeCast: object,
|
|
159
|
-
valueBeforeCast: object[fieldName],
|
|
160
|
-
options,
|
|
161
|
-
computedOptions: { canBeNull, emptyAsNull }
|
|
162
|
-
});
|
|
163
|
-
if (result !== undefined) {
|
|
164
|
-
validatedObject[fieldName] = result;
|
|
165
|
-
}
|
|
166
|
-
} catch (error) {
|
|
167
|
-
const errorMessage = definition.errorMessage ||
|
|
168
|
-
definition.castErrorMessage ||
|
|
169
|
-
error.message ||
|
|
170
|
-
'Type casting failed';
|
|
171
|
-
errors.push({
|
|
172
|
-
field: fieldName,
|
|
173
|
-
message: errorMessage,
|
|
174
|
-
code: 'TYPE_ERROR'
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Parameter validation
|
|
180
|
-
if (!skipParams) {
|
|
181
|
-
for (const paramName in definition) {
|
|
182
|
-
if (paramName === 'type' ||
|
|
183
|
-
this._skipParam(paramName, options.skipParams, fieldName)) {
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
if (this.params.has(paramName)) {
|
|
188
|
-
try {
|
|
189
|
-
const paramHandler = this.params.get(paramName);
|
|
190
|
-
const result = await paramHandler.call(this, {
|
|
191
|
-
definition,
|
|
192
|
-
value: validatedObject[fieldName],
|
|
193
|
-
fieldName,
|
|
194
|
-
object: validatedObject,
|
|
195
|
-
objectBeforeCast: object,
|
|
196
|
-
valueBeforeCast: object[fieldName],
|
|
197
|
-
parameterName: paramName,
|
|
198
|
-
parameterValue: definition[paramName],
|
|
199
|
-
options,
|
|
200
|
-
computedOptions: { canBeNull, emptyAsNull }
|
|
201
|
-
});
|
|
202
|
-
if (result !== undefined) {
|
|
203
|
-
validatedObject[fieldName] = result;
|
|
204
|
-
}
|
|
205
|
-
} catch (error) {
|
|
206
|
-
// Support parameter-specific error messages
|
|
207
|
-
const paramErrorKey = `${paramName}ErrorMessage`;
|
|
208
|
-
const errorMessage = definition[paramErrorKey] ||
|
|
209
|
-
definition.errorMessage ||
|
|
210
|
-
error.message ||
|
|
211
|
-
'Parameter validation failed';
|
|
212
|
-
errors.push({
|
|
213
|
-
field: fieldName,
|
|
214
|
-
message: errorMessage,
|
|
215
|
-
code: 'PARAM_ERROR'
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return { validatedObject, errors };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Clean up an object to only include fields with a specific parameter
|
|
228
|
-
*/
|
|
229
|
-
cleanup(object, parameterName) {
|
|
230
|
-
const cleanedObject = {};
|
|
231
|
-
for (const key in object) {
|
|
232
|
-
if (this.structure[key] && this.structure[key][parameterName]) {
|
|
233
|
-
cleanedObject[key] = object[key];
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
return cleanedObject;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Helper method to validate with onlyObjectValues option
|
|
241
|
-
* Useful for partial updates
|
|
242
|
-
*/
|
|
243
|
-
async validatePartial(object, options = {}) {
|
|
244
|
-
return this.validate(object, { ...options, onlyObjectValues: true });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Built-in type handlers
|
|
248
|
-
_noneType({ value }) {
|
|
249
|
-
return value;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
_stringType({ value, definition }) {
|
|
253
|
-
if (value === undefined || value === null) return '';
|
|
254
|
-
if (typeof value.toString !== 'function') {
|
|
255
|
-
throw new Error('Cannot cast to string');
|
|
256
|
-
}
|
|
257
|
-
const str = value.toString();
|
|
258
|
-
return definition.noTrim ? str : str.trim();
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
_blobType({ value }) {
|
|
262
|
-
return value;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
_numberType({ value }) {
|
|
266
|
-
if (value === undefined) return 0;
|
|
267
|
-
const num = Number(value);
|
|
268
|
-
if (isNaN(num)) {
|
|
269
|
-
throw new Error('Invalid number');
|
|
270
|
-
}
|
|
271
|
-
return num;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
_timestampType({ value, computedOptions }) {
|
|
275
|
-
const num = Number(value);
|
|
276
|
-
if (isNaN(num)) {
|
|
277
|
-
throw new Error('Invalid timestamp');
|
|
278
|
-
}
|
|
279
|
-
if (!num && computedOptions.canBeNull) return null;
|
|
280
|
-
return num;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
_dateTimeType({ value }) {
|
|
284
|
-
if (!value) return null;
|
|
285
|
-
const date = new Date(value);
|
|
286
|
-
if (isNaN(date.getTime())) {
|
|
287
|
-
throw new Error('Invalid date');
|
|
288
|
-
}
|
|
289
|
-
return date.toISOString().slice(0, 19).replace('T', ' ');
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
_dateType(params) {
|
|
293
|
-
const result = this._dateTimeType(params);
|
|
294
|
-
return result ? result.slice(0, 10) : result;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
_arrayType({ value }) {
|
|
298
|
-
return Array.isArray(value) ? value : [value];
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
_objectType({ value }) {
|
|
302
|
-
return value;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
_serializeType({ value }) {
|
|
306
|
-
try {
|
|
307
|
-
return serialize(value);
|
|
308
|
-
} catch (error) {
|
|
309
|
-
throw new Error('Serialization failed');
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
_booleanType({ value, definition }) {
|
|
314
|
-
if (typeof value === 'string') {
|
|
315
|
-
const falseValue = definition.stringFalseWhen || 'false';
|
|
316
|
-
const trueValue = definition.stringTrueWhen || 'true';
|
|
317
|
-
if (value === falseValue) return false;
|
|
318
|
-
if (value === trueValue || value === 'on') return true;
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
return !!value;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
_idType({ value }) {
|
|
325
|
-
const num = parseInt(value, 10);
|
|
326
|
-
if (isNaN(num)) {
|
|
327
|
-
throw new Error('Invalid ID');
|
|
328
|
-
}
|
|
329
|
-
return num;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Built-in parameter handlers
|
|
333
|
-
_requiredParam({ value, fieldName }) {
|
|
334
|
-
if (value === undefined || value === null) {
|
|
335
|
-
throw new Error(`${fieldName} is required`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
_minParam({ value, parameterValue, definition, fieldName }) {
|
|
340
|
-
if (value === undefined) return;
|
|
341
|
-
if (definition.type === 'number' && value < parameterValue) {
|
|
342
|
-
const errorMessage = definition.minErrorMessage ||
|
|
343
|
-
definition.errorMessage ||
|
|
344
|
-
`${fieldName} is too low`;
|
|
345
|
-
throw new Error(errorMessage);
|
|
346
|
-
}
|
|
347
|
-
if (definition.type === 'string' && value.length < parameterValue) {
|
|
348
|
-
const errorMessage = definition.minErrorMessage ||
|
|
349
|
-
definition.errorMessage ||
|
|
350
|
-
`${fieldName} is too short`;
|
|
351
|
-
throw new Error(errorMessage);
|
|
352
|
-
}
|
|
353
|
-
if (definition.type === 'array' && value.length < parameterValue) {
|
|
354
|
-
const errorMessage = definition.minErrorMessage ||
|
|
355
|
-
definition.errorMessage ||
|
|
356
|
-
`${fieldName} must have at least ${parameterValue} items`;
|
|
357
|
-
throw new Error(errorMessage);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
_maxParam({ value, parameterValue, definition, fieldName }) {
|
|
362
|
-
if (value === undefined) return;
|
|
363
|
-
if (definition.type === 'number' && value > parameterValue) {
|
|
364
|
-
const errorMessage = definition.maxErrorMessage ||
|
|
365
|
-
definition.errorMessage ||
|
|
366
|
-
`${fieldName} is too high`;
|
|
367
|
-
throw new Error(errorMessage);
|
|
368
|
-
}
|
|
369
|
-
if (definition.type === 'string' && value.length > parameterValue) {
|
|
370
|
-
const errorMessage = definition.maxErrorMessage ||
|
|
371
|
-
definition.errorMessage ||
|
|
372
|
-
`${fieldName} is too long`;
|
|
373
|
-
throw new Error(errorMessage);
|
|
374
|
-
}
|
|
375
|
-
if (definition.type === 'array' && value.length > parameterValue) {
|
|
376
|
-
const errorMessage = definition.maxErrorMessage ||
|
|
377
|
-
definition.errorMessage ||
|
|
378
|
-
`${fieldName} must have at most ${parameterValue} items`;
|
|
379
|
-
throw new Error(errorMessage);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
_validatorParam({ parameterValue, value, object, fieldName, definition }) {
|
|
384
|
-
if (typeof parameterValue !== 'function') {
|
|
385
|
-
throw new Error('Validator must be a function');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// Support custom validator with custom error message
|
|
389
|
-
const validatorResult = parameterValue(value, object, {
|
|
390
|
-
schema: this,
|
|
391
|
-
fieldName,
|
|
392
|
-
definition
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Handle different return types
|
|
396
|
-
if (validatorResult === false) {
|
|
397
|
-
// Use custom error message if provided
|
|
398
|
-
const errorMessage = definition.validatorMessage ||
|
|
399
|
-
definition.errorMessage ||
|
|
400
|
-
`Validation failed for ${fieldName}`;
|
|
401
|
-
throw new Error(errorMessage);
|
|
402
|
-
} else if (typeof validatorResult === 'string') {
|
|
403
|
-
// Validator returned a custom error message
|
|
404
|
-
throw new Error(validatorResult);
|
|
405
|
-
} else if (validatorResult instanceof Error) {
|
|
406
|
-
throw validatorResult;
|
|
407
|
-
}
|
|
408
|
-
// If result is true or undefined, validation passed
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
_uppercaseParam({ value, definition }) {
|
|
412
|
-
if (definition.type === 'string' && typeof value === 'string') {
|
|
413
|
-
return value.toUpperCase();
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
_lowercaseParam({ value, definition }) {
|
|
418
|
-
if (definition.type === 'string' && typeof value === 'string') {
|
|
419
|
-
return value.toLowerCase();
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
_trimParam({ value, parameterValue, definition, fieldName, valueBeforeCast }) {
|
|
424
|
-
if (definition.type === 'string' && typeof value === 'string') {
|
|
425
|
-
return value.slice(0, parameterValue);
|
|
426
|
-
}
|
|
427
|
-
if (Number.isInteger(Number(valueBeforeCast)) &&
|
|
428
|
-
String(Number(valueBeforeCast)).length > parameterValue) {
|
|
429
|
-
throw new Error(`${fieldName} out of range`);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
_lengthParam(params) {
|
|
434
|
-
return this._trimParam(params);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
_defaultParam({ valueBeforeCast, parameterValue, object, fieldName, definition }) {
|
|
438
|
-
if (valueBeforeCast === undefined) {
|
|
439
|
-
if (typeof parameterValue === 'function') {
|
|
440
|
-
// Pass context to default function for conditional defaults
|
|
441
|
-
return parameterValue({
|
|
442
|
-
object,
|
|
443
|
-
fieldName,
|
|
444
|
-
definition,
|
|
445
|
-
schema: this
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
return parameterValue;
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
_notEmptyParam({ valueBeforeCast, parameterValue, fieldName, definition }) {
|
|
453
|
-
const str = valueBeforeCast?.toString ? valueBeforeCast.toString() : '';
|
|
454
|
-
if (parameterValue && valueBeforeCast !== undefined && str === '') {
|
|
455
|
-
const errorMessage = definition.notEmptyErrorMessage ||
|
|
456
|
-
definition.errorMessage ||
|
|
457
|
-
`${fieldName} cannot be empty`;
|
|
458
|
-
throw new Error(errorMessage);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
// Helper methods
|
|
463
|
-
_skipParam(param, skipParams, field) {
|
|
464
|
-
if (!skipParams || typeof skipParams !== 'object') return false;
|
|
465
|
-
const fieldSkips = skipParams[field];
|
|
466
|
-
return Array.isArray(fieldSkips) && fieldSkips.includes(param);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
_getCanBeNull(definition, options) {
|
|
470
|
-
if (definition.default === null) return true;
|
|
471
|
-
if (typeof definition.canBeNull !== 'undefined') return definition.canBeNull;
|
|
472
|
-
if (typeof options.canBeNull !== 'undefined') return !!options.canBeNull;
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
_getEmptyAsNull(definition, options) {
|
|
477
|
-
if (typeof definition.emptyAsNull !== 'undefined') return definition.emptyAsNull;
|
|
478
|
-
if (typeof options.emptyAsNull !== 'undefined') return !!options.emptyAsNull;
|
|
479
|
-
return false;
|
|
480
|
-
}
|
|
481
|
-
}
|
package/test-basic.js
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { createApi, Schema } from './index.js';
|
|
4
|
-
|
|
5
|
-
console.log('Testing basic jsonrestapi functionality...\n');
|
|
6
|
-
|
|
7
|
-
// Test 1: Create simple API with memory storage
|
|
8
|
-
console.log('1. Creating API with memory storage...');
|
|
9
|
-
const api = createApi({
|
|
10
|
-
storage: 'memory',
|
|
11
|
-
http: {
|
|
12
|
-
basePath: '/api/v1'
|
|
13
|
-
}
|
|
14
|
-
});
|
|
15
|
-
console.log('✓ API created successfully');
|
|
16
|
-
|
|
17
|
-
// Test 2: Define and register schema
|
|
18
|
-
console.log('\n2. Creating and registering user schema...');
|
|
19
|
-
const userSchema = new Schema({
|
|
20
|
-
id: { type: 'id' },
|
|
21
|
-
name: { type: 'string', required: true },
|
|
22
|
-
email: { type: 'string', required: true },
|
|
23
|
-
age: { type: 'number', min: 0, max: 150 }
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
// Define hooks for users
|
|
27
|
-
const userHooks = {
|
|
28
|
-
async transformResult(context) {
|
|
29
|
-
// Add a test field to verify hooks work
|
|
30
|
-
if (context.result) {
|
|
31
|
-
context.result.testField = 'hooks-working';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
api.addResource('users', userSchema, userHooks);
|
|
37
|
-
console.log('✓ Schema registered successfully with hooks');
|
|
38
|
-
|
|
39
|
-
// Test 3: Insert data
|
|
40
|
-
console.log('\n3. Testing insert operation...');
|
|
41
|
-
try {
|
|
42
|
-
const newUser = await api.insert({
|
|
43
|
-
name: 'John Doe',
|
|
44
|
-
email: 'john@example.com',
|
|
45
|
-
age: 30
|
|
46
|
-
}, {
|
|
47
|
-
type: 'users'
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
console.log('✓ User created:', newUser.data);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error('✗ Insert failed:', error.message);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Test 4: Query data
|
|
56
|
-
console.log('\n4. Testing query operation...');
|
|
57
|
-
try {
|
|
58
|
-
const result = await api.query({
|
|
59
|
-
filter: { age: 30 }
|
|
60
|
-
}, {
|
|
61
|
-
type: 'users'
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
console.log('✓ Query successful. Found', result.meta.total, 'users');
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('✗ Query failed:', error.message);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Test 5: Get by ID
|
|
70
|
-
console.log('\n5. Testing get operation...');
|
|
71
|
-
try {
|
|
72
|
-
const user = await api.get(1, {
|
|
73
|
-
type: 'users'
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
console.log('✓ User retrieved:', user.data);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('✗ Get failed:', error.message);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Test 6: Update
|
|
82
|
-
console.log('\n6. Testing update operation...');
|
|
83
|
-
try {
|
|
84
|
-
const updated = await api.update(1, {
|
|
85
|
-
age: 31
|
|
86
|
-
}, {
|
|
87
|
-
type: 'users'
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
console.log('✓ User updated:', updated.data);
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error('✗ Update failed:', error.message);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Test 7: Delete
|
|
96
|
-
console.log('\n7. Testing delete operation...');
|
|
97
|
-
try {
|
|
98
|
-
await api.delete(1, {
|
|
99
|
-
type: 'users'
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
console.log('✓ User deleted successfully');
|
|
103
|
-
} catch (error) {
|
|
104
|
-
console.error('✗ Delete failed:', error.message);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
console.log('\n✓ All basic tests completed!');
|