@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,157 @@
1
+ /**
2
+ * Regeneration safety for ReactAppStarter
3
+ *
4
+ * When realize runs Factory B, generated files can end up in two states:
5
+ *
6
+ * 1. Pristine — the file on disk is byte-identical to what we last
7
+ * wrote. Safe to overwrite with a new version.
8
+ *
9
+ * 2. User-edited — the user modified the file. We must NOT clobber
10
+ * their work.
11
+ *
12
+ * The mechanism is an SHA-256 hash recorded per file in
13
+ * `.specverse-gen/hashes.json` at write time. At regeneration, we
14
+ * compare the on-disk hash against the recorded one. Mismatch → skip
15
+ * with a warning. Match → overwrite and update the hash.
16
+ *
17
+ * The factory orchestrator wraps realize's write calls through
18
+ * `reconcileWrites` below, which handles all three outcomes:
19
+ * - brand-new file → write, record hash
20
+ * - pristine existing → overwrite, update hash
21
+ * - user-edited existing → skip, leave hash unchanged
22
+ */
23
+
24
+ import { createHash } from 'crypto';
25
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
26
+ import { join, relative } from 'path';
27
+
28
+ /** Relative path (from project root) → SHA-256 hex digest. */
29
+ export type HashManifest = Record<string, string>;
30
+
31
+ export const HASHES_DIR = '.specverse-gen';
32
+ export const HASHES_FILE = 'hashes.json';
33
+
34
+ export function sha256(s: string): string {
35
+ return createHash('sha256').update(s, 'utf8').digest('hex');
36
+ }
37
+
38
+ /**
39
+ * Load a `.specverse-gen/hashes.json` from the project root. Returns
40
+ * an empty manifest if the file is missing or malformed — the
41
+ * calling convention is that a missing manifest means "no record of
42
+ * prior generation, treat everything as new."
43
+ */
44
+ export function loadHashManifest(projectRoot: string): HashManifest {
45
+ const path = join(projectRoot, HASHES_DIR, HASHES_FILE);
46
+ if (!existsSync(path)) return {};
47
+ try {
48
+ const raw = readFileSync(path, 'utf8');
49
+ const parsed = JSON.parse(raw);
50
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
51
+ const out: HashManifest = {};
52
+ for (const [k, v] of Object.entries(parsed)) {
53
+ if (typeof v === 'string') out[k] = v;
54
+ }
55
+ return out;
56
+ }
57
+ } catch {
58
+ // Malformed — treat as missing. Next generate pass will overwrite.
59
+ }
60
+ return {};
61
+ }
62
+
63
+ /** Write the manifest back to `.specverse-gen/hashes.json`. */
64
+ export function saveHashManifest(projectRoot: string, manifest: HashManifest): void {
65
+ const dir = join(projectRoot, HASHES_DIR);
66
+ mkdirSync(dir, { recursive: true });
67
+ const path = join(dir, HASHES_FILE);
68
+ writeFileSync(path, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
69
+ }
70
+
71
+ export interface ReconcileResult {
72
+ /** Relative path → content for every file approved for writing. */
73
+ approvedWrites: Record<string, string>;
74
+ /** Files we skipped because the user edited them (or couldn't confirm origin). */
75
+ skipped: { path: string; reason: string }[];
76
+ /** The updated hash manifest reflecting all approved writes. */
77
+ manifest: HashManifest;
78
+ }
79
+
80
+ /**
81
+ * Pure-planning triage. For each proposed file, decides whether the
82
+ * write is safe:
83
+ * - Path doesn't exist → APPROVE, record hash.
84
+ * - Path exists + on-disk hash matches recorded hash → APPROVE
85
+ * (pristine overwrite), update hash.
86
+ * - Path exists + hash mismatch → SKIP (user-edited), preserve old
87
+ * hash record.
88
+ * - Path exists + no recorded hash → SKIP (cautious default).
89
+ *
90
+ * Reads the filesystem but doesn't write. Returns the approved writes
91
+ * as a map the caller can pass to the realize write pipeline.
92
+ */
93
+ export function reconcileWrites(
94
+ projectRoot: string,
95
+ proposed: Record<string, string>,
96
+ prevManifest: HashManifest
97
+ ): ReconcileResult {
98
+ const manifest: HashManifest = { ...prevManifest };
99
+ const approvedWrites: Record<string, string> = {};
100
+ const skipped: { path: string; reason: string }[] = [];
101
+
102
+ for (const [relPath, content] of Object.entries(proposed)) {
103
+ const abs = join(projectRoot, relPath);
104
+ const newHash = sha256(content);
105
+
106
+ if (!existsSync(abs)) {
107
+ approvedWrites[relPath] = content;
108
+ manifest[relPath] = newHash;
109
+ continue;
110
+ }
111
+
112
+ const currentContent = readFileSync(abs, 'utf8');
113
+ const currentHash = sha256(currentContent);
114
+ const recordedHash = prevManifest[relPath];
115
+
116
+ if (recordedHash == null) {
117
+ skipped.push({
118
+ path: relPath,
119
+ reason: 'no prior hash recorded — cannot confirm this file was generated by us',
120
+ });
121
+ continue;
122
+ }
123
+
124
+ if (currentHash === recordedHash) {
125
+ approvedWrites[relPath] = content;
126
+ manifest[relPath] = newHash;
127
+ continue;
128
+ }
129
+
130
+ skipped.push({
131
+ path: relPath,
132
+ reason: 'file has been edited since last generation',
133
+ });
134
+ }
135
+
136
+ return { approvedWrites, skipped, manifest };
137
+ }
138
+
139
+ /**
140
+ * Human-readable summary of a ReconcileResult. Used by the factory
141
+ * orchestrator to log after a run.
142
+ */
143
+ export function summarize(result: ReconcileResult, projectRoot: string): string {
144
+ const lines: string[] = [];
145
+ const writeCount = Object.keys(result.approvedWrites).length;
146
+ lines.push(`[ReactAppStarter] Approved ${writeCount} file(s) for writing.`);
147
+ if (result.skipped.length > 0) {
148
+ lines.push(`[ReactAppStarter] Skipped ${result.skipped.length} user-edited file(s):`);
149
+ for (const { path, reason } of result.skipped) {
150
+ lines.push(` - ${path} (${reason})`);
151
+ }
152
+ lines.push(
153
+ `To accept upstream regeneration for a skipped file, delete it (\`rm ${relative(process.cwd(), projectRoot)}/PATH\`) and re-run \`spv realize\`.`
154
+ );
155
+ }
156
+ return lines.join('\n');
157
+ }
@@ -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
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * {{MODEL_NAME}}ListView — 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 { useState, useMemo } from 'react';
9
+ import { use{{PLURAL_MODEL}}Query, useDelete{{MODEL_NAME}}Mutation } from '../hooks/useApi';
10
+ {{RELATED_IMPORTS}}
11
+ import type { {{MODEL_NAME}} } from '../types/api';
12
+
13
+ interface {{MODEL_NAME}}ListViewProps {
14
+ onSelect?: (item: {{MODEL_NAME}}) => void;
15
+ onCreate?: () => void;
16
+ }
17
+
18
+ export function {{MODEL_NAME}}ListView({ onSelect, onCreate }: {{MODEL_NAME}}ListViewProps) {
19
+ const { data: items = [], isLoading, error } = use{{PLURAL_MODEL}}Query();
20
+ const deleteItem = useDelete{{MODEL_NAME}}Mutation();
21
+ {{RELATED_HOOKS}}
22
+ const [searchTerm, setSearchTerm] = useState('');
23
+
24
+ const filtered = useMemo(
25
+ () =>
26
+ items.filter((item: {{MODEL_NAME}}) =>
27
+ // Cast to `any` inside JSX context — `Record<string, unknown>`
28
+ // looks like a JSX opening tag to the TSX parser when this
29
+ // expression ends up inside JSX. `any` is safe here: we only
30
+ // read values for substring-matching against the search term.
31
+ Object.values(item as any).some(v =>
32
+ String(v ?? '').toLowerCase().includes(searchTerm.toLowerCase())
33
+ )
34
+ ),
35
+ [items, searchTerm]
36
+ );
37
+
38
+ if (isLoading) return <div className="p-4 text-gray-500">Loading {{PLURAL_LOWER}}…</div>;
39
+ if (error) return <div className="p-4 text-red-600">Error loading {{PLURAL_LOWER}}: {String(error)}</div>;
40
+
41
+ return (
42
+ <div className="p-6 space-y-4">
43
+ <div className="flex items-center justify-between">
44
+ <input
45
+ type="search"
46
+ placeholder="Search {{PLURAL_LOWER}}…"
47
+ value={searchTerm}
48
+ onChange={e => setSearchTerm(e.target.value)}
49
+ className="w-64 rounded border border-gray-300 px-3 py-2 text-sm"
50
+ />
51
+ <button
52
+ type="button"
53
+ onClick={onCreate}
54
+ className="rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700"
55
+ >
56
+ + New {{MODEL_NAME}}
57
+ </button>
58
+ </div>
59
+
60
+ {/* Pattern-rendered list body (table + rows). Edit freely. */}
61
+ {{BODY}}
62
+
63
+ {filtered.length === 0 && (
64
+ <div className="p-8 text-center text-gray-400">No {{PLURAL_LOWER}} yet.</div>
65
+ )}
66
+
67
+ {deleteItem.isError && (
68
+ <div className="p-2 text-sm text-red-600">
69
+ Delete failed: {String(deleteItem.error)}
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,124 @@
1
+ /**
2
+ * useApi hooks generator for ReactAppStarter
3
+ *
4
+ * Emits typed per-model React Query hooks that the starter's view
5
+ * skeletons import directly (`useTasksQuery`, `useCreateTaskMutation`,
6
+ * etc.). All hooks hit the realized backend's REST endpoints under
7
+ * `/api/{resource}` via the sibling `apiClient` helper.
8
+ *
9
+ * Why a separate generator from the runtime one: ReactAppRuntime
10
+ * ships a generic `useEntitiesQuery(controller, model)` because its
11
+ * views are rendered by @specverse/runtime using runtime dispatch.
12
+ * The starter wants plain typed hooks the user can read and edit.
13
+ */
14
+
15
+ /**
16
+ * Minimal English pluralizer. Matches the conventions used by
17
+ * view-emitter so the generated hooks pair cleanly with the generated
18
+ * view imports.
19
+ */
20
+ function pluralize(s: string): string {
21
+ if (/y$/.test(s) && !/[aeiou]y$/.test(s)) return s.replace(/y$/, 'ies');
22
+ if (/(s|x|z|ch|sh)$/.test(s)) return s + 'es';
23
+ return s + 's';
24
+ }
25
+
26
+ export interface UseApiHooksStarterContext {
27
+ spec: {
28
+ models?: Record<string, any>;
29
+ };
30
+ manifest?: unknown;
31
+ }
32
+
33
+ export async function generate(context: UseApiHooksStarterContext): Promise<string> {
34
+ const models = Object.keys(context.spec.models ?? {});
35
+
36
+ const importsAndTypes = `/**
37
+ * useApi — typed per-model React Query hooks (ReactAppStarter)
38
+ *
39
+ * Safe to edit. Users who want to reshape their API client or add
40
+ * request/response interceptors should start here. Each hook calls
41
+ * the sibling \`apiClient\` which does the actual fetch; edit
42
+ * \`apiClient.ts\` to change transport concerns (headers, auth, etc.).
43
+ */
44
+
45
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
46
+ ${models.map(m => `import type { ${m} } from '../types/api';`).join('\n')}
47
+
48
+ const API_BASE = import.meta.env.VITE_API_BASE_URL || '';
49
+
50
+ async function fetchJSON<T = unknown>(url: string, init?: RequestInit): Promise<T> {
51
+ const res = await fetch(url, {
52
+ headers: { 'Content-Type': 'application/json' },
53
+ ...init,
54
+ });
55
+ if (!res.ok) {
56
+ throw new Error(\`\${res.status} \${res.statusText} — \${url}\`);
57
+ }
58
+ if (res.status === 204) return undefined as T;
59
+ return (await res.json()) as T;
60
+ }
61
+ `;
62
+
63
+ const hookBlocks = models.map(m => generateModelHooks(m)).join('\n\n');
64
+
65
+ return importsAndTypes + '\n' + hookBlocks + '\n';
66
+ }
67
+
68
+ function generateModelHooks(model: string): string {
69
+ const plural = pluralize(model);
70
+ const resource = plural.toLowerCase();
71
+ const modelLower = model.toLowerCase();
72
+
73
+ return `// ─── ${model} ───────────────────────────────────────────────────
74
+
75
+ export function use${plural}Query() {
76
+ return useQuery({
77
+ queryKey: ['${resource}'],
78
+ queryFn: () => fetchJSON<${model}[]>(\`\${API_BASE}/api/${resource}\`),
79
+ });
80
+ }
81
+
82
+ export function use${model}Query(id: string | number | undefined) {
83
+ return useQuery({
84
+ queryKey: ['${resource}', id],
85
+ queryFn: () => fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`),
86
+ enabled: id !== undefined && id !== null,
87
+ });
88
+ }
89
+
90
+ export function useCreate${model}Mutation() {
91
+ const qc = useQueryClient();
92
+ return useMutation({
93
+ mutationFn: (data: Partial<${model}>) =>
94
+ fetchJSON<${model}>(\`\${API_BASE}/api/${resource}\`, {
95
+ method: 'POST',
96
+ body: JSON.stringify(data),
97
+ }),
98
+ onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
99
+ });
100
+ }
101
+
102
+ export function useUpdate${model}Mutation() {
103
+ const qc = useQueryClient();
104
+ return useMutation({
105
+ mutationFn: ({ id, data }: { id: string | number; data: Partial<${model}> }) =>
106
+ fetchJSON<${model}>(\`\${API_BASE}/api/${resource}/\${id}\`, {
107
+ method: 'PATCH',
108
+ body: JSON.stringify(data),
109
+ }),
110
+ onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
111
+ });
112
+ }
113
+
114
+ export function useDelete${model}Mutation() {
115
+ const qc = useQueryClient();
116
+ return useMutation({
117
+ mutationFn: (id: string | number) =>
118
+ fetchJSON<void>(\`\${API_BASE}/api/${resource}/\${id}\`, { method: 'DELETE' }),
119
+ onSuccess: () => qc.invalidateQueries({ queryKey: ['${resource}'] }),
120
+ });
121
+ }`;
122
+ }
123
+
124
+ export default generate;