@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.
Files changed (285) hide show
  1. package/assets/examples/manifests/frontend-only.yaml +3 -6
  2. package/assets/examples/manifests/fullstack-app.yaml +5 -7
  3. package/assets/examples/manifests/fullstack-monorepo.yaml +3 -6
  4. package/assets/templates/default/specs/main.specly +65 -0
  5. package/dist/inference/comprehensive-engine.d.ts.map +1 -1
  6. package/dist/inference/comprehensive-engine.js +3 -19
  7. package/dist/inference/comprehensive-engine.js.map +1 -1
  8. package/dist/inference/core/rule-engine.d.ts +31 -0
  9. package/dist/inference/core/rule-engine.d.ts.map +1 -1
  10. package/dist/inference/core/rule-engine.js +117 -33
  11. package/dist/inference/core/rule-engine.js.map +1 -1
  12. package/dist/inference/core/rule-file-types.d.ts +0 -2
  13. package/dist/inference/core/rule-file-types.d.ts.map +1 -1
  14. package/dist/inference/core/rule-file-types.js +3 -6
  15. package/dist/inference/core/rule-file-types.js.map +1 -1
  16. package/dist/inference/core/rule-loader.d.ts +5 -15
  17. package/dist/inference/core/rule-loader.d.ts.map +1 -1
  18. package/dist/inference/core/rule-loader.js +43 -132
  19. package/dist/inference/core/rule-loader.js.map +1 -1
  20. package/dist/inference/core/types.d.ts +0 -6
  21. package/dist/inference/core/types.d.ts.map +1 -1
  22. package/dist/inference/core/types.js +0 -4
  23. package/dist/inference/core/types.js.map +1 -1
  24. package/dist/inference/logical/generators/component-type-resolver.d.ts +0 -26
  25. package/dist/inference/logical/generators/component-type-resolver.d.ts.map +1 -1
  26. package/dist/inference/logical/generators/component-type-resolver.js +0 -19
  27. package/dist/inference/logical/generators/component-type-resolver.js.map +1 -1
  28. package/dist/inference/logical/generators/specialist-view-expander.d.ts +1 -17
  29. package/dist/inference/logical/generators/specialist-view-expander.d.ts.map +1 -1
  30. package/dist/inference/logical/generators/specialist-view-expander.js +0 -15
  31. package/dist/inference/logical/generators/specialist-view-expander.js.map +1 -1
  32. package/dist/inference/logical/generators/view-generator.d.ts +4 -14
  33. package/dist/inference/logical/generators/view-generator.d.ts.map +1 -1
  34. package/dist/inference/logical/generators/view-generator.js +6 -26
  35. package/dist/inference/logical/generators/view-generator.js.map +1 -1
  36. package/dist/inference/logical/index.d.ts +2 -2
  37. package/dist/inference/logical/index.d.ts.map +1 -1
  38. package/dist/inference/logical/logical-engine.d.ts.map +1 -1
  39. package/dist/inference/logical/logical-engine.js +17 -80
  40. package/dist/inference/logical/logical-engine.js.map +1 -1
  41. package/dist/inference/quint-transpiler.d.ts +5 -3
  42. package/dist/inference/quint-transpiler.d.ts.map +1 -1
  43. package/dist/inference/quint-transpiler.js +11 -6
  44. package/dist/inference/quint-transpiler.js.map +1 -1
  45. package/dist/libs/instance-factories/CURVED-INTERFACE.md +278 -0
  46. package/dist/libs/instance-factories/README.md +73 -0
  47. package/dist/libs/instance-factories/applications/README.md +51 -0
  48. package/dist/libs/instance-factories/applications/generic-app.yaml +52 -0
  49. package/{libs/instance-factories/applications/react-app.yaml → dist/libs/instance-factories/applications/react-app-runtime.yaml} +30 -77
  50. package/dist/libs/instance-factories/applications/react-app-starter.yaml +143 -0
  51. package/dist/libs/instance-factories/applications/templates/react/env-example-generator.js +24 -2
  52. package/dist/libs/instance-factories/applications/templates/react/vite-config-generator.js +54 -33
  53. package/dist/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
  54. package/dist/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.js +69 -0
  55. package/dist/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.js +110 -0
  56. package/dist/libs/instance-factories/applications/templates/react-starter/belongs-to.js +40 -0
  57. package/dist/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.js +129 -0
  58. package/dist/libs/instance-factories/applications/templates/react-starter/detail-body-composer.js +80 -0
  59. package/dist/libs/instance-factories/applications/templates/react-starter/form-body-composer.js +217 -0
  60. package/dist/libs/instance-factories/applications/templates/react-starter/helpers-emitter.js +51 -0
  61. package/dist/libs/instance-factories/applications/templates/react-starter/html-to-jsx.js +192 -0
  62. package/dist/libs/instance-factories/applications/templates/react-starter/list-body-composer.js +56 -0
  63. package/dist/libs/instance-factories/applications/templates/react-starter/orchestrator.js +41 -0
  64. package/dist/libs/instance-factories/applications/templates/react-starter/package-json-generator.js +38 -0
  65. package/dist/libs/instance-factories/applications/templates/react-starter/regen-safety.js +89 -0
  66. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
  67. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
  68. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
  69. package/dist/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
  70. package/dist/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.js +95 -0
  71. package/dist/libs/instance-factories/applications/templates/react-starter/view-emitter.js +81 -0
  72. package/dist/libs/instance-factories/applications/templates/react-starter/views-generator.js +66 -0
  73. package/dist/libs/instance-factories/archived/fastify-prisma.yaml +104 -0
  74. package/dist/libs/instance-factories/cli/README.md +43 -0
  75. package/dist/libs/instance-factories/cli/commander-js.yaml +55 -0
  76. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +63 -12
  77. package/dist/libs/instance-factories/communication/README.md +47 -0
  78. package/dist/libs/instance-factories/communication/event-emitter.yaml +60 -0
  79. package/dist/libs/instance-factories/communication/rabbitmq-events.yaml +87 -0
  80. package/dist/libs/instance-factories/controllers/README.md +42 -0
  81. package/dist/libs/instance-factories/controllers/fastify.yaml +139 -0
  82. package/dist/libs/instance-factories/controllers/templates/fastify/server-generator.js +29 -2
  83. package/dist/libs/instance-factories/infrastructure/README.md +29 -0
  84. package/dist/libs/instance-factories/infrastructure/docker-k8s.yaml +61 -0
  85. package/dist/libs/instance-factories/orms/README.md +54 -0
  86. package/dist/libs/instance-factories/orms/prisma.yaml +89 -0
  87. package/dist/libs/instance-factories/orms/templates/prisma/schema-generator.js +2 -2
  88. package/dist/libs/instance-factories/scaffolding/README.md +49 -0
  89. package/dist/libs/instance-factories/scaffolding/generic-scaffold.yaml +65 -0
  90. package/dist/libs/instance-factories/sdks/README.md +28 -0
  91. package/dist/libs/instance-factories/sdks/python-sdk.yaml +66 -0
  92. package/dist/libs/instance-factories/sdks/typescript-sdk.yaml +59 -0
  93. package/dist/libs/instance-factories/services/README.md +55 -0
  94. package/dist/libs/instance-factories/services/prisma-services.yaml +71 -0
  95. package/dist/libs/instance-factories/storage/README.md +34 -0
  96. package/dist/libs/instance-factories/storage/mongodb.yaml +79 -0
  97. package/dist/libs/instance-factories/storage/postgresql.yaml +75 -0
  98. package/dist/libs/instance-factories/storage/redis.yaml +79 -0
  99. package/dist/libs/instance-factories/testing/README.md +40 -0
  100. package/dist/libs/instance-factories/testing/vitest-tests.yaml +63 -0
  101. package/dist/libs/instance-factories/tools/README.md +70 -0
  102. package/dist/libs/instance-factories/tools/mcp.yaml +36 -0
  103. package/dist/libs/instance-factories/tools/vscode.yaml +35 -0
  104. package/dist/libs/instance-factories/validation/README.md +38 -0
  105. package/dist/libs/instance-factories/validation/zod.yaml +56 -0
  106. package/dist/realize/engines/code-generator.d.ts.map +1 -1
  107. package/dist/realize/engines/code-generator.js +3 -0
  108. package/dist/realize/engines/code-generator.js.map +1 -1
  109. package/dist/realize/index.d.ts.map +1 -1
  110. package/dist/realize/index.js +15 -22
  111. package/dist/realize/index.js.map +1 -1
  112. package/dist/registry/utils/manifest-adapter.d.ts +8 -1
  113. package/dist/registry/utils/manifest-adapter.d.ts.map +1 -1
  114. package/dist/registry/utils/manifest-adapter.js +8 -1
  115. package/dist/registry/utils/manifest-adapter.js.map +1 -1
  116. package/libs/instance-factories/applications/react-app-starter.yaml +143 -0
  117. package/libs/instance-factories/applications/templates/react/env-example-generator.ts +24 -2
  118. package/libs/instance-factories/applications/templates/react/vite-config-generator.ts +54 -33
  119. package/libs/instance-factories/applications/templates/react-starter/README.md +211 -0
  120. package/libs/instance-factories/applications/templates/react-starter/__tests__/dashboard-body-composer.test.ts +153 -0
  121. package/libs/instance-factories/applications/templates/react-starter/__tests__/detail-body-composer.test.ts +146 -0
  122. package/libs/instance-factories/applications/templates/react-starter/__tests__/form-body-composer.test.ts +188 -0
  123. package/libs/instance-factories/applications/templates/react-starter/__tests__/helpers-emitter.test.ts +55 -0
  124. package/libs/instance-factories/applications/templates/react-starter/__tests__/html-to-jsx.test.ts +140 -0
  125. package/libs/instance-factories/applications/templates/react-starter/__tests__/list-body-composer.test.ts +146 -0
  126. package/libs/instance-factories/applications/templates/react-starter/__tests__/orchestrator.test.ts +184 -0
  127. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p2-factory-imports.test.ts +116 -0
  128. package/libs/instance-factories/applications/templates/react-starter/__tests__/parity-p3-rendered-output.test.ts +183 -0
  129. package/libs/instance-factories/applications/templates/react-starter/__tests__/regen-safety.test.ts +144 -0
  130. package/libs/instance-factories/applications/templates/react-starter/__tests__/starter-generators.test.ts +114 -0
  131. package/libs/instance-factories/applications/templates/react-starter/__tests__/view-emitter.test.ts +107 -0
  132. package/libs/instance-factories/applications/templates/react-starter/__tests__/views-generator.test.ts +139 -0
  133. package/libs/instance-factories/applications/templates/react-starter/api-types-starter-generator.ts +98 -0
  134. package/libs/instance-factories/applications/templates/react-starter/app-tsx-generator.ts +141 -0
  135. package/libs/instance-factories/applications/templates/react-starter/belongs-to.ts +82 -0
  136. package/libs/instance-factories/applications/templates/react-starter/dashboard-body-composer.ts +189 -0
  137. package/libs/instance-factories/applications/templates/react-starter/detail-body-composer.ts +135 -0
  138. package/libs/instance-factories/applications/templates/react-starter/form-body-composer.ts +383 -0
  139. package/libs/instance-factories/applications/templates/react-starter/helpers-emitter.ts +66 -0
  140. package/libs/instance-factories/applications/templates/react-starter/html-to-jsx.ts +334 -0
  141. package/libs/instance-factories/applications/templates/react-starter/list-body-composer.ts +146 -0
  142. package/libs/instance-factories/applications/templates/react-starter/orchestrator.ts +95 -0
  143. package/libs/instance-factories/applications/templates/react-starter/package-json-generator.ts +57 -0
  144. package/libs/instance-factories/applications/templates/react-starter/regen-safety.ts +157 -0
  145. package/libs/instance-factories/applications/templates/react-starter/skeletons/dashboard.tsx.template +49 -0
  146. package/libs/instance-factories/applications/templates/react-starter/skeletons/detail.tsx.template +96 -0
  147. package/libs/instance-factories/applications/templates/react-starter/skeletons/form.tsx.template +116 -0
  148. package/libs/instance-factories/applications/templates/react-starter/skeletons/list.tsx.template +74 -0
  149. package/libs/instance-factories/applications/templates/react-starter/use-api-hooks-starter-generator.ts +124 -0
  150. package/libs/instance-factories/applications/templates/react-starter/view-emitter.ts +209 -0
  151. package/libs/instance-factories/applications/templates/react-starter/views-generator.ts +137 -0
  152. package/libs/instance-factories/cli/templates/commander/command-generator.ts +63 -12
  153. package/libs/instance-factories/controllers/fastify.yaml +7 -0
  154. package/libs/instance-factories/controllers/templates/fastify/server-generator.ts +36 -2
  155. package/libs/instance-factories/orms/templates/prisma/schema-generator.ts +11 -4
  156. package/package.json +3 -3
  157. package/dist/libs/instance-factories/applications/templates/react/_view-components-source.js +0 -530
  158. package/dist/libs/instance-factories/applications/templates/react/app-tsx-generator.js +0 -73
  159. package/dist/libs/instance-factories/applications/templates/react/field-helpers-generator.js +0 -99
  160. package/dist/libs/instance-factories/applications/templates/react/package-json-generator.js +0 -49
  161. package/dist/libs/instance-factories/applications/templates/react/pattern-adapter-generator.js +0 -156
  162. package/dist/libs/instance-factories/applications/templates/react/react-pattern-adapter.js +0 -935
  163. package/dist/libs/instance-factories/applications/templates/react/relationship-field-generator.js +0 -143
  164. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.js +0 -646
  165. package/dist/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.js +0 -65
  166. package/dist/libs/instance-factories/applications/templates/react/view-dashboard-generator.js +0 -143
  167. package/dist/libs/instance-factories/applications/templates/react/view-detail-generator.js +0 -143
  168. package/dist/libs/instance-factories/applications/templates/react/view-form-generator.js +0 -355
  169. package/dist/libs/instance-factories/applications/templates/react/view-list-generator.js +0 -91
  170. package/dist/libs/instance-factories/applications/templates/react/view-router-generator.js +0 -79
  171. package/dist/libs/instance-factories/views/index.js +0 -48
  172. package/dist/libs/instance-factories/views/templates/react/adapters/antd-adapter.js +0 -742
  173. package/dist/libs/instance-factories/views/templates/react/adapters/mui-adapter.js +0 -824
  174. package/dist/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.js +0 -719
  175. package/dist/libs/instance-factories/views/templates/react/app-generator.js +0 -45
  176. package/dist/libs/instance-factories/views/templates/react/components-generator.js +0 -820
  177. package/dist/libs/instance-factories/views/templates/react/forms-generator.js +0 -275
  178. package/dist/libs/instance-factories/views/templates/react/frontend-package-json-generator.js +0 -46
  179. package/dist/libs/instance-factories/views/templates/react/hooks-generator.js +0 -81
  180. package/dist/libs/instance-factories/views/templates/react/index-css-generator.js +0 -9
  181. package/dist/libs/instance-factories/views/templates/react/index-html-generator.js +0 -23
  182. package/dist/libs/instance-factories/views/templates/react/main-tsx-generator.js +0 -21
  183. package/dist/libs/instance-factories/views/templates/react/react-component-generator.js +0 -299
  184. package/dist/libs/instance-factories/views/templates/react/router-generator.js +0 -136
  185. package/dist/libs/instance-factories/views/templates/react/router-generic-generator.js +0 -107
  186. package/dist/libs/instance-factories/views/templates/react/shared-utils-generator.js +0 -187
  187. package/dist/libs/instance-factories/views/templates/react/spec-json-generator.js +0 -7
  188. package/dist/libs/instance-factories/views/templates/react/types-generator.js +0 -56
  189. package/dist/libs/instance-factories/views/templates/react/views-metadata-generator.js +0 -27
  190. package/dist/libs/instance-factories/views/templates/react/vite-config-generator.js +0 -29
  191. package/dist/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js +0 -261
  192. package/dist/libs/instance-factories/views/templates/shared/adapter-types.js +0 -34
  193. package/dist/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -800
  194. package/dist/libs/instance-factories/views/templates/shared/base-generator.js +0 -305
  195. package/dist/libs/instance-factories/views/templates/shared/component-metadata.js +0 -517
  196. package/dist/libs/instance-factories/views/templates/shared/composite-pattern-types.js +0 -0
  197. package/dist/libs/instance-factories/views/templates/shared/composite-patterns.js +0 -445
  198. package/dist/libs/instance-factories/views/templates/shared/index.js +0 -80
  199. package/dist/libs/instance-factories/views/templates/shared/pattern-validator.js +0 -210
  200. package/dist/libs/instance-factories/views/templates/shared/property-mapper.js +0 -492
  201. package/dist/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -321
  202. package/dist/realize/index.js.bak +0 -758
  203. package/libs/instance-factories/applications/templates/react/_view-components-source.ts +0 -555
  204. package/libs/instance-factories/applications/templates/react/app-tsx-generator.ts +0 -94
  205. package/libs/instance-factories/applications/templates/react/field-helpers-generator.ts +0 -106
  206. package/libs/instance-factories/applications/templates/react/package-json-generator.ts +0 -57
  207. package/libs/instance-factories/applications/templates/react/pattern-adapter-generator.ts +0 -179
  208. package/libs/instance-factories/applications/templates/react/react-pattern-adapter.tsx +0 -1347
  209. package/libs/instance-factories/applications/templates/react/relationship-field-generator.ts +0 -150
  210. package/libs/instance-factories/applications/templates/react/tailwind-adapter-generator.ts +0 -704
  211. package/libs/instance-factories/applications/templates/react/tailwind-adapter-wrapper-generator.ts +0 -84
  212. package/libs/instance-factories/applications/templates/react/view-dashboard-generator.ts +0 -150
  213. package/libs/instance-factories/applications/templates/react/view-detail-generator.ts +0 -150
  214. package/libs/instance-factories/applications/templates/react/view-form-generator.ts +0 -362
  215. package/libs/instance-factories/applications/templates/react/view-list-generator.ts +0 -98
  216. package/libs/instance-factories/applications/templates/react/view-router-generator.ts +0 -89
  217. package/libs/instance-factories/views/README.md +0 -62
  218. package/libs/instance-factories/views/index.d.ts +0 -13
  219. package/libs/instance-factories/views/index.d.ts.map +0 -1
  220. package/libs/instance-factories/views/index.js +0 -18
  221. package/libs/instance-factories/views/index.js.map +0 -1
  222. package/libs/instance-factories/views/index.ts +0 -45
  223. package/libs/instance-factories/views/react-components.yaml +0 -129
  224. package/libs/instance-factories/views/templates/ARCHITECTURE.md +0 -198
  225. package/libs/instance-factories/views/templates/react/adapters/antd-adapter.ts +0 -869
  226. package/libs/instance-factories/views/templates/react/adapters/mui-adapter.ts +0 -953
  227. package/libs/instance-factories/views/templates/react/adapters/shadcn-adapter.ts +0 -806
  228. package/libs/instance-factories/views/templates/react/app-generator.ts +0 -55
  229. package/libs/instance-factories/views/templates/react/components-generator.ts +0 -938
  230. package/libs/instance-factories/views/templates/react/forms-generator.ts +0 -325
  231. package/libs/instance-factories/views/templates/react/frontend-package-json-generator.ts +0 -57
  232. package/libs/instance-factories/views/templates/react/hooks-generator.ts +0 -106
  233. package/libs/instance-factories/views/templates/react/index-css-generator.ts +0 -14
  234. package/libs/instance-factories/views/templates/react/index-html-generator.ts +0 -34
  235. package/libs/instance-factories/views/templates/react/main-tsx-generator.ts +0 -29
  236. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts +0 -152
  237. package/libs/instance-factories/views/templates/react/react-component-generator.d.ts.map +0 -1
  238. package/libs/instance-factories/views/templates/react/react-component-generator.js +0 -398
  239. package/libs/instance-factories/views/templates/react/react-component-generator.js.map +0 -1
  240. package/libs/instance-factories/views/templates/react/react-component-generator.ts +0 -533
  241. package/libs/instance-factories/views/templates/react/router-generator.ts +0 -197
  242. package/libs/instance-factories/views/templates/react/router-generic-generator.ts +0 -132
  243. package/libs/instance-factories/views/templates/react/shared-utils-generator.ts +0 -196
  244. package/libs/instance-factories/views/templates/react/spec-json-generator.ts +0 -17
  245. package/libs/instance-factories/views/templates/react/types-generator.ts +0 -76
  246. package/libs/instance-factories/views/templates/react/views-metadata-generator.ts +0 -42
  247. package/libs/instance-factories/views/templates/react/vite-config-generator.ts +0 -38
  248. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.d.ts.map +0 -1
  249. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.js.map +0 -1
  250. package/libs/instance-factories/views/templates/runtime/runtime-view-renderer.ts +0 -474
  251. package/libs/instance-factories/views/templates/shared/__tests__/composite-patterns.test.ts +0 -242
  252. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts +0 -77
  253. package/libs/instance-factories/views/templates/shared/adapter-types.d.ts.map +0 -1
  254. package/libs/instance-factories/views/templates/shared/adapter-types.js +0 -47
  255. package/libs/instance-factories/views/templates/shared/adapter-types.js.map +0 -1
  256. package/libs/instance-factories/views/templates/shared/adapter-types.ts +0 -142
  257. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts +0 -63
  258. package/libs/instance-factories/views/templates/shared/atomic-components-registry.d.ts.map +0 -1
  259. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js +0 -822
  260. package/libs/instance-factories/views/templates/shared/atomic-components-registry.js.map +0 -1
  261. package/libs/instance-factories/views/templates/shared/atomic-components-registry.ts +0 -908
  262. package/libs/instance-factories/views/templates/shared/base-generator.d.ts +0 -247
  263. package/libs/instance-factories/views/templates/shared/base-generator.d.ts.map +0 -1
  264. package/libs/instance-factories/views/templates/shared/base-generator.js +0 -363
  265. package/libs/instance-factories/views/templates/shared/base-generator.js.map +0 -1
  266. package/libs/instance-factories/views/templates/shared/base-generator.ts +0 -608
  267. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts +0 -254
  268. package/libs/instance-factories/views/templates/shared/component-metadata.d.ts.map +0 -1
  269. package/libs/instance-factories/views/templates/shared/component-metadata.js +0 -602
  270. package/libs/instance-factories/views/templates/shared/component-metadata.js.map +0 -1
  271. package/libs/instance-factories/views/templates/shared/component-metadata.ts +0 -803
  272. package/libs/instance-factories/views/templates/shared/composite-pattern-types.ts +0 -250
  273. package/libs/instance-factories/views/templates/shared/composite-patterns.ts +0 -535
  274. package/libs/instance-factories/views/templates/shared/index.ts +0 -68
  275. package/libs/instance-factories/views/templates/shared/pattern-validator.ts +0 -279
  276. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts +0 -149
  277. package/libs/instance-factories/views/templates/shared/property-mapper.d.ts.map +0 -1
  278. package/libs/instance-factories/views/templates/shared/property-mapper.js +0 -580
  279. package/libs/instance-factories/views/templates/shared/property-mapper.js.map +0 -1
  280. package/libs/instance-factories/views/templates/shared/property-mapper.ts +0 -700
  281. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts +0 -143
  282. package/libs/instance-factories/views/templates/shared/syntax-mapper.d.ts.map +0 -1
  283. package/libs/instance-factories/views/templates/shared/syntax-mapper.js +0 -420
  284. package/libs/instance-factories/views/templates/shared/syntax-mapper.js.map +0 -1
  285. package/libs/instance-factories/views/templates/shared/syntax-mapper.ts +0 -539
