@jskit-ai/kernel 0.1.154 → 0.1.156

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.154",
3
+ "version": "0.1.156",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "json-rest-schema": "^1.0.17"
@@ -44,9 +44,6 @@
44
44
  "./shared/support/tokens": "./shared/support/tokens.js",
45
45
  "./shared/support/normalize": "./shared/support/normalize.js",
46
46
  "./shared/support/permissions": "./shared/support/permissions.js",
47
- "./shared/support/crudListFilters": "./shared/support/crudListFilters.js",
48
- "./shared/support/crudFieldContract": "./shared/support/crudFieldContract.js",
49
- "./shared/support/crudLookup": "./shared/support/crudLookup.js",
50
47
  "./shared/support/jsonApiFieldsets": "./shared/support/jsonApiFieldsets.js",
51
48
  "./shared/support/generatedUiContract": "./shared/support/generatedUiContract.js",
52
49
  "./shared/support/deepFreeze": "./shared/support/deepFreeze.js",
@@ -1,322 +0,0 @@
1
- import { normalizeSchemaDefinition } from "../validators/schemaDefinitions.js";
2
- import { normalizeObject, normalizeText } from "./normalize.js";
3
-
4
- const CRUD_FIELD_STORAGE_COLUMN = "column";
5
- const CRUD_FIELD_STORAGE_VIRTUAL = "virtual";
6
- const CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC = "datetime-utc";
7
- const CRUD_LOOKUP_FORM_CONTROL_AUTOCOMPLETE = "autocomplete";
8
- const CRUD_LOOKUP_FORM_CONTROL_SELECT = "select";
9
-
10
- function checkCrudLookupFormControl(
11
- value,
12
- {
13
- context = "crud field ui.formControl",
14
- defaultValue = CRUD_LOOKUP_FORM_CONTROL_AUTOCOMPLETE
15
- } = {}
16
- ) {
17
- const resolvedValue = value === undefined || value === null || value === "" ? defaultValue : value;
18
- if (resolvedValue === "") {
19
- return "";
20
- }
21
-
22
- if (
23
- resolvedValue === CRUD_LOOKUP_FORM_CONTROL_AUTOCOMPLETE ||
24
- resolvedValue === CRUD_LOOKUP_FORM_CONTROL_SELECT
25
- ) {
26
- return resolvedValue;
27
- }
28
-
29
- throw new Error(
30
- `${context} must be "${CRUD_LOOKUP_FORM_CONTROL_AUTOCOMPLETE}" or "${CRUD_LOOKUP_FORM_CONTROL_SELECT}". ` +
31
- `Received: ${JSON.stringify(resolvedValue)}.`
32
- );
33
- }
34
-
35
- function cloneStructuredFieldMetadata(value = {}) {
36
- if (!value || typeof value !== "object" || Array.isArray(value)) {
37
- return null;
38
- }
39
-
40
- const normalized = {};
41
- for (const [key, entry] of Object.entries(value)) {
42
- if (entry === undefined) {
43
- continue;
44
- }
45
-
46
- if (Array.isArray(entry)) {
47
- normalized[key] = entry.map((item) =>
48
- item && typeof item === "object" && !Array.isArray(item)
49
- ? cloneStructuredFieldMetadata(item) || {}
50
- : item
51
- );
52
- continue;
53
- }
54
-
55
- if (entry && typeof entry === "object") {
56
- normalized[key] = cloneStructuredFieldMetadata(entry) || {};
57
- continue;
58
- }
59
-
60
- normalized[key] = entry;
61
- }
62
-
63
- return Object.keys(normalized).length > 0 ? Object.freeze(normalized) : null;
64
- }
65
-
66
- function resolveCrudFieldSchemaProperties(value, { context = "crud resource field definitions" } = {}) {
67
- if (value == null) {
68
- return {};
69
- }
70
-
71
- const normalized = normalizeSchemaDefinition(value, {
72
- context,
73
- defaultMode: "patch"
74
- });
75
- if (!normalized) {
76
- return {};
77
- }
78
-
79
- return normalizeObject(normalized.schema.getFieldDefinitions());
80
- }
81
-
82
- function normalizeCrudFieldStorageConfig(
83
- fieldDefinition = {},
84
- {
85
- context = "crud field storage",
86
- fieldKey = ""
87
- } = {}
88
- ) {
89
- const normalizedFieldKey = normalizeText(fieldKey);
90
- const actualField = normalizeText(fieldDefinition.actualField);
91
- const storage = fieldDefinition?.storage;
92
-
93
- if (storage === undefined || storage === null) {
94
- return Object.freeze({
95
- mode: CRUD_FIELD_STORAGE_COLUMN,
96
- column: actualField,
97
- writeSerializer: ""
98
- });
99
- }
100
-
101
- if (!storage || typeof storage !== "object" || Array.isArray(storage)) {
102
- throw new TypeError(
103
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} must be an object when provided.`
104
- );
105
- }
106
-
107
- for (const storageKey of Object.keys(storage)) {
108
- if (storageKey !== "column" && storageKey !== "virtual" && storageKey !== "writeSerializer") {
109
- throw new Error(
110
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} does not support storage.${storageKey}.`
111
- );
112
- }
113
- }
114
-
115
- const column = normalizeText(storage.column) || actualField;
116
- const virtual = storage.virtual === true;
117
- const writeSerializer = normalizeText(storage.writeSerializer).toLowerCase();
118
-
119
- if (actualField && normalizeText(storage.column) && normalizeText(storage.column) !== actualField) {
120
- throw new Error(
121
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} actualField and storage.column must match when both are provided.`
122
- );
123
- }
124
-
125
- if (writeSerializer && writeSerializer !== CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC) {
126
- throw new Error(
127
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} storage.writeSerializer must be ` +
128
- `"${CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC}" when provided.`
129
- );
130
- }
131
-
132
- if (virtual && column) {
133
- throw new Error(
134
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} virtual fields cannot define actualField or storage.column.`
135
- );
136
- }
137
-
138
- if (virtual && writeSerializer) {
139
- throw new Error(
140
- `${context}${normalizedFieldKey ? `["${normalizedFieldKey}"]` : ""} virtual fields cannot define storage.writeSerializer.`
141
- );
142
- }
143
-
144
- return Object.freeze({
145
- mode: virtual ? CRUD_FIELD_STORAGE_VIRTUAL : CRUD_FIELD_STORAGE_COLUMN,
146
- column,
147
- writeSerializer
148
- });
149
- }
150
-
151
- function mergeFieldContractEntry(target, source, { context = "crud field contract", fieldKey = "" } = {}) {
152
- if (!source || typeof source !== "object") {
153
- return target;
154
- }
155
-
156
- const next = target ? { ...target } : {};
157
- const normalizedFieldKey = normalizeText(fieldKey);
158
-
159
- const mergeScalar = (key) => {
160
- const value = normalizeText(source[key]);
161
- if (!value) {
162
- return;
163
- }
164
- if (next[key] && next[key] !== value) {
165
- throw new Error(`${context}["${normalizedFieldKey}"] has conflicting ${key} metadata.`);
166
- }
167
- next[key] = value;
168
- };
169
-
170
- mergeScalar("actualField");
171
- mergeScalar("parentRouteParamKey");
172
-
173
- const storage = source.storage && typeof source.storage === "object" ? source.storage : null;
174
- if (storage) {
175
- const currentStorage = next.storage || {};
176
- if (storage.mode && currentStorage.mode && currentStorage.mode !== storage.mode) {
177
- throw new Error(`${context}["${normalizedFieldKey}"] has conflicting storage.mode metadata.`);
178
- }
179
- if (storage.column && currentStorage.column && currentStorage.column !== storage.column) {
180
- throw new Error(`${context}["${normalizedFieldKey}"] has conflicting storage.column metadata.`);
181
- }
182
- if (storage.writeSerializer &&
183
- currentStorage.writeSerializer &&
184
- currentStorage.writeSerializer !== storage.writeSerializer
185
- ) {
186
- throw new Error(`${context}["${normalizedFieldKey}"] has conflicting storage.writeSerializer metadata.`);
187
- }
188
- next.storage = {
189
- ...currentStorage,
190
- ...storage
191
- };
192
- }
193
-
194
- if (source.relation && typeof source.relation === "object") {
195
- next.relation = {
196
- ...(next.relation && typeof next.relation === "object" ? next.relation : {}),
197
- ...source.relation
198
- };
199
- }
200
-
201
- if (source.ui && typeof source.ui === "object") {
202
- next.ui = {
203
- ...(next.ui && typeof next.ui === "object" ? next.ui : {}),
204
- ...source.ui
205
- };
206
- }
207
-
208
- return next;
209
- }
210
-
211
- function buildCrudFieldContractMap(resource = {}, { context = "crud resource field contract" } = {}) {
212
- const sections = [
213
- resolveCrudFieldSchemaProperties(resource?.operations?.view?.output, {
214
- context: `${context}.operations.view.output`
215
- }),
216
- resolveCrudFieldSchemaProperties(resource?.operations?.create?.body, {
217
- context: `${context}.operations.create.body`
218
- }),
219
- resolveCrudFieldSchemaProperties(resource?.operations?.patch?.body, {
220
- context: `${context}.operations.patch.body`
221
- })
222
- ];
223
-
224
- const entries = {};
225
- for (const definitions of sections) {
226
- for (const [rawKey, rawDefinition] of Object.entries(definitions)) {
227
- const key = normalizeText(rawKey);
228
- if (!key) {
229
- continue;
230
- }
231
- const definition = normalizeObject(rawDefinition);
232
- const storage = normalizeCrudFieldStorageConfig(definition, {
233
- context: `${context}.storage`,
234
- fieldKey: key
235
- });
236
- const relation = cloneStructuredFieldMetadata(definition.relation);
237
- const ui = cloneStructuredFieldMetadata(definition.ui);
238
- const parentRouteParamKey =
239
- normalizeText(definition.parentRouteParamKey) ||
240
- normalizeText(definition?.relation?.parentRouteParamKey);
241
-
242
- entries[key] = mergeFieldContractEntry(entries[key], {
243
- actualField: normalizeText(definition.actualField),
244
- parentRouteParamKey,
245
- storage,
246
- relation,
247
- ui
248
- }, {
249
- context,
250
- fieldKey: key
251
- });
252
- }
253
- }
254
-
255
- return Object.freeze(
256
- Object.fromEntries(
257
- Object.entries(entries).map(([key, value]) => [
258
- key,
259
- Object.freeze({
260
- key,
261
- actualField: normalizeText(value.actualField),
262
- parentRouteParamKey: normalizeText(value.parentRouteParamKey),
263
- storage: Object.freeze({
264
- mode: normalizeText(value?.storage?.mode) || CRUD_FIELD_STORAGE_COLUMN,
265
- column: normalizeText(value?.storage?.column),
266
- writeSerializer: normalizeText(value?.storage?.writeSerializer).toLowerCase()
267
- }),
268
- relation: cloneStructuredFieldMetadata(value.relation),
269
- ui: cloneStructuredFieldMetadata(value.ui)
270
- })
271
- ])
272
- )
273
- );
274
- }
275
-
276
- function buildCrudOperationSchemaFields(fields = {}, operationName = "") {
277
- const definitions = {};
278
-
279
- for (const [fieldKey, fieldDefinition] of Object.entries(fields)) {
280
- const operationConfig = fieldDefinition?.operations?.[operationName];
281
- if (!operationConfig) {
282
- continue;
283
- }
284
-
285
- const nextDefinition = {
286
- ...fieldDefinition
287
- };
288
- delete nextDefinition.operations;
289
-
290
- if (operationConfig !== true) {
291
- Object.assign(nextDefinition, operationConfig);
292
- }
293
-
294
- definitions[fieldKey] = nextDefinition;
295
- }
296
-
297
- return definitions;
298
- }
299
-
300
- function resolveCrudFieldContractEntry(resource = {}, fieldKey = "", options = {}) {
301
- const normalizedFieldKey = normalizeText(fieldKey);
302
- if (!normalizedFieldKey) {
303
- return null;
304
- }
305
-
306
- const entries = buildCrudFieldContractMap(resource, options);
307
- return entries[normalizedFieldKey] || null;
308
- }
309
-
310
- export {
311
- CRUD_FIELD_STORAGE_COLUMN,
312
- CRUD_FIELD_STORAGE_VIRTUAL,
313
- CRUD_FIELD_WRITE_SERIALIZER_DATETIME_UTC,
314
- CRUD_LOOKUP_FORM_CONTROL_AUTOCOMPLETE,
315
- CRUD_LOOKUP_FORM_CONTROL_SELECT,
316
- checkCrudLookupFormControl,
317
- resolveCrudFieldSchemaProperties,
318
- normalizeCrudFieldStorageConfig,
319
- buildCrudOperationSchemaFields,
320
- buildCrudFieldContractMap,
321
- resolveCrudFieldContractEntry
322
- };
@@ -1,67 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import test from "node:test";
3
-
4
- import { buildCrudOperationSchemaFields } from "./crudFieldContract.js";
5
-
6
- test("buildCrudOperationSchemaFields projects operation-aware field definitions without losing metadata", () => {
7
- const serializer = () => "serialized";
8
- const fields = Object.freeze({
9
- name: Object.freeze({
10
- type: "string",
11
- maxLength: 190,
12
- search: true,
13
- ui: Object.freeze({
14
- label: "Name"
15
- }),
16
- operations: Object.freeze({
17
- output: Object.freeze({
18
- required: true
19
- }),
20
- create: Object.freeze({
21
- required: true
22
- }),
23
- patch: Object.freeze({
24
- required: false
25
- })
26
- })
27
- }),
28
- createdAt: Object.freeze({
29
- type: "dateTime",
30
- storage: Object.freeze({
31
- column: "created_at",
32
- writeSerializer: "datetime-utc",
33
- serialize: serializer
34
- }),
35
- operations: Object.freeze({
36
- output: Object.freeze({
37
- required: true
38
- })
39
- })
40
- }),
41
- workspaceId: Object.freeze({
42
- type: "id",
43
- required: true,
44
- operations: Object.freeze({})
45
- })
46
- });
47
-
48
- const outputFields = buildCrudOperationSchemaFields(fields, "output");
49
- const createFields = buildCrudOperationSchemaFields(fields, "create");
50
-
51
- assert.deepEqual(Object.keys(outputFields).sort(), ["createdAt", "name"]);
52
- assert.deepEqual(Object.keys(createFields).sort(), ["name"]);
53
-
54
- assert.equal(outputFields.name.required, true);
55
- assert.equal(outputFields.name.maxLength, 190);
56
- assert.equal(outputFields.name.search, true);
57
- assert.equal(outputFields.name.ui.label, "Name");
58
- assert.equal(Object.hasOwn(outputFields.name, "operations"), false);
59
-
60
- assert.equal(outputFields.createdAt.storage.column, "created_at");
61
- assert.equal(outputFields.createdAt.storage.writeSerializer, "datetime-utc");
62
- assert.equal(outputFields.createdAt.storage.serialize, serializer);
63
-
64
- assert.equal(createFields.name.required, true);
65
- assert.equal(fields.name.operations.output.required, true);
66
- assert.equal(Object.hasOwn(fields.name, "operations"), true);
67
- });