@jskit-ai/crud-ui-generator 0.1.38 → 0.1.40
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 +2 -2
- package/package.json +4 -3
- package/src/server/buildTemplateContext.js +29 -15
- package/src/server/resourceSupport.js +147 -56
- package/src/server/subcommands/addField.js +10 -10
- package/templates/src/pages/admin/ui-generator/AddEditForm.vue +2 -1
- package/templates/src/pages/admin/ui-generator/EditElement.vue +10 -1
- package/templates/src/pages/admin/ui-generator/EditWrapperElement.vue +7 -0
- package/templates/src/pages/admin/ui-generator/ListElement.vue +17 -5
- package/templates/src/pages/admin/ui-generator/NewElement.vue +11 -1
- package/templates/src/pages/admin/ui-generator/NewWrapperElement.vue +7 -0
- package/templates/src/pages/admin/ui-generator/ViewElement.vue +9 -2
- package/test/addFieldSubcommand.test.js +54 -62
- package/test/buildTemplateContext.test.js +244 -255
package/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/crud-ui-generator",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.40",
|
|
5
5
|
kind: "generator",
|
|
6
6
|
description: "Generate CRUD route trees from resource validators at an explicit route root relative to src/pages/.",
|
|
7
7
|
options: {
|
|
@@ -168,7 +168,7 @@ export default Object.freeze({
|
|
|
168
168
|
mutations: {
|
|
169
169
|
dependencies: {
|
|
170
170
|
runtime: {
|
|
171
|
-
"@jskit-ai/users-web": "0.1.
|
|
171
|
+
"@jskit-ai/users-web": "0.1.72"
|
|
172
172
|
},
|
|
173
173
|
dev: {}
|
|
174
174
|
},
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/crud-ui-generator",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@jskit-ai/crud-core": "0.1.
|
|
10
|
-
"@jskit-ai/kernel": "0.1.
|
|
9
|
+
"@jskit-ai/crud-core": "0.1.65",
|
|
10
|
+
"@jskit-ai/kernel": "0.1.57",
|
|
11
|
+
"@jskit-ai/resource-crud-core": "0.1.2"
|
|
11
12
|
},
|
|
12
13
|
"exports": {
|
|
13
14
|
"./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
requireCrudNamespace
|
|
3
|
-
} from "@jskit-ai/crud-core/shared/crudNamespaceSupport";
|
|
3
|
+
} from "@jskit-ai/resource-crud-core/shared/crudNamespaceSupport";
|
|
4
4
|
import {
|
|
5
5
|
normalizePagesRelativeTargetRoot,
|
|
6
6
|
resolvePageLinkTargetDetails,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
requireObjectProperties,
|
|
17
17
|
resolveListItemProperties,
|
|
18
18
|
resolveLookupContainerKey,
|
|
19
|
-
|
|
19
|
+
buildResourceFieldContractMap,
|
|
20
20
|
createFieldDefinitions,
|
|
21
21
|
createFormFieldDefinitions,
|
|
22
22
|
resolveNearestParentRouteParamKey,
|
|
@@ -379,8 +379,21 @@ function ensureFields(fields, fallbackFields = createFieldDefinitions({})) {
|
|
|
379
379
|
return fallbackFields;
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
-
function resolveViewTitleFallbackFieldKey(fields = []) {
|
|
382
|
+
function resolveViewTitleFallbackFieldKey(fields = [], { recordIdFieldKey = "" } = {}) {
|
|
383
383
|
const sourceFields = Array.isArray(fields) ? fields : [];
|
|
384
|
+
const normalizedRecordIdFieldKey = normalizeText(recordIdFieldKey);
|
|
385
|
+
|
|
386
|
+
for (const field of sourceFields) {
|
|
387
|
+
if (normalizeText(field?.type).toLowerCase() !== "string") {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const key = normalizeText(field?.key);
|
|
392
|
+
if (key && key !== normalizedRecordIdFieldKey) {
|
|
393
|
+
return key;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
384
397
|
for (const field of sourceFields) {
|
|
385
398
|
if (normalizeText(field?.type).toLowerCase() !== "string") {
|
|
386
399
|
continue;
|
|
@@ -468,7 +481,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
468
481
|
const lookupContainerKey = resolveLookupContainerKey(resource, {
|
|
469
482
|
context: "crud-ui-generator"
|
|
470
483
|
});
|
|
471
|
-
const
|
|
484
|
+
const fieldContractMap = buildResourceFieldContractMap(resource);
|
|
472
485
|
|
|
473
486
|
const hasListOperation = selectedOperations.has("list");
|
|
474
487
|
const hasViewOperation = selectedOperations.has("view");
|
|
@@ -485,7 +498,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
485
498
|
context: "crud-ui-generator operations.list.realtime"
|
|
486
499
|
});
|
|
487
500
|
listFieldsAll = createFieldDefinitions(resolveListItemProperties(listOutputSchema, { context: "crud-ui-generator" }), {
|
|
488
|
-
|
|
501
|
+
fieldContractMap,
|
|
489
502
|
lookupContainerKey
|
|
490
503
|
});
|
|
491
504
|
validateDisplayFieldsForOperation(selectedDisplayFields, listFieldsAll, "list");
|
|
@@ -498,7 +511,7 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
498
511
|
viewFieldsAll = createFieldDefinitions(
|
|
499
512
|
requireObjectProperties(viewOutputSchema, "operations.view output", { context: "crud-ui-generator" }),
|
|
500
513
|
{
|
|
501
|
-
|
|
514
|
+
fieldContractMap,
|
|
502
515
|
lookupContainerKey
|
|
503
516
|
}
|
|
504
517
|
);
|
|
@@ -508,11 +521,11 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
508
521
|
let createFieldsAll = [];
|
|
509
522
|
if (hasNewOperation) {
|
|
510
523
|
const createOperation = requireOperation(resource, "create", { context: "crud-ui-generator" });
|
|
511
|
-
const
|
|
524
|
+
const createInputSchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator" });
|
|
512
525
|
createFieldsAll = createFormFieldDefinitions(
|
|
513
|
-
requireObjectProperties(
|
|
526
|
+
requireObjectProperties(createInputSchema, "operations.create body", { context: "crud-ui-generator" }),
|
|
514
527
|
{
|
|
515
|
-
|
|
528
|
+
fieldContractMap,
|
|
516
529
|
lookupContainerKey,
|
|
517
530
|
parentRouteParamKey
|
|
518
531
|
}
|
|
@@ -523,11 +536,11 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
523
536
|
let editFieldsAll = [];
|
|
524
537
|
if (hasEditOperation) {
|
|
525
538
|
const patchOperation = requireOperation(resource, "patch", { context: "crud-ui-generator" });
|
|
526
|
-
const
|
|
539
|
+
const patchInputSchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator" });
|
|
527
540
|
editFieldsAll = createFormFieldDefinitions(
|
|
528
|
-
requireObjectProperties(
|
|
541
|
+
requireObjectProperties(patchInputSchema, "operations.patch body", { context: "crud-ui-generator" }),
|
|
529
542
|
{
|
|
530
|
-
|
|
543
|
+
fieldContractMap,
|
|
531
544
|
lookupContainerKey,
|
|
532
545
|
parentRouteParamKey
|
|
533
546
|
}
|
|
@@ -556,9 +569,6 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
556
569
|
const editFields = hasEditOperation
|
|
557
570
|
? filterDisplayFields(selectedDisplayFields, editFieldsAll)
|
|
558
571
|
: [];
|
|
559
|
-
const viewTitleFallbackFieldKey = hasViewOperation
|
|
560
|
-
? resolveViewTitleFallbackFieldKey(viewFieldsAll)
|
|
561
|
-
: "";
|
|
562
572
|
const recordIdFields =
|
|
563
573
|
listFieldsAll.length > 0
|
|
564
574
|
? listFieldsAll
|
|
@@ -567,6 +577,10 @@ async function buildUiTemplateContext({ appRoot, options } = {}) {
|
|
|
567
577
|
: editFieldsAll.length > 0
|
|
568
578
|
? editFieldsAll
|
|
569
579
|
: createFieldDefinitions({});
|
|
580
|
+
const recordIdFieldKey = resolveRecordIdFieldKey(recordIdFields);
|
|
581
|
+
const viewTitleFallbackFieldKey = hasViewOperation
|
|
582
|
+
? resolveViewTitleFallbackFieldKey(viewFieldsAll, { recordIdFieldKey })
|
|
583
|
+
: "";
|
|
570
584
|
|
|
571
585
|
const pageLinkTarget = hasListOperation
|
|
572
586
|
? await resolvePageLinkTargetDetails({
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { resolveCrudRecordChangedEvent } from "@jskit-ai/crud-core/shared/crudNamespaceSupport";
|
|
2
|
+
import { resolveCrudRecordChangedEvent } from "@jskit-ai/resource-crud-core/shared/crudNamespaceSupport";
|
|
3
3
|
import {
|
|
4
4
|
checkCrudLookupFormControl,
|
|
5
5
|
isCrudRuntimeOutputOnlyFieldKey
|
|
6
|
-
} from "@jskit-ai/crud-core/shared/
|
|
6
|
+
} from "@jskit-ai/crud-core/shared/crudFieldSupport";
|
|
7
7
|
import { importFreshModuleFromAbsolutePath } from "@jskit-ai/kernel/server/support";
|
|
8
8
|
import {
|
|
9
9
|
normalizeCrudLookupApiPath,
|
|
@@ -12,10 +12,13 @@ import {
|
|
|
12
12
|
resolveCrudLookupApiPathFromNamespace,
|
|
13
13
|
resolveCrudLookupContainerKey
|
|
14
14
|
} from "@jskit-ai/kernel/shared/support/crudLookup";
|
|
15
|
+
import { buildCrudFieldContractMap } from "@jskit-ai/kernel/shared/support/crudFieldContract";
|
|
16
|
+
import { normalizeSchemaDefinition } from "@jskit-ai/kernel/shared/validators";
|
|
15
17
|
import { normalizeSurfaceId } from "@jskit-ai/kernel/shared/surface/registry";
|
|
16
18
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
17
19
|
|
|
18
20
|
const JS_IDENTIFIER_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
21
|
+
const JSON_REST_TRANSPORT_EXTENSION_KEY = "x-json-rest-schema";
|
|
19
22
|
|
|
20
23
|
function requireOption(options, optionName, { context = "ui-generator" } = {}) {
|
|
21
24
|
const value = normalizeText(options?.[optionName]);
|
|
@@ -151,36 +154,56 @@ function resolveOperationRealtimeEvents(
|
|
|
151
154
|
return normalizedEvents.length > 0 ? normalizedEvents : fallbackEvents;
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
function resolveOperationTransportSchema(definition, {
|
|
158
|
+
context = "ui-generator",
|
|
159
|
+
defaultMode = "replace",
|
|
160
|
+
label = "schema definition"
|
|
161
|
+
} = {}) {
|
|
162
|
+
const normalized = normalizeSchemaDefinition(definition, {
|
|
163
|
+
context: `${context} ${label}`,
|
|
164
|
+
defaultMode
|
|
165
|
+
});
|
|
166
|
+
if (!normalized?.schema) {
|
|
167
|
+
throw new Error(`${context} ${label} is missing schema.`);
|
|
158
168
|
}
|
|
159
169
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
170
|
+
return normalized.schema.toJsonSchema({
|
|
171
|
+
mode: normalized.mode
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function requireOutputSchema(operation, operationName, { context = "ui-generator" } = {}) {
|
|
176
|
+
const output = operation?.output;
|
|
177
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) {
|
|
178
|
+
throw new Error(`${context} resource operations.${operationName} is missing output.`);
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
return
|
|
181
|
+
return resolveOperationTransportSchema(output, {
|
|
182
|
+
context,
|
|
183
|
+
defaultMode: "replace",
|
|
184
|
+
label: `resource operations.${operationName}.output`
|
|
185
|
+
});
|
|
166
186
|
}
|
|
167
187
|
|
|
168
188
|
function requireBodySchema(operation, operationName, { context = "ui-generator" } = {}) {
|
|
169
|
-
const
|
|
170
|
-
if (!
|
|
171
|
-
throw new Error(`${context} resource operations.${operationName} is missing
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const schema = bodyValidator.schema;
|
|
175
|
-
if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
176
|
-
throw new Error(`${context} resource operations.${operationName}.bodyValidator is missing schema.`);
|
|
189
|
+
const body = operation?.body;
|
|
190
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
191
|
+
throw new Error(`${context} resource operations.${operationName} is missing body.`);
|
|
177
192
|
}
|
|
178
193
|
|
|
179
|
-
return
|
|
194
|
+
return resolveOperationTransportSchema(body, {
|
|
195
|
+
context,
|
|
196
|
+
defaultMode: operationName === "create" ? "create" : "patch",
|
|
197
|
+
label: `resource operations.${operationName}.body`
|
|
198
|
+
});
|
|
180
199
|
}
|
|
181
200
|
|
|
182
|
-
function requireObjectProperties(schema, contextLabel, { context = "ui-generator" } = {}) {
|
|
183
|
-
const
|
|
201
|
+
function requireObjectProperties(schema, contextLabel, { context = "ui-generator", rootSchema = schema } = {}) {
|
|
202
|
+
const resolvedSchema = resolveObjectSchema(schema, contextLabel, {
|
|
203
|
+
context,
|
|
204
|
+
rootSchema
|
|
205
|
+
});
|
|
206
|
+
const properties = resolvedSchema?.properties;
|
|
184
207
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) {
|
|
185
208
|
throw new Error(`${context} expected ${contextLabel} to be an object schema with properties.`);
|
|
186
209
|
}
|
|
@@ -189,7 +212,10 @@ function requireObjectProperties(schema, contextLabel, { context = "ui-generator
|
|
|
189
212
|
}
|
|
190
213
|
|
|
191
214
|
function resolveListItemProperties(listOutputSchema, { context = "ui-generator" } = {}) {
|
|
192
|
-
const listProperties = requireObjectProperties(listOutputSchema, "operations.list output", {
|
|
215
|
+
const listProperties = requireObjectProperties(listOutputSchema, "operations.list output", {
|
|
216
|
+
context,
|
|
217
|
+
rootSchema: listOutputSchema
|
|
218
|
+
});
|
|
193
219
|
const itemsSchema = listProperties.items;
|
|
194
220
|
if (!itemsSchema || typeof itemsSchema !== "object" || Array.isArray(itemsSchema)) {
|
|
195
221
|
throw new Error(`${context} expected operations.list output schema to include object items schema.`);
|
|
@@ -200,7 +226,10 @@ function resolveListItemProperties(listOutputSchema, { context = "ui-generator"
|
|
|
200
226
|
throw new Error(`${context} expected operations.list output schema items.items to be an object schema.`);
|
|
201
227
|
}
|
|
202
228
|
|
|
203
|
-
return requireObjectProperties(itemSchema, "operations.list output items", {
|
|
229
|
+
return requireObjectProperties(itemSchema, "operations.list output items", {
|
|
230
|
+
context,
|
|
231
|
+
rootSchema: listOutputSchema
|
|
232
|
+
});
|
|
204
233
|
}
|
|
205
234
|
|
|
206
235
|
function resolveUnionSchemaVariant(schema = {}) {
|
|
@@ -235,6 +264,68 @@ function resolveUnionSchemaVariant(schema = {}) {
|
|
|
235
264
|
return source;
|
|
236
265
|
}
|
|
237
266
|
|
|
267
|
+
function decodeJsonPointerSegment(segment = "") {
|
|
268
|
+
return String(segment || "")
|
|
269
|
+
.replace(/~1/g, "/")
|
|
270
|
+
.replace(/~0/g, "~");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function resolveSchemaReference(ref = "", rootSchema = {}, { context = "ui-generator", contextLabel = "schema" } = {}) {
|
|
274
|
+
const normalizedRef = normalizeText(ref);
|
|
275
|
+
if (!normalizedRef.startsWith("#/")) {
|
|
276
|
+
throw new Error(`${context} expected ${contextLabel} to use an internal schema reference.`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const segments = normalizedRef
|
|
280
|
+
.slice(2)
|
|
281
|
+
.split("/")
|
|
282
|
+
.map((segment) => decodeJsonPointerSegment(segment))
|
|
283
|
+
.filter(Boolean);
|
|
284
|
+
|
|
285
|
+
let current = rootSchema;
|
|
286
|
+
for (const segment of segments) {
|
|
287
|
+
if (!current || typeof current !== "object" || Array.isArray(current) || !Object.hasOwn(current, segment)) {
|
|
288
|
+
throw new Error(`${context} could not resolve ${contextLabel} reference "${normalizedRef}".`);
|
|
289
|
+
}
|
|
290
|
+
current = current[segment];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return current;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function resolveObjectSchema(
|
|
297
|
+
schema = {},
|
|
298
|
+
contextLabel,
|
|
299
|
+
{ context = "ui-generator", rootSchema = schema } = {}
|
|
300
|
+
) {
|
|
301
|
+
const source = resolveUnionSchemaVariant(schema);
|
|
302
|
+
if (source?.properties && typeof source.properties === "object" && !Array.isArray(source.properties)) {
|
|
303
|
+
return source;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const refCandidate = normalizeText(source?.$ref) || normalizeText(source?.allOf?.[0]?.$ref);
|
|
307
|
+
if (refCandidate) {
|
|
308
|
+
return resolveObjectSchema(
|
|
309
|
+
resolveSchemaReference(refCandidate, rootSchema, {
|
|
310
|
+
context,
|
|
311
|
+
contextLabel
|
|
312
|
+
}),
|
|
313
|
+
contextLabel,
|
|
314
|
+
{
|
|
315
|
+
context,
|
|
316
|
+
rootSchema
|
|
317
|
+
}
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return source;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function resolveJsonRestCastType(schema = {}) {
|
|
325
|
+
const source = schema && typeof schema === "object" && !Array.isArray(schema) ? schema : {};
|
|
326
|
+
return normalizeText(source?.[JSON_REST_TRANSPORT_EXTENSION_KEY]?.castType).toLowerCase();
|
|
327
|
+
}
|
|
328
|
+
|
|
238
329
|
function resolveSchemaType(schema) {
|
|
239
330
|
const source = resolveUnionSchemaVariant(schema);
|
|
240
331
|
const rawType = source?.type;
|
|
@@ -252,10 +343,21 @@ function resolveSchemaType(schema) {
|
|
|
252
343
|
const hasNullableType = Array.isArray(rawType)
|
|
253
344
|
? rawType.some((entry) => normalizeText(entry).toLowerCase() === "null")
|
|
254
345
|
: false;
|
|
346
|
+
const castType = resolveJsonRestCastType(source) || resolveJsonRestCastType(schema);
|
|
347
|
+
let format = normalizeText(source?.format).toLowerCase();
|
|
348
|
+
if (!format) {
|
|
349
|
+
if (castType === "date") {
|
|
350
|
+
format = "date";
|
|
351
|
+
} else if (castType === "datetime") {
|
|
352
|
+
format = "date-time";
|
|
353
|
+
} else if (castType === "time") {
|
|
354
|
+
format = "time";
|
|
355
|
+
}
|
|
356
|
+
}
|
|
255
357
|
|
|
256
358
|
return {
|
|
257
359
|
type: schemaType,
|
|
258
|
-
format
|
|
360
|
+
format,
|
|
259
361
|
schema: source,
|
|
260
362
|
nullable: hasNullableAnyOf || hasNullableOneOf || hasNullableType
|
|
261
363
|
};
|
|
@@ -301,7 +403,7 @@ function toSelectOptionIdentity(value) {
|
|
|
301
403
|
return `${typeof value}:${String(value)}`;
|
|
302
404
|
}
|
|
303
405
|
|
|
304
|
-
function normalizeFieldUiOptions(rawOptions, { context = "resource
|
|
406
|
+
function normalizeFieldUiOptions(rawOptions, { context = "resource field ui.options" } = {}) {
|
|
305
407
|
if (rawOptions === undefined || rawOptions === null) {
|
|
306
408
|
return [];
|
|
307
409
|
}
|
|
@@ -464,37 +566,26 @@ function normalizeLookupRelation(relation = {}) {
|
|
|
464
566
|
return normalized;
|
|
465
567
|
}
|
|
466
568
|
|
|
467
|
-
function
|
|
569
|
+
function buildResourceFieldContractMap(resource = {}) {
|
|
468
570
|
const map = {};
|
|
469
|
-
const entries =
|
|
571
|
+
const entries = Object.values(buildCrudFieldContractMap(resource, {
|
|
572
|
+
context: "crud ui resource field contract"
|
|
573
|
+
}));
|
|
470
574
|
for (const rawEntry of entries) {
|
|
471
|
-
|
|
472
|
-
continue;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
const key = normalizeText(rawEntry.key);
|
|
575
|
+
const key = normalizeText(rawEntry?.key);
|
|
476
576
|
if (!key) {
|
|
477
577
|
continue;
|
|
478
578
|
}
|
|
479
579
|
|
|
480
|
-
const nextEntry = {
|
|
481
|
-
key
|
|
482
|
-
};
|
|
483
|
-
const repositoryColumn = normalizeText(rawEntry?.repository?.column);
|
|
484
|
-
if (repositoryColumn) {
|
|
485
|
-
nextEntry.repository = {
|
|
486
|
-
column: repositoryColumn
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
580
|
+
const nextEntry = { key };
|
|
490
581
|
const relation = normalizeLookupRelation(rawEntry.relation);
|
|
491
582
|
const fieldUiOptions = normalizeFieldUiOptions(rawEntry?.ui?.options, {
|
|
492
|
-
context: `resource
|
|
583
|
+
context: `resource schema field "${key}" ui.options`
|
|
493
584
|
});
|
|
494
585
|
if (relation) {
|
|
495
586
|
nextEntry.relation = relation;
|
|
496
587
|
const formControl = checkCrudLookupFormControl(rawEntry?.ui?.formControl, {
|
|
497
|
-
context: `resource
|
|
588
|
+
context: `resource schema field "${key}" ui.formControl`,
|
|
498
589
|
defaultValue: "autocomplete"
|
|
499
590
|
});
|
|
500
591
|
if (formControl) {
|
|
@@ -522,13 +613,13 @@ function resolveLookupContainerKey(resource = {}, { context = "ui-generator" } =
|
|
|
522
613
|
});
|
|
523
614
|
}
|
|
524
615
|
|
|
525
|
-
function toLookupRelation(
|
|
616
|
+
function toLookupRelation(fieldContractMap = {}, fieldKey = "", { lookupContainerKey = "lookups" } = {}) {
|
|
526
617
|
const key = normalizeText(fieldKey);
|
|
527
618
|
if (!key) {
|
|
528
619
|
return null;
|
|
529
620
|
}
|
|
530
621
|
|
|
531
|
-
const relation = normalizeLookupRelation(
|
|
622
|
+
const relation = normalizeLookupRelation(fieldContractMap?.[key]?.relation);
|
|
532
623
|
if (!relation) {
|
|
533
624
|
return null;
|
|
534
625
|
}
|
|
@@ -585,7 +676,7 @@ function toPositiveInteger(value) {
|
|
|
585
676
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
586
677
|
}
|
|
587
678
|
|
|
588
|
-
function createFieldDefinitions(properties = {}, {
|
|
679
|
+
function createFieldDefinitions(properties = {}, { fieldContractMap = {}, lookupContainerKey = "lookups" } = {}) {
|
|
589
680
|
const fields = [];
|
|
590
681
|
|
|
591
682
|
for (const [rawKey, schema] of Object.entries(properties)) {
|
|
@@ -595,7 +686,7 @@ function createFieldDefinitions(properties = {}, { fieldMetaMap = {}, lookupCont
|
|
|
595
686
|
}
|
|
596
687
|
|
|
597
688
|
const schemaType = resolveSchemaType(schema);
|
|
598
|
-
const relation = toLookupRelation(
|
|
689
|
+
const relation = toLookupRelation(fieldContractMap, key, { lookupContainerKey });
|
|
599
690
|
fields.push({
|
|
600
691
|
key,
|
|
601
692
|
label: resolveFieldLabel(key, relation),
|
|
@@ -623,7 +714,7 @@ function createFieldDefinitions(properties = {}, { fieldMetaMap = {}, lookupCont
|
|
|
623
714
|
function createFormFieldDefinitions(
|
|
624
715
|
properties = {},
|
|
625
716
|
{
|
|
626
|
-
|
|
717
|
+
fieldContractMap = {},
|
|
627
718
|
lookupContainerKey = "lookups",
|
|
628
719
|
parentRouteParamKey = ""
|
|
629
720
|
} = {}
|
|
@@ -638,14 +729,14 @@ function createFormFieldDefinitions(
|
|
|
638
729
|
}
|
|
639
730
|
|
|
640
731
|
const schemaType = resolveSchemaType(schema);
|
|
641
|
-
const relation = toLookupRelation(
|
|
642
|
-
const fieldUiOptions = Array.isArray(
|
|
643
|
-
?
|
|
732
|
+
const relation = toLookupRelation(fieldContractMap, key, { lookupContainerKey });
|
|
733
|
+
const fieldUiOptions = Array.isArray(fieldContractMap?.[key]?.ui?.options)
|
|
734
|
+
? fieldContractMap[key].ui.options
|
|
644
735
|
: [];
|
|
645
736
|
const schemaEnumValues = Array.isArray(schemaType.schema?.enum) ? schemaType.schema.enum : [];
|
|
646
737
|
if (!relation && schemaEnumValues.length > 0 && fieldUiOptions.length < 1) {
|
|
647
738
|
throw new Error(
|
|
648
|
-
`resource form field "${key}" defines schema enum values but is missing
|
|
739
|
+
`resource form field "${key}" defines schema enum values but is missing schema ui.options metadata.`
|
|
649
740
|
);
|
|
650
741
|
}
|
|
651
742
|
const selectOptions = relation
|
|
@@ -660,8 +751,8 @@ function createFormFieldDefinitions(
|
|
|
660
751
|
)
|
|
661
752
|
);
|
|
662
753
|
const lookupFormControl = relation
|
|
663
|
-
? checkCrudLookupFormControl(
|
|
664
|
-
context: `resource
|
|
754
|
+
? checkCrudLookupFormControl(fieldContractMap?.[key]?.ui?.formControl, {
|
|
755
|
+
context: `resource schema field "${key}" ui.formControl`,
|
|
665
756
|
defaultValue: "autocomplete"
|
|
666
757
|
})
|
|
667
758
|
: "";
|
|
@@ -972,7 +1063,7 @@ export {
|
|
|
972
1063
|
requireObjectProperties,
|
|
973
1064
|
resolveListItemProperties,
|
|
974
1065
|
resolveLookupContainerKey,
|
|
975
|
-
|
|
1066
|
+
buildResourceFieldContractMap,
|
|
976
1067
|
createFieldDefinitions,
|
|
977
1068
|
createFormFieldDefinitions,
|
|
978
1069
|
resolveNearestParentRouteParamKey,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
requireObjectProperties,
|
|
10
10
|
resolveListItemProperties,
|
|
11
11
|
resolveLookupContainerKey,
|
|
12
|
-
|
|
12
|
+
buildResourceFieldContractMap,
|
|
13
13
|
createFieldDefinitions,
|
|
14
14
|
createFormFieldDefinitions,
|
|
15
15
|
buildListHeaderColumns,
|
|
@@ -124,7 +124,7 @@ function resolveResourceOptions(options = {}, inferredOptions = {}) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
function resolveOperationFields(resource, operationName) {
|
|
127
|
-
const
|
|
127
|
+
const fieldContractMap = buildResourceFieldContractMap(resource);
|
|
128
128
|
const lookupContainerKey = resolveLookupContainerKey(resource, {
|
|
129
129
|
context: "crud-ui-generator field"
|
|
130
130
|
});
|
|
@@ -134,7 +134,7 @@ function resolveOperationFields(resource, operationName) {
|
|
|
134
134
|
const listOutputSchema = requireOutputSchema(listOperation, "list", { context: "crud-ui-generator field" });
|
|
135
135
|
return createFieldDefinitions(
|
|
136
136
|
resolveListItemProperties(listOutputSchema, { context: "crud-ui-generator field" }),
|
|
137
|
-
{
|
|
137
|
+
{ fieldContractMap, lookupContainerKey }
|
|
138
138
|
);
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -143,24 +143,24 @@ function resolveOperationFields(resource, operationName) {
|
|
|
143
143
|
const viewOutputSchema = requireOutputSchema(viewOperation, "view", { context: "crud-ui-generator field" });
|
|
144
144
|
return createFieldDefinitions(
|
|
145
145
|
requireObjectProperties(viewOutputSchema, "operations.view output", { context: "crud-ui-generator field" }),
|
|
146
|
-
{
|
|
146
|
+
{ fieldContractMap, lookupContainerKey }
|
|
147
147
|
);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
if (operationName === "new") {
|
|
151
151
|
const createOperation = requireOperation(resource, "create", { context: "crud-ui-generator field" });
|
|
152
|
-
const
|
|
152
|
+
const createInputSchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator field" });
|
|
153
153
|
return createFormFieldDefinitions(
|
|
154
|
-
requireObjectProperties(
|
|
155
|
-
{
|
|
154
|
+
requireObjectProperties(createInputSchema, "operations.create body", { context: "crud-ui-generator field" }),
|
|
155
|
+
{ fieldContractMap, lookupContainerKey }
|
|
156
156
|
);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
const patchOperation = requireOperation(resource, "patch", { context: "crud-ui-generator field" });
|
|
160
|
-
const
|
|
160
|
+
const patchInputSchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator field" });
|
|
161
161
|
return createFormFieldDefinitions(
|
|
162
|
-
requireObjectProperties(
|
|
163
|
-
{
|
|
162
|
+
requireObjectProperties(patchInputSchema, "operations.patch body", { context: "crud-ui-generator field" }),
|
|
163
|
+
{ fieldContractMap, lookupContainerKey }
|
|
164
164
|
);
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
</div>
|
|
10
10
|
<v-spacer />
|
|
11
11
|
<div class="d-flex ga-2 flex-wrap">
|
|
12
|
-
<v-btn v-if="cancelTo" variant="
|
|
12
|
+
<v-btn v-if="cancelTo" color="primary" variant="outlined" :to="resolveCancelTo(cancelTo)">Cancel</v-btn>
|
|
13
13
|
<v-btn
|
|
14
14
|
color="primary"
|
|
15
|
+
variant="flat"
|
|
15
16
|
:loading="addEdit.isSaving"
|
|
16
17
|
:disabled="addEdit.isSubmitDisabled"
|
|
17
18
|
@click="addEdit.submit"
|
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
<v-spacer />
|
|
11
11
|
<v-btn
|
|
12
12
|
v-if="UI_CANCEL_URL"
|
|
13
|
-
|
|
13
|
+
color="primary"
|
|
14
|
+
variant="outlined"
|
|
14
15
|
:to="{ path: formRuntime.addEdit.resolveParams(UI_CANCEL_URL), query: $route.query }"
|
|
15
16
|
>
|
|
16
17
|
Cancel
|
|
17
18
|
</v-btn>
|
|
18
19
|
<v-btn
|
|
19
20
|
color="primary"
|
|
21
|
+
variant="flat"
|
|
20
22
|
:loading="formRuntime.addEdit.isSaving"
|
|
21
23
|
:disabled="formRuntime.addEdit.isSubmitDisabled"
|
|
22
24
|
@click="formRuntime.addEdit.submit"
|
|
@@ -61,6 +63,12 @@ const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
|
61
63
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
62
64
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
63
65
|
const UI_EDIT_FORM_FIELDS = [];
|
|
66
|
+
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
67
|
+
kind: "jsonapi-resource",
|
|
68
|
+
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
69
|
+
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
70
|
+
responseKind: "record"
|
|
71
|
+
});
|
|
64
72
|
|
|
65
73
|
// @jskit-contract crud.ui.form-fields.__JSKIT_UI_RESOURCE_NAMESPACE__.edit.v1
|
|
66
74
|
void UI_EDIT_FORM_FIELDS;
|
|
@@ -97,6 +105,7 @@ const formRuntime = useCrudAddEdit({
|
|
|
97
105
|
routeRecordId.value
|
|
98
106
|
],
|
|
99
107
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
108
|
+
transport: UI_EDIT_TRANSPORT,
|
|
100
109
|
writeMethod: "PATCH",
|
|
101
110
|
fallbackLoadError: "Unable to load record.",
|
|
102
111
|
fallbackSaveError: "Unable to save record.",
|
|
@@ -26,6 +26,12 @@ const UI_LIST_URL = __JSKIT_UI_EDIT_PAGE_LIST_URL__;
|
|
|
26
26
|
const UI_VIEW_URL = __JSKIT_UI_EDIT_PAGE_VIEW_URL__;
|
|
27
27
|
const UI_CANCEL_URL = UI_VIEW_URL || UI_LIST_URL;
|
|
28
28
|
const UI_RECORD_CHANGED_EVENT = __JSKIT_UI_RECORD_CHANGED_EVENT__;
|
|
29
|
+
const UI_EDIT_TRANSPORT = Object.freeze({
|
|
30
|
+
kind: "jsonapi-resource",
|
|
31
|
+
requestType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
32
|
+
responseType: "__JSKIT_UI_RESOURCE_NAMESPACE__",
|
|
33
|
+
responseKind: "record"
|
|
34
|
+
});
|
|
29
35
|
const route = useRoute();
|
|
30
36
|
|
|
31
37
|
// jskit:crud-ui-fields-target ../_components/__JSKIT_UI_FORM_COMPONENT_FILE__
|
|
@@ -58,6 +64,7 @@ const formRuntime = useCrudAddEdit({
|
|
|
58
64
|
routeRecordId.value
|
|
59
65
|
],
|
|
60
66
|
placementSource: "ui-generator.__JSKIT_UI_RESOURCE_NAMESPACE__.edit",
|
|
67
|
+
transport: UI_EDIT_TRANSPORT,
|
|
61
68
|
writeMethod: "PATCH",
|
|
62
69
|
fallbackLoadError: "Unable to load record.",
|
|
63
70
|
fallbackSaveError: "Unable to save record.",
|