@@ -0,0 +1,192 @@
1
+ const VOID_ELEMENTS = /* @__PURE__ */ new Set([
2
+ "area",
3
+ "base",
4
+ "br",
5
+ "col",
6
+ "embed",
7
+ "hr",
8
+ "img",
9
+ "input",
10
+ "link",
11
+ "meta",
12
+ "source",
13
+ "track",
14
+ "wbr"
15
+ ]);
16
+ const ATTRIBUTE_RENAMES = {
17
+ class: "className",
18
+ for: "htmlFor",
19
+ tabindex: "tabIndex",
20
+ readonly: "readOnly",
21
+ maxlength: "maxLength",
22
+ minlength: "minLength",
23
+ colspan: "colSpan",
24
+ rowspan: "rowSpan",
25
+ autofocus: "autoFocus",
26
+ autocomplete: "autoComplete",
27
+ cellpadding: "cellPadding",
28
+ cellspacing: "cellSpacing",
29
+ enctype: "encType",
30
+ usemap: "useMap",
31
+ accesskey: "accessKey",
32
+ contenteditable: "contentEditable",
33
+ spellcheck: "spellCheck",
34
+ crossorigin: "crossOrigin",
35
+ srcset: "srcSet",
36
+ srcdoc: "srcDoc"
37
+ };
38
+ function htmlToJsx(html) {
39
+ if (!html) return "";
40
+ const tokens = tokenize(html);
41
+ return tokens.map(transformToken).join("");
42
+ }
43
+ function tokenize(input) {
44
+ const tokens = [];
45
+ let i = 0;
46
+ const n = input.length;
47
+ while (i < n) {
48
+ const ch = input[i];
49
+ if (ch === "<") {
50
+ const tagStart = i;
51
+ i++;
52
+ let quote = null;
53
+ while (i < n) {
54
+ const c = input[i];
55
+ if (quote) {
56
+ if (c === quote) quote = null;
57
+ i++;
58
+ continue;
59
+ }
60
+ if (c === '"' || c === "'") {
61
+ quote = c;
62
+ i++;
63
+ continue;
64
+ }
65
+ if (c === ">") {
66
+ i++;
67
+ break;
68
+ }
69
+ i++;
70
+ }
71
+ const raw = input.slice(tagStart, i);
72
+ const isClosing = raw.startsWith("</");
73
+ const isSelfClosed = raw.endsWith("/>");
74
+ const tagName = parseTagName(raw);
75
+ tokens.push({ kind: "tag", raw, tagName, isClosing, isSelfClosed });
76
+ } else {
77
+ const textStart = i;
78
+ while (i < n && input[i] !== "<") i++;
79
+ tokens.push({ kind: "text", value: input.slice(textStart, i) });
80
+ }
81
+ }
82
+ return tokens;
83
+ }
84
+ function parseTagName(tagRaw) {
85
+ const m = tagRaw.match(/^<\/?([a-zA-Z][a-zA-Z0-9-]*)/);
86
+ return m ? m[1].toLowerCase() : "";
87
+ }
88
+ function transformToken(token) {
89
+ if (token.kind === "text") {
90
+ return token.value;
91
+ }
92
+ return transformTag(token.raw, token.tagName, token.isClosing, token.isSelfClosed);
93
+ }
94
+ function transformTag(raw, tagName, isClosing, isSelfClosed) {
95
+ if (isClosing) {
96
+ return raw;
97
+ }
98
+ const inner = isSelfClosed ? raw.slice(1, -2).trimEnd() : raw.slice(1, -1);
99
+ const nameMatch = inner.match(/^([a-zA-Z][a-zA-Z0-9-]*)(.*)$/s);
100
+ if (!nameMatch) return raw;
101
+ const elementName = nameMatch[1];
102
+ const attrsSection = nameMatch[2];
103
+ const attrs = parseAttributes(attrsSection);
104
+ const renamedAttrs = attrs.map(renameAttribute);
105
+ const rebuiltAttrs = serializeAttributes(renamedAttrs);
106
+ const shouldSelfClose = VOID_ELEMENTS.has(tagName) && !isSelfClosed;
107
+ if (shouldSelfClose || isSelfClosed) {
108
+ return `<${elementName}${rebuiltAttrs} />`;
109
+ }
110
+ return `<${elementName}${rebuiltAttrs}>`;
111
+ }
112
+ function parseAttributes(section) {
113
+ const attrs = [];
114
+ let i = 0;
115
+ const n = section.length;
116
+ while (i < n) {
117
+ while (i < n && /\s/.test(section[i])) i++;
118
+ if (i >= n) break;
119
+ const nameStart = i;
120
+ while (i < n && /[a-zA-Z0-9:_-]/.test(section[i])) i++;
121
+ if (i === nameStart) {
122
+ i++;
123
+ continue;
124
+ }
125
+ const name = section.slice(nameStart, i);
126
+ let j = i;
127
+ while (j < n && /\s/.test(section[j])) j++;
128
+ if (j >= n || section[j] !== "=") {
129
+ attrs.push({ name });
130
+ continue;
131
+ }
132
+ i = j + 1;
133
+ while (i < n && /\s/.test(section[i])) i++;
134
+ if (i >= n) {
135
+ attrs.push({ name });
136
+ break;
137
+ }
138
+ const vc = section[i];
139
+ if (vc === '"' || vc === "'") {
140
+ const quote = vc;
141
+ i++;
142
+ const valStart = i;
143
+ while (i < n && section[i] !== quote) i++;
144
+ const value = section.slice(valStart, i);
145
+ if (i < n) i++;
146
+ attrs.push({ name, value, quote });
147
+ } else {
148
+ const valStart = i;
149
+ while (i < n && !/[\s/>]/.test(section[i])) i++;
150
+ const value = section.slice(valStart, i);
151
+ attrs.push({ name, value });
152
+ }
153
+ }
154
+ return attrs;
155
+ }
156
+ function renameAttribute(attr) {
157
+ const lower = attr.name.toLowerCase();
158
+ const newName = ATTRIBUTE_RENAMES[lower] ?? attr.name;
159
+ if (lower === "style" && attr.value !== void 0) {
160
+ return { name: newName, value: cssStringToJsxObject(attr.value), quote: void 0 };
161
+ }
162
+ return { ...attr, name: newName };
163
+ }
164
+ function serializeAttributes(attrs) {
165
+ if (attrs.length === 0) return "";
166
+ const parts = attrs.map((attr) => {
167
+ if (attr.value === void 0) return ` ${attr.name}`;
168
+ if (attr.value.startsWith("{{") && attr.value.endsWith("}}")) {
169
+ return ` ${attr.name}=${attr.value}`;
170
+ }
171
+ const q = attr.quote ?? '"';
172
+ return ` ${attr.name}=${q}${attr.value}${q}`;
173
+ });
174
+ return parts.join("");
175
+ }
176
+ function cssStringToJsxObject(css) {
177
+ const declarations = css.split(";").map((d) => d.trim()).filter((d) => d.length > 0);
178
+ if (declarations.length === 0) return "{{}}";
179
+ const entries = declarations.map((decl) => {
180
+ const colonIdx = decl.indexOf(":");
181
+ if (colonIdx === -1) return null;
182
+ const prop = decl.slice(0, colonIdx).trim();
183
+ const value = decl.slice(colonIdx + 1).trim();
184
+ const camelProp = prop.replace(/-([a-z])/g, (_m, c) => c.toUpperCase());
185
+ const escapedValue = value.replace(/'/g, "\\'");
186
+ return `${camelProp}: '${escapedValue}'`;
187
+ }).filter((e) => e !== null);
188
+ return `{{ ${entries.join(", ")} }}`;
189
+ }
190
+ export {
191
+ htmlToJsx
192
+ };
@@ -0,0 +1,56 @@
1
+ import { createUniversalTailwindAdapter } from "@specverse/runtime/views/tailwind";
2
+ import { inferFieldsFromSchema } from "@specverse/runtime/views/core";
3
+ import { htmlToJsx } from "./html-to-jsx.js";
4
+ import { buildFKMap } from "./belongs-to.js";
5
+ const TBODY_SENTINEL = "__SPECVERSE_TBODY_ROWS__";
6
+ function composeListBody(context) {
7
+ const fkMap = buildFKMap(context.model);
8
+ const inferred = inferColumns(context);
9
+ const inferredSet = new Set(inferred);
10
+ const extraFKs = [...fkMap.keys()].filter((k) => !inferredSet.has(k));
11
+ const columns = [...inferred, ...extraFKs];
12
+ const headers = columns.map((col) => {
13
+ const fk = fkMap.get(col);
14
+ return humanize(fk ? fk.name : col);
15
+ });
16
+ const adapter = createUniversalTailwindAdapter({ darkMode: true });
17
+ const shellHtml = adapter.components.table.render({
18
+ properties: { columns: headers },
19
+ children: TBODY_SENTINEL
20
+ });
21
+ const shellJsx = htmlToJsx(shellHtml);
22
+ const rowMap = buildRowMap(columns, fkMap);
23
+ if (!shellJsx.includes(TBODY_SENTINEL)) {
24
+ throw new Error(
25
+ "composeListBody: tbody sentinel not present in adapter output. The canonical Tailwind adapter may have changed its table rendering."
26
+ );
27
+ }
28
+ return shellJsx.replace(TBODY_SENTINEL, rowMap);
29
+ }
30
+ function inferColumns(context) {
31
+ return inferFieldsFromSchema(context.modelSchemas, context.model.name);
32
+ }
33
+ function humanize(name) {
34
+ return name.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase()).trim();
35
+ }
36
+ function buildRowMap(columns, fkMap) {
37
+ const cells = columns.map((col) => {
38
+ const fk = fkMap.get(col);
39
+ const expr = fk ? `{resolveEntityDisplayName((item as any).${col}, ${fk.name}Options)}` : `{String((item as any).${col} ?? '')}`;
40
+ return ` <td className="px-6 py-4 text-sm text-gray-700 dark:text-gray-300">${expr}</td>`;
41
+ }).join("\n");
42
+ return [
43
+ "{filtered.map((item, idx) => (",
44
+ " <tr",
45
+ " key={idx}",
46
+ " onClick={() => onSelect?.(item)}",
47
+ ' className="hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer"',
48
+ " >",
49
+ cells,
50
+ " </tr>",
51
+ "))}"
52
+ ].join("\n");
53
+ }
54
+ export {
55
+ composeListBody
56
+ };
@@ -0,0 +1,41 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { generate as generateViews } from "./views-generator.js";
4
+ import { generate as generateAppTsx } from "./app-tsx-generator.js";
5
+ import { generate as generatePackageJson } from "./package-json-generator.js";
6
+ import {
7
+ loadHashManifest,
8
+ reconcileWrites,
9
+ summarize,
10
+ HASHES_DIR,
11
+ HASHES_FILE
12
+ } from "./regen-safety.js";
13
+ async function generate(context) {
14
+ const { spec, outputDir } = context;
15
+ const frontendDir = context.frontendDir || "frontend";
16
+ const projectRoot = frontendDir === "." ? outputDir : join(outputDir, frontendDir);
17
+ const proposed = {};
18
+ const viewFiles = await generateViews({ spec });
19
+ Object.assign(proposed, viewFiles);
20
+ proposed["src/App.tsx"] = await generateAppTsx({ spec });
21
+ proposed["package.json"] = await generatePackageJson({ spec });
22
+ const prevManifest = loadHashManifest(projectRoot);
23
+ const result = reconcileWrites(projectRoot, proposed, prevManifest);
24
+ console.log(summarize(result, projectRoot));
25
+ const toWrite = {
26
+ ...result.approvedWrites,
27
+ [`${HASHES_DIR}/${HASHES_FILE}`]: JSON.stringify(result.manifest, null, 2) + "\n"
28
+ };
29
+ for (const [rel, content] of Object.entries(toWrite)) {
30
+ const abs = join(projectRoot, rel);
31
+ const dir = dirname(abs);
32
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
33
+ writeFileSync(abs, content, "utf-8");
34
+ }
35
+ return "";
36
+ }
37
+ var stdin_default = generate;
38
+ export {
39
+ stdin_default as default,
40
+ generate
41
+ };
@@ -0,0 +1,38 @@
1
+ async function generate(context) {
2
+ const appName = context.spec?.metadata?.name ?? context.spec?.name ?? "specverse-starter-app";
3
+ const packageName = slugify(appName);
4
+ const pkg = {
5
+ name: packageName,
6
+ private: true,
7
+ version: "0.1.0",
8
+ type: "module",
9
+ scripts: {
10
+ dev: "vite",
11
+ build: "tsc && vite build",
12
+ preview: "vite preview",
13
+ typecheck: "tsc --noEmit"
14
+ },
15
+ dependencies: {
16
+ react: "^18.2.0",
17
+ "react-dom": "^18.2.0",
18
+ "@tanstack/react-query": "^5.0.0"
19
+ },
20
+ devDependencies: {
21
+ "@types/react": "^18.2.0",
22
+ "@types/react-dom": "^18.2.0",
23
+ "@vitejs/plugin-react": "^4.2.0",
24
+ autoprefixer: "^10.4.20",
25
+ postcss: "^8.4.47",
26
+ tailwindcss: "^3.4.13",
27
+ typescript: "^5.2.0",
28
+ vite: "^6.0.0"
29
+ }
30
+ };
31
+ return JSON.stringify(pkg, null, 2) + "\n";
32
+ }
33
+ function slugify(s) {
34
+ return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "specverse-starter-app";
35
+ }
36
+ export {
37
+ generate
38
+ };
@@ -0,0 +1,89 @@
1
+ import { createHash } from "crypto";
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
3
+ import { join, relative } from "path";
4
+ const HASHES_DIR = ".specverse-gen";
5
+ const HASHES_FILE = "hashes.json";
6
+ function sha256(s) {
7
+ return createHash("sha256").update(s, "utf8").digest("hex");
8
+ }
9
+ function loadHashManifest(projectRoot) {
10
+ const path = join(projectRoot, HASHES_DIR, HASHES_FILE);
11
+ if (!existsSync(path)) return {};
12
+ try {
13
+ const raw = readFileSync(path, "utf8");
14
+ const parsed = JSON.parse(raw);
15
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
16
+ const out = {};
17
+ for (const [k, v] of Object.entries(parsed)) {
18
+ if (typeof v === "string") out[k] = v;
19
+ }
20
+ return out;
21
+ }
22
+ } catch {
23
+ }
24
+ return {};
25
+ }
26
+ function saveHashManifest(projectRoot, manifest) {
27
+ const dir = join(projectRoot, HASHES_DIR);
28
+ mkdirSync(dir, { recursive: true });
29
+ const path = join(dir, HASHES_FILE);
30
+ writeFileSync(path, JSON.stringify(manifest, null, 2) + "\n", "utf8");
31
+ }
32
+ function reconcileWrites(projectRoot, proposed, prevManifest) {
33
+ const manifest = { ...prevManifest };
34
+ const approvedWrites = {};
35
+ const skipped = [];
36
+ for (const [relPath, content] of Object.entries(proposed)) {
37
+ const abs = join(projectRoot, relPath);
38
+ const newHash = sha256(content);
39
+ if (!existsSync(abs)) {
40
+ approvedWrites[relPath] = content;
41
+ manifest[relPath] = newHash;
42
+ continue;
43
+ }
44
+ const currentContent = readFileSync(abs, "utf8");
45
+ const currentHash = sha256(currentContent);
46
+ const recordedHash = prevManifest[relPath];
47
+ if (recordedHash == null) {
48
+ skipped.push({
49
+ path: relPath,
50
+ reason: "no prior hash recorded \u2014 cannot confirm this file was generated by us"
51
+ });
52
+ continue;
53
+ }
54
+ if (currentHash === recordedHash) {
55
+ approvedWrites[relPath] = content;
56
+ manifest[relPath] = newHash;
57
+ continue;
58
+ }
59
+ skipped.push({
60
+ path: relPath,
61
+ reason: "file has been edited since last generation"
62
+ });
63
+ }
64
+ return { approvedWrites, skipped, manifest };
65
+ }
66
+ function summarize(result, projectRoot) {
67
+ const lines = [];
68
+ const writeCount = Object.keys(result.approvedWrites).length;
69
+ lines.push(`[ReactAppStarter] Approved ${writeCount} file(s) for writing.`);
70
+ if (result.skipped.length > 0) {
71
+ lines.push(`[ReactAppStarter] Skipped ${result.skipped.length} user-edited file(s):`);
72
+ for (const { path, reason } of result.skipped) {
73
+ lines.push(` - ${path} (${reason})`);
74
+ }
75
+ lines.push(
76
+ `To accept upstream regeneration for a skipped file, delete it (\`rm ${relative(process.cwd(), projectRoot)}/PATH\`) and re-run \`spv realize\`.`
77
+ );
78
+ }
79
+ return lines.join("\n");
80
+ }
81
+ export {
82
+ HASHES_DIR,
83
+ HASHES_FILE,
84
+ loadHashManifest,
85
+ reconcileWrites,
86
+ saveHashManifest,
87
+ sha256,
88
+ summarize
89
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * {{MODEL_NAME}}DashboardView — generated by @specverse/realize (ReactAppStarter)
3
+ *
4
+ * Safe to edit. Edits are preserved across regeneration via content
5
+ * hashing (see .specverse-gen/hashes.json). To accept an upstream
6
+ * regeneration of this file, delete it first, then run `spv realize`.
7
+ *
8
+ * A minimal dashboard: summary counts derived from the list query,
9
+ * plus a compact preview of recent records. Charts and aggregation
10
+ * metrics are deferred — add them as the backend grows suitable
11
+ * endpoints.
12
+ */
13
+ import { useMemo } from 'react';
14
+ import { use{{PLURAL_MODEL}}Query } from '../hooks/useApi';
15
+ {{RELATED_IMPORTS}}
16
+ import type { {{MODEL_NAME}} } from '../types/api';
17
+
18
+ interface {{MODEL_NAME}}DashboardViewProps {
19
+ /** Number of recent records to preview. Default 5. */
20
+ previewLimit?: number;
21
+ onSelect?: (item: {{MODEL_NAME}}) => void;
22
+ }
23
+
24
+ export function {{MODEL_NAME}}DashboardView({
25
+ previewLimit = 5,
26
+ onSelect,
27
+ }: {{MODEL_NAME}}DashboardViewProps) {
28
+ const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
29
+ {{RELATED_HOOKS}}
30
+
31
+ const preview = useMemo(
32
+ () => items.slice(0, previewLimit),
33
+ [items, previewLimit]
34
+ );
35
+
36
+ if (isLoading) return <div className="p-4 text-gray-500">Loading dashboard…</div>;
37
+ if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
38
+
39
+ return (
40
+ <div className="p-6 space-y-6">
41
+ <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
42
+ {{MODEL_NAME}} dashboard
43
+ </h2>
44
+
45
+ {/* Pattern-rendered dashboard body (metrics + preview). Edit freely. */}
46
+ {{BODY}}
47
+ </div>
48
+ );
49
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * {{MODEL_NAME}}DetailView — generated by @specverse/realize (ReactAppStarter)
3
+ *
4
+ * Safe to edit. Edits are preserved across regeneration via content
5
+ * hashing (see .specverse-gen/hashes.json). To accept an upstream
6
+ * regeneration of this file, delete it first, then run `spv realize`.
7
+ */
8
+ import { use{{PLURAL_MODEL}}Query, useDelete{{MODEL_NAME}}Mutation } from '../hooks/useApi';
9
+ {{RELATED_IMPORTS}}
10
+ import type { {{MODEL_NAME}} } from '../types/api';
11
+
12
+ interface {{MODEL_NAME}}DetailViewProps {
13
+ entityId: string | number;
14
+ onEdit?: (item: {{MODEL_NAME}}) => void;
15
+ onBack?: () => void;
16
+ /** Called after the delete mutation succeeds. */
17
+ onDeleted?: () => void;
18
+ }
19
+
20
+ export function {{MODEL_NAME}}DetailView({
21
+ entityId,
22
+ onEdit,
23
+ onBack,
24
+ onDeleted,
25
+ }: {{MODEL_NAME}}DetailViewProps) {
26
+ const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
27
+ const deleteItem = useDelete{{MODEL_NAME}}Mutation();
28
+ {{RELATED_HOOKS}}
29
+
30
+ const item = items.find(
31
+ (x: {{MODEL_NAME}}) => (x as any).id === entityId
32
+ );
33
+
34
+ if (isLoading) return <div className="p-4 text-gray-500">Loading…</div>;
35
+ if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
36
+ if (!item) return <div className="p-4 text-gray-400">No {{SINGULAR_LOWER}} matching id {String(entityId)}.</div>;
37
+
38
+ const handleDelete = async () => {
39
+ if (!confirm('Delete this {{SINGULAR_LOWER}}?')) return;
40
+ try {
41
+ await deleteItem.mutateAsync((item as any).id);
42
+ onDeleted?.();
43
+ } catch {
44
+ // deleteItem.error is surfaced below
45
+ }
46
+ };
47
+
48
+ return (
49
+ <div className="p-6 space-y-4">
50
+ <div className="flex items-center justify-between">
51
+ <div className="flex items-center gap-3">
52
+ {onBack && (
53
+ <button
54
+ type="button"
55
+ onClick={onBack}
56
+ className="text-sm text-gray-500 hover:text-gray-700"
57
+ >
58
+ ← Back
59
+ </button>
60
+ )}
61
+ <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
62
+ {{MODEL_NAME}} detail
63
+ </h2>
64
+ </div>
65
+ <div className="flex gap-2">
66
+ {onEdit && (
67
+ <button
68
+ type="button"
69
+ onClick={() => onEdit(item)}
70
+ className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
71
+ >
72
+ Edit
73
+ </button>
74
+ )}
75
+ <button
76
+ type="button"
77
+ onClick={handleDelete}
78
+ disabled={deleteItem.isPending}
79
+ className="rounded bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700 disabled:opacity-60"
80
+ >
81
+ {deleteItem.isPending ? 'Deleting…' : 'Delete'}
82
+ </button>
83
+ </div>
84
+ </div>
85
+
86
+ {/* Pattern-rendered detail body (fields). Edit freely. */}
87
+ {{BODY}}
88
+
89
+ {deleteItem.isError && (
90
+ <div className="p-2 text-sm text-red-600">
91
+ Delete failed: {String(deleteItem.error)}
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * {{MODEL_NAME}}FormView — generated by @specverse/realize (ReactAppStarter)
3
+ *
4
+ * Safe to edit. Edits are preserved across regeneration via content
5
+ * hashing (see .specverse-gen/hashes.json). To accept an upstream
6
+ * regeneration of this file, delete it first, then run `spv realize`.
7
+ *
8
+ * Controlled form. One field per non-auto-generated attribute.
9
+ * Mode: 'create' (default) or 'update' — passed as a prop.
10
+ */
11
+ import { useEffect, useState } from 'react';
12
+ import {
13
+ use{{PLURAL_MODEL}}Query,
14
+ useCreate{{MODEL_NAME}}Mutation,
15
+ useUpdate{{MODEL_NAME}}Mutation,
16
+ } from '../hooks/useApi';
17
+ {{RELATED_IMPORTS}}
18
+ import type { {{MODEL_NAME}} } from '../types/api';
19
+
20
+ type FormMode = 'create' | 'update';
21
+
22
+ interface {{MODEL_NAME}}FormViewProps {
23
+ mode?: FormMode;
24
+ /** Required in update mode. */
25
+ entityId?: string | number;
26
+ onSuccess?: (item: {{MODEL_NAME}}) => void;
27
+ onCancel?: () => void;
28
+ }
29
+
30
+ export function {{MODEL_NAME}}FormView({
31
+ mode = 'create',
32
+ entityId,
33
+ onSuccess,
34
+ onCancel,
35
+ }: {{MODEL_NAME}}FormViewProps) {
36
+ const { data: items = [] } = use{{PLURAL_MODEL}}Query();
37
+ const createItem = useCreate{{MODEL_NAME}}Mutation();
38
+ const updateItem = useUpdate{{MODEL_NAME}}Mutation();
39
+ {{RELATED_HOOKS}}
40
+
41
+ const existing =
42
+ mode === 'update'
43
+ ? items.find((x: {{MODEL_NAME}}) => (x as any).id === entityId)
44
+ : undefined;
45
+
46
+ const [formData, setFormData] = useState<Partial<{{MODEL_NAME}}>>(existing ?? {});
47
+
48
+ // When the fetched list lands after initial render (update mode),
49
+ // hydrate the form with the loaded entity's data.
50
+ useEffect(() => {
51
+ if (existing) setFormData(existing);
52
+ }, [existing]);
53
+
54
+ const handleChange = (field: string, value: unknown) => {
55
+ setFormData(prev => ({ ...prev, [field]: value }) as Partial<{{MODEL_NAME}}>);
56
+ };
57
+
58
+ const handleSubmit = async (event: React.FormEvent) => {
59
+ event.preventDefault();
60
+ try {
61
+ const result =
62
+ mode === 'create'
63
+ ? await createItem.mutateAsync(formData as {{MODEL_NAME}})
64
+ : await updateItem.mutateAsync({
65
+ id: entityId as any,
66
+ data: formData as Partial<{{MODEL_NAME}}>,
67
+ });
68
+ onSuccess?.(result as {{MODEL_NAME}});
69
+ } catch {
70
+ // Mutation errors are surfaced below via createItem / updateItem.
71
+ }
72
+ };
73
+
74
+ const mutation = mode === 'create' ? createItem : updateItem;
75
+ const submitLabel =
76
+ mode === 'create'
77
+ ? mutation.isPending ? 'Creating…' : 'Create {{MODEL_NAME}}'
78
+ : mutation.isPending ? 'Updating…' : 'Update {{MODEL_NAME}}';
79
+
80
+ return (
81
+ <form onSubmit={handleSubmit} className="p-6 space-y-6">
82
+ <h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100">
83
+ {mode === 'create' ? 'New {{MODEL_NAME}}' : 'Edit {{MODEL_NAME}}'}
84
+ </h2>
85
+
86
+ {/* Pattern-rendered form fields. Edit freely. */}
87
+ {{BODY}}
88
+
89
+ <div className="flex gap-2 border-t border-gray-200 dark:border-gray-700 pt-4">
90
+ <button
91
+ type="submit"
92
+ disabled={mutation.isPending}
93
+ className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-60"
94
+ >
95
+ {submitLabel}
96
+ </button>
97
+ {onCancel && (
98
+ <button
99
+ type="button"
100
+ onClick={onCancel}
101
+ className="rounded border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:text-gray-200 dark:hover:bg-gray-800"
102
+ >
103
+ Cancel
104
+ </button>
105
+ )}
106
+ </div>
107
+
108
+ {mutation.isError && (
109
+ <div className="rounded border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-950 dark:text-red-300">
110
+ {mode === 'create' ? 'Create failed: ' : 'Update failed: '}
111
+ {String(mutation.error)}
112
+ </div>
113
+ )}
114
+ </form>
115
+ );
116
+ }