@antelopejs/interface-data-api 0.4.0 → 0.5.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/components.d.ts +15 -7
- package/dist/components.js +98 -645
- package/dist/components.js.map +1 -1
- package/dist/index.d.ts +41 -36
- package/dist/index.js +94 -92
- package/dist/index.js.map +1 -1
- package/dist/internal/computed.d.ts +6 -0
- package/dist/internal/computed.js +43 -0
- package/dist/internal/computed.js.map +1 -0
- package/dist/internal/controller-types.d.ts +19 -0
- package/dist/internal/controller-types.js +4 -0
- package/dist/internal/controller-types.js.map +1 -0
- package/dist/internal/default-filter.d.ts +4 -0
- package/dist/internal/default-filter.js +19 -0
- package/dist/internal/default-filter.js.map +1 -0
- package/dist/internal/deferred-joined-stream.d.ts +11 -0
- package/dist/internal/deferred-joined-stream.js +112 -0
- package/dist/internal/deferred-joined-stream.js.map +1 -0
- package/dist/internal/field-types.d.ts +183 -0
- package/dist/internal/field-types.js +4 -0
- package/dist/internal/field-types.js.map +1 -0
- package/dist/internal/filters.d.ts +4 -0
- package/dist/internal/filters.js +15 -0
- package/dist/internal/filters.js.map +1 -0
- package/dist/internal/foreign.d.ts +6 -0
- package/dist/internal/foreign.js +116 -0
- package/dist/internal/foreign.js.map +1 -0
- package/dist/internal/guards.d.ts +20 -0
- package/dist/internal/guards.js +74 -0
- package/dist/internal/guards.js.map +1 -0
- package/dist/internal/joined.d.ts +28 -0
- package/dist/internal/joined.js +127 -0
- package/dist/internal/joined.js.map +1 -0
- package/dist/internal/list.d.ts +16 -0
- package/dist/internal/list.js +178 -0
- package/dist/internal/list.js.map +1 -0
- package/dist/internal/meta-records.d.ts +17 -0
- package/dist/internal/meta-records.js +73 -0
- package/dist/internal/meta-records.js.map +1 -0
- package/dist/internal/properties.d.ts +4 -0
- package/dist/internal/properties.js +43 -0
- package/dist/internal/properties.js.map +1 -0
- package/dist/internal/table-reference.d.ts +11 -0
- package/dist/internal/table-reference.js +46 -0
- package/dist/internal/table-reference.js.map +1 -0
- package/dist/internal/tables.d.ts +18 -0
- package/dist/internal/tables.js +37 -0
- package/dist/internal/tables.js.map +1 -0
- package/dist/internal/validation.d.ts +11 -0
- package/dist/internal/validation.js +88 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/metadata.d.ts +27 -228
- package/dist/metadata.js +61 -188
- package/dist/metadata.js.map +1 -1
- package/dist/tests/components/access_control.test.js +20 -20
- package/dist/tests/components/access_control.test.js.map +1 -1
- package/dist/tests/components/computed.test.js +28 -30
- package/dist/tests/components/computed.test.js.map +1 -1
- package/dist/tests/components/cross_instance.test.js +8 -6
- package/dist/tests/components/cross_instance.test.js.map +1 -1
- package/dist/tests/components/deferred_joined.test.js +56 -53
- package/dist/tests/components/deferred_joined.test.js.map +1 -1
- package/dist/tests/components/filter_options.test.js +8 -7
- package/dist/tests/components/filter_options.test.js.map +1 -1
- package/dist/tests/components/foreign_computed.test.js +14 -16
- package/dist/tests/components/foreign_computed.test.js.map +1 -1
- package/dist/tests/components/foreign_joined.test.js +19 -27
- package/dist/tests/components/foreign_joined.test.js.map +1 -1
- package/dist/tests/components/joined.test.js +41 -45
- package/dist/tests/components/joined.test.js.map +1 -1
- package/dist/tests/components/listable.test.js +59 -58
- package/dist/tests/components/listable.test.js.map +1 -1
- package/dist/tests/components/localized_sort.test.js +10 -11
- package/dist/tests/components/localized_sort.test.js.map +1 -1
- package/dist/tests/components/mandatory.test.js +25 -25
- package/dist/tests/components/mandatory.test.js.map +1 -1
- package/dist/tests/components/modifier_key.test.js +19 -18
- package/dist/tests/components/modifier_key.test.js.map +1 -1
- package/dist/tests/components/parse_body.test.d.ts +1 -0
- package/dist/tests/components/parse_body.test.js +75 -0
- package/dist/tests/components/parse_body.test.js.map +1 -0
- package/dist/tests/components/parsed_value.test.js +23 -21
- package/dist/tests/components/parsed_value.test.js.map +1 -1
- package/dist/tests/components/partial_edit.test.js +33 -32
- package/dist/tests/components/partial_edit.test.js.map +1 -1
- package/dist/tests/components/per_action_access.test.js +12 -12
- package/dist/tests/components/per_action_access.test.js.map +1 -1
- package/dist/tests/components/validator.test.js +45 -37
- package/dist/tests/components/validator.test.js.map +1 -1
- package/dist/tests/helpers/other-instance.d.ts +11 -1
- package/dist/tests/helpers/other-instance.js +27 -5
- package/dist/tests/helpers/other-instance.js.map +1 -1
- package/dist/tests/index/get_guard.test.js +6 -6
- package/dist/tests/index/get_guard.test.js.map +1 -1
- package/dist/tests/index/inheritance.test.js +10 -9
- package/dist/tests/index/inheritance.test.js.map +1 -1
- package/dist/tests/index/routes.test.js +40 -44
- package/dist/tests/index/routes.test.js.map +1 -1
- package/dist/tests/integration.test.d.ts +1 -1
- package/dist/tests/integration.test.js +53 -729
- package/dist/tests/integration.test.js.map +1 -1
- package/dist/tests/internal/http.d.ts +23 -0
- package/dist/tests/internal/http.js +35 -0
- package/dist/tests/internal/http.js.map +1 -0
- package/dist/tests/internal/shop-api.d.ts +1 -0
- package/dist/tests/internal/shop-api.js +331 -0
- package/dist/tests/internal/shop-api.js.map +1 -0
- package/dist/tests/internal/shop-schema.d.ts +154 -0
- package/dist/tests/internal/shop-schema.js +387 -0
- package/dist/tests/internal/shop-schema.js.map +1 -0
- package/dist/tests/utils.d.ts +5 -5
- package/dist/tests/utils.js +1 -3
- package/dist/tests/utils.js.map +1 -1
- package/dist/tests/utils.test.js +10 -11
- package/dist/tests/utils.test.js.map +1 -1
- package/docs/3.routes.md +8 -3
- package/docs/5.validators.md +12 -3
- package/docs/8.filters.md +7 -3
- package/package.json +14 -3
- package/skills/data-api-interface/SKILL.md +4 -0
- package/skills/data-api-interface/cases.json +99 -0
package/dist/components.js
CHANGED
|
@@ -1,29 +1,61 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.Validation = exports.Query = exports.Parameters = void 0;
|
|
4
37
|
const interface_api_util_1 = require("@antelopejs/interface-api-util");
|
|
5
38
|
const common_1 = require("@antelopejs/interface-database-decorators/common");
|
|
6
39
|
const decorators_1 = require("@antelopejs/interface-core/decorators");
|
|
7
40
|
const interface_api_1 = require("@antelopejs/interface-api");
|
|
8
|
-
|
|
41
|
+
require("@antelopejs/interface-database");
|
|
9
42
|
const common_2 = require("@antelopejs/interface-database-decorators/modifiers/common");
|
|
10
43
|
const metadata_1 = require("./metadata");
|
|
44
|
+
const computed = __importStar(require("./internal/computed"));
|
|
45
|
+
const filters_1 = require("./internal/filters");
|
|
46
|
+
const foreign = __importStar(require("./internal/foreign"));
|
|
47
|
+
const guards_1 = require("./internal/guards");
|
|
48
|
+
const joined = __importStar(require("./internal/joined"));
|
|
49
|
+
const list = __importStar(require("./internal/list"));
|
|
50
|
+
const properties = __importStar(require("./internal/properties"));
|
|
51
|
+
const tables = __importStar(require("./internal/tables"));
|
|
52
|
+
const validation = __importStar(require("./internal/validation"));
|
|
11
53
|
var Parameters;
|
|
12
54
|
(function (Parameters) {
|
|
13
55
|
function GetOptionOverrides(reqCtx) {
|
|
14
56
|
return reqCtx.dataAPIEntry?.options ?? {};
|
|
15
57
|
}
|
|
16
58
|
Parameters.GetOptionOverrides = GetOptionOverrides;
|
|
17
|
-
function parseFilter(searchVal) {
|
|
18
|
-
const match = searchVal.match(/([^:]+):(.*)/);
|
|
19
|
-
return match ? [match[2], match[1]] : [searchVal, "eq"];
|
|
20
|
-
}
|
|
21
|
-
function copyFilter(filter) {
|
|
22
|
-
const copies = (0, metadata_1.FilterValuesOf)(filter).map((value) => [
|
|
23
|
-
...value,
|
|
24
|
-
]);
|
|
25
|
-
return copies.length === 1 ? copies[0] : copies;
|
|
26
|
-
}
|
|
27
59
|
/**
|
|
28
60
|
* Extracts request filters and mandatory route filters, with route field
|
|
29
61
|
* keys winning. A field the request filters once (`?filter_status=open`)
|
|
@@ -36,14 +68,14 @@ var Parameters;
|
|
|
36
68
|
for (const filter of Object.keys(meta.filters)) {
|
|
37
69
|
const values = reqCtx.url.searchParams
|
|
38
70
|
.getAll(`filter_${filter}`)
|
|
39
|
-
.map(parseFilter);
|
|
71
|
+
.map(filters_1.parseFilter);
|
|
40
72
|
if (values.length > 0) {
|
|
41
73
|
result[filter] = values.length === 1 ? values[0] : values;
|
|
42
74
|
}
|
|
43
75
|
}
|
|
44
76
|
const overrides = GetOptionOverrides(reqCtx);
|
|
45
77
|
for (const [key, value] of Object.entries(overrides.filters ?? {})) {
|
|
46
|
-
result[key] = copyFilter(value);
|
|
78
|
+
result[key] = (0, filters_1.copyFilter)(value);
|
|
47
79
|
}
|
|
48
80
|
return result;
|
|
49
81
|
}
|
|
@@ -54,29 +86,40 @@ var Parameters;
|
|
|
54
86
|
bool: (val) => val !== "0",
|
|
55
87
|
string: (val) => val,
|
|
56
88
|
};
|
|
89
|
+
function isConverterKey(name) {
|
|
90
|
+
return Object.hasOwn(converters, name);
|
|
91
|
+
}
|
|
92
|
+
function converterOf(name) {
|
|
93
|
+
if (!isConverterKey(name)) {
|
|
94
|
+
throw new TypeError(`Unknown parameter converter "${name}"`);
|
|
95
|
+
}
|
|
96
|
+
return converters[name];
|
|
97
|
+
}
|
|
98
|
+
function extractFromSearchParams(reqCtx, key, extractor) {
|
|
99
|
+
if (extractor.startsWith("multi:")) {
|
|
100
|
+
const name = extractor.substring(6);
|
|
101
|
+
return reqCtx.url.searchParams
|
|
102
|
+
.getAll(key)
|
|
103
|
+
.map((searchVal) => converterOf(name)(searchVal));
|
|
104
|
+
}
|
|
105
|
+
const searchVal = reqCtx.url.searchParams.get(key);
|
|
106
|
+
return searchVal === null ? undefined : converterOf(extractor)(searchVal);
|
|
107
|
+
}
|
|
57
108
|
function ExtractGeneric(reqCtx, meta, dynamic) {
|
|
58
109
|
const overrides = GetOptionOverrides(reqCtx);
|
|
59
110
|
const result = { ...overrides };
|
|
60
111
|
for (const key of Object.keys(dynamic)) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (searchVal !== null) {
|
|
73
|
-
result[key] = converters[extractor](searchVal);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
else {
|
|
78
|
-
result[key] = extractor(reqCtx, meta);
|
|
79
|
-
}
|
|
112
|
+
const extractor = dynamic[key];
|
|
113
|
+
if (key in result && extractor !== ExtractFilters)
|
|
114
|
+
continue;
|
|
115
|
+
if ((0, guards_1.isString)(extractor)) {
|
|
116
|
+
const value = extractFromSearchParams(reqCtx, key, extractor);
|
|
117
|
+
if (value !== undefined)
|
|
118
|
+
result[key] = value;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// SAFETY: key comes from Object.keys(dynamic), typed GenericParams<Partial<T>> (a mapped type over keyof T), so for a type-checked argument it is a keyof T; false only for a stray key on a non-literal argument, which is harmless as the key is only written on result.
|
|
122
|
+
result[key] = extractor(reqCtx, meta);
|
|
80
123
|
}
|
|
81
124
|
}
|
|
82
125
|
return result;
|
|
@@ -101,7 +144,7 @@ var Parameters;
|
|
|
101
144
|
const params = ExtractGeneric(context, (0, metadata_1.GetDataControllerMeta)(this), {
|
|
102
145
|
id: "string",
|
|
103
146
|
});
|
|
104
|
-
(0, interface_api_util_1.assert)(params.id &&
|
|
147
|
+
(0, interface_api_util_1.assert)(params.id && (0, guards_1.isString)(params.id), 400, "Missing id.");
|
|
105
148
|
return params;
|
|
106
149
|
}));
|
|
107
150
|
Parameters.New = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
|
|
@@ -112,7 +155,7 @@ var Parameters;
|
|
|
112
155
|
const params = ExtractGeneric(context, (0, metadata_1.GetDataControllerMeta)(this), {
|
|
113
156
|
id: "string",
|
|
114
157
|
});
|
|
115
|
-
(0, interface_api_util_1.assert)(params.id &&
|
|
158
|
+
(0, interface_api_util_1.assert)(params.id && (0, guards_1.isString)(params.id), 400, "Missing id.");
|
|
116
159
|
return params;
|
|
117
160
|
}));
|
|
118
161
|
Parameters.Delete = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
|
|
@@ -131,394 +174,15 @@ var Query;
|
|
|
131
174
|
}
|
|
132
175
|
Query.GetModel = GetModel;
|
|
133
176
|
function Foreign(db, meta, query, pluck) {
|
|
134
|
-
|
|
135
|
-
if ((0, interface_database_1.IsStream)(query)) {
|
|
136
|
-
for (const [name, field] of Object.entries(meta.fields)) {
|
|
137
|
-
if (!field.foreign || (pluck && !pluck.has(name))) {
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const [table, tableClass, index, multi, pluckField, schemaName, targetJoined,] = field.foreign;
|
|
141
|
-
const other = targetTable(db, { table, tableClass, schemaName });
|
|
142
|
-
const hasTargetJoined = !multi && !!targetJoined?.length;
|
|
143
|
-
if (pluckField) {
|
|
144
|
-
// The join keys of the target's @Joined fields must be fetched too
|
|
145
|
-
// (so the second lookup can resolve them), but they are NOT stored in
|
|
146
|
-
// `foreign[4]`, so they get stripped from the response by
|
|
147
|
-
// `ClearInternal` afterwards.
|
|
148
|
-
const lookupPluck = hasTargetJoined
|
|
149
|
-
? Array.from(new Set([
|
|
150
|
-
...pluckField,
|
|
151
|
-
...targetJoined.map((g) => g.localKey),
|
|
152
|
-
]))
|
|
153
|
-
: pluckField;
|
|
154
|
-
query = query.lookup(other.pluck("_internal", ...lookupPluck), name, index || "_id");
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
query = query.lookup(other, name, index || "_id");
|
|
158
|
-
}
|
|
159
|
-
if (hasTargetJoined) {
|
|
160
|
-
query = applyForeignJoinedStream(db, query, name, targetJoined);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return query;
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
const oldForeign = (obj) => {
|
|
167
|
-
const changedFields = {};
|
|
168
|
-
for (const [name, field] of Object.entries(meta.fields)) {
|
|
169
|
-
if (!field.foreign || (pluck && !pluck.has(name))) {
|
|
170
|
-
continue;
|
|
171
|
-
}
|
|
172
|
-
const [table, tableClass, index, multi, pluckField, schemaName, targetJoined,] = field.foreign;
|
|
173
|
-
const remote = targetTable(db, { table, tableClass, schemaName });
|
|
174
|
-
if (multi) {
|
|
175
|
-
changedFields[name] = obj.key(name)
|
|
176
|
-
.default([])
|
|
177
|
-
.map((val) => {
|
|
178
|
-
let foreignObject = Get(remote, val, index);
|
|
179
|
-
if (pluckField) {
|
|
180
|
-
foreignObject = foreignObject.pluck("_internal", ...pluckField);
|
|
181
|
-
}
|
|
182
|
-
return foreignObject.default(null);
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
let foreignObject = Get(remote, obj.key(name), index);
|
|
187
|
-
if (targetJoined && targetJoined.length > 0) {
|
|
188
|
-
foreignObject = applyForeignJoinedDatum(db, foreignObject, targetJoined);
|
|
189
|
-
}
|
|
190
|
-
changedFields[name] = foreignObject.default(null);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return obj.merge(changedFields);
|
|
194
|
-
};
|
|
195
|
-
return query.do(oldForeign);
|
|
196
|
-
}
|
|
177
|
+
return foreign.Foreign(db, meta, query, pluck);
|
|
197
178
|
}
|
|
198
179
|
Query.Foreign = Foreign;
|
|
199
|
-
/**
|
|
200
|
-
* Re-materializes the `@Joined` fields of a relation target on the looked-up
|
|
201
|
-
* sub-object (Stream/list path). The base `Foreign` lookup only plucks the
|
|
202
|
-
* target's physical columns, so a joined label would come back empty; here we
|
|
203
|
-
* resolve each joined field with a second lookup keyed on the target's
|
|
204
|
-
* (already plucked) local key, then merge it back into the relation object.
|
|
205
|
-
*/
|
|
206
|
-
function applyForeignJoinedStream(db, stream, foreignName, groups) {
|
|
207
|
-
let s = stream;
|
|
208
|
-
for (const group of groups) {
|
|
209
|
-
const remoteTable = targetTable(db, group).pluck("_internal", group.remoteIndex, group.remoteField);
|
|
210
|
-
const tmpKey = `__fjoin_${foreignName}_${group.name}`;
|
|
211
|
-
s = s
|
|
212
|
-
.map((row) => row.merge({
|
|
213
|
-
[tmpKey]: row.key(foreignName).key(group.localKey),
|
|
214
|
-
}))
|
|
215
|
-
.lookup(remoteTable, tmpKey, group.remoteIndex)
|
|
216
|
-
.map((row) => row.merge({
|
|
217
|
-
// For an orphan foreign key this yields `{ [joined]: null }`; it is
|
|
218
|
-
// normalized back to a null relation in `ClearInternal` (the DSL has
|
|
219
|
-
// no value-level conditional to do it here).
|
|
220
|
-
[foreignName]: row.key(foreignName).merge({
|
|
221
|
-
[group.name]: row
|
|
222
|
-
.key(tmpKey)
|
|
223
|
-
.default({})
|
|
224
|
-
.key(group.remoteField)
|
|
225
|
-
.default(null),
|
|
226
|
-
}),
|
|
227
|
-
}))
|
|
228
|
-
.without(tmpKey);
|
|
229
|
-
}
|
|
230
|
-
return s;
|
|
231
|
-
}
|
|
232
|
-
/**
|
|
233
|
-
* Datum/get counterpart of {@link applyForeignJoinedStream}: resolves each of
|
|
234
|
-
* the target's `@Joined` fields with a secondary `Get` and merges it into the
|
|
235
|
-
* relation object.
|
|
236
|
-
*/
|
|
237
|
-
function applyForeignJoinedDatum(db, datum, groups) {
|
|
238
|
-
let d = datum;
|
|
239
|
-
for (const group of groups) {
|
|
240
|
-
// Use the raw table here (not a plucked stream): `Get` relies on
|
|
241
|
-
// `getAll`/`get`, which exist on Selection/Table but not on a Stream.
|
|
242
|
-
const remoteTable = targetTable(db, group);
|
|
243
|
-
d = d.do((row) => row.merge({
|
|
244
|
-
[group.name]: Get(remoteTable, row.key(group.localKey), group.remoteIndex)
|
|
245
|
-
.key(group.remoteField)
|
|
246
|
-
.default(null),
|
|
247
|
-
}));
|
|
248
|
-
}
|
|
249
|
-
return d;
|
|
250
|
-
}
|
|
251
|
-
function collectJoinedGroups(meta) {
|
|
252
|
-
const groups = new Map();
|
|
253
|
-
for (const [name, field] of Object.entries(meta.fields)) {
|
|
254
|
-
if (!field.joined)
|
|
255
|
-
continue;
|
|
256
|
-
const j = field.joined;
|
|
257
|
-
const groupKey = `${j.schemaName ?? ""}|${j.table}|${j.localKey}|${j.remoteIndex}`;
|
|
258
|
-
let group = groups.get(groupKey);
|
|
259
|
-
if (!group) {
|
|
260
|
-
group = {
|
|
261
|
-
table: j.table,
|
|
262
|
-
tableClass: j.tableClass,
|
|
263
|
-
localKey: j.localKey,
|
|
264
|
-
remoteIndex: j.remoteIndex,
|
|
265
|
-
schemaName: j.schemaName,
|
|
266
|
-
fields: [],
|
|
267
|
-
};
|
|
268
|
-
groups.set(groupKey, group);
|
|
269
|
-
}
|
|
270
|
-
group.fields.push({ name, remoteField: j.remoteField });
|
|
271
|
-
}
|
|
272
|
-
return Array.from(groups.values());
|
|
273
|
-
}
|
|
274
|
-
function addAll(target, names) {
|
|
275
|
-
for (const name of names) {
|
|
276
|
-
target.add(name);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Splits the joined fields of `meta` between those {@link List} must
|
|
281
|
-
* materialize before the filter and count (`filter`) and those it only
|
|
282
|
-
* needs after the count (`sort`). Joined fields referenced by neither are
|
|
283
|
-
* left out so the list route can defer their lookups to the returned page.
|
|
284
|
-
*
|
|
285
|
-
* Computed expressions may read joined fields, so materializing a computed
|
|
286
|
-
* field pulls every remaining joined group in with it. Groups whose local
|
|
287
|
-
* key is also a foreign field must resolve before `Foreign` replaces the
|
|
288
|
-
* key with the looked-up record.
|
|
289
|
-
*/
|
|
290
|
-
function splitJoinedFields(meta, sorting, filters) {
|
|
291
|
-
const split = { filter: new Set(), sort: new Set() };
|
|
292
|
-
const filterNames = Object.keys(meta.filters).filter((name) => filters && name in filters);
|
|
293
|
-
const hasComputedFilter = filterNames.some((name) => meta.fields[name]?.computed);
|
|
294
|
-
const sortField = sorting?.[0];
|
|
295
|
-
const hasComputedSort = !!sortField && !!meta.fields[sortField]?.computed;
|
|
296
|
-
for (const group of collectJoinedGroups(meta)) {
|
|
297
|
-
const names = group.fields.map((field) => field.name);
|
|
298
|
-
if (hasComputedFilter ||
|
|
299
|
-
names.some((name) => filterNames.includes(name))) {
|
|
300
|
-
addAll(split.filter, names);
|
|
301
|
-
}
|
|
302
|
-
else if (hasComputedSort ||
|
|
303
|
-
(sortField && names.includes(sortField)) ||
|
|
304
|
-
meta.fields[group.localKey]?.foreign) {
|
|
305
|
-
addAll(split.sort, names);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return split;
|
|
309
|
-
}
|
|
310
|
-
/**
|
|
311
|
-
* Joined field names that {@link List} does not materialize for the given
|
|
312
|
-
* split. The default list route resolves these after pagination so their
|
|
313
|
-
* lookups only run on the returned page.
|
|
314
|
-
*/
|
|
315
|
-
function deferredJoinedFields(meta, split) {
|
|
316
|
-
return new Set(Object.entries(meta.fields)
|
|
317
|
-
.filter(([name, field]) => field.joined && !split.filter.has(name) && !split.sort.has(name))
|
|
318
|
-
.map(([name]) => name));
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Stream returned by {@link List} when the deferred joined fields are not
|
|
322
|
-
* exposed to the caller: it resolves them itself, so consumers that
|
|
323
|
-
* destructure `[query, total]` alone still observe every `@Joined` field.
|
|
324
|
-
* The lookups are appended after `slice`/`nth`, keeping them on the
|
|
325
|
-
* requested page only; bare `count()` calls and field aggregates on
|
|
326
|
-
* non-joined fields skip them (left-join lookups never change the row
|
|
327
|
-
* count nor other fields); `changes()` attaches to the raw stream, since
|
|
328
|
-
* change feeds cannot carry the lookups. Any other operation may observe
|
|
329
|
-
* the joined fields, so it materializes them first.
|
|
330
|
-
*/
|
|
331
|
-
class DeferredJoinedStream extends interface_database_1.Stream {
|
|
332
|
-
}
|
|
333
|
-
function materializeDeferredJoin(stream) {
|
|
334
|
-
const { db, meta, names, raw } = stream.deferredJoin;
|
|
335
|
-
return Joined(db, meta, raw, names);
|
|
336
|
-
}
|
|
337
|
-
function pagedDeferredJoinOperation(name) {
|
|
338
|
-
return function (...args) {
|
|
339
|
-
const { db, meta, names, raw } = this.deferredJoin;
|
|
340
|
-
const paged = raw[name](...args);
|
|
341
|
-
return Joined(db, meta, paged, names);
|
|
342
|
-
};
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Resolves the joined fields through the stream form of {@link Joined}
|
|
346
|
-
* rather than its datum form: only the datum form has no way to drop the
|
|
347
|
-
* temporary key it stages (neither `Datum` nor `ValueProxy` exposes
|
|
348
|
-
* `without`), so it would leave `__joined_orig_*` on the row.
|
|
349
|
-
*/
|
|
350
|
-
function nthDeferredJoinOperation() {
|
|
351
|
-
return function (n) {
|
|
352
|
-
const { db, meta, names, raw } = this.deferredJoin;
|
|
353
|
-
return Joined(db, meta, raw.slice(n, 1), names).nth(0);
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
function rawDeferredJoinOperation(name) {
|
|
357
|
-
return function (...args) {
|
|
358
|
-
return this.deferredJoin.raw[name](...args);
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
function fieldAggregateDeferredJoinOperation(name, bareCallOnRaw) {
|
|
362
|
-
return function (field) {
|
|
363
|
-
const { db, meta, names, raw } = this.deferredJoin;
|
|
364
|
-
if (!field) {
|
|
365
|
-
const bareTarget = bareCallOnRaw ? raw : materializeDeferredJoin(this);
|
|
366
|
-
return bareTarget[name]();
|
|
367
|
-
}
|
|
368
|
-
const target = names.has(field)
|
|
369
|
-
? Joined(db, meta, raw, new Set([field]))
|
|
370
|
-
: raw;
|
|
371
|
-
return target[name](field);
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
function forwardingDeferredJoinOperation(name) {
|
|
375
|
-
return function (...args) {
|
|
376
|
-
return materializeDeferredJoin(this)[name](...args);
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
const DEFERRED_JOIN_OPERATION_FACTORIES = {
|
|
380
|
-
slice: pagedDeferredJoinOperation,
|
|
381
|
-
nth: nthDeferredJoinOperation,
|
|
382
|
-
changes: rawDeferredJoinOperation,
|
|
383
|
-
count: (name) => fieldAggregateDeferredJoinOperation(name, true),
|
|
384
|
-
sum: (name) => fieldAggregateDeferredJoinOperation(name, false),
|
|
385
|
-
avg: (name) => fieldAggregateDeferredJoinOperation(name, false),
|
|
386
|
-
min: (name) => fieldAggregateDeferredJoinOperation(name, false),
|
|
387
|
-
max: (name) => fieldAggregateDeferredJoinOperation(name, false),
|
|
388
|
-
distinct: (name) => fieldAggregateDeferredJoinOperation(name, false),
|
|
389
|
-
};
|
|
390
|
-
const DEFERRED_JOIN_INHERITED_OPERATIONS = new Set(["constructor", "cast"]);
|
|
391
|
-
function wireDeferredJoinedStream() {
|
|
392
|
-
const operationNames = [
|
|
393
|
-
...Object.getOwnPropertyNames(interface_database_1.Stream.prototype),
|
|
394
|
-
"run",
|
|
395
|
-
"cursor",
|
|
396
|
-
"build",
|
|
397
|
-
].filter((name) => !DEFERRED_JOIN_INHERITED_OPERATIONS.has(name));
|
|
398
|
-
for (const name of operationNames) {
|
|
399
|
-
const factory = DEFERRED_JOIN_OPERATION_FACTORIES[name] ??
|
|
400
|
-
forwardingDeferredJoinOperation;
|
|
401
|
-
DeferredJoinedStream.prototype[name] = factory(name);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
wireDeferredJoinedStream();
|
|
405
|
-
/**
|
|
406
|
-
* Wraps `raw` into a {@link DeferredJoinedStream}. The wrapper keeps the
|
|
407
|
-
* raw stages as its own (an unobservable fallback, since every operation
|
|
408
|
-
* is overridden) while `build()` serializes the materialized pipeline, so
|
|
409
|
-
* a wrapper embedded as an argument of another query never leaks rows
|
|
410
|
-
* without the joined fields.
|
|
411
|
-
*/
|
|
412
|
-
function wrapDeferredJoined(db, meta, names, raw) {
|
|
413
|
-
const wrapped = Object.create(DeferredJoinedStream.prototype);
|
|
414
|
-
wrapped.stages = raw.build();
|
|
415
|
-
wrapped.deferredJoin = { db, meta, names, raw };
|
|
416
|
-
return wrapped;
|
|
417
|
-
}
|
|
418
|
-
function listResultTuple(tmpRequest, total, meta, joinedSplit, db, options) {
|
|
419
|
-
const deferredJoined = deferredJoinedFields(meta, joinedSplit);
|
|
420
|
-
if (options?.exposeDeferredJoined || !db || deferredJoined.size === 0) {
|
|
421
|
-
return [tmpRequest, total, deferredJoined];
|
|
422
|
-
}
|
|
423
|
-
return [
|
|
424
|
-
wrapDeferredJoined(db, meta, deferredJoined, tmpRequest),
|
|
425
|
-
total,
|
|
426
|
-
new Set(),
|
|
427
|
-
];
|
|
428
|
-
}
|
|
429
|
-
function targetTable(sourceDb, target) {
|
|
430
|
-
if (!target.schemaName)
|
|
431
|
-
return sourceDb.table(target.table);
|
|
432
|
-
(0, interface_api_util_1.assert)(target.tableClass, 500, `Table "${target.table}" of schema "${target.schemaName}" has no registered table class`);
|
|
433
|
-
return target.tableClass.table();
|
|
434
|
-
}
|
|
435
180
|
function Joined(db, meta, query, only) {
|
|
436
|
-
|
|
437
|
-
if (groups.length === 0) {
|
|
438
|
-
return query;
|
|
439
|
-
}
|
|
440
|
-
const emptyRemote = {};
|
|
441
|
-
const tmpKeyFor = (localKey) => `__joined_orig_${localKey}`;
|
|
442
|
-
if ((0, interface_database_1.IsStream)(query)) {
|
|
443
|
-
let stream = query;
|
|
444
|
-
const tmpKeys = [];
|
|
445
|
-
for (const group of groups) {
|
|
446
|
-
const remoteFields = group.fields.map((f) => f.remoteField);
|
|
447
|
-
const remoteTable = targetTable(db, group).pluck("_internal", group.remoteIndex, ...remoteFields);
|
|
448
|
-
const tmpKey = tmpKeyFor(group.localKey);
|
|
449
|
-
tmpKeys.push(tmpKey);
|
|
450
|
-
stream = stream
|
|
451
|
-
.map((row) => row.merge({ [tmpKey]: row.key(group.localKey) }))
|
|
452
|
-
.lookup(remoteTable, group.localKey, group.remoteIndex)
|
|
453
|
-
.map((row) => {
|
|
454
|
-
const merged = {};
|
|
455
|
-
const remote = row.key(group.localKey).default(emptyRemote);
|
|
456
|
-
for (const f of group.fields) {
|
|
457
|
-
merged[f.name] = remote.key(f.remoteField).default(null);
|
|
458
|
-
}
|
|
459
|
-
merged[group.localKey] = row.key(tmpKey);
|
|
460
|
-
return row.merge(merged);
|
|
461
|
-
});
|
|
462
|
-
}
|
|
463
|
-
return stream.without(...tmpKeys);
|
|
464
|
-
}
|
|
465
|
-
let datum = query;
|
|
466
|
-
for (const group of groups) {
|
|
467
|
-
const remoteFields = group.fields.map((f) => f.remoteField);
|
|
468
|
-
const remoteTable = targetTable(db, group).pluck("_internal", group.remoteIndex, ...remoteFields);
|
|
469
|
-
const tmpKey = tmpKeyFor(group.localKey);
|
|
470
|
-
datum = datum
|
|
471
|
-
.do((row) => row.merge({ [tmpKey]: row.key(group.localKey) }))
|
|
472
|
-
.lookup(remoteTable, group.localKey, group.remoteIndex)
|
|
473
|
-
.do((row) => {
|
|
474
|
-
const merged = {};
|
|
475
|
-
const remote = row.key(group.localKey).default(emptyRemote);
|
|
476
|
-
for (const f of group.fields) {
|
|
477
|
-
merged[f.name] = remote.key(f.remoteField).default(null);
|
|
478
|
-
}
|
|
479
|
-
merged[group.localKey] = row.key(tmpKey);
|
|
480
|
-
return row.merge(merged);
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
return datum;
|
|
181
|
+
return joined.Joined(db, meta, query, only);
|
|
484
182
|
}
|
|
485
183
|
Query.Joined = Joined;
|
|
486
|
-
function collectComputedEntries(meta, only) {
|
|
487
|
-
return Object.entries(meta.fields)
|
|
488
|
-
.filter(([name, field]) => field.computed && (!only || only.has(name)))
|
|
489
|
-
.map(([name, field]) => [name, field.computed]);
|
|
490
|
-
}
|
|
491
|
-
function buildComputedMappers(db, entries) {
|
|
492
|
-
const mergeExpressions = (row) => {
|
|
493
|
-
const merged = {};
|
|
494
|
-
for (const [name, computed] of entries) {
|
|
495
|
-
merged[name] = computed.expr(row, db);
|
|
496
|
-
}
|
|
497
|
-
return row.merge(merged);
|
|
498
|
-
};
|
|
499
|
-
const defaulted = entries.filter(([, computed]) => computed.default !== undefined);
|
|
500
|
-
if (defaulted.length === 0) {
|
|
501
|
-
return [mergeExpressions];
|
|
502
|
-
}
|
|
503
|
-
const applyDefaults = (row) => {
|
|
504
|
-
const merged = {};
|
|
505
|
-
for (const [name, computed] of defaulted) {
|
|
506
|
-
merged[name] = row.key(name).default(computed.default);
|
|
507
|
-
}
|
|
508
|
-
return row.merge(merged);
|
|
509
|
-
};
|
|
510
|
-
return [mergeExpressions, applyDefaults];
|
|
511
|
-
}
|
|
512
184
|
function Computed(db, meta, query, only) {
|
|
513
|
-
|
|
514
|
-
if (entries.length === 0) {
|
|
515
|
-
return query;
|
|
516
|
-
}
|
|
517
|
-
const mappers = buildComputedMappers(db, entries);
|
|
518
|
-
if ((0, interface_database_1.IsStream)(query)) {
|
|
519
|
-
return mappers.reduce((stream, mapper) => stream.map(mapper), query);
|
|
520
|
-
}
|
|
521
|
-
return mappers.reduce((datum, mapper) => datum.do(mapper), query);
|
|
185
|
+
return computed.Computed(db, meta, query, only);
|
|
522
186
|
}
|
|
523
187
|
Query.Computed = Computed;
|
|
524
188
|
async function ReadProperties(obj, meta, dbData, action, onlyList) {
|
|
@@ -539,27 +203,11 @@ var Query;
|
|
|
539
203
|
continue;
|
|
540
204
|
}
|
|
541
205
|
const val = field.desc?.get?.apply(instance);
|
|
542
|
-
res[key] = await (
|
|
206
|
+
res[key] = await ((0, guards_1.isFunction)(val) ? val() : val);
|
|
543
207
|
}
|
|
544
208
|
return res;
|
|
545
209
|
}
|
|
546
210
|
Query.ReadProperties = ReadProperties;
|
|
547
|
-
function writableInBody(writable, bodyData) {
|
|
548
|
-
return {
|
|
549
|
-
props: writable.props.filter(([key]) => Object.hasOwn(bodyData, key)),
|
|
550
|
-
setters: writable.setters.filter(([key]) => Object.hasOwn(bodyData, key)),
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
function writeTargetDefaults(instance, meta, dbData) {
|
|
554
|
-
for (const [key, value] of Object.entries(new meta.target())) {
|
|
555
|
-
if (value !== undefined) {
|
|
556
|
-
instance[key] = value;
|
|
557
|
-
if (key in meta.fields) {
|
|
558
|
-
dbData[meta.fields[key].dbName || key] = value;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
211
|
/**
|
|
564
212
|
* Writes the writable fields of `bodyData` into a database row.
|
|
565
213
|
*
|
|
@@ -571,45 +219,13 @@ var Query;
|
|
|
571
219
|
* `null` clears it.
|
|
572
220
|
*/
|
|
573
221
|
async function WriteProperties(obj, meta, bodyData, action, existingDBData) {
|
|
574
|
-
|
|
575
|
-
const writable = existingDBData
|
|
576
|
-
? writableInBody(actionWritable, bodyData)
|
|
577
|
-
: actionWritable;
|
|
578
|
-
const instance = { ...obj };
|
|
579
|
-
const dbData = existingDBData || {};
|
|
580
|
-
Object.setPrototypeOf(dbData, meta.tableClass.prototype);
|
|
581
|
-
if (!existingDBData) {
|
|
582
|
-
writeTargetDefaults(instance, meta, dbData);
|
|
583
|
-
}
|
|
584
|
-
for (const [key, field] of writable.props) {
|
|
585
|
-
instance[key] = bodyData[key];
|
|
586
|
-
dbData[field.dbName || key] = bodyData[key];
|
|
587
|
-
}
|
|
588
|
-
instance.table = dbData;
|
|
589
|
-
Object.setPrototypeOf(instance, meta.target.prototype);
|
|
590
|
-
for (const [key, field] of writable.setters) {
|
|
591
|
-
field.desc?.set?.apply(instance, [bodyData[key]]);
|
|
592
|
-
}
|
|
593
|
-
return dbData;
|
|
222
|
+
return properties.WriteProperties(obj, meta, bodyData, action, existingDBData);
|
|
594
223
|
}
|
|
595
224
|
Query.WriteProperties = WriteProperties;
|
|
596
225
|
function Get(table, id, index) {
|
|
597
|
-
return index
|
|
598
|
-
? table.getAll(id, index).nth(0)
|
|
599
|
-
: table.get(id);
|
|
226
|
+
return tables.Get(table, id, index);
|
|
600
227
|
}
|
|
601
228
|
Query.Get = Get;
|
|
602
|
-
/** Where a list sorted on a modifier-held field keeps the value it sorts on. */
|
|
603
|
-
const MODIFIED_SORT_KEY = "__dataApiSortValue";
|
|
604
|
-
/**
|
|
605
|
-
* Whether a field is held by a modifier the controller keys (a localized
|
|
606
|
-
* field read in the request's locale): stored under the modifier, it sorts
|
|
607
|
-
* on the value the request reads, not on the stored field.
|
|
608
|
-
*/
|
|
609
|
-
function isHeldByKeyedModifier(meta, field) {
|
|
610
|
-
const keyed = new Set(Array.from(meta.modifierKeys.keys(), (modifier) => modifier.name));
|
|
611
|
-
return ((0, common_1.getMetadata)(meta.tableClass, common_2.ModifiersStaticMetadata).fields[field]?.some(({ id }) => keyed.has(id)) ?? false);
|
|
612
|
-
}
|
|
613
229
|
/**
|
|
614
230
|
* Builds the filtered and sorted stream of a list request along with its
|
|
615
231
|
* total count. Joined fields that no filter or sort references are not
|
|
@@ -620,93 +236,15 @@ var Query;
|
|
|
620
236
|
* since no lookup can be built without one — the raw stream is returned
|
|
621
237
|
* and the third element names the joined fields left for the caller.
|
|
622
238
|
*/
|
|
623
|
-
function List(obj, meta, request, reqCtx, sorting, filters, db, options) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
values[0][1] === "eq" &&
|
|
631
|
-
meta.fields[name]?.indexable);
|
|
632
|
-
})?.[0];
|
|
633
|
-
const index = indexFilter
|
|
634
|
-
? meta.fields[indexFilter].dbName || indexFilter
|
|
635
|
-
: undefined;
|
|
636
|
-
const indexedFilter = indexFilter
|
|
637
|
-
? (0, metadata_1.FilterValuesOf)(filters?.[indexFilter] ?? [])[0]
|
|
638
|
-
: undefined;
|
|
639
|
-
let tmpRequest = index
|
|
640
|
-
? request.getAll(indexedFilter?.[0] ?? "", index)
|
|
641
|
-
: request;
|
|
642
|
-
const joinedSplit = splitJoinedFields(meta, sorting, filters);
|
|
643
|
-
if (db && joinedSplit.filter.size > 0) {
|
|
644
|
-
tmpRequest = Joined(db, meta, tmpRequest, joinedSplit.filter);
|
|
645
|
-
}
|
|
646
|
-
const filteredComputed = new Set(filterList
|
|
647
|
-
.map(([name]) => name)
|
|
648
|
-
.filter((name) => meta.fields[name]?.computed));
|
|
649
|
-
if (db && filteredComputed.size > 0) {
|
|
650
|
-
tmpRequest = Computed(db, meta, tmpRequest, filteredComputed);
|
|
651
|
-
}
|
|
652
|
-
const sortField = sorting?.[0];
|
|
653
|
-
const shouldSort = sortField ? meta.fields[sortField]?.sortable : undefined;
|
|
654
|
-
const isModifiedSort = !!shouldSort && !!sortField && isHeldByKeyedModifier(meta, sortField);
|
|
655
|
-
if (shouldSort?.indexed && sortField && !isModifiedSort) {
|
|
656
|
-
tmpRequest = tmpRequest.orderBy(sortField, sorting?.[1] ?? "asc");
|
|
657
|
-
}
|
|
658
|
-
if (filterList.length > 0) {
|
|
659
|
-
// TODO: rework for modifier-affected fields (unlockrequest)
|
|
660
|
-
tmpRequest = filterList.reduce((req, [name, filter]) => {
|
|
661
|
-
const fieldFilter = filters?.[name];
|
|
662
|
-
if (!fieldFilter || name === indexFilter)
|
|
663
|
-
return req;
|
|
664
|
-
return (0, metadata_1.FilterValuesOf)(fieldFilter).reduce((filtered, [value, mode]) => filtered.filter((row) => filter(Object.assign(reqCtx, { this: obj }), Validation.UnlockRequest(obj, meta, row, name), name, value, mode, row)), req);
|
|
665
|
-
}, tmpRequest);
|
|
666
|
-
}
|
|
667
|
-
const total = tmpRequest.count();
|
|
668
|
-
if (db && joinedSplit.sort.size > 0) {
|
|
669
|
-
tmpRequest = Joined(db, meta, tmpRequest, joinedSplit.sort);
|
|
670
|
-
}
|
|
671
|
-
if (db &&
|
|
672
|
-
sortField &&
|
|
673
|
-
meta.fields[sortField]?.computed &&
|
|
674
|
-
!filteredComputed.has(sortField)) {
|
|
675
|
-
tmpRequest = Computed(db, meta, tmpRequest, new Set([sortField]));
|
|
676
|
-
}
|
|
677
|
-
if (isModifiedSort && sortField) {
|
|
678
|
-
// Sorts on the value in the request's locale, else its fallback.
|
|
679
|
-
tmpRequest = tmpRequest
|
|
680
|
-
.map((row) => row.merge({
|
|
681
|
-
[MODIFIED_SORT_KEY]: Validation.UnlockRequest(obj, meta, row, sortField),
|
|
682
|
-
}))
|
|
683
|
-
.orderBy(MODIFIED_SORT_KEY, sorting?.[1] ?? "asc")
|
|
684
|
-
.without(MODIFIED_SORT_KEY);
|
|
685
|
-
}
|
|
686
|
-
else if (shouldSort && !shouldSort.indexed && sortField) {
|
|
687
|
-
tmpRequest = tmpRequest.orderBy(sortField, sorting?.[1] ?? "asc");
|
|
688
|
-
}
|
|
689
|
-
return listResultTuple(tmpRequest, total, meta, joinedSplit, db, options);
|
|
239
|
+
function List(obj, meta, request, reqCtx, ...[sorting, filters, db, options]) {
|
|
240
|
+
return list.List(obj, meta, request, reqCtx, {
|
|
241
|
+
sorting,
|
|
242
|
+
filters,
|
|
243
|
+
db,
|
|
244
|
+
options,
|
|
245
|
+
});
|
|
690
246
|
}
|
|
691
247
|
Query.List = List;
|
|
692
|
-
function pageComputedFields(meta, options) {
|
|
693
|
-
const merged = new Set(Object.keys(options.filters ?? {}).filter((name) => name in meta.filters));
|
|
694
|
-
if (options.sortKey)
|
|
695
|
-
merged.add(options.sortKey);
|
|
696
|
-
const names = Object.entries(meta.fields)
|
|
697
|
-
.filter(([name, field]) => field.computed &&
|
|
698
|
-
!merged.has(name) &&
|
|
699
|
-
(!options.pluck || options.pluck.has(name)))
|
|
700
|
-
.map(([name]) => name);
|
|
701
|
-
return new Set(names);
|
|
702
|
-
}
|
|
703
|
-
function pageJoinedFields(options, materializeAll) {
|
|
704
|
-
const deferred = options.deferredJoined ?? new Set();
|
|
705
|
-
if (materializeAll || !options.pluck)
|
|
706
|
-
return deferred;
|
|
707
|
-
const pluck = options.pluck;
|
|
708
|
-
return new Set(Array.from(deferred).filter((name) => pluck.has(name)));
|
|
709
|
-
}
|
|
710
248
|
/**
|
|
711
249
|
* One page of the stream {@link List} built, with the fields it answers:
|
|
712
250
|
* the deferred joined fields, then the computed fields no filter or sort
|
|
@@ -716,19 +254,7 @@ var Query;
|
|
|
716
254
|
* it.
|
|
717
255
|
*/
|
|
718
256
|
function ListPage(db, meta, query, options) {
|
|
719
|
-
|
|
720
|
-
const computed = pageComputedFields(meta, options);
|
|
721
|
-
// A computed expression may read any joined field, so when one is
|
|
722
|
-
// materialized every deferred joined group is merged first, whatever
|
|
723
|
-
// the pluck; the final pluck strips what the page did not ask for.
|
|
724
|
-
const joined = pageJoinedFields(options, computed.size > 0);
|
|
725
|
-
if (joined.size > 0)
|
|
726
|
-
page = Joined(db, meta, page, joined);
|
|
727
|
-
if (computed.size > 0)
|
|
728
|
-
page = Computed(db, meta, page, computed);
|
|
729
|
-
if (!options.noForeign)
|
|
730
|
-
page = Foreign(db, meta, page, options.pluck);
|
|
731
|
-
return options.pluck ? page.pluck("_internal", ...options.pluck) : page;
|
|
257
|
+
return list.ListPage(db, meta, query, options);
|
|
732
258
|
}
|
|
733
259
|
Query.ListPage = ListPage;
|
|
734
260
|
function Delete(table, id) {
|
|
@@ -743,8 +269,8 @@ var Validation;
|
|
|
743
269
|
function isClearingValue(value) {
|
|
744
270
|
if (value === null || value === undefined)
|
|
745
271
|
return true;
|
|
746
|
-
if (
|
|
747
|
-
return
|
|
272
|
+
if (value === "")
|
|
273
|
+
return true;
|
|
748
274
|
return Array.isArray(value) && value.length === 0;
|
|
749
275
|
}
|
|
750
276
|
const isAbsentFromBody = (body, name) => !(name in body);
|
|
@@ -777,7 +303,7 @@ var Validation;
|
|
|
777
303
|
}
|
|
778
304
|
Validation.MandatoryFields = MandatoryFields;
|
|
779
305
|
function toParseResult(result, value) {
|
|
780
|
-
if (
|
|
306
|
+
if (result !== true && result !== false)
|
|
781
307
|
return result;
|
|
782
308
|
return result ? { success: true, data: value } : { success: false };
|
|
783
309
|
}
|
|
@@ -810,17 +336,7 @@ var Validation;
|
|
|
810
336
|
* body already parsed is checked, not parsed again.
|
|
811
337
|
*/
|
|
812
338
|
function ParseBody(body) {
|
|
813
|
-
|
|
814
|
-
if (Buffer.isBuffer(body)) {
|
|
815
|
-
try {
|
|
816
|
-
data = JSON.parse(body.toString());
|
|
817
|
-
}
|
|
818
|
-
catch {
|
|
819
|
-
(0, interface_api_util_1.assert)(false, 400, "Invalid JSON body.");
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
(0, interface_api_util_1.assert)(typeof data === "object" && data !== null && !Array.isArray(data), 400, "The request body must be a JSON object.");
|
|
823
|
-
return data;
|
|
339
|
+
return validation.ParseBody(body);
|
|
824
340
|
}
|
|
825
341
|
Validation.ParseBody = ParseBody;
|
|
826
342
|
/**
|
|
@@ -838,78 +354,15 @@ var Validation;
|
|
|
838
354
|
}
|
|
839
355
|
Validation.Lock = Lock;
|
|
840
356
|
function Unlock(obj, meta, dbData) {
|
|
841
|
-
|
|
842
|
-
if (field.foreign &&
|
|
843
|
-
dbData[name] &&
|
|
844
|
-
typeof dbData[name] === "object" &&
|
|
845
|
-
field.foreign[1]) {
|
|
846
|
-
const foreignSchema = field.foreign[1];
|
|
847
|
-
dbData[name] = Array.isArray(dbData[name])
|
|
848
|
-
? dbData[name].map((entry) => (0, common_2.fromDatabase)(entry, foreignSchema))
|
|
849
|
-
: (0, common_2.fromDatabase)(dbData[name], field.foreign[1]);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
for (const [modifier, field] of meta.modifierKeys.entries()) {
|
|
853
|
-
const key = obj[field];
|
|
854
|
-
(0, common_2.unlock)(dbData, modifier, undefined, key);
|
|
855
|
-
for (const [foreign, fieldData] of Object.entries(meta.fields)) {
|
|
856
|
-
if (fieldData.foreign &&
|
|
857
|
-
dbData[foreign] &&
|
|
858
|
-
typeof dbData[foreign] === "object") {
|
|
859
|
-
(0, common_2.unlock)(dbData[foreign], modifier, undefined, key);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
357
|
+
validation.Unlock(obj, meta, dbData);
|
|
863
358
|
}
|
|
864
359
|
Validation.Unlock = Unlock;
|
|
865
360
|
function UnlockRequest(obj, meta, row, field) {
|
|
866
|
-
|
|
867
|
-
modifier,
|
|
868
|
-
args: [obj[field]],
|
|
869
|
-
}));
|
|
870
|
-
return (0, common_2.unlockrequest)(meta.tableClass, row, field, modifiers);
|
|
361
|
+
return validation.UnlockRequest(obj, meta, row, field);
|
|
871
362
|
}
|
|
872
363
|
Validation.UnlockRequest = UnlockRequest;
|
|
873
364
|
function ClearInternal(meta, obj) {
|
|
874
|
-
|
|
875
|
-
const foreignFields = Object.entries(meta.fields).filter(([, field]) => field.foreign);
|
|
876
|
-
for (const entry of results) {
|
|
877
|
-
delete entry._internal;
|
|
878
|
-
for (const [name, { foreign }] of foreignFields) {
|
|
879
|
-
if (foreign &&
|
|
880
|
-
entry[name] &&
|
|
881
|
-
typeof entry[name] === "object" &&
|
|
882
|
-
(!Array.isArray(entry[name]) || typeof entry[name][0] === "object")) {
|
|
883
|
-
const processForeign = (data) => {
|
|
884
|
-
const plain = (0, common_2.toPlainData)(data);
|
|
885
|
-
if (!foreign?.[4]) {
|
|
886
|
-
return plain;
|
|
887
|
-
}
|
|
888
|
-
// An orphan foreign key whose target exposes @Joined fields produces
|
|
889
|
-
// an object with only those joined fields (all null) and none of the
|
|
890
|
-
// physical pluck fields. Normalize it back to a null relation, so it
|
|
891
|
-
// matches the get path and non-joined foreigns (which already null).
|
|
892
|
-
const targetJoined = foreign[6];
|
|
893
|
-
if (targetJoined?.length) {
|
|
894
|
-
const joinedNames = new Set(targetJoined.map((g) => g.name));
|
|
895
|
-
const physicalKeys = foreign[4].filter((k) => !joinedNames.has(k));
|
|
896
|
-
if (physicalKeys.length > 0 &&
|
|
897
|
-
physicalKeys.every((k) => plain[k] === null || plain[k] === undefined)) {
|
|
898
|
-
return null;
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
const plainPlucked = {};
|
|
902
|
-
for (const key of foreign[4]) {
|
|
903
|
-
plainPlucked[key] = plain[key];
|
|
904
|
-
}
|
|
905
|
-
return plainPlucked;
|
|
906
|
-
};
|
|
907
|
-
entry[name] = Array.isArray(entry[name])
|
|
908
|
-
? entry[name].map(processForeign)
|
|
909
|
-
: processForeign(entry[name]);
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
}
|
|
365
|
+
validation.ClearInternal(meta, obj);
|
|
913
366
|
}
|
|
914
367
|
Validation.ClearInternal = ClearInternal;
|
|
915
368
|
})(Validation || (exports.Validation = Validation = {}));
|