@jskit-ai/crud-ui-generator 0.1.26 → 0.1.28

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.26",
4
+ version: "0.1.28",
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.58"
171
+ "@jskit-ai/users-web": "0.1.60"
172
172
  },
173
173
  dev: {}
174
174
  },
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@jskit-ai/crud-ui-generator",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "test": "node --test"
7
7
  },
8
8
  "dependencies": {
9
- "@jskit-ai/crud-core": "0.1.51",
10
- "@jskit-ai/kernel": "0.1.43"
9
+ "@jskit-ai/crud-core": "0.1.53",
10
+ "@jskit-ai/kernel": "0.1.45"
11
11
  },
12
12
  "exports": {
13
13
  "./server/buildTemplateContext": "./src/server/buildTemplateContext.js"
@@ -570,6 +570,14 @@ function resolveFormFieldComponent(fieldType, relation = null) {
570
570
  return "text";
571
571
  }
572
572
 
573
+ function buildDefaultNullableBooleanOptions() {
574
+ return [
575
+ { label: "Unset", value: null },
576
+ { label: "Yes", value: true },
577
+ { label: "No", value: false }
578
+ ];
579
+ }
580
+
573
581
  function toPositiveInteger(value) {
574
582
  const parsed = Number(value);
575
583
  return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
@@ -638,7 +646,17 @@ function createFormFieldDefinitions(
638
646
  `resource form field "${key}" defines schema enum values but is missing resource.fieldMeta["${key}"].ui.options.`
639
647
  );
640
648
  }
641
- const selectOptions = relation ? [] : fieldUiOptions;
649
+ const selectOptions = relation
650
+ ? []
651
+ : (
652
+ fieldUiOptions.length > 0
653
+ ? fieldUiOptions
654
+ : (
655
+ schemaType.type === "boolean" && schemaType.nullable === true
656
+ ? buildDefaultNullableBooleanOptions()
657
+ : []
658
+ )
659
+ );
642
660
  const lookupFormControl = relation
643
661
  ? checkCrudLookupFormControl(fieldMetaMap?.[key]?.ui?.formControl, {
644
662
  context: `resource.fieldMeta["${key}"].ui.formControl`,
@@ -126,6 +126,70 @@ const resource = {
126
126
  export { resource };
127
127
  `;
128
128
 
129
+ const NULLABLE_BOOLEAN_RESOURCE_SOURCE = `const customerRecordSchema = {
130
+ type: "object",
131
+ properties: {
132
+ id: { type: "integer" },
133
+ firstName: { type: "string" },
134
+ reviewPassed: { type: ["boolean", "null"] }
135
+ },
136
+ additionalProperties: false
137
+ };
138
+
139
+ const customerBodySchema = {
140
+ type: "object",
141
+ properties: {
142
+ firstName: { type: "string", maxLength: 120 },
143
+ reviewPassed: { type: ["boolean", "null"] }
144
+ },
145
+ additionalProperties: false
146
+ };
147
+
148
+ const resource = {
149
+ resource: "customers",
150
+ operations: {
151
+ list: {
152
+ outputValidator: {
153
+ schema: {
154
+ type: "object",
155
+ properties: {
156
+ items: {
157
+ type: "array",
158
+ items: customerRecordSchema
159
+ },
160
+ nextCursor: { type: ["string", "null"] }
161
+ },
162
+ additionalProperties: false
163
+ }
164
+ }
165
+ },
166
+ view: {
167
+ outputValidator: {
168
+ schema: customerRecordSchema
169
+ }
170
+ },
171
+ create: {
172
+ bodyValidator: {
173
+ schema: customerBodySchema
174
+ },
175
+ outputValidator: {
176
+ schema: customerRecordSchema
177
+ }
178
+ },
179
+ patch: {
180
+ bodyValidator: {
181
+ schema: customerBodySchema
182
+ },
183
+ outputValidator: {
184
+ schema: customerRecordSchema
185
+ }
186
+ }
187
+ }
188
+ };
189
+
190
+ export { resource };
191
+ `;
192
+
129
193
  const LOOKUP_RESOURCE_SOURCE = `const recordSchema = {
130
194
  type: "object",
131
195
  properties: {
@@ -269,6 +333,38 @@ test("buildUiTemplateContext derives CRUD placeholders from the explicit target-
269
333
  });
270
334
  });
271
335
 
336
+ test("buildUiTemplateContext keeps non-nullable booleans as switches", async () => {
337
+ await withTempApp(async (appRoot) => {
338
+ await writeResource(appRoot, RESOURCE_FILE, FULL_RESOURCE_SOURCE);
339
+
340
+ const context = await buildUiTemplateContext({
341
+ appRoot,
342
+ options: createOptions()
343
+ });
344
+
345
+ assert.match(context.__JSKIT_UI_CREATE_FORM_COLUMNS__, /<v-switch/);
346
+ assert.match(context.__JSKIT_UI_CREATE_FORM_COLUMNS__, /formRuntime\.form\.vip/);
347
+ });
348
+ });
349
+
350
+ test("buildUiTemplateContext renders nullable booleans as tri-state selects by default", async () => {
351
+ await withTempApp(async (appRoot) => {
352
+ await writeResource(appRoot, RESOURCE_FILE, NULLABLE_BOOLEAN_RESOURCE_SOURCE);
353
+
354
+ const context = await buildUiTemplateContext({
355
+ appRoot,
356
+ options: createOptions()
357
+ });
358
+
359
+ assert.match(context.__JSKIT_UI_CREATE_FORM_FIELDS__, /"key":"reviewPassed"/);
360
+ assert.match(context.__JSKIT_UI_CREATE_FORM_FIELDS__, /"component":"select"/);
361
+ assert.match(context.__JSKIT_UI_CREATE_FORM_FIELDS__, /"nullable":true/);
362
+ assert.match(context.__JSKIT_UI_CREATE_FORM_FIELDS__, /"options":\[\{"label":"Unset","value":null\},\{"label":"Yes","value":true\},\{"label":"No","value":false\}\]/);
363
+ assert.match(context.__JSKIT_UI_CREATE_FORM_COLUMNS__, /<v-select/);
364
+ assert.doesNotMatch(context.__JSKIT_UI_CREATE_FORM_COLUMNS__, /<v-switch/);
365
+ });
366
+ });
367
+
272
368
  test("buildUiTemplateContext derives menu auth visibility from the target surface policy", async () => {
273
369
  await withTempApp(async (appRoot) => {
274
370
  await writeFile(