@specverse/engines 4.1.30 → 4.2.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/assets/examples/manifests/frontend-only.yaml +3 -6
- package/assets/examples/manifests/fullstack-app.yaml +5 -7
- package/assets/examples/manifests/fullstack-monorepo.yaml +3 -6
- package/assets/templates/default/specs/main.specly +65 -0
- package/dist/inference/comprehensive-engine.d.ts.map +1 -1
- package/dist/inference/comprehensive-engine.js +3 -19
- package/dist/inference/comprehensive-engine.js.map +1 -1
- package/dist/inference/core/rule-engine.d.ts +31 -0
- package/dist/inference/core/rule-engine.d.ts.map +1 -1
- package/dist/inference/core/rule-engine.js +117 -33
- package/dist/inference/core/rule-engine.js.map +1 -1
- package/dist/inference/core/rule-file-types.d.ts +0 -2
- package/dist/inference/core/rule-file-types.d.ts.map +1 -1
- package/dist/inference/core/rule-file-types.js +3 -6
- package/dist/inference/core/rule-file-types.js.map +1 -1
- package/dist/inference/core/rule-loader.d.ts +5 -15
- package/dist/inference/core/rule-loader.d.ts.map +1 -1
- package/dist/inference/core/rule-loader.js +43 -132
- package/dist/inference/core/rule-loader.js.map +1 -1
- package/dist/inference/core/types.d.ts +0 -6
- package/dist/inference/core/types.d.ts.map +1 -1
- package/dist/inference/core/types.js +0 -4
- package/dist/inference/core/types.js.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.d.ts +0 -26
- package/dist/inference/logical/generators/component-type-resolver.d.ts.map +1 -1
- package/dist/inference/logical/generators/component-type-resolver.js +0 -19
- package/dist/inference/logical/generators/component-type-resolver.js.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.d.ts +1 -17
- package/dist/inference/logical/generators/specialist-view-expander.d.ts.map +1 -1
- package/dist/inference/logical/generators/specialist-view-expander.js +0 -15
- package/dist/inference/logical/generators/specialist-view-expander.js.map +1 -1
- package/dist/inference/logical/generators/view-generator.d.ts +4 -14
- package/dist/inference/logical/generators/view-generator.d.ts.map +1 -1
- package/dist/inference/logical/generators/view-generator.js +6 -26
- package/dist/inference/logical/generators/view-generator.js.map +1 -1
- package/dist/inference/logical/index.d.ts +2 -2
- package/dist/inference/logical/index.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.d.ts.map +1 -1
- package/dist/inference/logical/logical-engine.js +17 -80
- package/dist/inference/logical/logical-engine.js.map +1 -1
- package/dist/inference/quint-transpiler.d.ts +5 -3
- package/dist/inference/quint-transpiler.d.ts.map +1 -1
- package/dist/inference/quint-transpiler.js +11 -6
- package/dist/inference/quint-transpiler.js.map +1 -1
- package/dist/libs/instance-factories/CURVED-INTERFACE.md +278 -0
- package/dist/libs/instance-factories/README.md +73 -0
- package/dist/libs/instance-factories/applications/README.md +51 -0
- package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
- package/{libs/instance-factories/applications/react-app.yaml → dist/libs/instance-factories/applications/react-app-runtime.yaml} +30 -77
- package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
- package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
- package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +129 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +80 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +217 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +51 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +56 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +41 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +81 -0
- package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
- package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
- package/dist/libs/instance-factories/cli/README.md +43 -0
- package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +63 -12
- package/dist/libs/instance-factories/communication/README.md +47 -0
- package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
- package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
- package/dist/libs/instance-factories/controllers/README.md +42 -0
- package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
- package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
- package/dist/libs/instance-factories/infrastructure/README.md +29 -0
- package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
- package/dist/libs/instance-factories/orms/README.md +54 -0
- package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
- package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
- package/dist/libs/instance-factories/scaffolding/README.md +49 -0
- package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
- package/dist/libs/instance-factories/sdks/README.md +28 -0
- package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
- package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
- package/dist/libs/instance-factories/services/README.md +55 -0
- package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
- package/dist/libs/instance-factories/storage/README.md +34 -0
- package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
- package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
- package/dist/libs/instance-factories/storage/redis.yaml +79 -0
- package/dist/libs/instance-factories/testing/README.md +40 -0
- package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
- package/dist/libs/instance-factories/tools/README.md +70 -0
- package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
- package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
- package/dist/libs/instance-factories/validation/README.md +38 -0
- package/dist/libs/instance-factories/validation/zod.yaml +56 -0
- package/dist/realize/engines/code-generator.d.ts.map +1 -1
- package/dist/realize/engines/code-generator.js +3 -0
- package/dist/realize/engines/code-generator.js.map +1 -1
- package/dist/realize/index.d.ts.map +1 -1
- package/dist/realize/index.js +15 -22
- package/dist/realize/index.js.map +1 -1
- package/dist/registry/utils/manifest-adapter.d.ts +8 -1
- package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
- package/dist/registry/utils/manifest-adapter.js +8 -1
- package/dist/registry/utils/manifest-adapter.js.map +1 -1
- package/libs/instance-factories/applications/react-app-starter.yaml +143 -0
- package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
- package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
- package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +188 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +184 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
- package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
- package/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts +98 -0
- package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
- package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
- package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +189 -0
- package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
- package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +383 -0
- package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +66 -0
- package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
- package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +146 -0
- package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +95 -0
- package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
- package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
- package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
- package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
- package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +209 -0
- package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +63 -12
- package/libs/instance-factories/controllers/fastify.yaml +7 -0
- package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
- package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
- package/package.json +3 -3
- package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
- package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
- package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
- package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
- package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
- package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
- package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
- package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
- package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
- package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
- package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
- package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
- package/dist/libs/instance-factories/views/index.js +0 -48
- package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
- package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
- package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
- package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
- package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
- package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
- package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
- package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
- package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
- package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
- package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
- package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
- package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
- package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
- package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
- package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
- package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
- package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
- package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
- package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
- package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
- package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
- package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
- package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
- package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
- package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
- package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
- package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
- package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
- package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
- package/dist/realize/index.js.bak +0 -758
- package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
- package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
- package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
- package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
- package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
- package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
- package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
- package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
- package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
- package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
- package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
- package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
- package/libs/instance-factories/views/README.md +0 -62
- package/libs/instance-factories/views/index.d.ts +0 -13
- package/libs/instance-factories/views/index.d.ts.map +0 -1
- package/libs/instance-factories/views/index.js +0 -18
- package/libs/instance-factories/views/index.js.map +0 -1
- package/libs/instance-factories/views/index.ts +0 -45
- package/libs/instance-factories/views/react-components.yaml +0 -129
- package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
- package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
- package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
- package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
- package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
- package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
- package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
- package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
- package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
- package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
- package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
- package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
- package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
- package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
- package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
- package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
- package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
- package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
- package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
- package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
- package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
- package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
- package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
- package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
- package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
- package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
- package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
- package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
- package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
- package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
- package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
- package/libs/instance-factories/views/templates/shared/index.ts +0 -68
- package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
- package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
- package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
- package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
- package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
- package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +0 -539
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function extractBelongsToTargets(model) {
|
|
2
|
+
const rels = model.relationships ?? {};
|
|
3
|
+
const out = [];
|
|
4
|
+
for (const [name, rawDef] of Object.entries(rels)) {
|
|
5
|
+
const parsed = parseBelongsTo(rawDef);
|
|
6
|
+
if (parsed) out.push({ name, target: parsed });
|
|
7
|
+
}
|
|
8
|
+
return out;
|
|
9
|
+
}
|
|
10
|
+
function parseBelongsTo(def) {
|
|
11
|
+
if (typeof def === "string") {
|
|
12
|
+
const parts = def.trim().split(/\s+/);
|
|
13
|
+
if (parts[0] === "belongsTo" && parts[1]) return parts[1];
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (def && typeof def === "object") {
|
|
17
|
+
const o = def;
|
|
18
|
+
if (o.type === "belongsTo") {
|
|
19
|
+
return o.target || o.to || o.model || o.targetModel || null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function pluralize(s) {
|
|
25
|
+
if (/[^aeiou]y$/i.test(s)) return s.slice(0, -1) + "ies";
|
|
26
|
+
if (/(s|x|z|ch|sh)$/i.test(s)) return s + "es";
|
|
27
|
+
return s + "s";
|
|
28
|
+
}
|
|
29
|
+
function buildFKMap(model) {
|
|
30
|
+
const map = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const rel of extractBelongsToTargets(model)) {
|
|
32
|
+
map.set(`${rel.name}Id`, rel);
|
|
33
|
+
}
|
|
34
|
+
return map;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
buildFKMap,
|
|
38
|
+
extractBelongsToTargets,
|
|
39
|
+
pluralize
|
|
40
|
+
};
|
package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { METADATA_FIELDS } from "@specverse/runtime/views/core";
|
|
2
|
+
import { buildFKMap } from "./belongs-to.js";
|
|
3
|
+
const METADATA_FIELD_NAMES = new Set(METADATA_FIELDS);
|
|
4
|
+
function composeDashboardBody(context) {
|
|
5
|
+
const modelName = context.model.name;
|
|
6
|
+
const previewColumns = inferPreviewColumns(context.model);
|
|
7
|
+
const enumFields = inferEnumFields(context.model);
|
|
8
|
+
const fkMap = buildFKMap(context.model);
|
|
9
|
+
const lines = [];
|
|
10
|
+
lines.push('<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">');
|
|
11
|
+
lines.push(...renderTotalCard(modelName));
|
|
12
|
+
for (const enumField of enumFields) {
|
|
13
|
+
lines.push(...renderEnumBreakdownCard(enumField));
|
|
14
|
+
}
|
|
15
|
+
if (enumFields.length === 0) {
|
|
16
|
+
lines.push(...renderPlaceholderCard());
|
|
17
|
+
}
|
|
18
|
+
lines.push("</div>");
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push("{/* TODO: add aggregation metrics (averages / sums / time series)");
|
|
21
|
+
lines.push(" by adding backend endpoints and per-metric hooks. Wire them in");
|
|
22
|
+
lines.push(" alongside the count cards above. */}");
|
|
23
|
+
lines.push("");
|
|
24
|
+
lines.push('<div className="rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-900">');
|
|
25
|
+
lines.push(' <div className="border-b border-gray-200 dark:border-gray-700 px-6 py-3">');
|
|
26
|
+
lines.push(' <h3 className="text-sm font-semibold text-gray-700 dark:text-gray-200 uppercase tracking-wider">');
|
|
27
|
+
lines.push(` Recent ${humanize(modelName)}s`);
|
|
28
|
+
lines.push(" </h3>");
|
|
29
|
+
lines.push(" </div>");
|
|
30
|
+
if (previewColumns.length === 0) {
|
|
31
|
+
lines.push(' <div className="px-6 py-4 text-sm text-gray-400">No displayable fields.</div>');
|
|
32
|
+
} else {
|
|
33
|
+
lines.push(' <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">');
|
|
34
|
+
lines.push(" <thead>");
|
|
35
|
+
lines.push(" <tr>");
|
|
36
|
+
for (const col of previewColumns) {
|
|
37
|
+
const fk = fkMap.get(col);
|
|
38
|
+
const headerLabel = humanize(fk ? fk.name : col);
|
|
39
|
+
lines.push(
|
|
40
|
+
` <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">` + headerLabel + `</th>`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
lines.push(" </tr>");
|
|
44
|
+
lines.push(" </thead>");
|
|
45
|
+
lines.push(' <tbody className="divide-y divide-gray-200 dark:divide-gray-700">');
|
|
46
|
+
lines.push(" {preview.map((item, idx) => (");
|
|
47
|
+
lines.push(" <tr");
|
|
48
|
+
lines.push(" key={idx}");
|
|
49
|
+
lines.push(" onClick={() => onSelect?.(item)}");
|
|
50
|
+
lines.push(' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"');
|
|
51
|
+
lines.push(" >");
|
|
52
|
+
for (const col of previewColumns) {
|
|
53
|
+
const fk = fkMap.get(col);
|
|
54
|
+
const expr = fk ? `{resolveEntityDisplayName((item as any).${col}, ${fk.name}Options)}` : `{String((item as any).${col} ?? '')}`;
|
|
55
|
+
lines.push(
|
|
56
|
+
` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">${expr}</td>`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
lines.push(" </tr>");
|
|
60
|
+
lines.push(" ))}");
|
|
61
|
+
lines.push(" {preview.length === 0 && (");
|
|
62
|
+
lines.push(` <tr><td colSpan={${previewColumns.length}} className="px-6 py-4 text-sm text-gray-400">No records yet.</td></tr>`);
|
|
63
|
+
lines.push(" )}");
|
|
64
|
+
lines.push(" </tbody>");
|
|
65
|
+
lines.push(" </table>");
|
|
66
|
+
}
|
|
67
|
+
lines.push("</div>");
|
|
68
|
+
return lines.join("\n");
|
|
69
|
+
}
|
|
70
|
+
function renderTotalCard(modelName) {
|
|
71
|
+
const pluralLower = humanize(modelName).toLowerCase() + "s";
|
|
72
|
+
return [
|
|
73
|
+
' <div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-900">',
|
|
74
|
+
' <p className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">',
|
|
75
|
+
` Total ${pluralLower}`,
|
|
76
|
+
" </p>",
|
|
77
|
+
' <p className="mt-2 text-3xl font-semibold text-gray-900 dark:text-gray-100">',
|
|
78
|
+
" {items.length}",
|
|
79
|
+
" </p>",
|
|
80
|
+
" </div>"
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
function renderEnumBreakdownCard(field) {
|
|
84
|
+
const cards = [];
|
|
85
|
+
for (const value of field.values) {
|
|
86
|
+
cards.push(
|
|
87
|
+
' <div className="rounded-lg border border-gray-200 bg-white p-6 shadow-sm dark:border-gray-700 dark:bg-gray-900">',
|
|
88
|
+
' <p className="text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">',
|
|
89
|
+
` ${humanize(field.name)}: ${humanize(value)}`,
|
|
90
|
+
" </p>",
|
|
91
|
+
' <p className="mt-2 text-3xl font-semibold text-gray-900 dark:text-gray-100">',
|
|
92
|
+
` {items.filter((i: any) => i.${field.name} === ${JSON.stringify(value)}).length}`,
|
|
93
|
+
" </p>",
|
|
94
|
+
" </div>"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return cards;
|
|
98
|
+
}
|
|
99
|
+
function renderPlaceholderCard() {
|
|
100
|
+
return [
|
|
101
|
+
' <div className="rounded-lg border border-dashed border-gray-300 p-6 text-sm text-gray-400 dark:border-gray-600">',
|
|
102
|
+
" Add a metric here \u2014 e.g. a sum, average, or time-windowed count.",
|
|
103
|
+
" </div>"
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
function inferEnumFields(model) {
|
|
107
|
+
const out = [];
|
|
108
|
+
const attrs = model.attributes ?? {};
|
|
109
|
+
for (const [name, rawDef] of Object.entries(attrs)) {
|
|
110
|
+
const def = rawDef;
|
|
111
|
+
const values = def?.values;
|
|
112
|
+
if (Array.isArray(values) && values.length > 0 && values.length <= 6) {
|
|
113
|
+
out.push({ name, values });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return out.slice(0, 1);
|
|
117
|
+
}
|
|
118
|
+
function inferPreviewColumns(model) {
|
|
119
|
+
const attrs = model.attributes ?? {};
|
|
120
|
+
const attrCols = Object.keys(attrs).filter((n) => !METADATA_FIELD_NAMES.has(n));
|
|
121
|
+
const fkCols = [...buildFKMap(model).keys()].filter((k) => !attrCols.includes(k));
|
|
122
|
+
return [...fkCols, ...attrCols].slice(0, Math.max(5, fkCols.length + 1));
|
|
123
|
+
}
|
|
124
|
+
function humanize(name) {
|
|
125
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
composeDashboardBody
|
|
129
|
+
};
|
package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { METADATA_FIELDS } from "@specverse/runtime/views/core";
|
|
2
|
+
import { extractBelongsToTargets } from "./belongs-to.js";
|
|
3
|
+
const METADATA_FIELD_NAMES = new Set(METADATA_FIELDS);
|
|
4
|
+
function composeDetailBody(context) {
|
|
5
|
+
const belongsTo = extractBelongsToTargets(context.model);
|
|
6
|
+
const fkExcludes = new Set(belongsTo.map((r) => `${r.name}Id`));
|
|
7
|
+
const { business, metadata } = partitionFields(context.model, fkExcludes);
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push('<div className="rounded-lg border border-gray-200 bg-white p-6 dark:border-gray-700 dark:bg-gray-900">');
|
|
10
|
+
if (business.length > 0) {
|
|
11
|
+
lines.push(' <dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">');
|
|
12
|
+
for (const field of business) {
|
|
13
|
+
lines.push(...renderField(field, { muted: false }));
|
|
14
|
+
}
|
|
15
|
+
lines.push(" </dl>");
|
|
16
|
+
} else if (belongsTo.length === 0) {
|
|
17
|
+
lines.push(' <p className="text-sm text-gray-400">No business fields defined for this model.</p>');
|
|
18
|
+
}
|
|
19
|
+
if (belongsTo.length > 0) {
|
|
20
|
+
lines.push("");
|
|
21
|
+
lines.push(' <dl className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 border-t border-gray-200 dark:border-gray-700 pt-4">');
|
|
22
|
+
for (const rel of belongsTo) {
|
|
23
|
+
lines.push(...renderRelationshipField(rel));
|
|
24
|
+
}
|
|
25
|
+
lines.push(" </dl>");
|
|
26
|
+
}
|
|
27
|
+
if (metadata.length > 0) {
|
|
28
|
+
lines.push("");
|
|
29
|
+
lines.push(' <dl className="mt-6 grid grid-cols-1 gap-2 sm:grid-cols-2 border-t border-gray-200 dark:border-gray-700 pt-4 text-xs text-gray-500 dark:text-gray-400">');
|
|
30
|
+
for (const field of metadata) {
|
|
31
|
+
lines.push(...renderField(field, { muted: true }));
|
|
32
|
+
}
|
|
33
|
+
lines.push(" </dl>");
|
|
34
|
+
}
|
|
35
|
+
lines.push("</div>");
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
function partitionFields(model, exclude = /* @__PURE__ */ new Set()) {
|
|
39
|
+
const attrs = Object.keys(model.attributes ?? {});
|
|
40
|
+
const business = [];
|
|
41
|
+
const metadata = [];
|
|
42
|
+
for (const name of attrs) {
|
|
43
|
+
if (exclude.has(name)) continue;
|
|
44
|
+
const field = { name, label: humanize(name) };
|
|
45
|
+
if (METADATA_FIELD_NAMES.has(name)) {
|
|
46
|
+
metadata.push(field);
|
|
47
|
+
} else {
|
|
48
|
+
business.push(field);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { business, metadata };
|
|
52
|
+
}
|
|
53
|
+
function renderRelationshipField(rel) {
|
|
54
|
+
const label = humanize(rel.name);
|
|
55
|
+
const fkName = `${rel.name}Id`;
|
|
56
|
+
const optionsVar = `${rel.name}Options`;
|
|
57
|
+
return [
|
|
58
|
+
" <div>",
|
|
59
|
+
` <dt className="text-sm font-medium text-gray-500 dark:text-gray-400">${label}</dt>`,
|
|
60
|
+
` <dd className="mt-1 text-sm text-gray-900 dark:text-gray-100 break-words">{resolveEntityDisplayName((item as any).${fkName}, ${optionsVar})}</dd>`,
|
|
61
|
+
" </div>"
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
function renderField(field, opts) {
|
|
65
|
+
const label = field.label ?? humanize(field.name);
|
|
66
|
+
const labelCls = opts.muted ? "font-medium uppercase tracking-wide text-gray-400 dark:text-gray-500" : "text-sm font-medium text-gray-500 dark:text-gray-400";
|
|
67
|
+
const valueCls = opts.muted ? "text-gray-500 dark:text-gray-400" : "mt-1 text-sm text-gray-900 dark:text-gray-100 break-words";
|
|
68
|
+
return [
|
|
69
|
+
" <div>",
|
|
70
|
+
` <dt className="${labelCls}">${label}</dt>`,
|
|
71
|
+
` <dd className="${valueCls}">{String((item as any).${field.name} ?? '')}</dd>`,
|
|
72
|
+
" </div>"
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
function humanize(name) {
|
|
76
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
composeDetailBody
|
|
80
|
+
};
|
package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { METADATA_FIELDS } from "@specverse/runtime/views/core";
|
|
2
|
+
import { extractBelongsToTargets } from "./belongs-to.js";
|
|
3
|
+
const AUTO_GENERATED_FIELD_NAMES = new Set(METADATA_FIELDS);
|
|
4
|
+
function composeFormBody(context) {
|
|
5
|
+
const belongsTo = extractBelongsToTargets(context.model);
|
|
6
|
+
const shadowedFKs = new Set(belongsTo.map((rel) => `${rel.name}Id`));
|
|
7
|
+
const fields = selectFormFields(context.model, shadowedFKs);
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push('<div className="space-y-4">');
|
|
10
|
+
if (fields.length === 0 && belongsTo.length === 0) {
|
|
11
|
+
lines.push(' <p className="text-sm text-gray-400">No editable fields for this model.</p>');
|
|
12
|
+
lines.push("</div>");
|
|
13
|
+
return lines.join("\n");
|
|
14
|
+
}
|
|
15
|
+
for (const field of fields) {
|
|
16
|
+
lines.push(...renderInput(field));
|
|
17
|
+
}
|
|
18
|
+
for (const rel of belongsTo) {
|
|
19
|
+
lines.push(...renderRelationshipSelect(rel));
|
|
20
|
+
}
|
|
21
|
+
lines.push("</div>");
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
function renderRelationshipSelect(rel) {
|
|
25
|
+
const fkName = `${rel.name}Id`;
|
|
26
|
+
const varName = `${rel.name}Options`;
|
|
27
|
+
const label = humanize(rel.name);
|
|
28
|
+
return [
|
|
29
|
+
" <div>",
|
|
30
|
+
` <label className="${LABEL_CLS}" htmlFor="${fkName}">${label} *</label>`,
|
|
31
|
+
` <select`,
|
|
32
|
+
` id="${fkName}"`,
|
|
33
|
+
` className="${INPUT_CLS}"`,
|
|
34
|
+
` value={String((formData as any).${fkName} ?? '')}`,
|
|
35
|
+
` onChange={e => handleChange('${fkName}', e.target.value)} required`,
|
|
36
|
+
` >`,
|
|
37
|
+
` <option value="">\u2014 choose \u2014</option>`,
|
|
38
|
+
` {${varName}.map((opt: any) => (`,
|
|
39
|
+
` <option key={opt.id} value={opt.id}>{getEntityDisplayName(opt)}</option>`,
|
|
40
|
+
` ))}`,
|
|
41
|
+
` </select>`,
|
|
42
|
+
" </div>"
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
function selectFormFields(model, skip = /* @__PURE__ */ new Set()) {
|
|
46
|
+
const out = [];
|
|
47
|
+
const attrs = model.attributes ?? {};
|
|
48
|
+
const lifecycleStates = extractLifecycleStates(model);
|
|
49
|
+
for (const [name, rawDef] of Object.entries(attrs)) {
|
|
50
|
+
if (AUTO_GENERATED_FIELD_NAMES.has(name)) continue;
|
|
51
|
+
if (skip.has(name)) continue;
|
|
52
|
+
const def = rawDef;
|
|
53
|
+
if (def?.auto) continue;
|
|
54
|
+
const type = def?.type ?? parseTypeFromConvention(def) ?? "String";
|
|
55
|
+
const required = def?.required === true || hasConventionFlag(def, "required");
|
|
56
|
+
const declaredValues = def?.values;
|
|
57
|
+
const lifecycleValues = lifecycleStates.get(name);
|
|
58
|
+
const values = declaredValues ?? lifecycleValues;
|
|
59
|
+
const maxLength = def?.maxLength ?? def?.max;
|
|
60
|
+
const isLongString = typeof maxLength === "number" && maxLength > 100;
|
|
61
|
+
out.push({
|
|
62
|
+
name,
|
|
63
|
+
label: humanize(name),
|
|
64
|
+
type: normalizeType(type),
|
|
65
|
+
required,
|
|
66
|
+
values,
|
|
67
|
+
isLongString
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
function extractLifecycleStates(model) {
|
|
73
|
+
const out = /* @__PURE__ */ new Map();
|
|
74
|
+
const lifecycles = model.lifecycles ?? {};
|
|
75
|
+
for (const [name, rawDef] of Object.entries(lifecycles)) {
|
|
76
|
+
if (!rawDef || typeof rawDef !== "object") continue;
|
|
77
|
+
const def = rawDef;
|
|
78
|
+
if (Array.isArray(def.states) && def.states.length > 0) {
|
|
79
|
+
const names = def.states.map((s) => typeof s === "string" ? s : s?.name ?? s?.id ?? "").filter(Boolean);
|
|
80
|
+
if (names.length > 0) {
|
|
81
|
+
out.set(name, names);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (typeof def.flow === "string") {
|
|
86
|
+
const states = def.flow.split(/\s*(?:->|,)\s*/).map((s) => s.trim()).filter(Boolean);
|
|
87
|
+
if (states.length > 0) out.set(name, states);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
function parseTypeFromConvention(def) {
|
|
93
|
+
if (typeof def === "string") {
|
|
94
|
+
const first = def.trim().split(/\s+/)[0];
|
|
95
|
+
return first;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function hasConventionFlag(def, flag) {
|
|
100
|
+
if (typeof def === "string") {
|
|
101
|
+
return def.split(/\s+/).includes(flag);
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
function normalizeType(type) {
|
|
106
|
+
return type.replace(/[^A-Za-z].*$/, "");
|
|
107
|
+
}
|
|
108
|
+
const INPUT_CLS = "w-full rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100";
|
|
109
|
+
const LABEL_CLS = "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1";
|
|
110
|
+
function renderInput(field) {
|
|
111
|
+
const requiredMark = field.required ? " *" : "";
|
|
112
|
+
const reqAttr = field.required ? " required" : "";
|
|
113
|
+
if (field.values && field.values.length > 0) {
|
|
114
|
+
const options = field.values.map((v) => ` <option value="${escapeAttr(v)}">${escapeText(v)}</option>`).join("\n");
|
|
115
|
+
return [
|
|
116
|
+
" <div>",
|
|
117
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
118
|
+
` <select`,
|
|
119
|
+
` id="${field.name}"`,
|
|
120
|
+
` className="${INPUT_CLS}"`,
|
|
121
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
122
|
+
` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
|
|
123
|
+
` >`,
|
|
124
|
+
` <option value="">\u2014 choose \u2014</option>`,
|
|
125
|
+
options,
|
|
126
|
+
` </select>`,
|
|
127
|
+
" </div>"
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
if (field.type === "Text" || field.isLongString) {
|
|
131
|
+
return [
|
|
132
|
+
" <div>",
|
|
133
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
134
|
+
` <textarea`,
|
|
135
|
+
` id="${field.name}"`,
|
|
136
|
+
` className="${INPUT_CLS}"`,
|
|
137
|
+
` rows={4}`,
|
|
138
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
139
|
+
` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
|
|
140
|
+
` />`,
|
|
141
|
+
" </div>"
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
if (field.type === "Boolean") {
|
|
145
|
+
return [
|
|
146
|
+
" <div>",
|
|
147
|
+
` <label className="inline-flex items-center gap-2">`,
|
|
148
|
+
` <input`,
|
|
149
|
+
` id="${field.name}"`,
|
|
150
|
+
` type="checkbox"`,
|
|
151
|
+
` checked={Boolean((formData as any).${field.name})}`,
|
|
152
|
+
` onChange={e => handleChange('${field.name}', e.target.checked)}`,
|
|
153
|
+
` />`,
|
|
154
|
+
` <span className="text-sm text-gray-700 dark:text-gray-300">${field.label}${requiredMark}</span>`,
|
|
155
|
+
` </label>`,
|
|
156
|
+
" </div>"
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
const inputType = mapInputType(field.type);
|
|
160
|
+
return [
|
|
161
|
+
" <div>",
|
|
162
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
163
|
+
` <input`,
|
|
164
|
+
` id="${field.name}"`,
|
|
165
|
+
` type="${inputType}"`,
|
|
166
|
+
` className="${INPUT_CLS}"`,
|
|
167
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
168
|
+
` onChange={e => handleChange('${field.name}', ${coerceOnChange(field.type)})}${reqAttr}`,
|
|
169
|
+
` />`,
|
|
170
|
+
" </div>"
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
function mapInputType(type) {
|
|
174
|
+
switch (type) {
|
|
175
|
+
case "Integer":
|
|
176
|
+
case "Float":
|
|
177
|
+
case "Number":
|
|
178
|
+
case "Money":
|
|
179
|
+
return "number";
|
|
180
|
+
case "DateTime":
|
|
181
|
+
return "datetime-local";
|
|
182
|
+
case "Date":
|
|
183
|
+
return "date";
|
|
184
|
+
case "Email":
|
|
185
|
+
return "email";
|
|
186
|
+
case "URL":
|
|
187
|
+
return "url";
|
|
188
|
+
case "String":
|
|
189
|
+
case "UUID":
|
|
190
|
+
default:
|
|
191
|
+
return "text";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function coerceOnChange(type) {
|
|
195
|
+
switch (type) {
|
|
196
|
+
case "Integer":
|
|
197
|
+
case "Number":
|
|
198
|
+
return "e.target.value === '' ? undefined : Number(e.target.value)";
|
|
199
|
+
case "Float":
|
|
200
|
+
case "Money":
|
|
201
|
+
return "e.target.value === '' ? undefined : parseFloat(e.target.value)";
|
|
202
|
+
default:
|
|
203
|
+
return "e.target.value";
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function humanize(name) {
|
|
207
|
+
return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
|
|
208
|
+
}
|
|
209
|
+
function escapeAttr(s) {
|
|
210
|
+
return s.replace(/"/g, """);
|
|
211
|
+
}
|
|
212
|
+
function escapeText(s) {
|
|
213
|
+
return s.replace(/</g, "<").replace(/>/g, ">");
|
|
214
|
+
}
|
|
215
|
+
export {
|
|
216
|
+
composeFormBody
|
|
217
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
function emitEntityDisplay() {
|
|
2
|
+
return `/**
|
|
3
|
+
* Pick the best human-readable label for an entity.
|
|
4
|
+
*
|
|
5
|
+
* Walks a prioritized list of candidate fields. Falls back to a
|
|
6
|
+
* truncated id if none match. Inlined into this project by
|
|
7
|
+
* @specverse/realize (ReactAppStarter); edit freely \u2014 nothing in
|
|
8
|
+
* the factory's regeneration will clobber your edits.
|
|
9
|
+
*/
|
|
10
|
+
export function getEntityDisplayName(entity: Record<string, unknown> | null | undefined): string {
|
|
11
|
+
if (!entity) return '';
|
|
12
|
+
|
|
13
|
+
const candidates = ['name', 'title', 'displayName', 'label', 'username', 'email'];
|
|
14
|
+
for (const key of candidates) {
|
|
15
|
+
const value = entity[key];
|
|
16
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const id = entity.id;
|
|
22
|
+
if (typeof id === 'string') {
|
|
23
|
+
return id.length > 8 ? id.slice(0, 8) + '\u2026' : id;
|
|
24
|
+
}
|
|
25
|
+
return id != null ? String(id) : '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Map an entity id to the display name of the record with that id in
|
|
30
|
+
* a provided list. Handy for resolving belongsTo FK columns in
|
|
31
|
+
* list / detail / dashboard views.
|
|
32
|
+
*
|
|
33
|
+
* Typed as \`readonly unknown[]\` so callers can pass arrays of
|
|
34
|
+
* specific model types (e.g. \`User[]\`) without needing to cast; the
|
|
35
|
+
* runtime check on \`.id\` narrows safely.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveEntityDisplayName(
|
|
38
|
+
id: unknown,
|
|
39
|
+
records: readonly unknown[]
|
|
40
|
+
): string {
|
|
41
|
+
if (id == null) return '';
|
|
42
|
+
const match = records.find((r): r is Record<string, unknown> =>
|
|
43
|
+
typeof r === 'object' && r !== null && (r as { id?: unknown }).id === id
|
|
44
|
+
);
|
|
45
|
+
return match ? getEntityDisplayName(match) : String(id);
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
emitEntityDisplay
|
|
51
|
+
};
|