@aeriajs/core 0.0.185 → 0.0.187

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.
@@ -4,7 +4,7 @@ import { ObjectId } from 'mongodb';
4
4
  export type TraverseOptionsBase = {
5
5
  autoCast?: boolean;
6
6
  validate?: boolean;
7
- validateRequired?: Description['required'];
7
+ validateWholeness?: boolean | 'deep';
8
8
  fromProperties?: boolean;
9
9
  allowOperators?: boolean;
10
10
  undefinedToNull?: boolean;
@@ -28,6 +28,7 @@ const types_1 = require("@aeriajs/types");
28
28
  const common_1 = require("@aeriajs/common");
29
29
  const validation_1 = require("@aeriajs/validation");
30
30
  const entrypoint_1 = require("@aeriajs/entrypoint");
31
+ const security_1 = require("@aeriajs/security");
31
32
  const mongodb_1 = require("mongodb");
32
33
  const assets_js_1 = require("../assets.js");
33
34
  const context_js_1 = require("../context.js");
@@ -191,6 +192,9 @@ const isValidTempFile = (value) => {
191
192
  return !!(value === undefined
192
193
  || value === null);
193
194
  };
195
+ const isMissingPropertyError = (error) => {
196
+ return 'code' in error && error.code === types_1.ValidationErrorCode.MissingProperties;
197
+ };
194
198
  const moveFiles = async (value, ctx) => {
195
199
  if (!('$ref' in ctx.property) || ctx.property.$ref !== 'file') {
196
200
  return value;
@@ -229,6 +233,12 @@ const recurseDeep = async (value, ctx) => {
229
233
  return value;
230
234
  }
231
235
  if ('properties' in ctx.property) {
236
+ if (ctx.options.validateWholeness) {
237
+ const wholenessError = (0, validation_1.validateWholeness)(value, ctx.property);
238
+ if (wholenessError) {
239
+ return types_1.Result.error(wholenessError);
240
+ }
241
+ }
232
242
  const { error, result } = await recurse(value, ctx);
233
243
  if (error) {
234
244
  return types_1.Result.error(error);
@@ -296,20 +306,20 @@ const recurse = async (target, ctx) => {
296
306
  continue;
297
307
  }
298
308
  }
299
- if (!property) {
300
- if (value && (value.constructor === Object || value.constructor === Array)) {
301
- // if first propName is preceded by '$' we assume
302
- // it contains MongoDB query operators
303
- if (Object.keys(value)[0]?.startsWith('$')) {
309
+ if (value && typeof value === 'object') {
310
+ for (const key in value) {
311
+ if (key.startsWith('$')) {
304
312
  if (!ctx.options.allowOperators) {
305
313
  return types_1.Result.error(types_1.ACError.InsecureOperator);
306
314
  }
307
- entries.push([
308
- propName,
309
- value,
310
- ]);
311
- continue;
315
+ if (security_1.INSECURE_OPERATORS.includes(key)) {
316
+ return types_1.Result.error(types_1.ACError.InsecureOperator);
317
+ }
312
318
  }
319
+ }
320
+ }
321
+ if (!property) {
322
+ if (value && (value.constructor === Object || value.constructor === Array)) {
313
323
  if (Array.isArray(value)) {
314
324
  const operations = [];
315
325
  for (const operation of value) {
@@ -340,7 +350,7 @@ const recurse = async (target, ctx) => {
340
350
  value,
341
351
  ]);
342
352
  }
343
- if (property) {
353
+ else {
344
354
  if (!ctx.options.preserveHidden && property.hidden) {
345
355
  continue;
346
356
  }
@@ -413,7 +423,9 @@ const traverseDocument = async (what, description, _options) => {
413
423
  return types_1.Result.result(what);
414
424
  }
415
425
  const whatCopy = Object.assign({}, what);
416
- const options = Object.assign({}, _options);
426
+ const options = Object.assign({
427
+ description,
428
+ }, _options);
417
429
  const functions = [];
418
430
  if (!options.validate && Object.keys(whatCopy).length === 0) {
419
431
  return types_1.Result.result(whatCopy);
@@ -428,13 +440,11 @@ const traverseDocument = async (what, description, _options) => {
428
440
  functions.push(getters);
429
441
  }
430
442
  if (options.validate) {
431
- const descriptionCopy = Object.assign({}, description);
432
- if (options.validateRequired) {
433
- descriptionCopy.required = options.validateRequired;
434
- }
435
- const wholenessError = (0, validation_1.validateWholeness)(whatCopy, descriptionCopy);
436
- if (wholenessError) {
437
- return types_1.Result.error(wholenessError);
443
+ if (options.validateWholeness === true) {
444
+ const wholenessError = (0, validation_1.validateWholeness)(whatCopy, options.description);
445
+ if (wholenessError) {
446
+ return types_1.Result.error(wholenessError);
447
+ }
438
448
  }
439
449
  functions.push(validate);
440
450
  }
@@ -444,8 +454,7 @@ const traverseDocument = async (what, description, _options) => {
444
454
  if ('moveFiles' in options && options.moveFiles) {
445
455
  functions.push(moveFiles);
446
456
  }
447
- let traverseError;
448
- let validationError;
457
+ let traverseError, validationError;
449
458
  const mutateTarget = (fn) => {
450
459
  return async (value, ctx) => {
451
460
  const result = await fn(value, ctx);
@@ -453,7 +462,6 @@ const traverseDocument = async (what, description, _options) => {
453
462
  return result;
454
463
  };
455
464
  };
456
- options.description = description;
457
465
  Object.assign(options, {
458
466
  pipe: (0, common_1.pipe)(functions.map(mutateTarget), {
459
467
  returnFirst: (value) => {
@@ -464,8 +472,9 @@ const traverseDocument = async (what, description, _options) => {
464
472
  case types_1.TraverseError.InvalidTempfile:
465
473
  traverseError = error;
466
474
  break;
467
- default:
475
+ default: {
468
476
  validationError = error;
477
+ }
469
478
  }
470
479
  return value;
471
480
  }
@@ -485,6 +494,9 @@ const traverseDocument = async (what, description, _options) => {
485
494
  return types_1.Result.error(traverseError);
486
495
  }
487
496
  if (validationError) {
497
+ if (isMissingPropertyError(validationError)) {
498
+ return types_1.Result.error(validationError);
499
+ }
488
500
  return types_1.Result.error((0, validation_1.makeValidationError)({
489
501
  code: types_1.ValidationErrorCode.InvalidProperties,
490
502
  errors: validationError,
@@ -3,6 +3,7 @@ import { Result, ACError, ValidationErrorCode, TraverseError } from "@aeriajs/ty
3
3
  import { throwIfError, pipe, isReference, getReferenceProperty, getValueFromPath, isError } from "@aeriajs/common";
4
4
  import { makeValidationError, validateProperty, validateWholeness } from "@aeriajs/validation";
5
5
  import { getCollection } from "@aeriajs/entrypoint";
6
+ import { INSECURE_OPERATORS } from "@aeriajs/security";
6
7
  import { ObjectId } from "mongodb";
7
8
  import { getCollectionAsset } from "../assets.mjs";
8
9
  import { createContext } from "../context.mjs";
@@ -158,6 +159,9 @@ const isValidTempFile = (value) => {
158
159
  }
159
160
  return !!(value === void 0 || value === null);
160
161
  };
162
+ const isMissingPropertyError = (error) => {
163
+ return "code" in error && error.code === ValidationErrorCode.MissingProperties;
164
+ };
161
165
  const moveFiles = async (value, ctx) => {
162
166
  if (!("$ref" in ctx.property) || ctx.property.$ref !== "file") {
163
167
  return value;
@@ -196,6 +200,12 @@ const recurseDeep = async (value, ctx) => {
196
200
  return value;
197
201
  }
198
202
  if ("properties" in ctx.property) {
203
+ if (ctx.options.validateWholeness) {
204
+ const wholenessError = validateWholeness(value, ctx.property);
205
+ if (wholenessError) {
206
+ return Result.error(wholenessError);
207
+ }
208
+ }
199
209
  const { error, result } = await recurse(value, ctx);
200
210
  if (error) {
201
211
  return Result.error(error);
@@ -260,18 +270,20 @@ const recurse = async (target, ctx) => {
260
270
  continue;
261
271
  }
262
272
  }
263
- if (!property) {
264
- if (value && (value.constructor === Object || value.constructor === Array)) {
265
- if (Object.keys(value)[0]?.startsWith("$")) {
273
+ if (value && typeof value === "object") {
274
+ for (const key in value) {
275
+ if (key.startsWith("$")) {
266
276
  if (!ctx.options.allowOperators) {
267
277
  return Result.error(ACError.InsecureOperator);
268
278
  }
269
- entries.push([
270
- propName,
271
- value
272
- ]);
273
- continue;
279
+ if (INSECURE_OPERATORS.includes(key)) {
280
+ return Result.error(ACError.InsecureOperator);
281
+ }
274
282
  }
283
+ }
284
+ }
285
+ if (!property) {
286
+ if (value && (value.constructor === Object || value.constructor === Array)) {
275
287
  if (Array.isArray(value)) {
276
288
  const operations = [];
277
289
  for (const operation of value) {
@@ -301,8 +313,7 @@ const recurse = async (target, ctx) => {
301
313
  propName,
302
314
  value
303
315
  ]);
304
- }
305
- if (property) {
316
+ } else {
306
317
  if (!ctx.options.preserveHidden && property.hidden) {
307
318
  continue;
308
319
  }
@@ -371,7 +382,9 @@ export const traverseDocument = async (what, description, _options) => {
371
382
  return Result.result(what);
372
383
  }
373
384
  const whatCopy = Object.assign({}, what);
374
- const options = Object.assign({}, _options);
385
+ const options = Object.assign({
386
+ description
387
+ }, _options);
375
388
  const functions = [];
376
389
  if (!options.validate && Object.keys(whatCopy).length === 0) {
377
390
  return Result.result(whatCopy);
@@ -386,13 +399,11 @@ export const traverseDocument = async (what, description, _options) => {
386
399
  functions.push(getters);
387
400
  }
388
401
  if (options.validate) {
389
- const descriptionCopy = Object.assign({}, description);
390
- if (options.validateRequired) {
391
- descriptionCopy.required = options.validateRequired;
392
- }
393
- const wholenessError = validateWholeness(whatCopy, descriptionCopy);
394
- if (wholenessError) {
395
- return Result.error(wholenessError);
402
+ if (options.validateWholeness === true) {
403
+ const wholenessError = validateWholeness(whatCopy, options.description);
404
+ if (wholenessError) {
405
+ return Result.error(wholenessError);
406
+ }
396
407
  }
397
408
  functions.push(validate);
398
409
  }
@@ -402,8 +413,7 @@ export const traverseDocument = async (what, description, _options) => {
402
413
  if ("moveFiles" in options && options.moveFiles) {
403
414
  functions.push(moveFiles);
404
415
  }
405
- let traverseError;
406
- let validationError;
416
+ let traverseError, validationError;
407
417
  const mutateTarget = (fn) => {
408
418
  return async (value, ctx) => {
409
419
  const result2 = await fn(value, ctx);
@@ -411,7 +421,6 @@ export const traverseDocument = async (what, description, _options) => {
411
421
  return result2;
412
422
  };
413
423
  };
414
- options.description = description;
415
424
  Object.assign(options, {
416
425
  pipe: pipe(functions.map(mutateTarget), {
417
426
  returnFirst: (value) => {
@@ -422,8 +431,9 @@ export const traverseDocument = async (what, description, _options) => {
422
431
  case TraverseError.InvalidTempfile:
423
432
  traverseError = error2;
424
433
  break;
425
- default:
434
+ default: {
426
435
  validationError = error2;
436
+ }
427
437
  }
428
438
  return value;
429
439
  }
@@ -443,6 +453,9 @@ export const traverseDocument = async (what, description, _options) => {
443
453
  return Result.error(traverseError);
444
454
  }
445
455
  if (validationError) {
456
+ if (isMissingPropertyError(validationError)) {
457
+ return Result.error(validationError);
458
+ }
446
459
  return Result.error(makeValidationError({
447
460
  code: ValidationErrorCode.InvalidProperties,
448
461
  errors: validationError
@@ -16,11 +16,20 @@ const internalGet = async (payload, context) => {
16
16
  const refMap = await (0, index_js_1.getReferences)(context.description.properties, {
17
17
  memoize: context.description.$id,
18
18
  });
19
+ const { error: filtersError, result: traversedFilters } = await (0, index_js_1.traverseDocument)(filters, context.description, {
20
+ autoCast: true,
21
+ allowOperators: true,
22
+ });
23
+ if (filtersError) {
24
+ switch (filtersError) {
25
+ case types_1.ACError.InsecureOperator: return context.error(types_1.HTTPStatus.Forbidden, {
26
+ code: filtersError,
27
+ });
28
+ default: throw new Error;
29
+ }
30
+ }
19
31
  pipeline.push({
20
- $match: (0, common_1.throwIfError)(await (0, index_js_1.traverseDocument)(filters, context.description, {
21
- autoCast: true,
22
- allowOperators: true,
23
- })),
32
+ $match: traversedFilters,
24
33
  });
25
34
  if (project) {
26
35
  const projection = (0, index_js_1.normalizeProjection)(project, context.description);
@@ -22,11 +22,22 @@ const internalGet = async (payload, context) => {
22
22
  const refMap = await getReferences(context.description.properties, {
23
23
  memoize: context.description.$id
24
24
  });
25
+ const { error: filtersError, result: traversedFilters } = await traverseDocument(filters, context.description, {
26
+ autoCast: true,
27
+ allowOperators: true
28
+ });
29
+ if (filtersError) {
30
+ switch (filtersError) {
31
+ case ACError.InsecureOperator:
32
+ return context.error(HTTPStatus.Forbidden, {
33
+ code: filtersError
34
+ });
35
+ default:
36
+ throw new Error();
37
+ }
38
+ }
25
39
  pipeline.push({
26
- $match: throwIfError(await traverseDocument(filters, context.description, {
27
- autoCast: true,
28
- allowOperators: true
29
- }))
40
+ $match: traversedFilters
30
41
  });
31
42
  if (project) {
32
43
  const projection = normalizeProjection(project, context.description);
@@ -39,12 +39,21 @@ const internalGetAll = async (payload, context) => {
39
39
  $sort: preferredSort,
40
40
  });
41
41
  }
42
+ const { error: filtersError, result: traversedFilters } = await (0, index_js_1.traverseDocument)(filters, context.description, {
43
+ autoCast: true,
44
+ allowOperators: true,
45
+ });
46
+ if (filtersError) {
47
+ switch (filtersError) {
48
+ case types_1.ACError.InsecureOperator: return context.error(types_1.HTTPStatus.Forbidden, {
49
+ code: filtersError,
50
+ });
51
+ default: throw new Error;
52
+ }
53
+ }
42
54
  if (Object.keys(filters).length > 0) {
43
55
  pipeline.push({
44
- $match: (0, common_1.throwIfError)(await (0, index_js_1.traverseDocument)(filters, context.description, {
45
- autoCast: true,
46
- allowOperators: true,
47
- })),
56
+ $match: traversedFilters,
48
57
  });
49
58
  }
50
59
  if (offset > 0) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  import { useSecurity, applyReadMiddlewares } from "@aeriajs/security";
3
- import { HTTPStatus, Result } from "@aeriajs/types";
3
+ import { ACError, HTTPStatus, Result } from "@aeriajs/types";
4
4
  import { throwIfError } from "@aeriajs/common";
5
5
  import {
6
6
  traverseDocument,
@@ -39,12 +39,23 @@ const internalGetAll = async (payload, context) => {
39
39
  $sort: preferredSort
40
40
  });
41
41
  }
42
+ const { error: filtersError, result: traversedFilters } = await traverseDocument(filters, context.description, {
43
+ autoCast: true,
44
+ allowOperators: true
45
+ });
46
+ if (filtersError) {
47
+ switch (filtersError) {
48
+ case ACError.InsecureOperator:
49
+ return context.error(HTTPStatus.Forbidden, {
50
+ code: filtersError
51
+ });
52
+ default:
53
+ throw new Error();
54
+ }
55
+ }
42
56
  if (Object.keys(filters).length > 0) {
43
57
  pipeline.push({
44
- $match: throwIfError(await traverseDocument(filters, context.description, {
45
- autoCast: true,
46
- allowOperators: true
47
- }))
58
+ $match: traversedFilters
48
59
  });
49
60
  }
50
61
  if (offset > 0) {
@@ -41,15 +41,15 @@ const internalInsert = async (payload, context) => {
41
41
  const { error, result: what } = await (0, index_js_1.traverseDocument)(payload.what, context.description, {
42
42
  recurseDeep: true,
43
43
  autoCast: true,
44
- validate: true,
45
- validateRequired: isUpdate
46
- ? []
47
- : context.description.required,
48
44
  moveFiles: true,
49
45
  cleanupReferences: true,
50
- fromProperties: !isUpdate,
51
46
  undefinedToNull: true,
52
47
  preserveHidden: true,
48
+ validate: true,
49
+ validateWholeness: isUpdate
50
+ ? 'deep'
51
+ : true,
52
+ fromProperties: !isUpdate,
53
53
  context,
54
54
  });
55
55
  if (error) {
@@ -38,13 +38,13 @@ const internalInsert = async (payload, context) => {
38
38
  const { error, result: what } = await traverseDocument(payload.what, context.description, {
39
39
  recurseDeep: true,
40
40
  autoCast: true,
41
- validate: true,
42
- validateRequired: isUpdate ? [] : context.description.required,
43
41
  moveFiles: true,
44
42
  cleanupReferences: true,
45
- fromProperties: !isUpdate,
46
43
  undefinedToNull: true,
47
44
  preserveHidden: true,
45
+ validate: true,
46
+ validateWholeness: isUpdate ? "deep" : true,
47
+ fromProperties: !isUpdate,
48
48
  context
49
49
  });
50
50
  if (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeriajs/core",
3
- "version": "0.0.185",
3
+ "version": "0.0.187",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "aeriaMain": "tests/fixtures/aeriaMain.js",
@@ -42,13 +42,13 @@
42
42
  "mongodb-memory-server": "^9.2.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "@aeriajs/builtins": "^0.0.185",
46
- "@aeriajs/common": "^0.0.113",
47
- "@aeriajs/entrypoint": "^0.0.116",
48
- "@aeriajs/http": "^0.0.127",
49
- "@aeriajs/security": "^0.0.185",
50
- "@aeriajs/types": "^0.0.96",
51
- "@aeriajs/validation": "^0.0.116"
45
+ "@aeriajs/builtins": "^0.0.187",
46
+ "@aeriajs/common": "^0.0.114",
47
+ "@aeriajs/entrypoint": "^0.0.117",
48
+ "@aeriajs/http": "^0.0.128",
49
+ "@aeriajs/security": "^0.0.187",
50
+ "@aeriajs/types": "^0.0.97",
51
+ "@aeriajs/validation": "^0.0.117"
52
52
  },
53
53
  "dependencies": {
54
54
  "mongodb": "^6.5.0",
@@ -59,8 +59,8 @@
59
59
  },
60
60
  "scripts": {
61
61
  "test": "vitest run",
62
- "lint": "eslint src",
63
- "lint:fix": "eslint src --fix",
62
+ "lint": "eslint .",
63
+ "lint:fix": "eslint . --fix",
64
64
  "build": "pnpm build:cjs && pnpm build:esm",
65
65
  "build:cjs": "tsc",
66
66
  "build:esm": "esbuild './src/**/*.ts' --outdir=dist --out-extension:.js=.mjs && pnpm build:esm-transform",