@jskit-ai/resource-crud-core 0.1.187 → 0.1.188
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.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/resource-crud-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.188",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./shared/crudResource": "./src/shared/crudResource.js"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@jskit-ai/resource-core": "0.1.
|
|
16
|
+
"@jskit-ai/resource-core": "0.1.188"
|
|
17
17
|
},
|
|
18
18
|
"description": "Shared CRUD resource, field, lookup, filter, and namespace contracts.",
|
|
19
19
|
"jskit": {
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@jskit-ai/kernel": "0.1.
|
|
37
|
+
"@jskit-ai/kernel": "0.1.246"
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeSingleSchemaDefinition } from "@jskit-ai/kernel/shared/validators";
|
|
2
2
|
import {
|
|
3
3
|
normalizeObject,
|
|
4
4
|
normalizeText
|
|
@@ -71,7 +71,7 @@ function resolveCrudFieldSchemaProperties(value, { context = "crud resource fiel
|
|
|
71
71
|
return {};
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const normalized =
|
|
74
|
+
const normalized = normalizeSingleSchemaDefinition(value, {
|
|
75
75
|
context,
|
|
76
76
|
defaultMode: "patch"
|
|
77
77
|
});
|
|
@@ -424,6 +424,19 @@ function normalizeCrudListNumberRangeQueryValue(value) {
|
|
|
424
424
|
};
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
function parseCrudListFilterMultiQueryValue(value, normalizeEntry, { invalidValues }) {
|
|
428
|
+
const normalizedEntries = (Array.isArray(value) ? value : [value]).map(normalizeEntry);
|
|
429
|
+
const acceptedValues = Array.from(new Set(normalizedEntries.filter(Boolean)));
|
|
430
|
+
|
|
431
|
+
if (invalidValues === CRUD_LIST_FILTER_INVALID_VALUES_DISCARD) {
|
|
432
|
+
return acceptedValues.length > 0 ? acceptedValues : null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return acceptedValues.length > 0 && normalizedEntries.every(Boolean)
|
|
436
|
+
? acceptedValues
|
|
437
|
+
: INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
|
|
438
|
+
}
|
|
439
|
+
|
|
427
440
|
function parseCrudListFilterQueryValue(
|
|
428
441
|
filter = {},
|
|
429
442
|
value,
|
|
@@ -468,16 +481,11 @@ function parseCrudListFilterQueryValue(
|
|
|
468
481
|
}
|
|
469
482
|
|
|
470
483
|
if (type === CRUD_LIST_FILTER_TYPE_ENUM_MANY) {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return normalizedValues.length > 0 && normalizedValues.length === values.length
|
|
479
|
-
? normalizedValues
|
|
480
|
-
: INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
|
|
484
|
+
return parseCrudListFilterMultiQueryValue(
|
|
485
|
+
value,
|
|
486
|
+
(entry) => normalizeAllowedFilterTextValue(entry, allowedValues),
|
|
487
|
+
{ invalidValues }
|
|
488
|
+
);
|
|
481
489
|
}
|
|
482
490
|
|
|
483
491
|
if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID) {
|
|
@@ -487,16 +495,11 @@ function parseCrudListFilterQueryValue(
|
|
|
487
495
|
}
|
|
488
496
|
|
|
489
497
|
if (type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
return normalizedValues.length > 0 && normalizedValues.length === values.length
|
|
498
|
-
? normalizedValues
|
|
499
|
-
: INVALID_CRUD_LIST_FILTER_QUERY_VALUE;
|
|
498
|
+
return parseCrudListFilterMultiQueryValue(
|
|
499
|
+
value,
|
|
500
|
+
(entry) => normalizeCanonicalRecordIdText(entry, { fallback: "" }),
|
|
501
|
+
{ invalidValues }
|
|
502
|
+
);
|
|
500
503
|
}
|
|
501
504
|
|
|
502
505
|
if (type === CRUD_LIST_FILTER_TYPE_DATE) {
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createCursorListValidator,
|
|
4
4
|
RECORD_ID_PATTERN
|
|
5
5
|
} from "@jskit-ai/kernel/shared/validators";
|
|
6
|
-
import { buildCrudOperationSchemaFields } from "
|
|
6
|
+
import { buildCrudOperationSchemaFields } from "./crudFieldContract.js";
|
|
7
7
|
import { deepFreeze } from "@jskit-ai/kernel/shared/support/deepFreeze";
|
|
8
8
|
import { normalizeObject, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
9
9
|
import {
|
|
@@ -76,7 +76,9 @@ function createCrudRecordIdFieldDefinition() {
|
|
|
76
76
|
};
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
// Only an explicitly configured container key adds a lookup field to the derived output;
|
|
80
|
+
// unlike crudLookup.js resolveCrudLookupContainerKey, the default "lookups" key is not applied.
|
|
81
|
+
function resolveConfiguredLookupContainerKey(resource = {}) {
|
|
80
82
|
return normalizeText(resource?.contract?.lookup?.containerKey);
|
|
81
83
|
}
|
|
82
84
|
|
|
@@ -96,7 +98,7 @@ function createDerivedCrudRecordOutputDefinition(resource = {}) {
|
|
|
96
98
|
id: createCrudRecordIdFieldDefinition(),
|
|
97
99
|
...outputFields
|
|
98
100
|
};
|
|
99
|
-
const lookupContainerKey =
|
|
101
|
+
const lookupContainerKey = resolveConfiguredLookupContainerKey(resource);
|
|
100
102
|
|
|
101
103
|
if (lookupContainerKey) {
|
|
102
104
|
fields[lookupContainerKey] = {
|
|
@@ -110,7 +112,7 @@ function createDerivedCrudRecordOutputDefinition(resource = {}) {
|
|
|
110
112
|
});
|
|
111
113
|
}
|
|
112
114
|
|
|
113
|
-
function createDerivedCrudBodyDefinition(resource = {}, operationName = "patch") {
|
|
115
|
+
function createDerivedCrudBodyDefinition(resource = {}, operationName = "patch", bodyMode = "patch") {
|
|
114
116
|
let fields = resolveFieldEntries(resource, operationName);
|
|
115
117
|
|
|
116
118
|
if (operationName === "replace" && Object.keys(fields).length === 0) {
|
|
@@ -126,13 +128,7 @@ function createDerivedCrudBodyDefinition(resource = {}, operationName = "patch")
|
|
|
126
128
|
);
|
|
127
129
|
}
|
|
128
130
|
|
|
129
|
-
|
|
130
|
-
? "create"
|
|
131
|
-
: operationName === "replace"
|
|
132
|
-
? "replace"
|
|
133
|
-
: "patch";
|
|
134
|
-
|
|
135
|
-
return createSchemaDefinition(createSchema(fields), defaultMode, {
|
|
131
|
+
return createSchemaDefinition(createSchema(fields), bodyMode, {
|
|
136
132
|
context: `defineCrudResource derived ${operationName} body`
|
|
137
133
|
});
|
|
138
134
|
}
|
|
@@ -169,8 +165,7 @@ function requireCrudOperationSpec(operationName = "") {
|
|
|
169
165
|
}
|
|
170
166
|
|
|
171
167
|
function resolveCrudOperationNames(resource = {}) {
|
|
172
|
-
const
|
|
173
|
-
const configuredOperations = hasConfiguredOperations
|
|
168
|
+
const configuredOperations = Array.isArray(resource?.crudOperations)
|
|
174
169
|
? resource.crudOperations
|
|
175
170
|
: DEFAULT_CRUD_OPERATION_NAMES;
|
|
176
171
|
const names = [];
|
|
@@ -187,11 +182,7 @@ function resolveCrudOperationNames(resource = {}) {
|
|
|
187
182
|
names.push(operationName);
|
|
188
183
|
}
|
|
189
184
|
|
|
190
|
-
|
|
191
|
-
return names;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return names.length > 0 ? names : [...DEFAULT_CRUD_OPERATION_NAMES];
|
|
185
|
+
return names;
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
function resolveFirstPresentValue(source = {}, keys = []) {
|
|
@@ -271,7 +262,7 @@ function resolveCrudBodyDefinition(spec, resource = {}, crudConfig = {}) {
|
|
|
271
262
|
return explicitBody;
|
|
272
263
|
}
|
|
273
264
|
|
|
274
|
-
return createDerivedCrudBodyDefinition(resource, spec.bodyOperation);
|
|
265
|
+
return createDerivedCrudBodyDefinition(resource, spec.bodyOperation, spec.bodyMode);
|
|
275
266
|
}
|
|
276
267
|
|
|
277
268
|
function resolveCrudOutputDefinition(spec, resolveRecordOutputDefinition, crudConfig = {}) {
|
|
@@ -215,6 +215,35 @@ test("crud list range helpers parse and format single-key range expressions", ()
|
|
|
215
215
|
);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
+
test("multi-value filters accept repeated valid values in reject mode and return them once", () => {
|
|
219
|
+
const filters = defineCrudListFilters({
|
|
220
|
+
status: {
|
|
221
|
+
type: "enumMany",
|
|
222
|
+
label: "Status",
|
|
223
|
+
options: [
|
|
224
|
+
{ value: "active", label: "Active" },
|
|
225
|
+
{ value: "archived", label: "Archived" }
|
|
226
|
+
]
|
|
227
|
+
},
|
|
228
|
+
supplierContactId: {
|
|
229
|
+
type: "recordIdMany",
|
|
230
|
+
label: "Supplier"
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
const reject = { invalidValues: CRUD_LIST_FILTER_INVALID_VALUES_REJECT };
|
|
234
|
+
|
|
235
|
+
assert.deepEqual(parseCrudListFilterQueryValue(filters.status, ["active", "active"], reject), ["active"]);
|
|
236
|
+
assert.deepEqual(parseCrudListFilterQueryValue(filters.supplierContactId, ["5", "5", 7], reject), ["5", "7"]);
|
|
237
|
+
assert.equal(
|
|
238
|
+
parseCrudListFilterQueryValue(filters.status, ["active", "active", "unexpected"], reject),
|
|
239
|
+
INVALID_CRUD_LIST_FILTER_QUERY_VALUE
|
|
240
|
+
);
|
|
241
|
+
assert.equal(
|
|
242
|
+
parseCrudListFilterQueryValue(filters.supplierContactId, ["5", "", "5"], reject),
|
|
243
|
+
INVALID_CRUD_LIST_FILTER_QUERY_VALUE
|
|
244
|
+
);
|
|
245
|
+
});
|
|
246
|
+
|
|
218
247
|
test("crud list filter helpers share canonical UI and query normalization", () => {
|
|
219
248
|
const filters = defineCrudListFilters({
|
|
220
249
|
status: {
|