lumen-framework 3.0.2 → 3.1.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/dist/cli.cjs +71 -38
- package/dist/cli.cjs.map +4 -4
- package/dist/index.js +978 -272
- package/dist/index.js.map +4 -4
- package/dist/index.mjs +969 -263
- package/dist/index.mjs.map +4 -4
- package/dist/types/errors/namespaced-serializer-missing-error.d.ts +15 -0
- package/dist/types/packages/application/utils/validate-namespaced-serializers.d.ts +15 -0
- package/dist/types/packages/controller/errors/index.d.ts +1 -0
- package/dist/types/packages/controller/errors/related-record-not-found-error.d.ts +14 -0
- package/dist/types/packages/controller/index.d.ts +65 -0
- package/dist/types/packages/controller/utils/find-many.d.ts +2 -1
- package/dist/types/packages/controller/utils/find-one.d.ts +2 -1
- package/dist/types/packages/controller/utils/params-to-query.d.ts +7 -2
- package/dist/types/packages/controller/utils/validate-relationships.d.ts +8 -0
- package/dist/types/packages/database/constants.d.ts +1 -0
- package/dist/types/packages/database/model/index.d.ts +11 -0
- package/dist/types/packages/database/model/utils/process-write-error.d.ts +9 -1
- package/dist/types/packages/database/validation/errors/validation-error.d.ts +11 -2
- package/dist/types/packages/jsonapi/errors/not-acceptable-error.d.ts +1 -1
- package/dist/types/packages/jsonapi/index.d.ts +3 -1
- package/dist/types/packages/jsonapi/interfaces.d.ts +1 -1
- package/dist/types/packages/jsonapi/utils/has-media-type-params.d.ts +6 -0
- package/dist/types/packages/jsonapi/utils/is-jsonapi.d.ts +2 -0
- package/dist/types/packages/jsonapi/utils/media-type.d.ts +19 -0
- package/dist/types/packages/router/route/params/errors/client-generated-id-error.d.ts +13 -0
- package/dist/types/packages/router/route/params/errors/forbidden-parameter-error.d.ts +14 -0
- package/dist/types/packages/router/route/params/errors/index.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/invalid-parameter-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/parameter-not-nullable-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/parameter-required-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/parameter-type-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/parameter-value-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/errors/resource-mismatch-error.d.ts +2 -0
- package/dist/types/packages/router/route/params/index.d.ts +1 -0
- package/dist/types/packages/router/route/params/parameter/forbidden-parameter.d.ts +12 -0
- package/dist/types/packages/router/route/params/parameter/index.d.ts +8 -0
- package/dist/types/packages/router/route/params/utils/validate-client-id.d.ts +8 -0
- package/dist/types/packages/serializer/index.d.ts +111 -36
- package/dist/types/packages/serializer/utils/include-tree.d.ts +29 -0
- package/dist/types/packages/serializer/utils/load-linkage.d.ts +22 -0
- package/dist/types/packages/server/index.d.ts +2 -1
- package/dist/types/packages/server/interfaces.d.ts +5 -0
- package/dist/types/packages/server/utils/source-for.d.ts +10 -0
- package/dist/types/packages/server/utils/validate-accept.d.ts +5 -1
- package/dist/types/packages/server/utils/validate-content-type.d.ts +4 -0
- package/package.json +1 -1
- package/dist/types/packages/jsonapi/utils/has-media-type.d.ts +0 -4
package/dist/index.js
CHANGED
|
@@ -43,6 +43,8 @@ module.exports = __toCommonJS(index_exports);
|
|
|
43
43
|
var import_chalk4 = require("chalk");
|
|
44
44
|
|
|
45
45
|
// src/packages/database/constants.ts
|
|
46
|
+
var UNIQUE_CONSTRAINT_CODES = /* @__PURE__ */ new Set(["23505", "ER_DUP_ENTRY"]);
|
|
47
|
+
var UNIQUE_CONSTRAINT = /unique(\s+key)?\s+constraint|duplicate key/i;
|
|
46
48
|
var VALID_DRIVERS = [
|
|
47
49
|
"pg",
|
|
48
50
|
"sqlite3",
|
|
@@ -892,7 +894,7 @@ function underscore(source = "", upper = false) {
|
|
|
892
894
|
var normalize_model_name_default = compose(import_inflection2.singularize, import_inflection2.dasherize, underscore);
|
|
893
895
|
|
|
894
896
|
// src/packages/database/model/index.ts
|
|
895
|
-
var
|
|
897
|
+
var import_inflection10 = require("inflection");
|
|
896
898
|
|
|
897
899
|
// src/packages/database/query/index.ts
|
|
898
900
|
var import_inflection5 = require("inflection");
|
|
@@ -1090,10 +1092,16 @@ async function buildResults({
|
|
|
1090
1092
|
}
|
|
1091
1093
|
);
|
|
1092
1094
|
}
|
|
1095
|
+
const missing = new Set(
|
|
1096
|
+
entries(record).filter(([key, value]) => value == null && pkPattern.test(key)).map(([key]) => key.split(".")[0])
|
|
1097
|
+
);
|
|
1098
|
+
const absent = Array.from(missing).filter(
|
|
1099
|
+
(name) => model.relationshipFor(name)?.type === "hasOne"
|
|
1100
|
+
);
|
|
1093
1101
|
const instance = Reflect.construct(model, [
|
|
1094
1102
|
entries(record).reduce((r, entry) => {
|
|
1095
1103
|
let [key, value] = entry;
|
|
1096
|
-
if (
|
|
1104
|
+
if (key.indexOf(".") >= 0 && missing.has(key.split(".")[0])) {
|
|
1097
1105
|
return r;
|
|
1098
1106
|
} else if (key.indexOf(".") >= 0) {
|
|
1099
1107
|
const [a, b] = key.split(".");
|
|
@@ -1113,6 +1121,7 @@ async function buildResults({
|
|
|
1113
1121
|
};
|
|
1114
1122
|
}, {})
|
|
1115
1123
|
]);
|
|
1124
|
+
absent.forEach((name) => instance.absentRelationships.add(name));
|
|
1116
1125
|
instance.currentChangeSet.persist();
|
|
1117
1126
|
return instance;
|
|
1118
1127
|
});
|
|
@@ -1124,6 +1133,17 @@ function createRunner(target, opts) {
|
|
|
1124
1133
|
const { resolve: resolve2, reject } = opts;
|
|
1125
1134
|
let didRun = false;
|
|
1126
1135
|
RUNNERS.set(target, async () => {
|
|
1136
|
+
if (didRun) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
didRun = true;
|
|
1140
|
+
try {
|
|
1141
|
+
await run();
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
reject(err);
|
|
1144
|
+
}
|
|
1145
|
+
});
|
|
1146
|
+
async function run() {
|
|
1127
1147
|
let results;
|
|
1128
1148
|
const {
|
|
1129
1149
|
model,
|
|
@@ -1133,10 +1153,6 @@ function createRunner(target, opts) {
|
|
|
1133
1153
|
shouldCount,
|
|
1134
1154
|
relationships
|
|
1135
1155
|
} = target;
|
|
1136
|
-
if (didRun) {
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
didRun = true;
|
|
1140
1156
|
if (!shouldCount && !snapshots.some(([name]) => name === "select")) {
|
|
1141
1157
|
target.select(...target.model.attributeNames);
|
|
1142
1158
|
}
|
|
@@ -1177,7 +1193,7 @@ function createRunner(target, opts) {
|
|
|
1177
1193
|
resolve2(result);
|
|
1178
1194
|
}
|
|
1179
1195
|
}
|
|
1180
|
-
}
|
|
1196
|
+
}
|
|
1181
1197
|
}
|
|
1182
1198
|
}
|
|
1183
1199
|
function runQuery(target) {
|
|
@@ -1882,7 +1898,9 @@ async function get(owner, key) {
|
|
|
1882
1898
|
let { foreignKey } = opts;
|
|
1883
1899
|
value = owner.currentChangeSet.get(key);
|
|
1884
1900
|
foreignKey = (0, import_inflection7.camelize)(foreignKey, true);
|
|
1885
|
-
if (!value) {
|
|
1901
|
+
if (!value && owner.absentRelationships.has(key)) {
|
|
1902
|
+
value = null;
|
|
1903
|
+
} else if (!value) {
|
|
1886
1904
|
switch (type) {
|
|
1887
1905
|
case "hasOne":
|
|
1888
1906
|
value = await getHasOne(owner, {
|
|
@@ -2544,13 +2562,37 @@ async function initializeClass({
|
|
|
2544
2562
|
return model;
|
|
2545
2563
|
}
|
|
2546
2564
|
|
|
2565
|
+
// src/packages/server/utils/source-for.ts
|
|
2566
|
+
var import_inflection9 = require("inflection");
|
|
2567
|
+
function memberName(key) {
|
|
2568
|
+
return (0, import_inflection9.dasherize)(underscore(key));
|
|
2569
|
+
}
|
|
2570
|
+
function escapePointer(segment) {
|
|
2571
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2572
|
+
}
|
|
2573
|
+
function sourceFor(path) {
|
|
2574
|
+
const [root, ...rest] = path.split(".").filter(Boolean).map(memberName);
|
|
2575
|
+
if (!root) {
|
|
2576
|
+
return {};
|
|
2577
|
+
} else if (root === "data") {
|
|
2578
|
+
return {
|
|
2579
|
+
pointer: `/${[root, ...rest].map(escapePointer).join("/")}`
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
return {
|
|
2583
|
+
parameter: `${root}${rest.map((key) => `[${key}]`).join("")}`
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2547
2587
|
// src/packages/database/validation/errors/validation-error.ts
|
|
2548
2588
|
var ValidationError = class extends Error {
|
|
2549
|
-
constructor(key
|
|
2550
|
-
super(`Validation failed for ${key}
|
|
2589
|
+
constructor(key) {
|
|
2590
|
+
super(`Validation failed for ${key}.`);
|
|
2591
|
+
this.key = key;
|
|
2592
|
+
this.source = sourceFor(`data.attributes.${key}`);
|
|
2551
2593
|
}
|
|
2552
2594
|
};
|
|
2553
|
-
var validation_error_default = ValidationError;
|
|
2595
|
+
var validation_error_default = createServerError(ValidationError, 422);
|
|
2554
2596
|
|
|
2555
2597
|
// src/packages/database/validation/index.ts
|
|
2556
2598
|
var Validation = class {
|
|
@@ -2590,12 +2632,32 @@ function validate(instance) {
|
|
|
2590
2632
|
validator: Reflect.get(instance.constructor.validates, key)
|
|
2591
2633
|
})).filter(({ validator }) => validator).map((props) => new validation_default(props)).reduce((result, validation) => {
|
|
2592
2634
|
if (!validation.isValid()) {
|
|
2593
|
-
throw new validation_error_default(validation.key
|
|
2635
|
+
throw new validation_error_default(validation.key);
|
|
2594
2636
|
}
|
|
2595
2637
|
return result;
|
|
2596
2638
|
}, true);
|
|
2597
2639
|
}
|
|
2598
2640
|
|
|
2641
|
+
// src/packages/database/model/utils/process-write-error.ts
|
|
2642
|
+
function driverMessage(message) {
|
|
2643
|
+
const index = message.lastIndexOf(" - ");
|
|
2644
|
+
return index >= 0 ? message.slice(index + 3) : message;
|
|
2645
|
+
}
|
|
2646
|
+
function processWriteError(err) {
|
|
2647
|
+
if (!(err instanceof Error)) {
|
|
2648
|
+
return err;
|
|
2649
|
+
}
|
|
2650
|
+
const { code } = err;
|
|
2651
|
+
const { message } = err;
|
|
2652
|
+
if (UNIQUE_CONSTRAINT_CODES.has(String(code)) || UNIQUE_CONSTRAINT.test(message)) {
|
|
2653
|
+
return new unique_constraint_error_default(driverMessage(message));
|
|
2654
|
+
}
|
|
2655
|
+
return err;
|
|
2656
|
+
}
|
|
2657
|
+
function rethrowWriteError(err) {
|
|
2658
|
+
throw processWriteError(err);
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2599
2661
|
// src/packages/database/model/utils/run-hooks.ts
|
|
2600
2662
|
function runHooks(record, trx, ...hooks) {
|
|
2601
2663
|
return hooks.filter((hook) => Boolean(hook)).reduce(
|
|
@@ -2635,6 +2697,12 @@ var Model = class {
|
|
|
2635
2697
|
writable: false,
|
|
2636
2698
|
enumerable: false,
|
|
2637
2699
|
configurable: false
|
|
2700
|
+
},
|
|
2701
|
+
absentRelationships: {
|
|
2702
|
+
value: /* @__PURE__ */ new Set(),
|
|
2703
|
+
writable: false,
|
|
2704
|
+
enumerable: false,
|
|
2705
|
+
configurable: false
|
|
2638
2706
|
}
|
|
2639
2707
|
});
|
|
2640
2708
|
const {
|
|
@@ -2981,9 +3049,9 @@ var Model = class {
|
|
|
2981
3049
|
return createTransactionResultProxy(this, hadDirtyAttrs || hadDirtyAssoc);
|
|
2982
3050
|
};
|
|
2983
3051
|
if (transaction) {
|
|
2984
|
-
return run(transaction);
|
|
3052
|
+
return run(transaction).catch(rethrowWriteError);
|
|
2985
3053
|
}
|
|
2986
|
-
return this.transaction(run);
|
|
3054
|
+
return this.transaction(run).catch(rethrowWriteError);
|
|
2987
3055
|
}
|
|
2988
3056
|
/**
|
|
2989
3057
|
* Permanently delete the instance from the database.
|
|
@@ -3103,9 +3171,9 @@ var Model = class {
|
|
|
3103
3171
|
return createTransactionResultProxy(instance, true);
|
|
3104
3172
|
};
|
|
3105
3173
|
if (transaction) {
|
|
3106
|
-
return run(transaction);
|
|
3174
|
+
return run(transaction).catch(rethrowWriteError);
|
|
3107
3175
|
}
|
|
3108
|
-
return this.transaction(run);
|
|
3176
|
+
return this.transaction(run).catch(rethrowWriteError);
|
|
3109
3177
|
}
|
|
3110
3178
|
/**
|
|
3111
3179
|
* Specify the transaction object to use for following save, update, or
|
|
@@ -3283,7 +3351,7 @@ var Model = class {
|
|
|
3283
3351
|
return Promise.resolve(this);
|
|
3284
3352
|
}
|
|
3285
3353
|
if (!this.tableName) {
|
|
3286
|
-
const getTableName = compose(
|
|
3354
|
+
const getTableName = compose(import_inflection10.pluralize, underscore);
|
|
3287
3355
|
const tableName = getTableName(this.name);
|
|
3288
3356
|
Reflect.defineProperty(this, "tableName", {
|
|
3289
3357
|
value: tableName,
|
|
@@ -3481,7 +3549,7 @@ function parseNestedObject(source) {
|
|
|
3481
3549
|
}
|
|
3482
3550
|
|
|
3483
3551
|
// src/packages/server/request/parser/utils/format.ts
|
|
3484
|
-
var
|
|
3552
|
+
var import_inflection12 = require("inflection");
|
|
3485
3553
|
|
|
3486
3554
|
// src/packages/server/request/parser/constants.ts
|
|
3487
3555
|
var INT = /^\d+$/;
|
|
@@ -3492,7 +3560,7 @@ var TRUE = /^true$/;
|
|
|
3492
3560
|
var BRACKETS = /(?:\[])/g;
|
|
3493
3561
|
|
|
3494
3562
|
// src/utils/transform-keys.ts
|
|
3495
|
-
var
|
|
3563
|
+
var import_inflection11 = require("inflection");
|
|
3496
3564
|
function transformKeys(source, transformer, deep = false) {
|
|
3497
3565
|
if (Array.isArray(source)) {
|
|
3498
3566
|
return source.slice(0);
|
|
@@ -3516,14 +3584,14 @@ function transformKeys(source, transformer, deep = false) {
|
|
|
3516
3584
|
function camelizeKeys(source, deep) {
|
|
3517
3585
|
return transformKeys(
|
|
3518
3586
|
source,
|
|
3519
|
-
(key) => (0,
|
|
3587
|
+
(key) => (0, import_inflection11.camelize)(underscore(key), true),
|
|
3520
3588
|
deep
|
|
3521
3589
|
);
|
|
3522
3590
|
}
|
|
3523
3591
|
function dasherizeKeys(source, deep) {
|
|
3524
3592
|
return transformKeys(
|
|
3525
3593
|
source,
|
|
3526
|
-
(key) => (0,
|
|
3594
|
+
(key) => (0, import_inflection11.dasherize)(underscore(key)),
|
|
3527
3595
|
deep
|
|
3528
3596
|
);
|
|
3529
3597
|
}
|
|
@@ -3538,7 +3606,7 @@ function makeArray(source) {
|
|
|
3538
3606
|
function formatString(source, method) {
|
|
3539
3607
|
if (method === "GET") {
|
|
3540
3608
|
if (source.indexOf(",") >= 0) {
|
|
3541
|
-
return source.split(",").map((str) => (0,
|
|
3609
|
+
return source.split(",").map((str) => (0, import_inflection12.camelize)(underscore(str), true));
|
|
3542
3610
|
} else if (INT.test(source)) {
|
|
3543
3611
|
return Number.parseInt(source, 10);
|
|
3544
3612
|
} else if (BOOL.test(source)) {
|
|
@@ -3565,9 +3633,9 @@ function formatObject(source, method, formatter) {
|
|
|
3565
3633
|
}
|
|
3566
3634
|
function formatSort(sort) {
|
|
3567
3635
|
if (sort.startsWith("-")) {
|
|
3568
|
-
return `-${(0,
|
|
3636
|
+
return `-${(0, import_inflection12.camelize)(underscore(sort.substr(1)), true)}`;
|
|
3569
3637
|
}
|
|
3570
|
-
return (0,
|
|
3638
|
+
return (0, import_inflection12.camelize)(underscore(sort), true);
|
|
3571
3639
|
}
|
|
3572
3640
|
function formatFields(fields) {
|
|
3573
3641
|
return entries(fields).reduce(
|
|
@@ -3728,10 +3796,11 @@ var MIME_TYPE = "application/vnd.api+json";
|
|
|
3728
3796
|
|
|
3729
3797
|
// src/packages/jsonapi/errors/not-acceptable-error.ts
|
|
3730
3798
|
var NotAcceptableError = class extends TypeError {
|
|
3731
|
-
constructor(
|
|
3799
|
+
constructor(accept) {
|
|
3732
3800
|
super(line`
|
|
3733
|
-
|
|
3734
|
-
|
|
3801
|
+
Every '${MIME_TYPE}' in Accept: '${accept}' has media type parameters,
|
|
3802
|
+
which are not supported. Try your request again with '${MIME_TYPE}'
|
|
3803
|
+
without parameters.
|
|
3735
3804
|
`);
|
|
3736
3805
|
}
|
|
3737
3806
|
};
|
|
@@ -3741,8 +3810,9 @@ var not_acceptable_error_default = createServerError(NotAcceptableError, 406);
|
|
|
3741
3810
|
var UnsupportedMediaTypeError = class extends TypeError {
|
|
3742
3811
|
constructor(contentType) {
|
|
3743
3812
|
super(line`
|
|
3744
|
-
Media type parameters
|
|
3745
|
-
|
|
3813
|
+
Media type parameters are not supported in Content-Type:
|
|
3814
|
+
'${contentType}'. Try your request again with Content-Type:
|
|
3815
|
+
'${MIME_TYPE}' without parameters.
|
|
3746
3816
|
`);
|
|
3747
3817
|
}
|
|
3748
3818
|
};
|
|
@@ -3757,17 +3827,59 @@ var InvalidContentTypeError = class extends TypeError {
|
|
|
3757
3827
|
`);
|
|
3758
3828
|
}
|
|
3759
3829
|
};
|
|
3760
|
-
var invalid_content_type_error_default = createServerError(InvalidContentTypeError,
|
|
3830
|
+
var invalid_content_type_error_default = createServerError(InvalidContentTypeError, 415);
|
|
3831
|
+
|
|
3832
|
+
// src/packages/jsonapi/utils/media-type.ts
|
|
3833
|
+
var QUALITY = /^q\s*=/i;
|
|
3834
|
+
function splitUnquoted(value, delimiter) {
|
|
3835
|
+
const parts = [];
|
|
3836
|
+
let current = "";
|
|
3837
|
+
let quoted = false;
|
|
3838
|
+
let escaped = false;
|
|
3839
|
+
for (const char of value) {
|
|
3840
|
+
if (escaped) {
|
|
3841
|
+
escaped = false;
|
|
3842
|
+
} else if (quoted && char === "\\") {
|
|
3843
|
+
escaped = true;
|
|
3844
|
+
} else if (char === '"') {
|
|
3845
|
+
quoted = !quoted;
|
|
3846
|
+
} else if (!quoted && char === delimiter) {
|
|
3847
|
+
parts.push(current);
|
|
3848
|
+
current = "";
|
|
3849
|
+
continue;
|
|
3850
|
+
}
|
|
3851
|
+
current += char;
|
|
3852
|
+
}
|
|
3853
|
+
return [...parts, current];
|
|
3854
|
+
}
|
|
3855
|
+
function parseMediaType(value) {
|
|
3856
|
+
const [type = "", ...params] = splitUnquoted(value, ";").map(
|
|
3857
|
+
(part) => part.trim()
|
|
3858
|
+
);
|
|
3859
|
+
return {
|
|
3860
|
+
type: type.toLowerCase(),
|
|
3861
|
+
params: params.filter(Boolean)
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
function parseAccept(value) {
|
|
3865
|
+
return splitUnquoted(value, ",").map((range) => {
|
|
3866
|
+
const { type, params } = parseMediaType(range);
|
|
3867
|
+
const quality = params.findIndex((param) => QUALITY.test(param));
|
|
3868
|
+
return {
|
|
3869
|
+
type,
|
|
3870
|
+
params: quality === -1 ? params : params.slice(0, quality)
|
|
3871
|
+
};
|
|
3872
|
+
}).filter(({ type }) => type.length > 0);
|
|
3873
|
+
}
|
|
3761
3874
|
|
|
3762
3875
|
// src/packages/jsonapi/utils/is-jsonapi.ts
|
|
3763
3876
|
function isJSONAPI(value) {
|
|
3764
|
-
return value.
|
|
3877
|
+
return parseMediaType(value).type === MIME_TYPE;
|
|
3765
3878
|
}
|
|
3766
3879
|
|
|
3767
|
-
// src/packages/jsonapi/utils/has-media-type.ts
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
return PATTERN2.test(value);
|
|
3880
|
+
// src/packages/jsonapi/utils/has-media-type-params.ts
|
|
3881
|
+
function hasMediaTypeParams(value) {
|
|
3882
|
+
return parseMediaType(value).params.length > 0;
|
|
3771
3883
|
}
|
|
3772
3884
|
|
|
3773
3885
|
// src/utils/env.ts
|
|
@@ -3788,6 +3900,10 @@ function dataFor(status, err) {
|
|
|
3788
3900
|
if (title) {
|
|
3789
3901
|
errData.title = title;
|
|
3790
3902
|
}
|
|
3903
|
+
const source = err?.source;
|
|
3904
|
+
if (source && (source.pointer || source.parameter)) {
|
|
3905
|
+
errData.source = source;
|
|
3906
|
+
}
|
|
3791
3907
|
if (err && (isDevelopment() || /^\[public\]/gi.test(err.message))) {
|
|
3792
3908
|
errData.detail = err.message.replace(/^\[public\]/gi, "");
|
|
3793
3909
|
}
|
|
@@ -3868,9 +3984,12 @@ function createResponder(req, res) {
|
|
|
3868
3984
|
}
|
|
3869
3985
|
|
|
3870
3986
|
// src/packages/server/utils/validate-accept.ts
|
|
3871
|
-
function validateAccept(
|
|
3872
|
-
if (
|
|
3873
|
-
|
|
3987
|
+
function validateAccept(accept) {
|
|
3988
|
+
if (accept) {
|
|
3989
|
+
const ranges = parseAccept(accept).filter(({ type }) => type === MIME_TYPE);
|
|
3990
|
+
if (ranges.length && ranges.every(({ params }) => params.length)) {
|
|
3991
|
+
throw new not_acceptable_error_default(accept);
|
|
3992
|
+
}
|
|
3874
3993
|
}
|
|
3875
3994
|
return true;
|
|
3876
3995
|
}
|
|
@@ -3879,7 +3998,7 @@ function validateAccept(contentType) {
|
|
|
3879
3998
|
function validateContentType(contentType) {
|
|
3880
3999
|
if (!contentType || !isJSONAPI(contentType)) {
|
|
3881
4000
|
throw new invalid_content_type_error_default(contentType);
|
|
3882
|
-
} else if (
|
|
4001
|
+
} else if (hasMediaTypeParams(contentType)) {
|
|
3883
4002
|
throw new unsupported_media_type_error_default(contentType);
|
|
3884
4003
|
}
|
|
3885
4004
|
return true;
|
|
@@ -4007,9 +4126,10 @@ function merge(dest, source) {
|
|
|
4007
4126
|
}
|
|
4008
4127
|
|
|
4009
4128
|
// src/packages/controller/utils/params-to-query.ts
|
|
4010
|
-
function paramsToQuery(model, { id, page, sort, filter, fields, include }) {
|
|
4129
|
+
function paramsToQuery(model, { id, page, sort, filter, fields, include }, serializerFor = (related) => related.serializer) {
|
|
4011
4130
|
const relationships = entries(model.relationships);
|
|
4012
4131
|
const includedFields = omit(fields, model.resourceName);
|
|
4132
|
+
const included = include && include.map((path) => path.split(".")[0]);
|
|
4013
4133
|
let query = {
|
|
4014
4134
|
id,
|
|
4015
4135
|
filter,
|
|
@@ -4052,9 +4172,9 @@ function paramsToQuery(model, { id, page, sort, filter, fields, include }) {
|
|
|
4052
4172
|
if (!value.includes(relationship.model.primaryKey)) {
|
|
4053
4173
|
value = [relationship.model.primaryKey, ...value];
|
|
4054
4174
|
}
|
|
4055
|
-
if (
|
|
4056
|
-
value = [...value, ...relationship.model.
|
|
4057
|
-
} else if (!
|
|
4175
|
+
if (included && value.length === 1 && included.includes(name)) {
|
|
4176
|
+
value = [...value, ...serializerFor(relationship.model).attributes];
|
|
4177
|
+
} else if (!included && value.length > 1) {
|
|
4058
4178
|
value = value.slice(0, 1);
|
|
4059
4179
|
}
|
|
4060
4180
|
return {
|
|
@@ -4069,18 +4189,19 @@ function paramsToQuery(model, { id, page, sort, filter, fields, include }) {
|
|
|
4069
4189
|
}
|
|
4070
4190
|
|
|
4071
4191
|
// src/packages/controller/utils/find-one.ts
|
|
4072
|
-
function findOne(model, req) {
|
|
4192
|
+
function findOne(model, req, serializerFor) {
|
|
4073
4193
|
const params = merge(req.defaultParams, req.params);
|
|
4074
|
-
const { id, select, include } = paramsToQuery(model, params);
|
|
4194
|
+
const { id, select, include } = paramsToQuery(model, params, serializerFor);
|
|
4075
4195
|
return model.find(id).select(...select).include(include);
|
|
4076
4196
|
}
|
|
4077
4197
|
|
|
4078
4198
|
// src/packages/controller/utils/find-many.ts
|
|
4079
|
-
function findMany(model, req) {
|
|
4199
|
+
function findMany(model, req, serializerFor) {
|
|
4080
4200
|
const params = merge(req.defaultParams, req.params);
|
|
4081
4201
|
const { sort, page, limit, select, filter, include } = paramsToQuery(
|
|
4082
4202
|
model,
|
|
4083
|
-
params
|
|
4203
|
+
params,
|
|
4204
|
+
serializerFor
|
|
4084
4205
|
);
|
|
4085
4206
|
return model.select(...select).include(include).limit(limit).page(page).where(filter).order(...sort);
|
|
4086
4207
|
}
|
|
@@ -4110,6 +4231,53 @@ function resolveRelationships(model, relationships = {}) {
|
|
|
4110
4231
|
);
|
|
4111
4232
|
}
|
|
4112
4233
|
|
|
4234
|
+
// src/packages/controller/errors/related-record-not-found-error.ts
|
|
4235
|
+
var RelatedRecordNotFoundError = class extends Error {
|
|
4236
|
+
constructor({ name, primaryKey }, primaryKeyValue, path) {
|
|
4237
|
+
super(
|
|
4238
|
+
`Could not find ${name} with ${primaryKey} ${stringify(primaryKeyValue)}.`
|
|
4239
|
+
);
|
|
4240
|
+
this.source = sourceFor(path);
|
|
4241
|
+
}
|
|
4242
|
+
};
|
|
4243
|
+
var related_record_not_found_error_default = createServerError(RelatedRecordNotFoundError, 404);
|
|
4244
|
+
|
|
4245
|
+
// src/packages/controller/utils/validate-relationships.ts
|
|
4246
|
+
async function validateRelationships(model, relationships = {}) {
|
|
4247
|
+
await Promise.all(
|
|
4248
|
+
entries(relationships).map(async ([key, value]) => {
|
|
4249
|
+
const opts = model.relationshipFor(key);
|
|
4250
|
+
const { data = null } = value || {};
|
|
4251
|
+
if (!opts || !data) {
|
|
4252
|
+
return;
|
|
4253
|
+
}
|
|
4254
|
+
const { model: related } = opts;
|
|
4255
|
+
const isMany = Array.isArray(data);
|
|
4256
|
+
const ids = (isMany ? data : [data]).map(
|
|
4257
|
+
(item) => Reflect.get(Object(item), "id")
|
|
4258
|
+
);
|
|
4259
|
+
if (!ids.length) {
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
const column = related.columnNameFor(related.primaryKey) || "id";
|
|
4263
|
+
const rows = await related.table().whereIn(
|
|
4264
|
+
`${related.tableName}.${column}`,
|
|
4265
|
+
ids.filter((id) => id != null)
|
|
4266
|
+
).select(`${related.tableName}.${column}`);
|
|
4267
|
+
const found = new Set(rows.map((row) => String(Reflect.get(row, column))));
|
|
4268
|
+
const missing = ids.findIndex((id) => !found.has(String(id)));
|
|
4269
|
+
if (missing >= 0) {
|
|
4270
|
+
const path = `data.relationships.${key}.data`;
|
|
4271
|
+
throw new related_record_not_found_error_default(
|
|
4272
|
+
related,
|
|
4273
|
+
ids[missing],
|
|
4274
|
+
isMany ? `${path}.${missing}` : path
|
|
4275
|
+
);
|
|
4276
|
+
}
|
|
4277
|
+
})
|
|
4278
|
+
);
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4113
4281
|
// src/packages/controller/constants.ts
|
|
4114
4282
|
var BUILT_IN_ACTIONS = Object.freeze(["show", "index", "create", "update", "destroy"]);
|
|
4115
4283
|
|
|
@@ -4295,6 +4463,74 @@ var Controller = class {
|
|
|
4295
4463
|
* @public
|
|
4296
4464
|
*/
|
|
4297
4465
|
defaultPerPage = 25;
|
|
4466
|
+
/**
|
|
4467
|
+
* How many relationships deep an `?include` path may go on this
|
|
4468
|
+
* controller's routes. `comments.reactions.user` is 3 levels deep; with `1`
|
|
4469
|
+
* only direct relationships (`comments`) can be included. Paths deeper than
|
|
4470
|
+
* this are rejected with `400 Bad Request`.
|
|
4471
|
+
*
|
|
4472
|
+
* Set it on `ApplicationController` to change it for the whole app, or on a
|
|
4473
|
+
* single controller to override it there.
|
|
4474
|
+
*
|
|
4475
|
+
* ```javascript
|
|
4476
|
+
* class ApplicationController extends Controller {
|
|
4477
|
+
* maxIncludeDepth = 2;
|
|
4478
|
+
* }
|
|
4479
|
+
* ```
|
|
4480
|
+
*
|
|
4481
|
+
* Every allowed path is enumerated up front from the serializers'
|
|
4482
|
+
* relationships, and each nested level costs its own queries per request, so
|
|
4483
|
+
* keep this small.
|
|
4484
|
+
*
|
|
4485
|
+
* @property maxIncludeDepth
|
|
4486
|
+
* @type {Number}
|
|
4487
|
+
* @default 3
|
|
4488
|
+
* @public
|
|
4489
|
+
*/
|
|
4490
|
+
maxIncludeDepth = 3;
|
|
4491
|
+
/**
|
|
4492
|
+
* Whether a namespace may fall back to the root Serializer of a type it has
|
|
4493
|
+
* no Serializer for. Read from a namespace's `ApplicationController` and
|
|
4494
|
+
* applies to the whole namespace.
|
|
4495
|
+
*
|
|
4496
|
+
* By default `app/controllers/admin/comments.js` without an
|
|
4497
|
+
* `app/serializers/admin/comments.js` — or an included type without one —
|
|
4498
|
+
* is serialized by the root Serializer, with every attribute and
|
|
4499
|
+
* relationship it declares. For a namespace that must only expose what it
|
|
4500
|
+
* declares itself, turn the fallback off:
|
|
4501
|
+
*
|
|
4502
|
+
* ```javascript
|
|
4503
|
+
* // app/controllers/admin/application.js
|
|
4504
|
+
* class AdminApplicationController extends ApplicationController {
|
|
4505
|
+
* serializerFallback = false;
|
|
4506
|
+
* }
|
|
4507
|
+
* ```
|
|
4508
|
+
*
|
|
4509
|
+
* The application then refuses to boot while any type the namespace can
|
|
4510
|
+
* serialize or `include` (down to each controller's `maxIncludeDepth`) has
|
|
4511
|
+
* no Serializer in that namespace, listing each missing one.
|
|
4512
|
+
*
|
|
4513
|
+
* @property serializerFallback
|
|
4514
|
+
* @type {Boolean}
|
|
4515
|
+
* @default true
|
|
4516
|
+
* @public
|
|
4517
|
+
*/
|
|
4518
|
+
serializerFallback = true;
|
|
4519
|
+
/**
|
|
4520
|
+
* The Serializer to serialize (and validate, and load) related resources of
|
|
4521
|
+
* this Controller's responses with: the related model's Serializer in this
|
|
4522
|
+
* Controller's namespace, falling back to the root one.
|
|
4523
|
+
*
|
|
4524
|
+
* Always this Controller's namespace — not its Serializer's, which is the
|
|
4525
|
+
* root one when the namespace has no Serializer for this resource.
|
|
4526
|
+
*
|
|
4527
|
+
* @method serializerFor
|
|
4528
|
+
* @private
|
|
4529
|
+
*/
|
|
4530
|
+
serializerFor(model) {
|
|
4531
|
+
const { serializer, namespace } = this;
|
|
4532
|
+
return serializer ? serializer.serializerFor(model, namespace) : model.serializer;
|
|
4533
|
+
}
|
|
4298
4534
|
constructor({ model, namespace, serializer }) {
|
|
4299
4535
|
Object.assign(this, {
|
|
4300
4536
|
model,
|
|
@@ -4320,7 +4556,7 @@ var Controller = class {
|
|
|
4320
4556
|
* @public
|
|
4321
4557
|
*/
|
|
4322
4558
|
index(req) {
|
|
4323
|
-
return findMany(this.model, req);
|
|
4559
|
+
return findMany(this.model, req, (related) => this.serializerFor(related));
|
|
4324
4560
|
}
|
|
4325
4561
|
/**
|
|
4326
4562
|
* This method supports including relationships, and sparse fieldsets via
|
|
@@ -4335,7 +4571,7 @@ var Controller = class {
|
|
|
4335
4571
|
* @public
|
|
4336
4572
|
*/
|
|
4337
4573
|
show(req) {
|
|
4338
|
-
return findOne(this.model, req);
|
|
4574
|
+
return findOne(this.model, req, (related) => this.serializerFor(related));
|
|
4339
4575
|
}
|
|
4340
4576
|
/**
|
|
4341
4577
|
* Create and return a single Model instance that the Controller instance
|
|
@@ -4356,6 +4592,7 @@ var Controller = class {
|
|
|
4356
4592
|
data: { attributes, relationships }
|
|
4357
4593
|
}
|
|
4358
4594
|
} = req;
|
|
4595
|
+
await validateRelationships(model, relationships);
|
|
4359
4596
|
const record = await model.create({
|
|
4360
4597
|
...attributes,
|
|
4361
4598
|
...resolveRelationships(model, relationships)
|
|
@@ -4381,12 +4618,13 @@ var Controller = class {
|
|
|
4381
4618
|
*/
|
|
4382
4619
|
update(req) {
|
|
4383
4620
|
const { model } = this;
|
|
4384
|
-
return findOne(model, req).then((record) => {
|
|
4621
|
+
return findOne(model, req, (related) => this.serializerFor(related)).then(async (record) => {
|
|
4385
4622
|
const {
|
|
4386
4623
|
params: {
|
|
4387
4624
|
data: { attributes, relationships }
|
|
4388
4625
|
}
|
|
4389
4626
|
} = req;
|
|
4627
|
+
await validateRelationships(model, relationships);
|
|
4390
4628
|
return record.update({
|
|
4391
4629
|
...attributes,
|
|
4392
4630
|
...resolveRelationships(model, relationships)
|
|
@@ -4410,7 +4648,7 @@ var Controller = class {
|
|
|
4410
4648
|
* @public
|
|
4411
4649
|
*/
|
|
4412
4650
|
destroy(req) {
|
|
4413
|
-
return findOne(this.model, req).then((record) => record.destroy()).then(() => 204);
|
|
4651
|
+
return findOne(this.model, req, (related) => this.serializerFor(related)).then((record) => record.destroy()).then(() => 204);
|
|
4414
4652
|
}
|
|
4415
4653
|
/**
|
|
4416
4654
|
* Respond to HEAD or OPTIONS requests.
|
|
@@ -4428,7 +4666,194 @@ var Controller = class {
|
|
|
4428
4666
|
var controller_default = Controller;
|
|
4429
4667
|
|
|
4430
4668
|
// src/packages/serializer/index.ts
|
|
4431
|
-
var
|
|
4669
|
+
var import_path3 = require("path");
|
|
4670
|
+
var import_inflection14 = require("inflection");
|
|
4671
|
+
|
|
4672
|
+
// src/packages/loader/resolver/utils/closest-ancestor.ts
|
|
4673
|
+
var import_path2 = require("path");
|
|
4674
|
+
function closestAncestor(source, key) {
|
|
4675
|
+
const name = import_path2.posix.basename(key);
|
|
4676
|
+
let namespace = import_path2.posix.dirname(key);
|
|
4677
|
+
if (namespace === ".") {
|
|
4678
|
+
return source.get(name);
|
|
4679
|
+
}
|
|
4680
|
+
namespace = import_path2.posix.dirname(namespace);
|
|
4681
|
+
const ancestor = source.get(import_path2.posix.join(namespace, name));
|
|
4682
|
+
if (ancestor) {
|
|
4683
|
+
return ancestor;
|
|
4684
|
+
}
|
|
4685
|
+
return closestAncestor(source, import_path2.posix.join(import_path2.posix.dirname(namespace), name));
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
// src/packages/serializer/utils/load-linkage.ts
|
|
4689
|
+
var import_inflection13 = require("inflection");
|
|
4690
|
+
var valueOf = (record, key) => Reflect.get(record, key);
|
|
4691
|
+
var toId = (value) => value == null ? null : String(value);
|
|
4692
|
+
async function loadLinkage(model, records, names) {
|
|
4693
|
+
const ids = Array.from(
|
|
4694
|
+
new Set(records.map((record) => record.getPrimaryKey()))
|
|
4695
|
+
);
|
|
4696
|
+
const linkage = /* @__PURE__ */ new Map();
|
|
4697
|
+
const belongsTo = [];
|
|
4698
|
+
const queries = [];
|
|
4699
|
+
ids.forEach((id) => {
|
|
4700
|
+
linkage.set(
|
|
4701
|
+
String(id),
|
|
4702
|
+
names.reduce((result, name) => {
|
|
4703
|
+
const opts = model.relationshipFor(name);
|
|
4704
|
+
result[name] = opts && opts.type === "hasMany" ? [] : null;
|
|
4705
|
+
return result;
|
|
4706
|
+
}, {})
|
|
4707
|
+
);
|
|
4708
|
+
});
|
|
4709
|
+
if (!ids.length) {
|
|
4710
|
+
return linkage;
|
|
4711
|
+
}
|
|
4712
|
+
const link = (ownerId, name, relatedId) => {
|
|
4713
|
+
const owner = linkage.get(String(ownerId));
|
|
4714
|
+
const id = toId(relatedId);
|
|
4715
|
+
if (!owner || id === null) {
|
|
4716
|
+
return;
|
|
4717
|
+
}
|
|
4718
|
+
const current = owner[name];
|
|
4719
|
+
if (Array.isArray(current)) {
|
|
4720
|
+
if (!current.includes(id)) {
|
|
4721
|
+
current.push(id);
|
|
4722
|
+
}
|
|
4723
|
+
} else if (current === null) {
|
|
4724
|
+
owner[name] = id;
|
|
4725
|
+
}
|
|
4726
|
+
};
|
|
4727
|
+
names.forEach((name) => {
|
|
4728
|
+
const opts = model.relationshipFor(name);
|
|
4729
|
+
if (!opts) {
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
const { type, through, model: related } = opts;
|
|
4733
|
+
const foreignKey = (0, import_inflection13.camelize)(opts.foreignKey, true);
|
|
4734
|
+
if (type === "belongsTo") {
|
|
4735
|
+
belongsTo.push([name, foreignKey, related]);
|
|
4736
|
+
} else if (through) {
|
|
4737
|
+
const inverse = related.relationshipFor(opts.inverse);
|
|
4738
|
+
if (!inverse) {
|
|
4739
|
+
return;
|
|
4740
|
+
}
|
|
4741
|
+
const relatedKey = (0, import_inflection13.camelize)(inverse.foreignKey, true);
|
|
4742
|
+
queries.push(
|
|
4743
|
+
(async () => {
|
|
4744
|
+
const rows = await through.select(foreignKey, relatedKey).where({ [foreignKey]: ids });
|
|
4745
|
+
rows.forEach((row) => {
|
|
4746
|
+
link(valueOf(row, foreignKey), name, valueOf(row, relatedKey));
|
|
4747
|
+
});
|
|
4748
|
+
})()
|
|
4749
|
+
);
|
|
4750
|
+
} else {
|
|
4751
|
+
queries.push(
|
|
4752
|
+
(async () => {
|
|
4753
|
+
const rows = await related.select(related.primaryKey, foreignKey).where({ [foreignKey]: ids }).order(related.primaryKey, "ASC");
|
|
4754
|
+
rows.forEach((row) => {
|
|
4755
|
+
link(valueOf(row, foreignKey), name, row.getPrimaryKey());
|
|
4756
|
+
});
|
|
4757
|
+
})()
|
|
4758
|
+
);
|
|
4759
|
+
}
|
|
4760
|
+
});
|
|
4761
|
+
if (belongsTo.length) {
|
|
4762
|
+
queries.push(
|
|
4763
|
+
(async () => {
|
|
4764
|
+
const rows = await model.select(model.primaryKey, ...belongsTo.map(([, key]) => key)).where({ [model.primaryKey]: ids });
|
|
4765
|
+
rows.forEach((row) => {
|
|
4766
|
+
belongsTo.forEach(([name, key]) => {
|
|
4767
|
+
link(row.getPrimaryKey(), name, valueOf(row, key));
|
|
4768
|
+
});
|
|
4769
|
+
});
|
|
4770
|
+
await dropDangling(linkage, belongsTo);
|
|
4771
|
+
})()
|
|
4772
|
+
);
|
|
4773
|
+
}
|
|
4774
|
+
await Promise.all(queries);
|
|
4775
|
+
return linkage;
|
|
4776
|
+
}
|
|
4777
|
+
async function dropDangling(linkage, belongsTo) {
|
|
4778
|
+
const wanted = /* @__PURE__ */ new Map();
|
|
4779
|
+
belongsTo.forEach(([name, , related]) => {
|
|
4780
|
+
const ids = wanted.get(related) || /* @__PURE__ */ new Set();
|
|
4781
|
+
linkage.forEach(({ [name]: id }) => {
|
|
4782
|
+
if (typeof id === "string") {
|
|
4783
|
+
ids.add(id);
|
|
4784
|
+
}
|
|
4785
|
+
});
|
|
4786
|
+
wanted.set(related, ids);
|
|
4787
|
+
});
|
|
4788
|
+
const existing = /* @__PURE__ */ new Map();
|
|
4789
|
+
await Promise.all(
|
|
4790
|
+
Array.from(wanted, async ([related, ids]) => {
|
|
4791
|
+
if (!ids.size) {
|
|
4792
|
+
existing.set(related, ids);
|
|
4793
|
+
return;
|
|
4794
|
+
}
|
|
4795
|
+
const rows = await related.select(related.primaryKey).where({ [related.primaryKey]: Array.from(ids) });
|
|
4796
|
+
existing.set(
|
|
4797
|
+
related,
|
|
4798
|
+
new Set(rows.map((row) => String(row.getPrimaryKey())))
|
|
4799
|
+
);
|
|
4800
|
+
})
|
|
4801
|
+
);
|
|
4802
|
+
belongsTo.forEach(([name, , related]) => {
|
|
4803
|
+
const found = existing.get(related);
|
|
4804
|
+
linkage.forEach((owner) => {
|
|
4805
|
+
const id = owner[name];
|
|
4806
|
+
if (typeof id === "string" && !found?.has(id)) {
|
|
4807
|
+
owner[name] = null;
|
|
4808
|
+
}
|
|
4809
|
+
});
|
|
4810
|
+
});
|
|
4811
|
+
}
|
|
4812
|
+
|
|
4813
|
+
// src/packages/serializer/utils/include-tree.ts
|
|
4814
|
+
function createIncludeTree(paths = []) {
|
|
4815
|
+
const tree = /* @__PURE__ */ new Map();
|
|
4816
|
+
paths.forEach((path) => {
|
|
4817
|
+
path.split(".").filter(Boolean).reduce((node, name) => {
|
|
4818
|
+
let child = node.get(name);
|
|
4819
|
+
if (!child) {
|
|
4820
|
+
child = /* @__PURE__ */ new Map();
|
|
4821
|
+
node.set(name, child);
|
|
4822
|
+
}
|
|
4823
|
+
return child;
|
|
4824
|
+
}, tree);
|
|
4825
|
+
});
|
|
4826
|
+
return tree;
|
|
4827
|
+
}
|
|
4828
|
+
function enumerateIncludePaths(model, names, depth, serializerFor = (related) => related.serializer) {
|
|
4829
|
+
if (depth < 1) {
|
|
4830
|
+
return [];
|
|
4831
|
+
}
|
|
4832
|
+
return names.reduce((paths, name) => {
|
|
4833
|
+
const opts = model.relationshipFor(name);
|
|
4834
|
+
if (!opts) {
|
|
4835
|
+
return paths;
|
|
4836
|
+
}
|
|
4837
|
+
const { model: related } = opts;
|
|
4838
|
+
const serializer = serializerFor(related);
|
|
4839
|
+
if (!serializer) {
|
|
4840
|
+
return [...paths, name];
|
|
4841
|
+
}
|
|
4842
|
+
const { hasOne, hasMany } = serializer;
|
|
4843
|
+
return [
|
|
4844
|
+
...paths,
|
|
4845
|
+
name,
|
|
4846
|
+
...enumerateIncludePaths(
|
|
4847
|
+
related,
|
|
4848
|
+
[...hasOne, ...hasMany],
|
|
4849
|
+
depth - 1,
|
|
4850
|
+
serializerFor
|
|
4851
|
+
).map((path) => `${name}.${path}`)
|
|
4852
|
+
];
|
|
4853
|
+
}, []);
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4856
|
+
// src/packages/serializer/index.ts
|
|
4432
4857
|
var Serializer = class {
|
|
4433
4858
|
/**
|
|
4434
4859
|
* An Array of the `hasOne` or `belongsTo` relationships on a Serializer
|
|
@@ -4515,9 +4940,17 @@ var Serializer = class {
|
|
|
4515
4940
|
* the resource and relationship objects in the returned [JSON API](
|
|
4516
4941
|
* http://jsonapi.org) document object.
|
|
4517
4942
|
*
|
|
4518
|
-
* @param {Array} options.include - An array of
|
|
4519
|
-
*
|
|
4520
|
-
* the returned [JSON API](http://jsonapi.org)
|
|
4943
|
+
* @param {Array} options.include - An array of relationship paths (e.g.
|
|
4944
|
+
* `'comments'` or `'comments.user'`) whose resources should be added to the
|
|
4945
|
+
* top level included object of the returned [JSON API](http://jsonapi.org)
|
|
4946
|
+
* document object. Intermediate resources of a nested path are included too.
|
|
4947
|
+
*
|
|
4948
|
+
* @param {String} options.namespace - The namespace of the request, i.e. of
|
|
4949
|
+
* the Controller handling it. Every link in the document is built in it, and
|
|
4950
|
+
* included resources are serialized by their Serializer in it (falling back
|
|
4951
|
+
* to the root). Defaults to this Serializer's namespace — which is the root
|
|
4952
|
+
* one when a namespaced Controller has no Serializer of its own, so the
|
|
4953
|
+
* Controller passes its namespace explicitly.
|
|
4521
4954
|
*
|
|
4522
4955
|
* @return {Promise} Resolves with a [JSON API](http://jsonapi.org) document
|
|
4523
4956
|
* object.
|
|
@@ -4528,38 +4961,61 @@ var Serializer = class {
|
|
|
4528
4961
|
data,
|
|
4529
4962
|
links,
|
|
4530
4963
|
domain,
|
|
4531
|
-
include
|
|
4964
|
+
include,
|
|
4965
|
+
namespace = this.namespace
|
|
4532
4966
|
}) {
|
|
4533
|
-
|
|
4534
|
-
const
|
|
4967
|
+
const tree = createIncludeTree(include);
|
|
4968
|
+
const related = /* @__PURE__ */ new Map();
|
|
4969
|
+
const included = /* @__PURE__ */ new Map();
|
|
4970
|
+
const names = Array.from(tree.keys());
|
|
4971
|
+
let primary;
|
|
4972
|
+
let serialized;
|
|
4535
4973
|
if (Array.isArray(data)) {
|
|
4536
|
-
|
|
4537
|
-
data
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
)
|
|
4974
|
+
primary = await Promise.all(
|
|
4975
|
+
data.map(
|
|
4976
|
+
(item) => this.formatOne({
|
|
4977
|
+
item,
|
|
4978
|
+
domain,
|
|
4979
|
+
related,
|
|
4980
|
+
namespace,
|
|
4981
|
+
include: names
|
|
4982
|
+
})
|
|
4546
4983
|
)
|
|
4547
|
-
|
|
4984
|
+
);
|
|
4985
|
+
serialized = { data: primary };
|
|
4548
4986
|
} else {
|
|
4549
|
-
|
|
4550
|
-
|
|
4987
|
+
const resource2 = await this.formatOne({
|
|
4988
|
+
domain,
|
|
4989
|
+
related,
|
|
4990
|
+
namespace,
|
|
4991
|
+
item: data,
|
|
4992
|
+
include: names,
|
|
4993
|
+
links: false
|
|
4994
|
+
});
|
|
4995
|
+
primary = [resource2];
|
|
4996
|
+
serialized = { data: resource2 };
|
|
4997
|
+
}
|
|
4998
|
+
for (const [name, children] of tree) {
|
|
4999
|
+
const opts = this.model.relationshipFor(name);
|
|
5000
|
+
const records = related.get(name);
|
|
5001
|
+
if (opts && records) {
|
|
5002
|
+
await this.addIncluded({
|
|
4551
5003
|
domain,
|
|
4552
|
-
|
|
5004
|
+
records,
|
|
4553
5005
|
included,
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
5006
|
+
namespace,
|
|
5007
|
+
model: opts.model,
|
|
5008
|
+
tree: children
|
|
5009
|
+
});
|
|
5010
|
+
}
|
|
4558
5011
|
}
|
|
4559
|
-
|
|
5012
|
+
primary.forEach((resource2) => {
|
|
5013
|
+
included.delete(resourceKey(resource2));
|
|
5014
|
+
});
|
|
5015
|
+
if (included.size) {
|
|
4560
5016
|
serialized = {
|
|
4561
5017
|
...serialized,
|
|
4562
|
-
included:
|
|
5018
|
+
included: Array.from(included.values())
|
|
4563
5019
|
};
|
|
4564
5020
|
}
|
|
4565
5021
|
return {
|
|
@@ -4574,6 +5030,12 @@ var Serializer = class {
|
|
|
4574
5030
|
* Transform a single Model instance into a [JSON API](http://jsonapi.org)
|
|
4575
5031
|
* resource object.
|
|
4576
5032
|
*
|
|
5033
|
+
* Relationships are serialized in one of two ways. By default each one is
|
|
5034
|
+
* read from the Model instance (for primary data the query has already
|
|
5035
|
+
* loaded them). When `linkage` is given — as it is for included resources,
|
|
5036
|
+
* whose relationships are batch-loaded by `addIncluded()` — the resource
|
|
5037
|
+
* linkage is built from it instead, without touching the database.
|
|
5038
|
+
*
|
|
4577
5039
|
* @method formatOne
|
|
4578
5040
|
*
|
|
4579
5041
|
* @param {Object} options - An options object used for building the returned
|
|
@@ -4590,17 +5052,19 @@ var Serializer = class {
|
|
|
4590
5052
|
* the top level links object or relationship links objects in the returned
|
|
4591
5053
|
* [JSON API](http://jsonapi.org) resource object.
|
|
4592
5054
|
*
|
|
4593
|
-
* @param {Array} options.include - An array of
|
|
4594
|
-
*
|
|
4595
|
-
* a [JSON API](http://jsonapi.org) document object.
|
|
5055
|
+
* @param {Array} options.include - An array of the relationship keys whose
|
|
5056
|
+
* related records should be collected into `options.related`.
|
|
4596
5057
|
*
|
|
4597
|
-
* @param {
|
|
4598
|
-
*
|
|
4599
|
-
* included
|
|
5058
|
+
* @param {Map} options.related - Collects, per relationship key in
|
|
5059
|
+
* `options.include`, the related Model instances that belong in the top
|
|
5060
|
+
* level included object of a [JSON API](http://jsonapi.org) document object.
|
|
4600
5061
|
*
|
|
4601
|
-
* @param {
|
|
4602
|
-
*
|
|
4603
|
-
*
|
|
5062
|
+
* @param {Object} options.linkage - Pre-loaded resource linkage (related
|
|
5063
|
+
* primary keys per relationship key) to serialize relationships from.
|
|
5064
|
+
*
|
|
5065
|
+
* @param {String} options.namespace - The namespace to build links in.
|
|
5066
|
+
* Defaults to this Serializer's; included resources pass the namespace of
|
|
5067
|
+
* the request, so every link in a document points into the same namespace.
|
|
4604
5068
|
*
|
|
4605
5069
|
* @return {Promise} Resolves with a [JSON API](http://jsonapi.org) resource
|
|
4606
5070
|
* object.
|
|
@@ -4611,13 +5075,15 @@ var Serializer = class {
|
|
|
4611
5075
|
item,
|
|
4612
5076
|
links,
|
|
4613
5077
|
domain,
|
|
4614
|
-
include,
|
|
4615
|
-
|
|
4616
|
-
|
|
5078
|
+
include = [],
|
|
5079
|
+
related,
|
|
5080
|
+
linkage,
|
|
5081
|
+
namespace = this.namespace
|
|
4617
5082
|
}) {
|
|
4618
5083
|
const { resourceName: type } = item;
|
|
4619
5084
|
const id = String(item.getPrimaryKey());
|
|
4620
|
-
|
|
5085
|
+
const names = [...this.hasOne, ...this.hasMany];
|
|
5086
|
+
let relationships;
|
|
4621
5087
|
const attributes = dasherizeKeys(
|
|
4622
5088
|
item.getAttributes(
|
|
4623
5089
|
...Object.keys(item.rawColumnData).filter(
|
|
@@ -4630,34 +5096,41 @@ var Serializer = class {
|
|
|
4630
5096
|
type,
|
|
4631
5097
|
attributes
|
|
4632
5098
|
};
|
|
4633
|
-
if (
|
|
5099
|
+
if (linkage) {
|
|
5100
|
+
relationships = names.reduce(
|
|
5101
|
+
(hash, name) => ({
|
|
5102
|
+
...hash,
|
|
5103
|
+
[(0, import_inflection14.dasherize)(underscore(name))]: this.formatLinkage(
|
|
5104
|
+
domain,
|
|
5105
|
+
this.model.relationshipFor(name)?.model.resourceName,
|
|
5106
|
+
linkage[name],
|
|
5107
|
+
namespace
|
|
5108
|
+
)
|
|
5109
|
+
}),
|
|
5110
|
+
{}
|
|
5111
|
+
);
|
|
5112
|
+
} else {
|
|
5113
|
+
const collect = (name, records) => {
|
|
5114
|
+
if (related && include.includes(name)) {
|
|
5115
|
+
related.set(name, [...related.get(name) || [], ...records]);
|
|
5116
|
+
}
|
|
5117
|
+
};
|
|
4634
5118
|
relationships = await promiseHash(
|
|
4635
|
-
|
|
5119
|
+
names.reduce(
|
|
4636
5120
|
(hash, name) => ({
|
|
4637
5121
|
...hash,
|
|
4638
|
-
[(0,
|
|
4639
|
-
const
|
|
4640
|
-
if (Array.isArray(
|
|
5122
|
+
[(0, import_inflection14.dasherize)(underscore(name))]: (async () => {
|
|
5123
|
+
const value = await Reflect.get(item, name);
|
|
5124
|
+
if (Array.isArray(value)) {
|
|
5125
|
+
collect(name, value);
|
|
4641
5126
|
return {
|
|
4642
|
-
data:
|
|
4643
|
-
|
|
4644
|
-
const { data: relatedData } = await this.formatRelationship({
|
|
4645
|
-
domain,
|
|
4646
|
-
included,
|
|
4647
|
-
item: relatedItem,
|
|
4648
|
-
include: include.includes(name)
|
|
4649
|
-
});
|
|
4650
|
-
return relatedData;
|
|
4651
|
-
})
|
|
5127
|
+
data: value.map(
|
|
5128
|
+
(relatedItem) => this.formatRelationship(relatedItem, domain, namespace).data
|
|
4652
5129
|
)
|
|
4653
5130
|
};
|
|
4654
|
-
} else if (
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
included,
|
|
4658
|
-
item: related,
|
|
4659
|
-
include: include.includes(name)
|
|
4660
|
-
});
|
|
5131
|
+
} else if (value && value.id != null) {
|
|
5132
|
+
collect(name, [value]);
|
|
5133
|
+
return this.formatRelationship(value, domain, namespace);
|
|
4661
5134
|
}
|
|
4662
5135
|
return {
|
|
4663
5136
|
data: null
|
|
@@ -4672,16 +5145,9 @@ var Serializer = class {
|
|
|
4672
5145
|
serialized.relationships = relationships;
|
|
4673
5146
|
}
|
|
4674
5147
|
if (links || typeof links !== "boolean") {
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
self: `${domain}/${namespace}/${type}/${id}`
|
|
4679
|
-
};
|
|
4680
|
-
} else {
|
|
4681
|
-
serialized.links = {
|
|
4682
|
-
self: `${domain}/${type}/${id}`
|
|
4683
|
-
};
|
|
4684
|
-
}
|
|
5148
|
+
serialized.links = {
|
|
5149
|
+
self: this.linkFor(domain, type, id, namespace)
|
|
5150
|
+
};
|
|
4685
5151
|
}
|
|
4686
5152
|
return serialized;
|
|
4687
5153
|
}
|
|
@@ -4691,70 +5157,167 @@ var Serializer = class {
|
|
|
4691
5157
|
*
|
|
4692
5158
|
* @method formatRelationship
|
|
4693
5159
|
*
|
|
4694
|
-
* @param {
|
|
5160
|
+
* @param {Model} item - The Model instance to transform into the returned
|
|
4695
5161
|
* [JSON API](http://jsonapi.org) relationship object.
|
|
4696
5162
|
*
|
|
4697
|
-
* @param {
|
|
5163
|
+
* @param {String} domain - A string used to build links included in the
|
|
4698
5164
|
* returned [JSON API](http://jsonapi.org) relationship object.
|
|
4699
5165
|
*
|
|
4700
|
-
* @
|
|
4701
|
-
* the returned [JSON API](http://jsonapi.org) relationship object.
|
|
5166
|
+
* @return {Object} A [JSON API](http://jsonapi.org) relationship object.
|
|
4702
5167
|
*
|
|
4703
|
-
* @
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
5168
|
+
* @private
|
|
5169
|
+
*/
|
|
5170
|
+
formatRelationship(item, domain, namespace = this.namespace) {
|
|
5171
|
+
return this.formatLinkage(
|
|
5172
|
+
domain,
|
|
5173
|
+
item.resourceName,
|
|
5174
|
+
String(item.getPrimaryKey()),
|
|
5175
|
+
namespace
|
|
5176
|
+
);
|
|
5177
|
+
}
|
|
5178
|
+
/**
|
|
5179
|
+
* Build a [JSON API](http://jsonapi.org) relationship object from resource
|
|
5180
|
+
* linkage, in the same shape `formatRelationship()` produces from Model
|
|
5181
|
+
* instances: to-one relationships carry a `links` object, to-many ones only
|
|
5182
|
+
* `data`, and a missing to-one relationship is `{ data: null }`.
|
|
4713
5183
|
*
|
|
5184
|
+
* @method formatLinkage
|
|
4714
5185
|
* @private
|
|
4715
5186
|
*/
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
included
|
|
4721
|
-
}) {
|
|
4722
|
-
const { namespace } = this;
|
|
4723
|
-
const {
|
|
4724
|
-
resourceName: type,
|
|
4725
|
-
constructor: { serializer }
|
|
4726
|
-
} = item;
|
|
4727
|
-
const id = String(item.getPrimaryKey());
|
|
4728
|
-
let links;
|
|
4729
|
-
if (namespace) {
|
|
4730
|
-
links = {
|
|
4731
|
-
self: `${domain}/${namespace}/${type}/${id}`
|
|
4732
|
-
};
|
|
4733
|
-
} else {
|
|
4734
|
-
links = {
|
|
4735
|
-
self: `${domain}/${type}/${id}`
|
|
5187
|
+
formatLinkage(domain, type, linkage, namespace = this.namespace) {
|
|
5188
|
+
if (Array.isArray(linkage)) {
|
|
5189
|
+
return {
|
|
5190
|
+
data: type ? linkage.map((id) => ({ id, type })) : []
|
|
4736
5191
|
};
|
|
4737
5192
|
}
|
|
4738
|
-
if (
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
domain,
|
|
4743
|
-
include: [],
|
|
4744
|
-
included: [],
|
|
4745
|
-
formatRelationships: false
|
|
4746
|
-
})
|
|
4747
|
-
);
|
|
5193
|
+
if (linkage == null || !type) {
|
|
5194
|
+
return {
|
|
5195
|
+
data: null
|
|
5196
|
+
};
|
|
4748
5197
|
}
|
|
4749
5198
|
return {
|
|
4750
5199
|
data: {
|
|
4751
|
-
id,
|
|
5200
|
+
id: linkage,
|
|
4752
5201
|
type
|
|
4753
5202
|
},
|
|
4754
|
-
links
|
|
5203
|
+
links: {
|
|
5204
|
+
self: this.linkFor(domain, type, linkage, namespace)
|
|
5205
|
+
}
|
|
4755
5206
|
};
|
|
4756
5207
|
}
|
|
5208
|
+
/**
|
|
5209
|
+
* Add `records` (instances of `model`) to `included` as resource objects,
|
|
5210
|
+
* then recurse into the relationships named in `tree`. Each is serialized by
|
|
5211
|
+
* `model`'s Serializer in this Serializer's namespace (`serializerFor()`), so
|
|
5212
|
+
* `/admin/posts?include=comments` uses `AdminCommentsSerializer` when there
|
|
5213
|
+
* is one and `CommentsSerializer` otherwise. The relationships of every level
|
|
5214
|
+
* are batch-loaded with one query per relationship, not one per record.
|
|
5215
|
+
*
|
|
5216
|
+
* @method addIncluded
|
|
5217
|
+
* @private
|
|
5218
|
+
*/
|
|
5219
|
+
async addIncluded({
|
|
5220
|
+
model,
|
|
5221
|
+
records,
|
|
5222
|
+
tree,
|
|
5223
|
+
domain,
|
|
5224
|
+
included,
|
|
5225
|
+
namespace
|
|
5226
|
+
}) {
|
|
5227
|
+
const serializer = this.serializerFor(model, namespace);
|
|
5228
|
+
const unique = Array.from(
|
|
5229
|
+
new Map(records.map((record) => [record.getPrimaryKey(), record])).values()
|
|
5230
|
+
);
|
|
5231
|
+
if (!unique.length) {
|
|
5232
|
+
return;
|
|
5233
|
+
}
|
|
5234
|
+
const names = [...serializer.hasOne, ...serializer.hasMany];
|
|
5235
|
+
const linkage = await loadLinkage(model, unique, names);
|
|
5236
|
+
for (const item of unique) {
|
|
5237
|
+
const id = String(item.getPrimaryKey());
|
|
5238
|
+
const key = resourceKey({ id, type: item.resourceName });
|
|
5239
|
+
if (!included.has(key)) {
|
|
5240
|
+
included.set(
|
|
5241
|
+
key,
|
|
5242
|
+
await serializer.formatOne({
|
|
5243
|
+
item,
|
|
5244
|
+
domain,
|
|
5245
|
+
linkage: linkage.get(id),
|
|
5246
|
+
namespace
|
|
5247
|
+
})
|
|
5248
|
+
);
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
for (const [name, children] of tree) {
|
|
5252
|
+
const opts = model.relationshipFor(name);
|
|
5253
|
+
if (!opts || !names.includes(name)) {
|
|
5254
|
+
continue;
|
|
5255
|
+
}
|
|
5256
|
+
const { model: next } = opts;
|
|
5257
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5258
|
+
linkage.forEach(({ [name]: value }) => {
|
|
5259
|
+
(Array.isArray(value) ? value : [value]).forEach((relatedId) => {
|
|
5260
|
+
if (relatedId != null) {
|
|
5261
|
+
ids.add(relatedId);
|
|
5262
|
+
}
|
|
5263
|
+
});
|
|
5264
|
+
});
|
|
5265
|
+
if (ids.size) {
|
|
5266
|
+
const attributes = this.serializerFor(
|
|
5267
|
+
next,
|
|
5268
|
+
namespace
|
|
5269
|
+
).attributes.filter((attr) => next.attributeNames.includes(attr));
|
|
5270
|
+
const nextRecords = await next.select(next.primaryKey, ...attributes).where({ [next.primaryKey]: Array.from(ids) });
|
|
5271
|
+
await this.addIncluded({
|
|
5272
|
+
domain,
|
|
5273
|
+
included,
|
|
5274
|
+
namespace,
|
|
5275
|
+
model: next,
|
|
5276
|
+
records: nextRecords,
|
|
5277
|
+
tree: children
|
|
5278
|
+
});
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
/**
|
|
5283
|
+
* Resolve the Serializer for `model` in `namespace` (this Serializer's by
|
|
5284
|
+
* default), the way a namespaced Controller resolves its own:
|
|
5285
|
+
* `admin/comments` if it exists, otherwise the closest ancestor namespace's,
|
|
5286
|
+
* down to the root `comments` Serializer. Falls back to `model.serializer`
|
|
5287
|
+
* when this Serializer was not created by an application (e.g. in
|
|
5288
|
+
* isolation).
|
|
5289
|
+
*
|
|
5290
|
+
* Pass the request's namespace when there is one: a Serializer's own
|
|
5291
|
+
* namespace is the root one whenever it is a namespaced Controller's
|
|
5292
|
+
* fallback.
|
|
5293
|
+
*
|
|
5294
|
+
* @method serializerFor
|
|
5295
|
+
* @private
|
|
5296
|
+
*/
|
|
5297
|
+
serializerFor(model, namespace = this.namespace) {
|
|
5298
|
+
const { serializers } = this;
|
|
5299
|
+
if (serializers) {
|
|
5300
|
+
const key = import_path3.posix.join(namespace || ".", model.resourceName);
|
|
5301
|
+
const serializer = serializers.get(key) || closestAncestor(serializers, key);
|
|
5302
|
+
if (serializer) {
|
|
5303
|
+
return serializer;
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
return model.serializer;
|
|
5307
|
+
}
|
|
5308
|
+
/**
|
|
5309
|
+
* @private
|
|
5310
|
+
*/
|
|
5311
|
+
linkFor(domain, type, id, namespace = this.namespace) {
|
|
5312
|
+
if (namespace) {
|
|
5313
|
+
return `${domain}/${namespace}/${type}/${id}`;
|
|
5314
|
+
}
|
|
5315
|
+
return `${domain}/${type}/${id}`;
|
|
5316
|
+
}
|
|
4757
5317
|
};
|
|
5318
|
+
function resourceKey({ id, type }) {
|
|
5319
|
+
return `${type}:${id}`;
|
|
5320
|
+
}
|
|
4758
5321
|
var serializer_default = Serializer;
|
|
4759
5322
|
|
|
4760
5323
|
// src/packages/config/index.ts
|
|
@@ -4953,7 +5516,10 @@ function resource(action) {
|
|
|
4953
5516
|
data,
|
|
4954
5517
|
links,
|
|
4955
5518
|
domain,
|
|
4956
|
-
include
|
|
5519
|
+
include,
|
|
5520
|
+
// The request's namespace, not the serializer's: a namespaced
|
|
5521
|
+
// controller without its own serializer is given the root one.
|
|
5522
|
+
namespace
|
|
4957
5523
|
});
|
|
4958
5524
|
}
|
|
4959
5525
|
return data;
|
|
@@ -5034,6 +5600,7 @@ var ParameterTypeError = class extends TypeError {
|
|
|
5034
5600
|
Expected type '${type || "undefined"}' for parameter '${path}' but got
|
|
5035
5601
|
'${actual}'.
|
|
5036
5602
|
`);
|
|
5603
|
+
this.source = sourceFor(path);
|
|
5037
5604
|
}
|
|
5038
5605
|
};
|
|
5039
5606
|
var parameter_type_error_default = createServerError(ParameterTypeError, 400);
|
|
@@ -5046,6 +5613,7 @@ var ParameterValueError = class extends TypeError {
|
|
|
5046
5613
|
[${param.size ? Array.from(param.values()).join(", ") : ""}] but got
|
|
5047
5614
|
${String(actual)}.
|
|
5048
5615
|
`);
|
|
5616
|
+
this.source = sourceFor(param.path);
|
|
5049
5617
|
}
|
|
5050
5618
|
};
|
|
5051
5619
|
var parameter_value_error_default = createServerError(ParameterValueError, 400);
|
|
@@ -5054,6 +5622,7 @@ var parameter_value_error_default = createServerError(ParameterValueError, 400);
|
|
|
5054
5622
|
var InvalidParameterError = class extends TypeError {
|
|
5055
5623
|
constructor(path) {
|
|
5056
5624
|
super(`'${path}' is not a valid parameter for this resource.`);
|
|
5625
|
+
this.source = sourceFor(path);
|
|
5057
5626
|
}
|
|
5058
5627
|
};
|
|
5059
5628
|
var invalid_parameter_error_default = createServerError(InvalidParameterError, 400);
|
|
@@ -5069,6 +5638,7 @@ var ResourceMismatchError = class extends TypeError {
|
|
|
5069
5638
|
Expected '${String(expected)}' for parameter '${path}' but got
|
|
5070
5639
|
${String(normalized)}.
|
|
5071
5640
|
`);
|
|
5641
|
+
this.source = sourceFor(path);
|
|
5072
5642
|
}
|
|
5073
5643
|
};
|
|
5074
5644
|
var resource_mismatch_error_default = createServerError(ResourceMismatchError, 409);
|
|
@@ -5077,14 +5647,34 @@ var resource_mismatch_error_default = createServerError(ResourceMismatchError, 4
|
|
|
5077
5647
|
var ParameterRequiredError = class extends TypeError {
|
|
5078
5648
|
constructor(path) {
|
|
5079
5649
|
super(`Missing required parameter '${path}'.`);
|
|
5650
|
+
this.source = sourceFor(path);
|
|
5080
5651
|
}
|
|
5081
5652
|
};
|
|
5082
5653
|
var parameter_required_error_default = createServerError(ParameterRequiredError, 400);
|
|
5083
5654
|
|
|
5655
|
+
// src/packages/router/route/params/errors/client-generated-id-error.ts
|
|
5656
|
+
var ClientGeneratedIdError = class extends TypeError {
|
|
5657
|
+
constructor() {
|
|
5658
|
+
super("Client-generated IDs are not supported for this resource.");
|
|
5659
|
+
this.source = sourceFor("data.id");
|
|
5660
|
+
}
|
|
5661
|
+
};
|
|
5662
|
+
var client_generated_id_error_default = createServerError(ClientGeneratedIdError, 403);
|
|
5663
|
+
|
|
5664
|
+
// src/packages/router/route/params/errors/forbidden-parameter-error.ts
|
|
5665
|
+
var ForbiddenParameterError = class extends TypeError {
|
|
5666
|
+
constructor(path) {
|
|
5667
|
+
super(`Setting '${path}' is not supported for this resource.`);
|
|
5668
|
+
this.source = sourceFor(path);
|
|
5669
|
+
}
|
|
5670
|
+
};
|
|
5671
|
+
var forbidden_parameter_error_default = createServerError(ForbiddenParameterError, 403);
|
|
5672
|
+
|
|
5084
5673
|
// src/packages/router/route/params/errors/parameter-not-nullable-error.ts
|
|
5085
5674
|
var ParameterNotNullableError = class extends TypeError {
|
|
5086
5675
|
constructor({ path }) {
|
|
5087
5676
|
super(`Parameter '${path}' is not nullable.`);
|
|
5677
|
+
this.source = sourceFor(path);
|
|
5088
5678
|
}
|
|
5089
5679
|
};
|
|
5090
5680
|
var parameter_not_nullable_error_default = createServerError(ParameterNotNullableError, 400);
|
|
@@ -5213,13 +5803,14 @@ var Parameter = class extends set_default {
|
|
|
5213
5803
|
path,
|
|
5214
5804
|
type,
|
|
5215
5805
|
required: Boolean(required),
|
|
5216
|
-
sanitize: Boolean(sanitize)
|
|
5806
|
+
sanitize: Boolean(sanitize),
|
|
5807
|
+
restricted: values !== void 0
|
|
5217
5808
|
});
|
|
5218
5809
|
this.freeze();
|
|
5219
5810
|
}
|
|
5220
5811
|
validate(value) {
|
|
5221
5812
|
validateType2(this, value);
|
|
5222
|
-
if (this.
|
|
5813
|
+
if (this.restricted) {
|
|
5223
5814
|
return validateValue(this, value);
|
|
5224
5815
|
}
|
|
5225
5816
|
return value;
|
|
@@ -5238,6 +5829,17 @@ function getURLParams(dynamicSegments) {
|
|
|
5238
5829
|
]);
|
|
5239
5830
|
}
|
|
5240
5831
|
|
|
5832
|
+
// src/packages/router/route/params/parameter/forbidden-parameter.ts
|
|
5833
|
+
var ForbiddenParameter = class extends parameter_default {
|
|
5834
|
+
constructor(path) {
|
|
5835
|
+
super({ path });
|
|
5836
|
+
}
|
|
5837
|
+
validate() {
|
|
5838
|
+
throw new forbidden_parameter_error_default(this.path);
|
|
5839
|
+
}
|
|
5840
|
+
};
|
|
5841
|
+
var forbidden_parameter_default = ForbiddenParameter;
|
|
5842
|
+
|
|
5241
5843
|
// src/packages/router/route/params/utils/get-data-params.ts
|
|
5242
5844
|
function getIDParam({ model }) {
|
|
5243
5845
|
const primaryKeyColumn = model.columnFor(model.primaryKey);
|
|
@@ -5290,16 +5892,48 @@ function getRelationshipsParam({
|
|
|
5290
5892
|
model,
|
|
5291
5893
|
params
|
|
5292
5894
|
}) {
|
|
5895
|
+
const forbidden = Object.keys(model.relationships).filter((key) => !params.includes(key)).map((key) => [
|
|
5896
|
+
key,
|
|
5897
|
+
new forbidden_parameter_default(`data.relationships.${key}`)
|
|
5898
|
+
]);
|
|
5293
5899
|
return [
|
|
5294
5900
|
"relationships",
|
|
5295
5901
|
new parameter_group_default(
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5902
|
+
[
|
|
5903
|
+
...params.reduce((group, param) => {
|
|
5904
|
+
const path = `data.relationships.${param}`;
|
|
5905
|
+
const opts = model.relationshipFor(param);
|
|
5906
|
+
if (!opts) {
|
|
5907
|
+
return group;
|
|
5908
|
+
}
|
|
5909
|
+
if (opts.type === "hasMany") {
|
|
5910
|
+
return [
|
|
5911
|
+
...group,
|
|
5912
|
+
[
|
|
5913
|
+
param,
|
|
5914
|
+
new parameter_group_default(
|
|
5915
|
+
[
|
|
5916
|
+
[
|
|
5917
|
+
"data",
|
|
5918
|
+
new parameter_default({
|
|
5919
|
+
type: "array",
|
|
5920
|
+
path: `${path}.data`,
|
|
5921
|
+
required: true
|
|
5922
|
+
})
|
|
5923
|
+
]
|
|
5924
|
+
],
|
|
5925
|
+
{
|
|
5926
|
+
path
|
|
5927
|
+
}
|
|
5928
|
+
)
|
|
5929
|
+
]
|
|
5930
|
+
];
|
|
5931
|
+
}
|
|
5932
|
+
const primaryKeyColumn = opts.model.columnFor(opts.model.primaryKey);
|
|
5933
|
+
let primaryKeyType = "number";
|
|
5934
|
+
if (primaryKeyColumn) {
|
|
5935
|
+
primaryKeyType = typeForColumn(primaryKeyColumn);
|
|
5936
|
+
}
|
|
5303
5937
|
return [
|
|
5304
5938
|
...group,
|
|
5305
5939
|
[
|
|
@@ -5308,11 +5942,32 @@ function getRelationshipsParam({
|
|
|
5308
5942
|
[
|
|
5309
5943
|
[
|
|
5310
5944
|
"data",
|
|
5311
|
-
new
|
|
5312
|
-
|
|
5313
|
-
|
|
5314
|
-
|
|
5315
|
-
|
|
5945
|
+
new parameter_group_default(
|
|
5946
|
+
[
|
|
5947
|
+
[
|
|
5948
|
+
"id",
|
|
5949
|
+
new parameter_default({
|
|
5950
|
+
type: primaryKeyType,
|
|
5951
|
+
path: `${path}.data.id`,
|
|
5952
|
+
required: true
|
|
5953
|
+
})
|
|
5954
|
+
],
|
|
5955
|
+
[
|
|
5956
|
+
"type",
|
|
5957
|
+
new parameter_default({
|
|
5958
|
+
type: "string",
|
|
5959
|
+
path: `${path}.data.type`,
|
|
5960
|
+
values: [opts.model.resourceName],
|
|
5961
|
+
required: true
|
|
5962
|
+
})
|
|
5963
|
+
]
|
|
5964
|
+
],
|
|
5965
|
+
{
|
|
5966
|
+
type: "array",
|
|
5967
|
+
path: `${path}.data`,
|
|
5968
|
+
required: true
|
|
5969
|
+
}
|
|
5970
|
+
)
|
|
5316
5971
|
]
|
|
5317
5972
|
],
|
|
5318
5973
|
{
|
|
@@ -5321,55 +5976,9 @@ function getRelationshipsParam({
|
|
|
5321
5976
|
)
|
|
5322
5977
|
]
|
|
5323
5978
|
];
|
|
5324
|
-
}
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
if (primaryKeyColumn) {
|
|
5328
|
-
primaryKeyType = typeForColumn(primaryKeyColumn);
|
|
5329
|
-
}
|
|
5330
|
-
return [
|
|
5331
|
-
...group,
|
|
5332
|
-
[
|
|
5333
|
-
param,
|
|
5334
|
-
new parameter_group_default(
|
|
5335
|
-
[
|
|
5336
|
-
[
|
|
5337
|
-
"data",
|
|
5338
|
-
new parameter_group_default(
|
|
5339
|
-
[
|
|
5340
|
-
[
|
|
5341
|
-
"id",
|
|
5342
|
-
new parameter_default({
|
|
5343
|
-
type: primaryKeyType,
|
|
5344
|
-
path: `${path}.data.id`,
|
|
5345
|
-
required: true
|
|
5346
|
-
})
|
|
5347
|
-
],
|
|
5348
|
-
[
|
|
5349
|
-
"type",
|
|
5350
|
-
new parameter_default({
|
|
5351
|
-
type: "string",
|
|
5352
|
-
path: `${path}.data.type`,
|
|
5353
|
-
values: [opts.model.resourceName],
|
|
5354
|
-
required: true
|
|
5355
|
-
})
|
|
5356
|
-
]
|
|
5357
|
-
],
|
|
5358
|
-
{
|
|
5359
|
-
type: "array",
|
|
5360
|
-
path: `${path}.data`,
|
|
5361
|
-
required: true
|
|
5362
|
-
}
|
|
5363
|
-
)
|
|
5364
|
-
]
|
|
5365
|
-
],
|
|
5366
|
-
{
|
|
5367
|
-
path
|
|
5368
|
-
}
|
|
5369
|
-
)
|
|
5370
|
-
]
|
|
5371
|
-
];
|
|
5372
|
-
}, []),
|
|
5979
|
+
}, []),
|
|
5980
|
+
...forbidden
|
|
5981
|
+
],
|
|
5373
5982
|
{
|
|
5374
5983
|
path: "data.relationships"
|
|
5375
5984
|
}
|
|
@@ -5477,10 +6086,9 @@ function getFilterParam({ filter }) {
|
|
|
5477
6086
|
)
|
|
5478
6087
|
];
|
|
5479
6088
|
}
|
|
5480
|
-
function getFieldsParam({
|
|
5481
|
-
model,
|
|
5482
|
-
|
|
5483
|
-
}) {
|
|
6089
|
+
function getFieldsParam(controller) {
|
|
6090
|
+
const { model, serializer } = controller;
|
|
6091
|
+
const { hasOne, hasMany, attributes } = serializer;
|
|
5484
6092
|
const relationships = [...hasOne, ...hasMany];
|
|
5485
6093
|
return [
|
|
5486
6094
|
"fields",
|
|
@@ -5509,7 +6117,7 @@ function getFieldsParam({
|
|
|
5509
6117
|
sanitize: true,
|
|
5510
6118
|
values: [
|
|
5511
6119
|
opts.model.primaryKey,
|
|
5512
|
-
...opts.model.
|
|
6120
|
+
...controller.serializerFor(opts.model).attributes
|
|
5513
6121
|
]
|
|
5514
6122
|
})
|
|
5515
6123
|
]
|
|
@@ -5527,16 +6135,30 @@ function getFieldsParam({
|
|
|
5527
6135
|
)
|
|
5528
6136
|
];
|
|
5529
6137
|
}
|
|
5530
|
-
function getIncludeParam({
|
|
5531
|
-
|
|
5532
|
-
}
|
|
6138
|
+
function getIncludeParam(controller) {
|
|
6139
|
+
const { model, maxIncludeDepth, serializer } = controller;
|
|
6140
|
+
const { hasOne, hasMany } = serializer;
|
|
5533
6141
|
const relationships = [...hasOne, ...hasMany];
|
|
5534
6142
|
return [
|
|
5535
6143
|
"include",
|
|
5536
6144
|
new parameter_default({
|
|
5537
6145
|
path: "include",
|
|
5538
6146
|
type: "array",
|
|
5539
|
-
|
|
6147
|
+
// Every top level name stays allowed (as before), plus the nested paths
|
|
6148
|
+
// (`comments.user`) reachable through each related serializer in the
|
|
6149
|
+
// controller's namespace (at every level, even below a root fallback
|
|
6150
|
+
// serializer), down to the controller's `maxIncludeDepth`.
|
|
6151
|
+
values: Array.from(
|
|
6152
|
+
/* @__PURE__ */ new Set([
|
|
6153
|
+
...relationships,
|
|
6154
|
+
...enumerateIncludePaths(
|
|
6155
|
+
model,
|
|
6156
|
+
relationships,
|
|
6157
|
+
maxIncludeDepth,
|
|
6158
|
+
(related) => controller.serializerFor(related)
|
|
6159
|
+
)
|
|
6160
|
+
])
|
|
6161
|
+
)
|
|
5540
6162
|
})
|
|
5541
6163
|
];
|
|
5542
6164
|
}
|
|
@@ -5574,6 +6196,15 @@ function getCollectionQueryParams(controller) {
|
|
|
5574
6196
|
return getCustomParams(controller);
|
|
5575
6197
|
}
|
|
5576
6198
|
|
|
6199
|
+
// src/packages/router/route/params/utils/validate-client-id.ts
|
|
6200
|
+
function validateClientId(params) {
|
|
6201
|
+
const { data } = params;
|
|
6202
|
+
if (data && typeof data === "object" && Reflect.has(data, "id")) {
|
|
6203
|
+
throw new client_generated_id_error_default();
|
|
6204
|
+
}
|
|
6205
|
+
return true;
|
|
6206
|
+
}
|
|
6207
|
+
|
|
5577
6208
|
// src/packages/router/route/params/utils/validate-resource-id.ts
|
|
5578
6209
|
function validateResourceId({
|
|
5579
6210
|
params: {
|
|
@@ -5739,6 +6370,9 @@ var Route = class extends set_default {
|
|
|
5739
6370
|
...req.params,
|
|
5740
6371
|
...this.parseParams(req.url.params)
|
|
5741
6372
|
};
|
|
6373
|
+
if (this.action === "create" && req.method === "POST") {
|
|
6374
|
+
validateClientId(params);
|
|
6375
|
+
}
|
|
5742
6376
|
if (req.method !== "OPTIONS") {
|
|
5743
6377
|
params = this.params.validate(params);
|
|
5744
6378
|
}
|
|
@@ -6051,10 +6685,10 @@ var Router = class extends map_default {
|
|
|
6051
6685
|
var router_default = Router;
|
|
6052
6686
|
|
|
6053
6687
|
// src/packages/loader/utils/bundle-for.ts
|
|
6054
|
-
var
|
|
6688
|
+
var import_path4 = require("path");
|
|
6055
6689
|
|
|
6056
6690
|
// src/packages/loader/utils/format-key.ts
|
|
6057
|
-
var
|
|
6691
|
+
var import_inflection15 = require("inflection");
|
|
6058
6692
|
var NAMESPACE_DELIMITER = /\$-/g;
|
|
6059
6693
|
function formatKey(key, formatter) {
|
|
6060
6694
|
return chain(key).pipe((str) => {
|
|
@@ -6062,7 +6696,7 @@ function formatKey(key, formatter) {
|
|
|
6062
6696
|
return formatter(str);
|
|
6063
6697
|
}
|
|
6064
6698
|
return str;
|
|
6065
|
-
}).pipe(underscore).pipe(
|
|
6699
|
+
}).pipe(underscore).pipe(import_inflection15.dasherize).pipe((str) => str.replace(NAMESPACE_DELIMITER, "/")).value();
|
|
6066
6700
|
}
|
|
6067
6701
|
|
|
6068
6702
|
// src/packages/loader/utils/bundle-for.ts
|
|
@@ -6129,31 +6763,15 @@ function normalize2(manifest) {
|
|
|
6129
6763
|
}
|
|
6130
6764
|
function bundleFor(path) {
|
|
6131
6765
|
const manifest = Reflect.apply(require, null, [
|
|
6132
|
-
(0,
|
|
6766
|
+
(0, import_path4.join)(path, "dist", "bundle")
|
|
6133
6767
|
]);
|
|
6134
6768
|
return chain(manifest).pipe(normalize2).pipe(entries).pipe((pairs) => new map_default(pairs)).value().freeze();
|
|
6135
6769
|
}
|
|
6136
6770
|
|
|
6137
|
-
// src/packages/loader/resolver/utils/closest-ancestor.ts
|
|
6138
|
-
var import_path3 = require("path");
|
|
6139
|
-
function closestAncestor(source, key) {
|
|
6140
|
-
const name = import_path3.posix.basename(key);
|
|
6141
|
-
let namespace = import_path3.posix.dirname(key);
|
|
6142
|
-
if (namespace === ".") {
|
|
6143
|
-
return source.get(name);
|
|
6144
|
-
}
|
|
6145
|
-
namespace = import_path3.posix.dirname(namespace);
|
|
6146
|
-
const ancestor = source.get(import_path3.posix.join(namespace, name));
|
|
6147
|
-
if (ancestor) {
|
|
6148
|
-
return ancestor;
|
|
6149
|
-
}
|
|
6150
|
-
return closestAncestor(source, import_path3.posix.join(import_path3.posix.dirname(namespace), name));
|
|
6151
|
-
}
|
|
6152
|
-
|
|
6153
6771
|
// src/packages/loader/resolver/utils/closest-child.ts
|
|
6154
|
-
var
|
|
6772
|
+
var import_path5 = require("path");
|
|
6155
6773
|
function closestChild(source, key) {
|
|
6156
|
-
const [[, result] = []] = Array.from(source).map(([path, value]) => [
|
|
6774
|
+
const [[, result] = []] = Array.from(source).map(([path, value]) => [import_path5.posix.basename(path), value]).filter(([resource2]) => key === resource2);
|
|
6157
6775
|
return result;
|
|
6158
6776
|
}
|
|
6159
6777
|
|
|
@@ -6176,7 +6794,7 @@ function resolve(group) {
|
|
|
6176
6794
|
}
|
|
6177
6795
|
|
|
6178
6796
|
// src/packages/loader/builder/utils/create-parent-builder.ts
|
|
6179
|
-
var
|
|
6797
|
+
var import_path6 = require("path");
|
|
6180
6798
|
|
|
6181
6799
|
// src/packages/loader/builder/utils/sort-by-namespace.ts
|
|
6182
6800
|
function sortByNamespace([a], [b]) {
|
|
@@ -6197,7 +6815,7 @@ function createParentBuilder(construct) {
|
|
|
6197
6815
|
let grandparent = null;
|
|
6198
6816
|
if (key !== "root") {
|
|
6199
6817
|
const found = result.find((namespace) => {
|
|
6200
|
-
const dirname =
|
|
6818
|
+
const dirname = import_path6.posix.dirname(key);
|
|
6201
6819
|
if (namespace.key === "root") {
|
|
6202
6820
|
return dirname === ".";
|
|
6203
6821
|
}
|
|
@@ -6267,13 +6885,13 @@ function normalizePort(port) {
|
|
|
6267
6885
|
}
|
|
6268
6886
|
|
|
6269
6887
|
// src/packages/application/utils/create-controller.ts
|
|
6270
|
-
var
|
|
6888
|
+
var import_path7 = require("path");
|
|
6271
6889
|
function createController(constructor, opts) {
|
|
6272
6890
|
const { key, store, serializers } = opts;
|
|
6273
|
-
const namespace =
|
|
6891
|
+
const namespace = import_path7.posix.dirname(key).replace(".", "");
|
|
6274
6892
|
let { parent } = opts;
|
|
6275
6893
|
let model = tryCatchSync(
|
|
6276
|
-
() => store.modelFor(
|
|
6894
|
+
() => store.modelFor(import_path7.posix.basename(key))
|
|
6277
6895
|
);
|
|
6278
6896
|
let serializer = serializers.get(key);
|
|
6279
6897
|
if (!model) {
|
|
@@ -6329,13 +6947,13 @@ function createController(constructor, opts) {
|
|
|
6329
6947
|
}
|
|
6330
6948
|
|
|
6331
6949
|
// src/packages/application/utils/create-serializer.ts
|
|
6332
|
-
var
|
|
6950
|
+
var import_path8 = require("path");
|
|
6333
6951
|
function createSerializer(constructor, opts) {
|
|
6334
6952
|
const { key, store } = opts;
|
|
6335
|
-
const namespace =
|
|
6953
|
+
const namespace = import_path8.posix.dirname(key).replace(".", "");
|
|
6336
6954
|
let { parent } = opts;
|
|
6337
6955
|
let model = tryCatchSync(
|
|
6338
|
-
() => store.modelFor(
|
|
6956
|
+
() => store.modelFor(import_path8.posix.basename(key))
|
|
6339
6957
|
);
|
|
6340
6958
|
if (!model) {
|
|
6341
6959
|
model = null;
|
|
@@ -6359,6 +6977,85 @@ function createSerializer(constructor, opts) {
|
|
|
6359
6977
|
return deepFreezeProps(instance, true, "hasOne", "hasMany", "attributes");
|
|
6360
6978
|
}
|
|
6361
6979
|
|
|
6980
|
+
// src/packages/application/utils/validate-namespaced-serializers.ts
|
|
6981
|
+
var import_path9 = require("path");
|
|
6982
|
+
|
|
6983
|
+
// src/errors/namespaced-serializer-missing-error.ts
|
|
6984
|
+
var NamespacedSerializerMissingError = class extends ReferenceError {
|
|
6985
|
+
/**
|
|
6986
|
+
* @param missing - Each missing serializer key (`admin/comments`) with an
|
|
6987
|
+
* example of how it is reached (`admin/posts?include=comments`).
|
|
6988
|
+
*/
|
|
6989
|
+
constructor(missing) {
|
|
6990
|
+
const lines = Array.from(missing).sort(([a], [b]) => a.localeCompare(b)).map(([key, via]) => ` - ${key} (reached from ${via})`);
|
|
6991
|
+
super(
|
|
6992
|
+
[
|
|
6993
|
+
"Missing namespaced serializers. These namespaces set `serializerFallback = false`, so every type their controllers can serialize or include needs a serializer of its own:",
|
|
6994
|
+
...lines
|
|
6995
|
+
].join("\n")
|
|
6996
|
+
);
|
|
6997
|
+
}
|
|
6998
|
+
};
|
|
6999
|
+
var namespaced_serializer_missing_error_default = NamespacedSerializerMissingError;
|
|
7000
|
+
|
|
7001
|
+
// src/packages/application/utils/validate-namespaced-serializers.ts
|
|
7002
|
+
var distance = (via) => {
|
|
7003
|
+
const [, include] = via.split("?include=");
|
|
7004
|
+
return include ? include.split(".").length : 0;
|
|
7005
|
+
};
|
|
7006
|
+
function validateNamespacedSerializers(controllers, serializers) {
|
|
7007
|
+
const missing = /* @__PURE__ */ new Map();
|
|
7008
|
+
controllers.forEach((controller, key) => {
|
|
7009
|
+
const { namespace, model } = controller;
|
|
7010
|
+
if (!namespace || !model) {
|
|
7011
|
+
return;
|
|
7012
|
+
}
|
|
7013
|
+
const application = controllers.get(import_path9.posix.join(namespace, "application"));
|
|
7014
|
+
if (!application || application.serializerFallback !== false) {
|
|
7015
|
+
return;
|
|
7016
|
+
}
|
|
7017
|
+
const require2 = (type, via) => {
|
|
7018
|
+
const serializerKey = import_path9.posix.join(namespace, type);
|
|
7019
|
+
const serializer = serializers.get(serializerKey);
|
|
7020
|
+
if (!serializer) {
|
|
7021
|
+
const known = missing.get(serializerKey);
|
|
7022
|
+
if (known === void 0 || distance(via) < distance(known)) {
|
|
7023
|
+
missing.set(serializerKey, via);
|
|
7024
|
+
}
|
|
7025
|
+
}
|
|
7026
|
+
return serializer;
|
|
7027
|
+
};
|
|
7028
|
+
const own = require2(model.resourceName, key);
|
|
7029
|
+
if (!own) {
|
|
7030
|
+
return;
|
|
7031
|
+
}
|
|
7032
|
+
const depth = Math.max(controller.maxIncludeDepth, 1);
|
|
7033
|
+
let level = [
|
|
7034
|
+
[model, own, []]
|
|
7035
|
+
];
|
|
7036
|
+
for (let current = 1; current <= depth && level.length; current += 1) {
|
|
7037
|
+
const next = [];
|
|
7038
|
+
level.forEach(([parent, serializer, path]) => {
|
|
7039
|
+
[...serializer.hasOne, ...serializer.hasMany].forEach((name) => {
|
|
7040
|
+
const opts = parent.relationshipFor(name);
|
|
7041
|
+
if (!opts) {
|
|
7042
|
+
return;
|
|
7043
|
+
}
|
|
7044
|
+
const nextPath = [...path, name];
|
|
7045
|
+
const related = require2(opts.model.resourceName, `${key}?include=${nextPath.join(".")}`);
|
|
7046
|
+
if (related) {
|
|
7047
|
+
next.push([opts.model, related, nextPath]);
|
|
7048
|
+
}
|
|
7049
|
+
});
|
|
7050
|
+
});
|
|
7051
|
+
level = next;
|
|
7052
|
+
}
|
|
7053
|
+
});
|
|
7054
|
+
if (missing.size) {
|
|
7055
|
+
throw new namespaced_serializer_missing_error_default(missing);
|
|
7056
|
+
}
|
|
7057
|
+
}
|
|
7058
|
+
|
|
6362
7059
|
// src/packages/application/initialize.ts
|
|
6363
7060
|
async function initialize3(app, { path, port, logging, database, server: serverConfig }) {
|
|
6364
7061
|
const load = createLoader(path);
|
|
@@ -6381,6 +7078,14 @@ async function initialize3(app, { path, port, logging, database, server: serverC
|
|
|
6381
7078
|
parent
|
|
6382
7079
|
})
|
|
6383
7080
|
);
|
|
7081
|
+
serializers.forEach((serializer) => {
|
|
7082
|
+
Reflect.defineProperty(serializer, "serializers", {
|
|
7083
|
+
value: serializers,
|
|
7084
|
+
writable: false,
|
|
7085
|
+
enumerable: false,
|
|
7086
|
+
configurable: false
|
|
7087
|
+
});
|
|
7088
|
+
});
|
|
6384
7089
|
models.forEach((model) => {
|
|
6385
7090
|
Reflect.defineProperty(model, "serializer", {
|
|
6386
7091
|
value: closestChild(serializers, model.resourceName),
|
|
@@ -6406,6 +7111,7 @@ async function initialize3(app, { path, port, logging, database, server: serverC
|
|
|
6406
7111
|
configurable: false
|
|
6407
7112
|
});
|
|
6408
7113
|
});
|
|
7114
|
+
validateNamespacedSerializers(controllers, serializers);
|
|
6409
7115
|
const ApplicationController = controllers.get("application");
|
|
6410
7116
|
if (!ApplicationController) {
|
|
6411
7117
|
throw new controller_missing_error_default("application");
|