@antelopejs/interface-data-api 0.4.0 → 0.6.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.
Files changed (165) hide show
  1. package/README.md +26 -22
  2. package/dist/antelope.test.js +1 -5
  3. package/dist/antelope.test.js.map +1 -1
  4. package/dist/components.d.ts +67 -39
  5. package/dist/components.js +106 -685
  6. package/dist/components.js.map +1 -1
  7. package/dist/index.d.ts +57 -68
  8. package/dist/index.js +114 -100
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/computed.d.ts +6 -0
  11. package/dist/internal/computed.js +51 -0
  12. package/dist/internal/computed.js.map +1 -0
  13. package/dist/internal/controller-types.d.ts +34 -0
  14. package/dist/internal/controller-types.js +4 -0
  15. package/dist/internal/controller-types.js.map +1 -0
  16. package/dist/internal/default-filter.d.ts +4 -0
  17. package/dist/internal/default-filter.js +29 -0
  18. package/dist/internal/default-filter.js.map +1 -0
  19. package/dist/internal/deferred-joined-stream.d.ts +11 -0
  20. package/dist/internal/deferred-joined-stream.js +125 -0
  21. package/dist/internal/deferred-joined-stream.js.map +1 -0
  22. package/dist/internal/extractors.d.ts +25 -0
  23. package/dist/internal/extractors.js +31 -0
  24. package/dist/internal/extractors.js.map +1 -0
  25. package/dist/internal/field-types.d.ts +199 -0
  26. package/dist/internal/field-types.js +4 -0
  27. package/dist/internal/field-types.js.map +1 -0
  28. package/dist/internal/filters.d.ts +6 -0
  29. package/dist/internal/filters.js +22 -0
  30. package/dist/internal/filters.js.map +1 -0
  31. package/dist/internal/foreign.d.ts +6 -0
  32. package/dist/internal/foreign.js +116 -0
  33. package/dist/internal/foreign.js.map +1 -0
  34. package/dist/internal/guards.d.ts +39 -0
  35. package/dist/internal/guards.js +116 -0
  36. package/dist/internal/guards.js.map +1 -0
  37. package/dist/internal/joined.d.ts +28 -0
  38. package/dist/internal/joined.js +127 -0
  39. package/dist/internal/joined.js.map +1 -0
  40. package/dist/internal/list.d.ts +16 -0
  41. package/dist/internal/list.js +182 -0
  42. package/dist/internal/list.js.map +1 -0
  43. package/dist/internal/meta-records.d.ts +19 -0
  44. package/dist/internal/meta-records.js +78 -0
  45. package/dist/internal/meta-records.js.map +1 -0
  46. package/dist/internal/properties.d.ts +4 -0
  47. package/dist/internal/properties.js +45 -0
  48. package/dist/internal/properties.js.map +1 -0
  49. package/dist/internal/prototypes.d.ts +14 -0
  50. package/dist/internal/prototypes.js +38 -0
  51. package/dist/internal/prototypes.js.map +1 -0
  52. package/dist/internal/read-properties.d.ts +3 -0
  53. package/dist/internal/read-properties.js +29 -0
  54. package/dist/internal/read-properties.js.map +1 -0
  55. package/dist/internal/table-reference.d.ts +11 -0
  56. package/dist/internal/table-reference.js +46 -0
  57. package/dist/internal/table-reference.js.map +1 -0
  58. package/dist/internal/tables.d.ts +28 -0
  59. package/dist/internal/tables.js +48 -0
  60. package/dist/internal/tables.js.map +1 -0
  61. package/dist/internal/validation.d.ts +12 -0
  62. package/dist/internal/validation.js +100 -0
  63. package/dist/internal/validation.js.map +1 -0
  64. package/dist/metadata.d.ts +27 -228
  65. package/dist/metadata.js +84 -213
  66. package/dist/metadata.js.map +1 -1
  67. package/dist/tests/components/access_control.test.js +28 -20
  68. package/dist/tests/components/access_control.test.js.map +1 -1
  69. package/dist/tests/components/computed.test.js +36 -27
  70. package/dist/tests/components/computed.test.js.map +1 -1
  71. package/dist/tests/components/cross_instance.test.js +8 -6
  72. package/dist/tests/components/cross_instance.test.js.map +1 -1
  73. package/dist/tests/components/deferred_joined.test.js +57 -54
  74. package/dist/tests/components/deferred_joined.test.js.map +1 -1
  75. package/dist/tests/components/extract_generic.test.d.ts +1 -0
  76. package/dist/tests/components/extract_generic.test.js +64 -0
  77. package/dist/tests/components/extract_generic.test.js.map +1 -0
  78. package/dist/tests/components/filter_comparisons.test.d.ts +1 -0
  79. package/dist/tests/components/filter_comparisons.test.js +34 -0
  80. package/dist/tests/components/filter_comparisons.test.js.map +1 -0
  81. package/dist/tests/components/filter_list_route.test.d.ts +1 -0
  82. package/dist/tests/components/filter_list_route.test.js +182 -0
  83. package/dist/tests/components/filter_list_route.test.js.map +1 -0
  84. package/dist/tests/components/filter_options.test.js +11 -10
  85. package/dist/tests/components/filter_options.test.js.map +1 -1
  86. package/dist/tests/components/foreign_computed.test.js +23 -14
  87. package/dist/tests/components/foreign_computed.test.js.map +1 -1
  88. package/dist/tests/components/foreign_joined.test.js +31 -27
  89. package/dist/tests/components/foreign_joined.test.js.map +1 -1
  90. package/dist/tests/components/joined.test.js +59 -45
  91. package/dist/tests/components/joined.test.js.map +1 -1
  92. package/dist/tests/components/listable.test.js +78 -58
  93. package/dist/tests/components/listable.test.js.map +1 -1
  94. package/dist/tests/components/localized_sort.test.js +14 -11
  95. package/dist/tests/components/localized_sort.test.js.map +1 -1
  96. package/dist/tests/components/mandatory.test.js +25 -25
  97. package/dist/tests/components/mandatory.test.js.map +1 -1
  98. package/dist/tests/components/modifier_key.test.js +27 -18
  99. package/dist/tests/components/modifier_key.test.js.map +1 -1
  100. package/dist/tests/components/parse_body.test.d.ts +1 -0
  101. package/dist/tests/components/parse_body.test.js +76 -0
  102. package/dist/tests/components/parse_body.test.js.map +1 -0
  103. package/dist/tests/components/parsed_value.test.js +23 -21
  104. package/dist/tests/components/parsed_value.test.js.map +1 -1
  105. package/dist/tests/components/partial_edit.test.js +38 -32
  106. package/dist/tests/components/partial_edit.test.js.map +1 -1
  107. package/dist/tests/components/per_action_access.test.js +20 -12
  108. package/dist/tests/components/per_action_access.test.js.map +1 -1
  109. package/dist/tests/components/prototype_named_filter.test.d.ts +1 -0
  110. package/dist/tests/components/prototype_named_filter.test.js +87 -0
  111. package/dist/tests/components/prototype_named_filter.test.js.map +1 -0
  112. package/dist/tests/components/validator.test.js +45 -37
  113. package/dist/tests/components/validator.test.js.map +1 -1
  114. package/dist/tests/components/written_row.test.d.ts +1 -0
  115. package/dist/tests/components/written_row.test.js +99 -0
  116. package/dist/tests/components/written_row.test.js.map +1 -0
  117. package/dist/tests/helpers/other-instance.d.ts +11 -1
  118. package/dist/tests/helpers/other-instance.js +35 -9
  119. package/dist/tests/helpers/other-instance.js.map +1 -1
  120. package/dist/tests/index/field_decorator_target.test.d.ts +1 -0
  121. package/dist/tests/index/field_decorator_target.test.js +20 -0
  122. package/dist/tests/index/field_decorator_target.test.js.map +1 -0
  123. package/dist/tests/index/get_guard.test.js +6 -6
  124. package/dist/tests/index/get_guard.test.js.map +1 -1
  125. package/dist/tests/index/inheritance.test.js +14 -9
  126. package/dist/tests/index/inheritance.test.js.map +1 -1
  127. package/dist/tests/index/metadata_names.test.d.ts +1 -0
  128. package/dist/tests/index/metadata_names.test.js +91 -0
  129. package/dist/tests/index/metadata_names.test.js.map +1 -0
  130. package/dist/tests/index/routes.test.js +49 -44
  131. package/dist/tests/index/routes.test.js.map +1 -1
  132. package/dist/tests/integration.test.d.ts +1 -1
  133. package/dist/tests/integration.test.js +66 -729
  134. package/dist/tests/integration.test.js.map +1 -1
  135. package/dist/tests/internal/controller-fields.test.d.ts +1 -0
  136. package/dist/tests/internal/controller-fields.test.js +25 -0
  137. package/dist/tests/internal/controller-fields.test.js.map +1 -0
  138. package/dist/tests/internal/http.d.ts +35 -0
  139. package/dist/tests/internal/http.js +56 -0
  140. package/dist/tests/internal/http.js.map +1 -0
  141. package/dist/tests/internal/shop-api.d.ts +1 -0
  142. package/dist/tests/internal/shop-api.js +331 -0
  143. package/dist/tests/internal/shop-api.js.map +1 -0
  144. package/dist/tests/internal/shop-schema.d.ts +162 -0
  145. package/dist/tests/internal/shop-schema.js +387 -0
  146. package/dist/tests/internal/shop-schema.js.map +1 -0
  147. package/dist/tests/utils.d.ts +8 -10
  148. package/dist/tests/utils.js +1 -3
  149. package/dist/tests/utils.js.map +1 -1
  150. package/dist/tests/utils.test.js +10 -11
  151. package/dist/tests/utils.test.js.map +1 -1
  152. package/docs/1.introduction.md +38 -35
  153. package/docs/10.joined.md +51 -40
  154. package/docs/11.computed.md +91 -47
  155. package/docs/2.data-controllers.md +50 -42
  156. package/docs/3.routes.md +156 -134
  157. package/docs/4.access-rights.md +48 -44
  158. package/docs/5.validators.md +65 -47
  159. package/docs/6.listable.md +53 -49
  160. package/docs/7.foreign-keys.md +55 -48
  161. package/docs/8.filters.md +65 -54
  162. package/docs/9.modifiers.md +29 -25
  163. package/package.json +34 -13
  164. package/skills/data-api-interface/SKILL.md +13 -3
  165. package/skills/data-api-interface/cases.json +99 -0
