@kematjaya/crud-ui-generator 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -55,8 +55,7 @@ Appended to (multi-entity, idempotent — each entity gets one marker-guarded bl
55
55
 
56
56
  ## Assumptions / caveats
57
57
 
58
- - **Ids are UUID strings.** `validId()` in the generated `[id]/route.ts` checks a 36-char
59
- hex-with-dashes pattern. Adjust by hand if an entity uses a different id type.
58
+ - **Id type comes from the spec's `idType`** (`uuid`/`int`/`string`, written by `ApiCrudRenderer::detectIdType()` off the entity's actual id column) — `validId()` in the generated `[id]/route.ts` and the `is{Entity}` type guard in `api-shapes.ts` are generated to match. Older spec files without `idType` default to `uuid`.
60
59
  - **The list search box only searches one field.** If more than one field is marked
61
60
  `searchable` in the spec, the list view (backed by ApiPlatform's `SearchFilter`, one query
62
61
  param per property) only wires up the first one. The export endpoint ORs across all of them.
package/dist/cli.js CHANGED
@@ -100,7 +100,7 @@ function main() {
100
100
  if (searchableFields(spec).length > 1) {
101
101
  console.log(` 4. Note: multiple searchable fields were configured (${searchableFields(spec).join(', ')}); the list view's single search box only queries by "${searchField(spec)}" (ApiPlatform SearchFilter's per-property param convention doesn't support one box matching several properties). The export endpoint does OR across all of them.`);
102
102
  }
103
- console.log(` Assumes UUID-shaped ids (matches this boilerplate's convention) — see validId() in the generated app/api/${names.entitiesKebab}/[id]/route.ts if this entity uses a different id type.`);
103
+ console.log(` Id type: "${spec.idType}" (from the spec's "idType") — validId() in the generated app/api/${names.entitiesKebab}/[id]/route.ts was generated to match.`);
104
104
  console.log(` Run "npm run format" afterwards — generated files aren't pre-formatted to this project's Prettier config.`);
105
105
  }
106
106
  main();
package/dist/spec.js CHANGED
@@ -37,11 +37,13 @@ export function loadSpec(specPath) {
37
37
  if (!Array.isArray(spec.fields) || !spec.fields.every(isFieldSpec)) {
38
38
  throw new Error(`Spec file "fields" is missing or malformed: ${specPath}`);
39
39
  }
40
+ const idType = spec.idType === 'int' || spec.idType === 'string' || spec.idType === 'uuid' ? spec.idType : 'uuid';
40
41
  return {
41
42
  entity: spec.entity,
42
43
  permissionPrefix: spec.permissionPrefix,
43
44
  ownerProperty: typeof spec.ownerProperty === 'string' ? spec.ownerProperty : null,
44
45
  timestampField: typeof spec.timestampField === 'string' ? spec.timestampField : null,
46
+ idType,
45
47
  fields: spec.fields,
46
48
  };
47
49
  }
@@ -18,7 +18,8 @@ export function apiShapesImport(names) {
18
18
  }
19
19
  export function apiShapesBlock(spec, names) {
20
20
  const { entityPascal, entitiesPascal } = names;
21
- const checks = ['isRecord(value)', "typeof value.id === 'string'"];
21
+ const idJsType = 'int' === spec.idType ? 'number' : 'string';
22
+ const checks = ['isRecord(value)', `typeof value.id === '${idJsType}'`];
22
23
  for (const field of spec.fields) {
23
24
  checks.push(`typeof value.${field.name} === '${tsType(field)}'`);
24
25
  }
@@ -35,7 +35,14 @@ export const dynamic = 'force-dynamic';
35
35
  export const revalidate = 0;
36
36
  `;
37
37
  }
38
- export function itemRoute(_spec, names) {
38
+ function validIdCheck(idType) {
39
+ if (idType === 'int')
40
+ return 'return /^[1-9][0-9]*$/.test(id);';
41
+ if (idType === 'string')
42
+ return "return id.length > 0;";
43
+ return "return /^[0-9a-fA-F-]{36}$/.test(id);";
44
+ }
45
+ export function itemRoute(spec, names) {
39
46
  const { entityCamel, entitiesKebab } = names;
40
47
  return `import type { NextRequest } from 'next/server';
41
48
  import { authedBackend } from '@/lib/bff';
@@ -45,7 +52,7 @@ import { ${entityCamel}Schema } from '@/lib/schemas';
45
52
  type Params = { params: Promise<{ id: string }> };
46
53
 
47
54
  function validId(id: string) {
48
- return /^[0-9a-fA-F-]{36}$/.test(id);
55
+ ${validIdCheck(spec.idType)}
49
56
  }
50
57
 
51
58
  export async function GET(_request: NextRequest, context: Params) {
@@ -79,7 +79,7 @@ export function ${entityPascal}Table() {
79
79
  const [error, setError] = useState('');
80
80
  const [deleteTarget, setDeleteTarget] = useState<${entityPascal} | null>(null);
81
81
  const [deleting, setDeleting] = useState(false);
82
- const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
82
+ const [selectedIds, setSelectedIds] = useState<Set<${entityPascal}['id']>>(new Set());
83
83
  const [bulkDeleteConfirming, setBulkDeleteConfirming] = useState(false);
84
84
  const [bulkDeleting, setBulkDeleting] = useState(false);
85
85
  const [toastMessage, setToastMessage] = useState(
@@ -188,7 +188,7 @@ ${hasTitleFilterLine} const trueEmpty = !loading && !error && !hasSearch && t
188
188
  }
189
189
  }, [someOnPageSelected, allOnPageSelected]);
190
190
 
191
- function toggleSelect(id: string) {
191
+ function toggleSelect(id: ${entityPascal}['id']) {
192
192
  setSelectedIds((prev) => {
193
193
  const next = new Set(prev);
194
194
  if (next.has(id)) next.delete(id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kematjaya/crud-ui-generator",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Next.js CRUD frontend generator that reads crud-specs/{Entity}.json sidecars written by kematjaya/crud-maker-bundle's make:kmj-api-crud and generates pages, components, and BFF routes matching the boilerplate's hand-written Notes feature.",
5
5
  "type": "module",
6
6
  "license": "MIT",