@jskit-ai/kernel 0.1.134 → 0.1.136
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/kernel",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.136",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"json-rest-schema": "1.x.x"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"./shared/support/crudListFilters": "./shared/support/crudListFilters.js",
|
|
48
48
|
"./shared/support/crudFieldContract": "./shared/support/crudFieldContract.js",
|
|
49
49
|
"./shared/support/crudLookup": "./shared/support/crudLookup.js",
|
|
50
|
+
"./shared/support/jsonApiFieldsets": "./shared/support/jsonApiFieldsets.js",
|
|
50
51
|
"./shared/support/generatedUiContract": "./shared/support/generatedUiContract.js",
|
|
51
52
|
"./shared/support/deepFreeze": "./shared/support/deepFreeze.js",
|
|
52
53
|
"./shared/support/listenerSet": "./shared/support/listenerSet.js",
|
|
@@ -219,7 +219,7 @@ function normalizeCrudListNumberRangeUiValue(rawValue) {
|
|
|
219
219
|
};
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
function
|
|
222
|
+
function createCrudListFilterEmptyValue(filter = {}) {
|
|
223
223
|
if (normalizeCrudListFilterType(filter.type) === CRUD_LIST_FILTER_TYPE_FLAG) {
|
|
224
224
|
return false;
|
|
225
225
|
}
|
|
@@ -242,6 +242,14 @@ function createCrudListFilterInitialValue(filter = {}) {
|
|
|
242
242
|
return "";
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
function createCrudListFilterInitialValue(filter = {}) {
|
|
246
|
+
if (!Object.hasOwn(filter, "defaultValue")) {
|
|
247
|
+
return createCrudListFilterEmptyValue(filter);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return normalizeCrudListFilterUiValue(filter, filter.defaultValue);
|
|
251
|
+
}
|
|
252
|
+
|
|
245
253
|
function isCrudListFilterMultiValue(filter = {}) {
|
|
246
254
|
const type = normalizeCrudListFilterType(filter.type);
|
|
247
255
|
return type === CRUD_LIST_FILTER_TYPE_ENUM_MANY || type === CRUD_LIST_FILTER_TYPE_RECORD_ID_MANY;
|
|
@@ -745,6 +753,13 @@ function normalizeCrudListFilterDefinition(rawKey = "", rawDefinition = null) {
|
|
|
745
753
|
const meta = source.meta && typeof source.meta === "object" && !Array.isArray(source.meta)
|
|
746
754
|
? deepFreeze({ ...source.meta })
|
|
747
755
|
: null;
|
|
756
|
+
const hasDefaultValue = Object.hasOwn(source, "defaultValue");
|
|
757
|
+
const defaultValue = hasDefaultValue
|
|
758
|
+
? deepFreeze(normalizeCrudListFilterUiValue({
|
|
759
|
+
type,
|
|
760
|
+
options
|
|
761
|
+
}, source.defaultValue))
|
|
762
|
+
: undefined;
|
|
748
763
|
|
|
749
764
|
if (type === CRUD_LIST_FILTER_TYPE_DATE_RANGE) {
|
|
750
765
|
if (normalizeText(source.fromKey) || normalizeText(source.toKey)) {
|
|
@@ -767,7 +782,8 @@ function normalizeCrudListFilterDefinition(rawKey = "", rawDefinition = null) {
|
|
|
767
782
|
lookup,
|
|
768
783
|
chipLabel,
|
|
769
784
|
ui,
|
|
770
|
-
meta
|
|
785
|
+
meta,
|
|
786
|
+
...(hasDefaultValue ? { defaultValue } : {})
|
|
771
787
|
});
|
|
772
788
|
}
|
|
773
789
|
|
|
@@ -823,6 +839,7 @@ export {
|
|
|
823
839
|
parseCrudListRangeQueryExpression,
|
|
824
840
|
formatCrudListRangeQueryExpression,
|
|
825
841
|
defineCrudListFilters,
|
|
842
|
+
createCrudListFilterEmptyValue,
|
|
826
843
|
createCrudListFilterInitialValue,
|
|
827
844
|
isCrudListFilterMultiValue,
|
|
828
845
|
isCrudListFilterStructuredValue,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
INVALID_CRUD_LIST_FILTER_QUERY_VALUE,
|
|
8
8
|
parseCrudListRangeQueryExpression,
|
|
9
9
|
formatCrudListRangeQueryExpression,
|
|
10
|
+
createCrudListFilterEmptyValue,
|
|
10
11
|
createCrudListFilterInitialValue,
|
|
11
12
|
isCrudListFilterMultiValue,
|
|
12
13
|
isCrudListFilterStructuredValue,
|
|
@@ -110,6 +111,23 @@ test("defineCrudListFilters uses fixed presence semantics with optional label ov
|
|
|
110
111
|
]);
|
|
111
112
|
});
|
|
112
113
|
|
|
114
|
+
test("defineCrudListFilters normalizes declarative defaults without changing the empty state", () => {
|
|
115
|
+
const filters = defineCrudListFilters({
|
|
116
|
+
status: {
|
|
117
|
+
type: "enum",
|
|
118
|
+
options: [
|
|
119
|
+
{ value: "active", label: "Active" },
|
|
120
|
+
{ value: "archived", label: "Archived" }
|
|
121
|
+
],
|
|
122
|
+
defaultValue: "active"
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
assert.equal(filters.status.defaultValue, "active");
|
|
127
|
+
assert.equal(createCrudListFilterInitialValue(filters.status), "active");
|
|
128
|
+
assert.equal(createCrudListFilterEmptyValue(filters.status), "");
|
|
129
|
+
});
|
|
130
|
+
|
|
113
131
|
test("defineCrudListFilters rejects duplicate query keys", () => {
|
|
114
132
|
assert.throws(
|
|
115
133
|
() => defineCrudListFilters({
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { deepFreeze } from "./deepFreeze.js";
|
|
2
|
+
import { normalizeText } from "./normalize.js";
|
|
3
|
+
|
|
4
|
+
function normalizeJsonApiFieldList(value = []) {
|
|
5
|
+
const values = Array.isArray(value) ? value : [value];
|
|
6
|
+
const fields = new Set();
|
|
7
|
+
|
|
8
|
+
for (const entry of values) {
|
|
9
|
+
const parts = typeof entry === "string" ? entry.split(",") : [entry];
|
|
10
|
+
for (const part of parts) {
|
|
11
|
+
const field = normalizeText(part);
|
|
12
|
+
if (field) {
|
|
13
|
+
fields.add(field);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return Object.freeze([...fields].sort((left, right) => left.localeCompare(right)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeJsonApiFieldsets(value = {}, { primaryType = "" } = {}) {
|
|
22
|
+
const normalizedPrimaryType = normalizeText(primaryType);
|
|
23
|
+
let source = {};
|
|
24
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
25
|
+
source = value;
|
|
26
|
+
} else if (normalizedPrimaryType) {
|
|
27
|
+
source = { [normalizedPrimaryType]: value };
|
|
28
|
+
}
|
|
29
|
+
const fieldsets = [];
|
|
30
|
+
|
|
31
|
+
for (const [rawType, rawFields] of Object.entries(source)) {
|
|
32
|
+
const type = normalizeText(rawType);
|
|
33
|
+
const fields = normalizeJsonApiFieldList(rawFields);
|
|
34
|
+
if (!type || fields.length < 1) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
fieldsets.push([type, fields]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return deepFreeze(
|
|
41
|
+
Object.fromEntries(
|
|
42
|
+
fieldsets.sort(([left], [right]) => left.localeCompare(right))
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildJsonApiFieldsetsToken(value = {}) {
|
|
48
|
+
const fieldsets = normalizeJsonApiFieldsets(value);
|
|
49
|
+
return Object.keys(fieldsets).length > 0 ? JSON.stringify(fieldsets) : "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
normalizeJsonApiFieldList,
|
|
54
|
+
normalizeJsonApiFieldsets,
|
|
55
|
+
buildJsonApiFieldsetsToken
|
|
56
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
normalizeJsonApiFieldList,
|
|
6
|
+
normalizeJsonApiFieldsets,
|
|
7
|
+
buildJsonApiFieldsetsToken
|
|
8
|
+
} from "./jsonApiFieldsets.js";
|
|
9
|
+
|
|
10
|
+
test("JSON:API fieldsets normalize comma lists into a stable typed map", () => {
|
|
11
|
+
assert.deepEqual(
|
|
12
|
+
normalizeJsonApiFieldsets({
|
|
13
|
+
jobs: ["status", "id,status"],
|
|
14
|
+
contacts: "displayName,id"
|
|
15
|
+
}),
|
|
16
|
+
{
|
|
17
|
+
contacts: ["displayName", "id"],
|
|
18
|
+
jobs: ["id", "status"]
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
assert.deepEqual(normalizeJsonApiFieldList(["name", "", "id,name"]), ["id", "name"]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("JSON:API fieldsets produce deterministic query tokens", () => {
|
|
25
|
+
const fieldsets = {
|
|
26
|
+
jobs: ["status", "id"],
|
|
27
|
+
contacts: ["id"]
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
assert.equal(
|
|
31
|
+
buildJsonApiFieldsetsToken(fieldsets),
|
|
32
|
+
buildJsonApiFieldsetsToken({
|
|
33
|
+
contacts: ["id"],
|
|
34
|
+
jobs: ["id", "status"]
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
assert.equal(buildJsonApiFieldsetsToken({}), "");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("JSON:API fieldset resource types cannot mutate object prototypes", () => {
|
|
41
|
+
const fieldsets = normalizeJsonApiFieldsets(
|
|
42
|
+
Object.fromEntries([
|
|
43
|
+
["__proto__", ["id", "name"]]
|
|
44
|
+
])
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
assert.equal(Object.getPrototypeOf(fieldsets), Object.prototype);
|
|
48
|
+
assert.equal(Object.hasOwn(fieldsets, "__proto__"), true);
|
|
49
|
+
assert.deepEqual(fieldsets.__proto__, ["id", "name"]);
|
|
50
|
+
});
|