@@ -1,29 +1,65 @@
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
- const interface_database_1 = require("@antelopejs/interface-database");
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 extractors = __importStar(require("./internal/extractors"));
48
+ const guards_1 = require("./internal/guards");
49
+ const joined = __importStar(require("./internal/joined"));
50
+ const list = __importStar(require("./internal/list"));
51
+ const properties = __importStar(require("./internal/properties"));
52
+ const prototypes_1 = require("./internal/prototypes");
53
+ const read_properties_1 = require("./internal/read-properties");
54
+ const tables = __importStar(require("./internal/tables"));
55
+ const validation = __importStar(require("./internal/validation"));
11
56
  var Parameters;
12
57
  (function (Parameters) {
58
+ /** The options the route's `WithOptions` set, none when it set none. */
13
59
  function GetOptionOverrides(reqCtx) {
14
60
  return reqCtx.dataAPIEntry?.options ?? {};
15
61
  }
16
62
  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
63
  /**
28
64
  * Extracts request filters and mandatory route filters, with route field
29
65
  * keys winning. A field the request filters once (`?filter_status=open`)
@@ -36,60 +72,55 @@ var Parameters;
36
72
  for (const filter of Object.keys(meta.filters)) {
37
73
  const values = reqCtx.url.searchParams
38
74
  .getAll(`filter_${filter}`)
39
- .map(parseFilter);
75
+ .map(filters_1.parseFilter);
40
76
  if (values.length > 0) {
41
77
  result[filter] = values.length === 1 ? values[0] : values;
42
78
  }
43
79
  }
44
80
  const overrides = GetOptionOverrides(reqCtx);
45
81
  for (const [key, value] of Object.entries(overrides.filters ?? {})) {
46
- result[key] = copyFilter(value);
82
+ result[key] = (0, filters_1.copyFilter)(value);
47
83
  }
48
84
  return result;
49
85
  }
50
86
  Parameters.ExtractFilters = ExtractFilters;
51
- const converters = {
52
- number: (val) => parseFloat(val),
53
- int: (val) => parseInt(val, 10),
54
- bool: (val) => val !== "0",
55
- string: (val) => val,
56
- };
87
+ Parameters.FromSearch = extractors.FromSearch;
88
+ function combinesWithOptions(extractor) {
89
+ return extractor === ExtractFilters;
90
+ }
91
+ /**
92
+ * Builds a route's parameters: the options its `WithOptions` set, then each
93
+ * extracted field those options leave out. An extractor of `undefined` is
94
+ * skipped like an absent key. {@link ExtractFilters} also runs for a route
95
+ * that sets `filters`, since it combines them with the request's.
96
+ */
57
97
  function ExtractGeneric(reqCtx, meta, dynamic) {
58
- const overrides = GetOptionOverrides(reqCtx);
59
- const result = { ...overrides };
60
- for (const key of Object.keys(dynamic)) {
61
- if (!(key in result) || dynamic[key] === ExtractFilters) {
62
- const extractor = dynamic[key];
63
- if (typeof extractor === "string") {
64
- if (extractor.startsWith("multi:")) {
65
- const converter = converters[extractor.substring(6)];
66
- result[key] = reqCtx.url.searchParams
67
- .getAll(key)
68
- .map((searchVal) => converter(searchVal));
69
- }
70
- else {
71
- const searchVal = reqCtx.url.searchParams.get(key);
72
- if (searchVal !== null) {
73
- result[key] = converters[extractor](searchVal);
74
- }
75
- }
76
- }
77
- else {
78
- result[key] = extractor(reqCtx, meta);
79
- }
98
+ const result = { ...GetOptionOverrides(reqCtx) };
99
+ for (const key in dynamic) {
100
+ const extractor = dynamic[key];
101
+ if (!extractor || (key in result && !combinesWithOptions(extractor))) {
102
+ continue;
80
103
  }
104
+ const value = extractor(reqCtx, meta, key);
105
+ if (value !== undefined)
106
+ result[key] = value;
81
107
  }
82
108
  return result;
83
109
  }
84
110
  Parameters.ExtractGeneric = ExtractGeneric;
111
+ /**
112
+ * The sort direction a request sends. Any other text sorts ascending, as an
113
+ * absent direction does.
114
+ */
115
+ const sortDirectionOf = extractors.fromSearch((text) => text === "asc" || text === "desc" ? text : undefined);
85
116
  Parameters.List = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
86
117
  const meta = (0, metadata_1.GetDataControllerMeta)(this);
87
118
  const params = ExtractGeneric(context, meta, {
88
119
  filters: ExtractFilters,
89
- offset: "int",
90
- limit: "int",
91
- sortKey: "string",
92
- sortDirection: "string",
120
+ offset: Parameters.FromSearch.int,
121
+ limit: Parameters.FromSearch.int,
122
+ sortKey: Parameters.FromSearch.string,
123
+ sortDirection: sortDirectionOf,
93
124
  });
94
125
  (0, interface_api_util_1.assert)(!params.sortKey || meta.fields[params.sortKey]?.sortable, 400, "Field is not sortable.");
95
126
  params.limit = params.limit
@@ -99,9 +130,9 @@ var Parameters;
99
130
  }));
100
131
  Parameters.Get = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
101
132
  const params = ExtractGeneric(context, (0, metadata_1.GetDataControllerMeta)(this), {
102
- id: "string",
133
+ id: Parameters.FromSearch.string,
103
134
  });
104
- (0, interface_api_util_1.assert)(params.id && typeof params.id === "string", 400, "Missing id.");
135
+ (0, interface_api_util_1.assert)(params.id && (0, guards_1.isString)(params.id), 400, "Missing id.");
105
136
  return params;
106
137
  }));
