@jskit-ai/crud-core 0.1.63 → 0.1.64
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/package.descriptor.mjs +4 -2
- package/package.json +18 -10
- package/src/server/crudModuleConfig.js +25 -5
- package/src/server/fieldAccess.js +9 -39
- package/src/server/listFilters.js +384 -389
- package/src/server/listQueryValidators.js +39 -77
- package/src/server/lookupHydration.js +4 -1
- package/src/server/repositorySupport.js +71 -121
- package/src/server/resourceRuntime/index.js +49 -74
- package/src/server/resourceRuntime/lookupHydration.js +4 -1
- package/src/server/routeContracts.js +74 -0
- package/src/server/serviceEvents.js +75 -4
- package/src/shared/crudFieldSupport.js +54 -0
- package/src/shared/crudNamespaceSupport.js +1 -27
- package/src/shared/crudResource.js +1 -0
- package/test/createCrudServiceFromResource.test.js +30 -28
- package/test/{crudFieldMetaSupport.test.js → crudFieldSupport.test.js} +1 -1
- package/test/crudModuleConfig.test.js +33 -0
- package/test/crudResource.test.js +97 -0
- package/test/listFilters.test.js +221 -59
- package/test/listQueryValidators.test.js +131 -97
- package/test/repositorySupport.test.js +241 -241
- package/test/resourceRuntime.test.js +204 -248
- package/test/routeContracts.test.js +146 -0
- package/test/serviceEvents.test.js +41 -1
- package/test/serviceMethods.test.js +12 -10
- package/src/shared/crudFieldMetaSupport.js +0 -153
|
@@ -1,53 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createSchema } from "json-rest-schema";
|
|
2
2
|
import {
|
|
3
|
-
normalizeObjectInput,
|
|
4
|
-
positiveIntegerValidator,
|
|
5
3
|
cursorPaginationQueryValidator
|
|
6
4
|
} from "@jskit-ai/kernel/shared/validators";
|
|
7
5
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
6
|
import { resolveCrudParentFilterKeys as resolveSharedCrudParentFilterKeys } from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
9
7
|
|
|
10
8
|
const listSearchQueryValidator = Object.freeze({
|
|
11
|
-
schema:
|
|
12
|
-
{
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
{ additionalProperties: false }
|
|
16
|
-
),
|
|
17
|
-
normalize(payload = {}) {
|
|
18
|
-
const source = normalizeObjectInput(payload);
|
|
19
|
-
if (!Object.hasOwn(source, "q")) {
|
|
20
|
-
return {};
|
|
9
|
+
schema: createSchema({
|
|
10
|
+
q: {
|
|
11
|
+
type: "string",
|
|
12
|
+
required: false
|
|
21
13
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
q: normalizeText(source.q)
|
|
25
|
-
};
|
|
26
|
-
}
|
|
14
|
+
}),
|
|
15
|
+
mode: "patch"
|
|
27
16
|
});
|
|
28
17
|
|
|
29
18
|
const lookupIncludeQueryValidator = Object.freeze({
|
|
30
|
-
schema:
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{ additionalProperties: false }
|
|
35
|
-
),
|
|
36
|
-
normalize(payload = {}) {
|
|
37
|
-
const source = normalizeObjectInput(payload);
|
|
38
|
-
if (!Object.hasOwn(source, "include")) {
|
|
39
|
-
return {};
|
|
19
|
+
schema: createSchema({
|
|
20
|
+
include: {
|
|
21
|
+
type: "string",
|
|
22
|
+
required: false
|
|
40
23
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
include: normalizeText(source.include)
|
|
44
|
-
};
|
|
45
|
-
}
|
|
24
|
+
}),
|
|
25
|
+
mode: "patch"
|
|
46
26
|
});
|
|
47
27
|
|
|
48
28
|
function resolveCrudListUsesOrderedCursor(list = {}) {
|
|
49
|
-
const
|
|
50
|
-
|
|
29
|
+
const entries = Array.isArray(list?.orderBy)
|
|
30
|
+
? list.orderBy
|
|
31
|
+
: list?.orderBy == null
|
|
32
|
+
? []
|
|
33
|
+
: [list.orderBy];
|
|
34
|
+
|
|
35
|
+
for (const entry of entries) {
|
|
51
36
|
if (typeof entry === "string" && normalizeText(entry)) {
|
|
52
37
|
return true;
|
|
53
38
|
}
|
|
@@ -65,32 +50,20 @@ function createCrudCursorPaginationQueryValidator(list = {}) {
|
|
|
65
50
|
}
|
|
66
51
|
|
|
67
52
|
return Object.freeze({
|
|
68
|
-
schema:
|
|
69
|
-
{
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
Type.String({ minLength: 1 })
|
|
74
|
-
])
|
|
75
|
-
),
|
|
76
|
-
limit: Type.Optional(positiveIntegerValidator.schema)
|
|
53
|
+
schema: createSchema({
|
|
54
|
+
cursor: {
|
|
55
|
+
type: "string",
|
|
56
|
+
required: false,
|
|
57
|
+
minLength: 1
|
|
77
58
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (Object.hasOwn(source, "cursor")) {
|
|
85
|
-
normalized.cursor = normalizeText(source.cursor);
|
|
59
|
+
limit: {
|
|
60
|
+
type: "number",
|
|
61
|
+
required: false,
|
|
62
|
+
min: 1,
|
|
63
|
+
unsigned: true
|
|
86
64
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
normalized.limit = positiveIntegerValidator.normalize(source.limit);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return normalized;
|
|
93
|
-
}
|
|
65
|
+
}),
|
|
66
|
+
mode: "patch"
|
|
94
67
|
});
|
|
95
68
|
}
|
|
96
69
|
|
|
@@ -102,27 +75,16 @@ function createCrudParentFilterQueryValidator(resource = {}) {
|
|
|
102
75
|
const keys = resolveCrudParentFilterKeys(resource);
|
|
103
76
|
const schemaProperties = {};
|
|
104
77
|
for (const key of keys) {
|
|
105
|
-
schemaProperties[key] =
|
|
78
|
+
schemaProperties[key] = {
|
|
79
|
+
type: "string",
|
|
80
|
+
required: false,
|
|
81
|
+
minLength: 1
|
|
82
|
+
};
|
|
106
83
|
}
|
|
107
84
|
|
|
108
85
|
return Object.freeze({
|
|
109
|
-
schema:
|
|
110
|
-
|
|
111
|
-
const source = normalizeObjectInput(payload);
|
|
112
|
-
const normalized = {};
|
|
113
|
-
for (const key of keys) {
|
|
114
|
-
if (!Object.hasOwn(source, key)) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const value = normalizeText(source[key]);
|
|
119
|
-
if (value) {
|
|
120
|
-
normalized[key] = value;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return normalized;
|
|
125
|
-
}
|
|
86
|
+
schema: createSchema(schemaProperties),
|
|
87
|
+
mode: "patch"
|
|
126
88
|
});
|
|
127
89
|
}
|
|
128
90
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
normalizeCrudLookupContainerKey,
|
|
7
7
|
resolveCrudLookupContainerKey
|
|
8
8
|
} from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
9
|
+
import { buildCrudFieldContractMap } from "@jskit-ai/kernel/shared/support/crudFieldContract";
|
|
9
10
|
import { normalizeCrudLookupApiPath } from "./lookupPathSupport.js";
|
|
10
11
|
|
|
11
12
|
const DEFAULT_LOOKUP_INCLUDE = "*";
|
|
@@ -136,7 +137,9 @@ function createCrudLookupRuntime(resource = {}, { outputKeys = [] } = {}) {
|
|
|
136
137
|
.filter(Boolean)
|
|
137
138
|
);
|
|
138
139
|
|
|
139
|
-
const sourceEntries =
|
|
140
|
+
const sourceEntries = Object.values(buildCrudFieldContractMap(resource, {
|
|
141
|
+
context: "crud lookup runtime field contract"
|
|
142
|
+
}));
|
|
140
143
|
const lookupEntries = [];
|
|
141
144
|
const seenKeys = new Set();
|
|
142
145
|
|
|
@@ -6,23 +6,25 @@ import { AppError } from "@jskit-ai/kernel/server/runtime/errors";
|
|
|
6
6
|
import { RECORD_ID_PATTERN } from "@jskit-ai/kernel/shared/validators";
|
|
7
7
|
import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
8
|
import { normalizeObjectInput } from "@jskit-ai/kernel/shared/validators/inputNormalization";
|
|
9
|
+
import { normalizeSchemaDefinition } from "@jskit-ai/kernel/shared/validators";
|
|
10
|
+
import {
|
|
11
|
+
buildCrudFieldContractMap,
|
|
12
|
+
resolveCrudFieldSchemaProperties,
|
|
13
|
+
CRUD_FIELD_STORAGE_COLUMN,
|
|
14
|
+
CRUD_FIELD_STORAGE_VIRTUAL,
|
|
15
|
+
CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC
|
|
16
|
+
} from "@jskit-ai/kernel/shared/support/crudFieldContract";
|
|
9
17
|
import { toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
10
18
|
import {
|
|
11
19
|
resolveCrudLookupContainerKey,
|
|
12
20
|
resolveCrudLookupFieldKeys
|
|
13
21
|
} from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
14
|
-
import {
|
|
15
|
-
CRUD_FIELD_REPOSITORY_STORAGE_COLUMN,
|
|
16
|
-
CRUD_FIELD_REPOSITORY_WRITE_SERIALIZER_DATETIME_UTC,
|
|
17
|
-
CRUD_FIELD_REPOSITORY_STORAGE_VIRTUAL,
|
|
18
|
-
isCrudRuntimeOutputOnlyFieldKey,
|
|
19
|
-
normalizeCrudFieldRepositoryConfig
|
|
20
|
-
} from "../shared/crudFieldMetaSupport.js";
|
|
22
|
+
import { isCrudRuntimeOutputOnlyFieldKey } from "../shared/crudFieldSupport.js";
|
|
21
23
|
|
|
22
24
|
const DEFAULT_LIST_LIMIT = 20;
|
|
23
25
|
const MAX_LIST_LIMIT = 100;
|
|
24
26
|
const CRUD_WRITE_SERIALIZERS = Object.freeze({
|
|
25
|
-
[
|
|
27
|
+
[CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC]: (value) => toDatabaseDateTimeUtc(value)
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
function normalizeCrudListCursor(cursor = null, { allowEmpty = true } = {}) {
|
|
@@ -93,7 +95,7 @@ function buildRepositoryColumnMetadata({
|
|
|
93
95
|
.filter(Boolean);
|
|
94
96
|
|
|
95
97
|
const deriveMapping = (key) => {
|
|
96
|
-
if (fieldStorageByKey?.[key] !==
|
|
98
|
+
if (fieldStorageByKey?.[key] !== CRUD_FIELD_STORAGE_COLUMN) {
|
|
97
99
|
return null;
|
|
98
100
|
}
|
|
99
101
|
const column = resolveColumnName(key, columnOverrides);
|
|
@@ -116,105 +118,55 @@ function buildRepositoryColumnMetadata({
|
|
|
116
118
|
});
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
function
|
|
120
|
-
if (!
|
|
121
|
+
function resolveOptionalFieldDefinitions(definition, options = {}) {
|
|
122
|
+
if (!definition) {
|
|
121
123
|
return {};
|
|
122
124
|
}
|
|
123
|
-
return
|
|
125
|
+
return requireFieldDefinitions(definition, options);
|
|
124
126
|
}
|
|
125
127
|
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
function requireFieldDefinitions(definition, {
|
|
129
|
+
context = "crudRepository",
|
|
130
|
+
schemaLabel = "schema definition",
|
|
131
|
+
defaultMode = "patch"
|
|
132
|
+
} = {}) {
|
|
133
|
+
const normalized = normalizeSchemaDefinition(definition, {
|
|
134
|
+
context: `${context} ${schemaLabel}`,
|
|
135
|
+
defaultMode
|
|
136
|
+
});
|
|
137
|
+
if (!normalized) {
|
|
138
|
+
throw new TypeError(`${context} requires ${schemaLabel}.`);
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
const properties =
|
|
141
|
+
const properties = resolveCrudFieldSchemaProperties(normalized, {
|
|
142
|
+
context: `${context} ${schemaLabel}`
|
|
143
|
+
});
|
|
132
144
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) {
|
|
133
|
-
throw new TypeError(`${context} requires ${schemaLabel}
|
|
145
|
+
throw new TypeError(`${context} requires ${schemaLabel} field definitions.`);
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
return properties;
|
|
137
149
|
}
|
|
138
150
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const normalized = [];
|
|
145
|
-
const seenKeys = new Set();
|
|
146
|
-
for (const rawEntry of fieldMeta) {
|
|
147
|
-
if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const key = normalizeText(rawEntry.key);
|
|
152
|
-
if (!key || seenKeys.has(key)) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
seenKeys.add(key);
|
|
156
|
-
normalized.push(rawEntry);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return normalized;
|
|
151
|
+
function resolveFieldDefinitionType(definition = {}) {
|
|
152
|
+
return normalizeText(definition?.type).toLowerCase();
|
|
160
153
|
}
|
|
161
154
|
|
|
162
|
-
function
|
|
163
|
-
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const type = normalizeText(schema.type).toLowerCase();
|
|
168
|
-
if (type === "string") {
|
|
169
|
-
return true;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const variants = Array.isArray(schema.anyOf)
|
|
173
|
-
? schema.anyOf
|
|
174
|
-
: Array.isArray(schema.oneOf)
|
|
175
|
-
? schema.oneOf
|
|
176
|
-
: [];
|
|
177
|
-
return variants.some((entry) => schemaIncludesStringType(entry));
|
|
155
|
+
function definitionIncludesStringType(definition = {}) {
|
|
156
|
+
return resolveFieldDefinitionType(definition) === "string";
|
|
178
157
|
}
|
|
179
158
|
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (normalizeText(schema.format).toLowerCase() === "date-time") {
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const variants = Array.isArray(schema.anyOf)
|
|
190
|
-
? schema.anyOf
|
|
191
|
-
: Array.isArray(schema.oneOf)
|
|
192
|
-
? schema.oneOf
|
|
193
|
-
: [];
|
|
194
|
-
return variants.some((entry) => schemaIncludesDateTimeFormat(entry));
|
|
159
|
+
function definitionIncludesDateTimeType(definition = {}) {
|
|
160
|
+
return resolveFieldDefinitionType(definition) === "datetime";
|
|
195
161
|
}
|
|
196
162
|
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const type = Array.isArray(schema.type)
|
|
203
|
-
? schema.type.map((entry) => normalizeText(entry).toLowerCase()).filter(Boolean)
|
|
204
|
-
: normalizeText(schema.type).toLowerCase();
|
|
205
|
-
const hasStringType = Array.isArray(type)
|
|
206
|
-
? type.includes("string")
|
|
207
|
-
: type === "string";
|
|
208
|
-
if (hasStringType && normalizeText(schema.pattern) === RECORD_ID_PATTERN) {
|
|
163
|
+
function definitionIncludesRecordIdType(definition = {}) {
|
|
164
|
+
const type = resolveFieldDefinitionType(definition);
|
|
165
|
+
if (type === "id") {
|
|
209
166
|
return true;
|
|
210
167
|
}
|
|
211
168
|
|
|
212
|
-
|
|
213
|
-
? schema.anyOf
|
|
214
|
-
: Array.isArray(schema.oneOf)
|
|
215
|
-
? schema.oneOf
|
|
216
|
-
: [];
|
|
217
|
-
return variants.some((entry) => schemaIncludesRecordIdType(entry));
|
|
169
|
+
return type === "string" && normalizeText(definition?.pattern) === RECORD_ID_PATTERN;
|
|
218
170
|
}
|
|
219
171
|
|
|
220
172
|
function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRepository" } = {}) {
|
|
@@ -227,20 +179,20 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
227
179
|
throw new TypeError(`${context} requires resource.operations.`);
|
|
228
180
|
}
|
|
229
181
|
|
|
230
|
-
const
|
|
231
|
-
const writeSchema = operations?.create?.bodyValidator?.schema;
|
|
232
|
-
const patchSchema = operations?.patch?.bodyValidator?.schema;
|
|
233
|
-
const outputProperties = requireObjectSchemaProperties(outputSchema, {
|
|
182
|
+
const outputProperties = requireFieldDefinitions(operations?.view?.output, {
|
|
234
183
|
context,
|
|
235
|
-
schemaLabel: "operations.view.
|
|
184
|
+
schemaLabel: "operations.view.output",
|
|
185
|
+
defaultMode: "replace"
|
|
236
186
|
});
|
|
237
|
-
const writeProperties =
|
|
187
|
+
const writeProperties = requireFieldDefinitions(operations?.create?.body, {
|
|
238
188
|
context,
|
|
239
|
-
schemaLabel: "operations.create.
|
|
189
|
+
schemaLabel: "operations.create.body",
|
|
190
|
+
defaultMode: "create"
|
|
240
191
|
});
|
|
241
|
-
const patchProperties =
|
|
192
|
+
const patchProperties = resolveOptionalFieldDefinitions(operations?.patch?.body, {
|
|
242
193
|
context,
|
|
243
|
-
schemaLabel: "operations.patch.
|
|
194
|
+
schemaLabel: "operations.patch.body",
|
|
195
|
+
defaultMode: "patch"
|
|
244
196
|
});
|
|
245
197
|
const lookupContainerKey = resolveCrudLookupContainerKey(resource, {
|
|
246
198
|
context: `${context} resource.contract.lookup.containerKey`
|
|
@@ -258,35 +210,33 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
258
210
|
const fieldStorageByKey = {};
|
|
259
211
|
const columnOverrides = {};
|
|
260
212
|
const writeSerializerByKey = {};
|
|
261
|
-
for (const entry of
|
|
213
|
+
for (const entry of Object.values(buildCrudFieldContractMap(resource, {
|
|
214
|
+
context: `${context} resource field contract`
|
|
215
|
+
}))) {
|
|
262
216
|
const key = normalizeText(entry.key);
|
|
263
217
|
if (!key) {
|
|
264
218
|
continue;
|
|
265
219
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
270
|
-
fieldStorageByKey[key] = repositoryConfig.storage;
|
|
271
|
-
if (repositoryConfig.column) {
|
|
272
|
-
columnOverrides[key] = repositoryConfig.column;
|
|
220
|
+
fieldStorageByKey[key] = entry?.storage?.mode || CRUD_FIELD_STORAGE_COLUMN;
|
|
221
|
+
if (entry?.storage?.column) {
|
|
222
|
+
columnOverrides[key] = entry.storage.column;
|
|
273
223
|
}
|
|
274
|
-
if (
|
|
275
|
-
writeSerializerByKey[key] =
|
|
224
|
+
if (entry?.storage?.writeSerializer) {
|
|
225
|
+
writeSerializerByKey[key] = entry.storage.writeSerializer;
|
|
276
226
|
}
|
|
277
227
|
}
|
|
278
228
|
|
|
279
229
|
for (const key of [...outputKeys, ...writeKeys]) {
|
|
280
230
|
if (!fieldStorageByKey[key]) {
|
|
281
|
-
fieldStorageByKey[key] =
|
|
231
|
+
fieldStorageByKey[key] = CRUD_FIELD_STORAGE_COLUMN;
|
|
282
232
|
}
|
|
283
233
|
}
|
|
284
234
|
|
|
285
235
|
const virtualOutputKeys = [];
|
|
286
236
|
const columnBackedOutputKeys = [];
|
|
287
237
|
for (const key of outputKeys) {
|
|
288
|
-
const storage = fieldStorageByKey[key] ||
|
|
289
|
-
if (storage ===
|
|
238
|
+
const storage = fieldStorageByKey[key] || CRUD_FIELD_STORAGE_COLUMN;
|
|
239
|
+
if (storage === CRUD_FIELD_STORAGE_VIRTUAL) {
|
|
290
240
|
virtualOutputKeys.push(key);
|
|
291
241
|
continue;
|
|
292
242
|
}
|
|
@@ -296,22 +246,22 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
296
246
|
for (const key of virtualOutputKeys) {
|
|
297
247
|
if (Object.hasOwn(writeProperties, key)) {
|
|
298
248
|
throw new Error(
|
|
299
|
-
`${context} resource create schema field "${key}" cannot use
|
|
249
|
+
`${context} resource create schema field "${key}" cannot use storage.virtual.`
|
|
300
250
|
);
|
|
301
251
|
}
|
|
302
252
|
if (Object.hasOwn(patchProperties, key)) {
|
|
303
253
|
throw new Error(
|
|
304
|
-
`${context} resource patch schema field "${key}" cannot use
|
|
254
|
+
`${context} resource patch schema field "${key}" cannot use storage.virtual.`
|
|
305
255
|
);
|
|
306
256
|
}
|
|
307
257
|
}
|
|
308
258
|
|
|
309
259
|
const listSearchColumns = [];
|
|
310
|
-
for (const [key,
|
|
311
|
-
if ((fieldStorageByKey[key] ||
|
|
260
|
+
for (const [key, definition] of Object.entries(outputProperties)) {
|
|
261
|
+
if ((fieldStorageByKey[key] || CRUD_FIELD_STORAGE_COLUMN) !== CRUD_FIELD_STORAGE_COLUMN) {
|
|
312
262
|
continue;
|
|
313
263
|
}
|
|
314
|
-
if (!
|
|
264
|
+
if (!definitionIncludesStringType(definition)) {
|
|
315
265
|
continue;
|
|
316
266
|
}
|
|
317
267
|
|
|
@@ -324,7 +274,7 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
324
274
|
|
|
325
275
|
const parentFilterColumns = {};
|
|
326
276
|
for (const key of resolveCrudLookupFieldKeys(resource, { allowKeys: writeKeys })) {
|
|
327
|
-
if ((fieldStorageByKey[key] ||
|
|
277
|
+
if ((fieldStorageByKey[key] || CRUD_FIELD_STORAGE_COLUMN) !== CRUD_FIELD_STORAGE_COLUMN) {
|
|
328
278
|
continue;
|
|
329
279
|
}
|
|
330
280
|
const columnName = resolveColumnName(key, columnOverrides);
|
|
@@ -335,14 +285,14 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
335
285
|
}
|
|
336
286
|
|
|
337
287
|
const outputRecordIdKeys = [];
|
|
338
|
-
for (const [key,
|
|
339
|
-
if (
|
|
288
|
+
for (const [key, definition] of Object.entries(outputProperties)) {
|
|
289
|
+
if (definitionIncludesRecordIdType(definition)) {
|
|
340
290
|
outputRecordIdKeys.push(key);
|
|
341
291
|
}
|
|
342
292
|
}
|
|
343
293
|
|
|
344
294
|
for (const key of writeKeys) {
|
|
345
|
-
if ((fieldStorageByKey[key] ||
|
|
295
|
+
if ((fieldStorageByKey[key] || CRUD_FIELD_STORAGE_COLUMN) !== CRUD_FIELD_STORAGE_COLUMN) {
|
|
346
296
|
continue;
|
|
347
297
|
}
|
|
348
298
|
|
|
@@ -350,12 +300,12 @@ function deriveRepositoryMappingFromResource(resource = {}, { context = "crudRep
|
|
|
350
300
|
continue;
|
|
351
301
|
}
|
|
352
302
|
|
|
353
|
-
const
|
|
354
|
-
if (!
|
|
303
|
+
const definition = writeProperties[key] || patchProperties[key];
|
|
304
|
+
if (!definitionIncludesDateTimeType(definition)) {
|
|
355
305
|
continue;
|
|
356
306
|
}
|
|
357
307
|
|
|
358
|
-
writeSerializerByKey[key] =
|
|
308
|
+
writeSerializerByKey[key] = CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC;
|
|
359
309
|
}
|
|
360
310
|
|
|
361
311
|
return Object.freeze({
|