@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,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form-view body composer for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Emits one <label>+<input> pair per editable attribute of the model.
|
|
5
|
+
* Input type is inferred from the attribute's type + constraints
|
|
6
|
+
* (mirrors the canonical mapping in
|
|
7
|
+
* `entities/src/core/views/inference/component-mappings.json`, minus
|
|
8
|
+
* the atomic-component indirection).
|
|
9
|
+
*
|
|
10
|
+
* Auto-generated fields (id, timestamps, `auto=...` markers) are
|
|
11
|
+
* omitted — the backend assigns them.
|
|
12
|
+
*
|
|
13
|
+
* Relationship fields (belongsTo) are emitted as <select> dropdowns
|
|
14
|
+
* backed by the related model's list query. The corresponding hook
|
|
15
|
+
* calls and imports are wired by view-emitter via the RELATED_HOOKS /
|
|
16
|
+
* RELATED_IMPORTS substitutions — this composer only emits the JSX
|
|
17
|
+
* that consumes the `${relName}Options` variable.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { METADATA_FIELDS } from '@specverse/runtime/views/core';
|
|
21
|
+
import type { EmitContext, ModelSpec } from './view-emitter.js';
|
|
22
|
+
import { extractBelongsToTargets, type BelongsToRel } from './belongs-to.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Attribute names the backend always generates — never render as
|
|
26
|
+
* inputs. Sourced from the pattern library's metadata set; any
|
|
27
|
+
* attribute with `auto=...` or category=metadata is additionally
|
|
28
|
+
* skipped via per-field checks in `selectFormFields` below.
|
|
29
|
+
*/
|
|
30
|
+
const AUTO_GENERATED_FIELD_NAMES = new Set(METADATA_FIELDS);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compose the form body as JSX-safe source. Dropped at `{{BODY}}`.
|
|
34
|
+
*/
|
|
35
|
+
export function composeFormBody(context: EmitContext): string {
|
|
36
|
+
const belongsTo = extractBelongsToTargets(context.model);
|
|
37
|
+
// FK columns shadowed by a belongsTo relationship are emitted in the
|
|
38
|
+
// belongsTo section as <select>s — skip them in the main loop so we
|
|
39
|
+
// don't render both a plain-text input and a dropdown for the same
|
|
40
|
+
// column.
|
|
41
|
+
const shadowedFKs = new Set(belongsTo.map(rel => `${rel.name}Id`));
|
|
42
|
+
const fields = selectFormFields(context.model, shadowedFKs);
|
|
43
|
+
|
|
44
|
+
const lines: string[] = [];
|
|
45
|
+
lines.push('<div className="space-y-4">');
|
|
46
|
+
|
|
47
|
+
if (fields.length === 0 && belongsTo.length === 0) {
|
|
48
|
+
lines.push(' <p className="text-sm text-gray-400">No editable fields for this model.</p>');
|
|
49
|
+
lines.push('</div>');
|
|
50
|
+
return lines.join('\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const field of fields) {
|
|
54
|
+
lines.push(...renderInput(field));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const rel of belongsTo) {
|
|
58
|
+
lines.push(...renderRelationshipSelect(rel));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lines.push('</div>');
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Emit a <select> dropdown for a belongsTo relationship. The options
|
|
67
|
+
* array (`${relName}Options`) is populated by the hook call wired in
|
|
68
|
+
* via view-emitter's RELATED_HOOKS substitution. Labels use
|
|
69
|
+
* `getEntityDisplayName` so users see human names instead of UUIDs.
|
|
70
|
+
*/
|
|
71
|
+
function renderRelationshipSelect(rel: BelongsToRel): string[] {
|
|
72
|
+
const fkName = `${rel.name}Id`;
|
|
73
|
+
const varName = `${rel.name}Options`;
|
|
74
|
+
const label = humanize(rel.name);
|
|
75
|
+
|
|
76
|
+
return [
|
|
77
|
+
' <div>',
|
|
78
|
+
` <label className="${LABEL_CLS}" htmlFor="${fkName}">${label} *</label>`,
|
|
79
|
+
` <select`,
|
|
80
|
+
` id="${fkName}"`,
|
|
81
|
+
` className="${INPUT_CLS}"`,
|
|
82
|
+
` value={String((formData as any).${fkName} ?? '')}`,
|
|
83
|
+
` onChange={e => handleChange('${fkName}', e.target.value)} required`,
|
|
84
|
+
` >`,
|
|
85
|
+
` <option value="">— choose —</option>`,
|
|
86
|
+
` {${varName}.map((opt: any) => (`,
|
|
87
|
+
` <option key={opt.id} value={opt.id}>{getEntityDisplayName(opt)}</option>`,
|
|
88
|
+
` ))}`,
|
|
89
|
+
` </select>`,
|
|
90
|
+
' </div>',
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
95
|
+
// Field selection
|
|
96
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
interface FieldInfo {
|
|
99
|
+
name: string;
|
|
100
|
+
label: string;
|
|
101
|
+
type: string;
|
|
102
|
+
required: boolean;
|
|
103
|
+
values?: string[]; // enum options if present
|
|
104
|
+
isLongString?: boolean;
|
|
105
|
+
auto?: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function selectFormFields(
|
|
109
|
+
model: ModelSpec,
|
|
110
|
+
skip: Set<string> = new Set()
|
|
111
|
+
): FieldInfo[] {
|
|
112
|
+
const out: FieldInfo[] = [];
|
|
113
|
+
const attrs = model.attributes ?? {};
|
|
114
|
+
const lifecycleStates = extractLifecycleStates(model);
|
|
115
|
+
|
|
116
|
+
for (const [name, rawDef] of Object.entries(attrs)) {
|
|
117
|
+
if (AUTO_GENERATED_FIELD_NAMES.has(name)) continue;
|
|
118
|
+
if (skip.has(name)) continue;
|
|
119
|
+
|
|
120
|
+
const def = rawDef as AttributeShape;
|
|
121
|
+
if (def?.auto) continue;
|
|
122
|
+
|
|
123
|
+
// Heuristic: parse a type like "String required unique" to detect
|
|
124
|
+
// convention-string shapes. We prefer the structured fields if
|
|
125
|
+
// they're present.
|
|
126
|
+
const type = (def?.type ?? parseTypeFromConvention(def)) ?? 'String';
|
|
127
|
+
const required = def?.required === true || hasConventionFlag(def, 'required');
|
|
128
|
+
const declaredValues = def?.values as string[] | undefined;
|
|
129
|
+
// Lifecycle-backed attributes: if the model declares a lifecycle
|
|
130
|
+
// with the same name as this attribute, the attribute's valid
|
|
131
|
+
// values are the lifecycle states. Matches the FK rule's spirit —
|
|
132
|
+
// any attribute whose legal values are declared elsewhere in the
|
|
133
|
+
// spec should render as a <select>, not free text.
|
|
134
|
+
const lifecycleValues = lifecycleStates.get(name);
|
|
135
|
+
const values = declaredValues ?? lifecycleValues;
|
|
136
|
+
const maxLength = def?.maxLength ?? def?.max as number | undefined;
|
|
137
|
+
const isLongString =
|
|
138
|
+
typeof maxLength === 'number' && maxLength > 100;
|
|
139
|
+
|
|
140
|
+
out.push({
|
|
141
|
+
name,
|
|
142
|
+
label: humanize(name),
|
|
143
|
+
type: normalizeType(type),
|
|
144
|
+
required,
|
|
145
|
+
values,
|
|
146
|
+
isLongString,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Map from attribute name → list of lifecycle states, extracted from
|
|
154
|
+
* the model's `lifecycles` section. Supports both the convention-string
|
|
155
|
+
* shape (`flow: "a -> b -> c"`) and structured (`states: ["a", ...]`).
|
|
156
|
+
*
|
|
157
|
+
* Example:
|
|
158
|
+
* lifecycles:
|
|
159
|
+
* status:
|
|
160
|
+
* flow: "planning -> active -> completed -> archived"
|
|
161
|
+
* returns { status → ['planning', 'active', 'completed', 'archived'] }
|
|
162
|
+
*/
|
|
163
|
+
function extractLifecycleStates(model: ModelSpec): Map<string, string[]> {
|
|
164
|
+
const out = new Map<string, string[]>();
|
|
165
|
+
const lifecycles = (model as ModelSpec & { lifecycles?: Record<string, unknown> }).lifecycles ?? {};
|
|
166
|
+
|
|
167
|
+
for (const [name, rawDef] of Object.entries(lifecycles)) {
|
|
168
|
+
if (!rawDef || typeof rawDef !== 'object') continue;
|
|
169
|
+
// The parser normalizes lifecycles into a rich structured form:
|
|
170
|
+
// { flow: "a -> b -> c",
|
|
171
|
+
// states: [{ name: "a" }, { name: "b" }, { name: "c" }],
|
|
172
|
+
// transitions: [...],
|
|
173
|
+
// initialState: "a" }
|
|
174
|
+
// Also support the pre-normalization shape (states as plain strings,
|
|
175
|
+
// or just a flow string) so the composer works on raw specs too.
|
|
176
|
+
const def = rawDef as {
|
|
177
|
+
flow?: string;
|
|
178
|
+
states?: Array<string | { name?: string; id?: string }>;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (Array.isArray(def.states) && def.states.length > 0) {
|
|
182
|
+
const names = def.states
|
|
183
|
+
.map(s => (typeof s === 'string' ? s : s?.name ?? s?.id ?? ''))
|
|
184
|
+
.filter(Boolean);
|
|
185
|
+
if (names.length > 0) {
|
|
186
|
+
out.set(name, names);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (typeof def.flow === 'string') {
|
|
192
|
+
// Split on -> with optional whitespace. Also accept commas as a
|
|
193
|
+
// fallback separator so a state list like "a, b, c" still works.
|
|
194
|
+
const states = def.flow
|
|
195
|
+
.split(/\s*(?:->|,)\s*/)
|
|
196
|
+
.map(s => s.trim())
|
|
197
|
+
.filter(Boolean);
|
|
198
|
+
if (states.length > 0) out.set(name, states);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Structured attribute object from the parsed spec. */
|
|
205
|
+
interface AttributeShape {
|
|
206
|
+
type?: string;
|
|
207
|
+
required?: boolean;
|
|
208
|
+
unique?: boolean;
|
|
209
|
+
auto?: string;
|
|
210
|
+
values?: string[];
|
|
211
|
+
maxLength?: number;
|
|
212
|
+
max?: number;
|
|
213
|
+
[k: string]: unknown;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function parseTypeFromConvention(def: unknown): string | undefined {
|
|
217
|
+
// Convention form: "String required unique"
|
|
218
|
+
if (typeof def === 'string') {
|
|
219
|
+
const first = def.trim().split(/\s+/)[0];
|
|
220
|
+
return first;
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function hasConventionFlag(def: unknown, flag: string): boolean {
|
|
226
|
+
if (typeof def === 'string') {
|
|
227
|
+
return def.split(/\s+/).includes(flag);
|
|
228
|
+
}
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function normalizeType(type: string): string {
|
|
233
|
+
// Strip any trailing modifiers from a compact convention-string form.
|
|
234
|
+
return type.replace(/[^A-Za-z].*$/, '');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// (`extractBelongsTo` was removed — the shared helper in
|
|
238
|
+
// `./belongs-to.ts` returns both the relationship name AND the target
|
|
239
|
+
// model so view-emitter can wire the hook call for each dropdown.)
|
|
240
|
+
|
|
241
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
242
|
+
// Input rendering
|
|
243
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
const INPUT_CLS =
|
|
246
|
+
'w-full rounded border border-gray-300 px-3 py-2 text-sm ' +
|
|
247
|
+
'focus:outline-none focus:ring-2 focus:ring-blue-500 ' +
|
|
248
|
+
'dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100';
|
|
249
|
+
|
|
250
|
+
const LABEL_CLS = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1';
|
|
251
|
+
|
|
252
|
+
function renderInput(field: FieldInfo): string[] {
|
|
253
|
+
const requiredMark = field.required ? ' *' : '';
|
|
254
|
+
const reqAttr = field.required ? ' required' : '';
|
|
255
|
+
|
|
256
|
+
// Enum → <select>
|
|
257
|
+
if (field.values && field.values.length > 0) {
|
|
258
|
+
const options = field.values
|
|
259
|
+
.map(v => ` <option value="${escapeAttr(v)}">${escapeText(v)}</option>`)
|
|
260
|
+
.join('\n');
|
|
261
|
+
return [
|
|
262
|
+
' <div>',
|
|
263
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
264
|
+
` <select`,
|
|
265
|
+
` id="${field.name}"`,
|
|
266
|
+
` className="${INPUT_CLS}"`,
|
|
267
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
268
|
+
` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
|
|
269
|
+
` >`,
|
|
270
|
+
` <option value="">— choose —</option>`,
|
|
271
|
+
options,
|
|
272
|
+
` </select>`,
|
|
273
|
+
' </div>',
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Long-string / Text → <textarea>
|
|
278
|
+
if (field.type === 'Text' || field.isLongString) {
|
|
279
|
+
return [
|
|
280
|
+
' <div>',
|
|
281
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
282
|
+
` <textarea`,
|
|
283
|
+
` id="${field.name}"`,
|
|
284
|
+
` className="${INPUT_CLS}"`,
|
|
285
|
+
` rows={4}`,
|
|
286
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
287
|
+
` onChange={e => handleChange('${field.name}', e.target.value)}${reqAttr}`,
|
|
288
|
+
` />`,
|
|
289
|
+
' </div>',
|
|
290
|
+
];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Boolean → checkbox
|
|
294
|
+
if (field.type === 'Boolean') {
|
|
295
|
+
return [
|
|
296
|
+
' <div>',
|
|
297
|
+
` <label className="inline-flex items-center gap-2">`,
|
|
298
|
+
` <input`,
|
|
299
|
+
` id="${field.name}"`,
|
|
300
|
+
` type="checkbox"`,
|
|
301
|
+
` checked={Boolean((formData as any).${field.name})}`,
|
|
302
|
+
` onChange={e => handleChange('${field.name}', e.target.checked)}`,
|
|
303
|
+
` />`,
|
|
304
|
+
` <span className="text-sm text-gray-700 dark:text-gray-300">${field.label}${requiredMark}</span>`,
|
|
305
|
+
` </label>`,
|
|
306
|
+
' </div>',
|
|
307
|
+
];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const inputType = mapInputType(field.type);
|
|
311
|
+
return [
|
|
312
|
+
' <div>',
|
|
313
|
+
` <label className="${LABEL_CLS}" htmlFor="${field.name}">${field.label}${requiredMark}</label>`,
|
|
314
|
+
` <input`,
|
|
315
|
+
` id="${field.name}"`,
|
|
316
|
+
` type="${inputType}"`,
|
|
317
|
+
` className="${INPUT_CLS}"`,
|
|
318
|
+
` value={String((formData as any).${field.name} ?? '')}`,
|
|
319
|
+
` onChange={e => handleChange('${field.name}', ${coerceOnChange(field.type)})}${reqAttr}`,
|
|
320
|
+
` />`,
|
|
321
|
+
' </div>',
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Scalar SpecVerse type → HTML input type attribute. */
|
|
326
|
+
function mapInputType(type: string): string {
|
|
327
|
+
switch (type) {
|
|
328
|
+
case 'Integer':
|
|
329
|
+
case 'Float':
|
|
330
|
+
case 'Number':
|
|
331
|
+
case 'Money':
|
|
332
|
+
return 'number';
|
|
333
|
+
case 'DateTime':
|
|
334
|
+
return 'datetime-local';
|
|
335
|
+
case 'Date':
|
|
336
|
+
return 'date';
|
|
337
|
+
case 'Email':
|
|
338
|
+
return 'email';
|
|
339
|
+
case 'URL':
|
|
340
|
+
return 'url';
|
|
341
|
+
case 'String':
|
|
342
|
+
case 'UUID':
|
|
343
|
+
default:
|
|
344
|
+
return 'text';
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* JSX snippet for the onChange handler's second argument — coerces
|
|
350
|
+
* the string value to the right shape for the field. The user can
|
|
351
|
+
* edit this; defaults are a reasonable starter.
|
|
352
|
+
*/
|
|
353
|
+
function coerceOnChange(type: string): string {
|
|
354
|
+
switch (type) {
|
|
355
|
+
case 'Integer':
|
|
356
|
+
case 'Number':
|
|
357
|
+
return "e.target.value === '' ? undefined : Number(e.target.value)";
|
|
358
|
+
case 'Float':
|
|
359
|
+
case 'Money':
|
|
360
|
+
return "e.target.value === '' ? undefined : parseFloat(e.target.value)";
|
|
361
|
+
default:
|
|
362
|
+
return 'e.target.value';
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
367
|
+
// Small helpers
|
|
368
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
369
|
+
|
|
370
|
+
function humanize(name: string): string {
|
|
371
|
+
return name
|
|
372
|
+
.replace(/([A-Z])/g, ' $1')
|
|
373
|
+
.replace(/^./, c => c.toUpperCase())
|
|
374
|
+
.trim();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function escapeAttr(s: string): string {
|
|
378
|
+
return s.replace(/"/g, '"');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function escapeText(s: string): string {
|
|
382
|
+
return s.replace(/</g, '<').replace(/>/g, '>');
|
|
383
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers emitter for ReactAppStarter
|
|
3
|
+
*
|
|
4
|
+
* Returns the source of small utility files that generated projects
|
|
5
|
+
* use. Inlined into `src/lib/` at realize time rather than imported
|
|
6
|
+
* from `@specverse/runtime` — Factory B's defining trait is that the
|
|
7
|
+
* generated code has no @specverse/* runtime dependency.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Source of `src/lib/entity-display.ts`.
|
|
12
|
+
*
|
|
13
|
+
* Mirrors `getEntityDisplayName` from
|
|
14
|
+
* `@specverse/runtime/views/core/entity-display`. Used by:
|
|
15
|
+
* - The FK-resolution TODO in detail-view output (users who upgrade
|
|
16
|
+
* the plain-FK display to a resolved display name import this).
|
|
17
|
+
* - Any user-written code that wants to label a record.
|
|
18
|
+
*/
|
|
19
|
+
export function emitEntityDisplay(): string {
|
|
20
|
+
return `/**
|
|
21
|
+
* Pick the best human-readable label for an entity.
|
|
22
|
+
*
|
|
23
|
+
* Walks a prioritized list of candidate fields. Falls back to a
|
|
24
|
+
* truncated id if none match. Inlined into this project by
|
|
25
|
+
* @specverse/realize (ReactAppStarter); edit freely — nothing in
|
|
26
|
+
* the factory's regeneration will clobber your edits.
|
|
27
|
+
*/
|
|
28
|
+
export function getEntityDisplayName(entity: Record<string, unknown> | null | undefined): string {
|
|
29
|
+
if (!entity) return '';
|
|
30
|
+
|
|
31
|
+
const candidates = ['name', 'title', 'displayName', 'label', 'username', 'email'];
|
|
32
|
+
for (const key of candidates) {
|
|
33
|
+
const value = entity[key];
|
|
34
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const id = entity.id;
|
|
40
|
+
if (typeof id === 'string') {
|
|
41
|
+
return id.length > 8 ? id.slice(0, 8) + '…' : id;
|
|
42
|
+
}
|
|
43
|
+
return id != null ? String(id) : '';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Map an entity id to the display name of the record with that id in
|
|
48
|
+
* a provided list. Handy for resolving belongsTo FK columns in
|
|
49
|
+
* list / detail / dashboard views.
|
|
50
|
+
*
|
|
51
|
+
* Typed as \`readonly unknown[]\` so callers can pass arrays of
|
|
52
|
+
* specific model types (e.g. \`User[]\`) without needing to cast; the
|
|
53
|
+
* runtime check on \`.id\` narrows safely.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveEntityDisplayName(
|
|
56
|
+
id: unknown,
|
|
57
|
+
records: readonly unknown[]
|
|
58
|
+
): string {
|
|
59
|
+
if (id == null) return '';
|
|
60
|
+
const match = records.find((r): r is Record<string, unknown> =>
|
|
61
|
+
typeof r === 'object' && r !== null && (r as { id?: unknown }).id === id
|
|
62
|
+
);
|
|
63
|
+
return match ? getEntityDisplayName(match) : String(id);
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
}
|