107
138
  Parameters.New = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
@@ -110,14 +141,14 @@ var Parameters;
110
141
  }));
111
142
  Parameters.Edit = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
112
143
  const params = ExtractGeneric(context, (0, metadata_1.GetDataControllerMeta)(this), {
113
- id: "string",
144
+ id: Parameters.FromSearch.string,
114
145
  });
115
- (0, interface_api_util_1.assert)(params.id && typeof params.id === "string", 400, "Missing id.");
146
+ (0, interface_api_util_1.assert)(params.id && (0, guards_1.isString)(params.id), 400, "Missing id.");
116
147
  return params;
117
148
  }));
118
149
  Parameters.Delete = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, param) => (0, interface_api_1.SetParameterProvider)(target, key, param, function (context) {
119
150
  const params = ExtractGeneric(context, (0, metadata_1.GetDataControllerMeta)(this), {
120
- id: "multi:string",
151
+ id: Parameters.FromSearch.strings,
121
152
  });
122
153
  (0, interface_api_util_1.assert)(params.id && Array.isArray(params.id) && params.id.length > 0, 400, "Missing id.");
123
154
  return params;
@@ -127,439 +158,27 @@ var Query;
127
158
  (function (Query) {
128
159
  function GetModel(obj, meta) {
129
160
  (0, interface_api_util_1.assert)(meta.modelKey, 500, "Missing model key.");
130
- return obj[meta.modelKey];
161
+ const model = (0, prototypes_1.fieldsOf)(obj)[meta.modelKey];
162
+ (0, interface_api_util_1.assert)(tables.isModelInstance(model), 500, "The controller holds no model.");
163
+ return model;
131
164
  }
132
165
  Query.GetModel = GetModel;
133
166
  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
- }
167
+ return foreign.Foreign(db, meta, query, pluck);
197
168
  }
198
169
  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
170
  function Joined(db, meta, query, only) {
436
- const groups = collectJoinedGroups(meta).filter((group) => !only || group.fields.some((field) => only.has(field.name)));
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;
171
+ return joined.Joined(db, meta, query, only);
484
172
  }
485
173
  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
174
  function Computed(db, meta, query, only) {
513
- const entries = collectComputedEntries(meta, only);
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);
175
+ return computed.Computed(db, meta, query, only);
522
176
  }
523
177
  Query.Computed = Computed;
524
178
  async function ReadProperties(obj, meta, dbData, action, onlyList) {
525
- const readable = meta.readable[action ?? "_default"] ?? meta.readable._default;
526
- const instance = { ...obj };
527
- const res = {};
528
- for (const [key, field] of readable.props) {
529
- if (onlyList && !field.listable) {
530
- continue;
531
- }
532
- instance[key] = dbData[field.dbName || key];
533
- res[key] = dbData[field.dbName || key];
534
- }
535
- instance.table = dbData;
536
- Object.setPrototypeOf(instance, meta.target.prototype);
537
- for (const [key, field] of readable.getters) {
538
- if (onlyList && !field.listable) {
539
- continue;
540
- }
541
- const val = field.desc?.get?.apply(instance);
542
- res[key] = await (typeof val === "function" ? val() : val);
543
- }
544
- return res;
179
+ return (0, read_properties_1.ReadProperties)(obj, meta, dbData, action, onlyList);
545
180
  }
546
181
  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
182
  /**
564
183
  * Writes the writable fields of `bodyData` into a database row.
565
184
  *
@@ -571,45 +190,13 @@ var Query;
571
190
  * `null` clears it.
572
191
  */
573
192
  async function WriteProperties(obj, meta, bodyData, action, existingDBData) {
574
- const actionWritable = meta.writable[action ?? "_default"] ?? meta.writable._default;
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;
193
+ return properties.WriteProperties(obj, meta, bodyData, action, existingDBData);
594
194
  }
595
195
  Query.WriteProperties = WriteProperties;
596
196
  function Get(table, id, index) {
597
- return index
598
- ? table.getAll(id, index).nth(0)
599
- : table.get(id);
197
+ return tables.Get(table, id, index);
600
198
  }
601
199
  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
200
  /**
614
201
  * Builds the filtered and sorted stream of a list request along with its
615
202
  * total count. Joined fields that no filter or sort references are not
@@ -620,93 +207,15 @@ var Query;
620
207
  * since no lookup can be built without one — the raw stream is returned
621
208
  * and the third element names the joined fields left for the caller.
622
209
  */
623
- function List(obj, meta, request, reqCtx, sorting, filters, db, options) {
624
- const filterList = Object.entries(meta.filters).filter(([name]) => filters && name in filters);
625
- // The index serves one equality; a field filtered several times is
626
- // filtered row by row, each of its filters in turn.
627
- const indexFilter = filterList.find(([name]) => {
628
- const values = filters ? (0, metadata_1.FilterValuesOf)(filters[name]) : [];
629
- return (values.length === 1 &&
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);
210
+ function List(obj, meta, request, reqCtx, ...[sorting, filters, db, options]) {
211
+ return list.List(obj, meta, request, reqCtx, {
212
+ sorting,
213
+ filters,
214
+ db,
215
+ options,
216
+ });
690
217
  }
691
218
  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
219
  /**
711
220
  * One page of the stream {@link List} built, with the fields it answers:
712
221
  * the deferred joined fields, then the computed fields no filter or sort
@@ -716,19 +225,7 @@ var Query;
716
225
  * it.
717
226
  */
718
227
  function ListPage(db, meta, query, options) {
719
- let page = query.slice(options.offset, options.limit);
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;
228
+ return list.ListPage(db, meta, query, options);
732
229
  }
733
230
  Query.ListPage = ListPage;
734
231
  function Delete(table, id) {
@@ -743,8 +240,8 @@ var Validation;
743
240
  function isClearingValue(value) {
744
241
  if (value === null || value === undefined)
745
242
  return true;
746
- if (typeof value === "string")
747
- return value.length === 0;
243
+ if (value === "")
244
+ return true;
748
245
  return Array.isArray(value) && value.length === 0;
749
246
  }
750
247
  const isAbsentFromBody = (body, name) => !(name in body);
@@ -777,7 +274,7 @@ var Validation;
777
274
  }
778
275
  Validation.MandatoryFields = MandatoryFields;
779
276
  function toParseResult(result, value) {
780
- if (typeof result !== "boolean")
277
+ if (result !== true && result !== false)
781
278
  return result;
782
279
  return result ? { success: true, data: value } : { success: false };
783
280
  }
@@ -810,17 +307,7 @@ var Validation;
810
307
  * body already parsed is checked, not parsed again.
811
308
  */
812
309
  function ParseBody(body) {
813
- let data = body;
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;
310
+ return validation.ParseBody(body);
824
311
  }
825
312
  Validation.ParseBody = ParseBody;
826
313
  /**
@@ -831,85 +318,19 @@ var Validation;
831
318
  */
832
319
  function Lock(obj, meta, data) {
833
320
  const written = Object.keys((0, common_1.getMetadata)(data, common_2.ModifiersDynamicMetadata).floating);
834
- for (const [modifier, field] of meta.modifierKeys.entries()) {
835
- const key = obj[field];
836
- (0, common_2.lock)(data, modifier, written, key);
837
- }
321
+ validation.Lock(obj, meta, data, written);
838
322
  }
839
323
  Validation.Lock = Lock;
840
324
  function Unlock(obj, meta, dbData) {
841
- for (const [name, field] of Object.entries(meta.fields)) {
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
- }
325
+ validation.Unlock(obj, meta, dbData);
863
326
  }
864
327
  Validation.Unlock = Unlock;
865
328
  function UnlockRequest(obj, meta, row, field) {
866
- const modifiers = Array.from(meta.modifierKeys.entries()).map(([modifier, field]) => ({
867
- modifier,
868
- args: [obj[field]],
869
- }));
870
- return (0, common_2.unlockrequest)(meta.tableClass, row, field, modifiers);
329
+ return validation.UnlockRequest(obj, meta, row, field);
871
330
  }
872
331
  Validation.UnlockRequest = UnlockRequest;
873
332
  function ClearInternal(meta, obj) {
874
- const results = Array.isArray(obj) ? obj : [obj];
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
- }
333
+ validation.ClearInternal(meta, obj);
913
334
  }
914
335
  Validation.ClearInternal = ClearInternal;
915
336
  })(Validation || (exports.Validation = Validation = {}));