@jskit-ai/crud-ui-generator 0.1.38 → 0.1.39

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.
@@ -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.38",
4
+ version: "0.1.39",
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.70"
171
+ "@jskit-ai/users-web": "0.1.71"
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.38",
3
+ "version": "0.1.39",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/crud-core": "0.1.63",
10
- "@jskit-ai/kernel": "0.1.55"
9
+ "@jskit-ai/crud-core": "0.1.64",
10
+ "@jskit-ai/kernel": "0.1.56",
11
+ "@jskit-ai/resource-crud-core": "0.1.1"
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
- buildResourceFieldMetaMap,
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 fieldMetaMap = buildResourceFieldMetaMap(resource);
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
- fieldMetaMap,
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
- fieldMetaMap,
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 createBodySchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator" });
524
+ const createInputSchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator" });
512
525
  createFieldsAll = createFormFieldDefinitions(
513
- requireObjectProperties(createBodySchema, "operations.create body", { context: "crud-ui-generator" }),
526
+ requireObjectProperties(createInputSchema, "operations.create body", { context: "crud-ui-generator" }),
514
527
  {
515
- fieldMetaMap,
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 patchBodySchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator" });
539
+ const patchInputSchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator" });
527
540
  editFieldsAll = createFormFieldDefinitions(
528
- requireObjectProperties(patchBodySchema, "operations.patch body", { context: "crud-ui-generator" }),
541
+ requireObjectProperties(patchInputSchema, "operations.patch body", { context: "crud-ui-generator" }),
529
542
  {
530
- fieldMetaMap,
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/crudFieldMetaSupport";
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 requireOutputSchema(operation, operationName, { context = "ui-generator" } = {}) {
155
- const outputValidator = operation?.outputValidator;
156
- if (!outputValidator || typeof outputValidator !== "object" || Array.isArray(outputValidator)) {
157
- throw new Error(`${context} resource operations.${operationName} is missing outputValidator.`);
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
- const schema = outputValidator.schema;
161
- if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
162
- throw new Error(`${context} resource operations.${operationName}.outputValidator is missing schema.`);
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 schema;
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 bodyValidator = operation?.bodyValidator;
170
- if (!bodyValidator || typeof bodyValidator !== "object" || Array.isArray(bodyValidator)) {
171
- throw new Error(`${context} resource operations.${operationName} is missing bodyValidator.`);
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 schema;
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 properties = schema?.properties;
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", { context });
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", { context });
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: normalizeText(source.format).toLowerCase(),
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 fieldMeta ui.options" } = {}) {
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 buildResourceFieldMetaMap(resource = {}) {
569
+ function buildResourceFieldContractMap(resource = {}) {
468
570
  const map = {};
469
- const entries = Array.isArray(resource?.fieldMeta) ? resource.fieldMeta : [];
571
+ const entries = Object.values(buildCrudFieldContractMap(resource, {
572
+ context: "crud ui resource field contract"
573
+ }));
470
574
  for (const rawEntry of entries) {
471
- if (!rawEntry || typeof rawEntry !== "object" || Array.isArray(rawEntry)) {
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.fieldMeta["${key}"].ui.options`
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.fieldMeta["${key}"].ui.formControl`,
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(fieldMetaMap = {}, fieldKey = "", { lookupContainerKey = "lookups" } = {}) {
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(fieldMetaMap?.[key]?.relation);
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 = {}, { fieldMetaMap = {}, lookupContainerKey = "lookups" } = {}) {
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(fieldMetaMap, key, { lookupContainerKey });
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
- fieldMetaMap = {},
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(fieldMetaMap, key, { lookupContainerKey });
642
- const fieldUiOptions = Array.isArray(fieldMetaMap?.[key]?.ui?.options)
643
- ? fieldMetaMap[key].ui.options
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 resource.fieldMeta["${key}"].ui.options.`
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(fieldMetaMap?.[key]?.ui?.formControl, {
664
- context: `resource.fieldMeta["${key}"].ui.formControl`,
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
- buildResourceFieldMetaMap,
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
- buildResourceFieldMetaMap,
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 fieldMetaMap = buildResourceFieldMetaMap(resource);
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
- { fieldMetaMap, lookupContainerKey }
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
- { fieldMetaMap, lookupContainerKey }
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 createBodySchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator field" });
152
+ const createInputSchema = requireBodySchema(createOperation, "create", { context: "crud-ui-generator field" });
153
153
  return createFormFieldDefinitions(
154
- requireObjectProperties(createBodySchema, "operations.create body", { context: "crud-ui-generator field" }),
155
- { fieldMetaMap, lookupContainerKey }
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 patchBodySchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator field" });
160
+ const patchInputSchema = requireBodySchema(patchOperation, "patch", { context: "crud-ui-generator field" });
161
161
  return createFormFieldDefinitions(
162
- requireObjectProperties(patchBodySchema, "operations.patch body", { context: "crud-ui-generator field" }),
163
- { fieldMetaMap, lookupContainerKey }
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="tonal" :to="resolveCancelTo(cancelTo)">Cancel</v-btn>
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
- variant="text"
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.",