adorn-api 1.0.22 → 1.0.24

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 (380) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.cjs +30 -0
  3. package/README.md +375 -531
  4. package/dist/core/express-adapter.d.ts +27 -0
  5. package/dist/core/express-adapter.d.ts.map +1 -0
  6. package/dist/core/express-adapter.js +146 -0
  7. package/dist/core/express-adapter.js.map +1 -0
  8. package/dist/core/http-error.d.ts +7 -0
  9. package/dist/core/http-error.d.ts.map +1 -0
  10. package/dist/core/http-error.js +14 -0
  11. package/dist/core/http-error.js.map +1 -0
  12. package/dist/decorators/controller.decorator.d.ts +2 -0
  13. package/dist/decorators/controller.decorator.d.ts.map +1 -0
  14. package/dist/decorators/controller.decorator.js +26 -0
  15. package/dist/decorators/controller.decorator.js.map +1 -0
  16. package/dist/decorators/create.decorator.d.ts +8 -0
  17. package/dist/decorators/create.decorator.d.ts.map +1 -0
  18. package/dist/decorators/create.decorator.js +67 -0
  19. package/dist/decorators/create.decorator.js.map +1 -0
  20. package/dist/decorators/http-method.decorator.d.ts +16 -0
  21. package/dist/decorators/http-method.decorator.d.ts.map +1 -0
  22. package/dist/decorators/http-method.decorator.js +117 -0
  23. package/dist/decorators/http-method.decorator.js.map +1 -0
  24. package/dist/decorators/http-params.d.ts +17 -0
  25. package/dist/decorators/http-params.d.ts.map +1 -0
  26. package/dist/decorators/http-params.js +26 -0
  27. package/dist/decorators/http-params.js.map +1 -0
  28. package/dist/decorators/index.d.ts +10 -5
  29. package/dist/decorators/index.d.ts.map +1 -1
  30. package/dist/decorators/index.js +14 -0
  31. package/dist/decorators/index.js.map +1 -0
  32. package/dist/decorators/list.decorator.d.ts +18 -0
  33. package/dist/decorators/list.decorator.d.ts.map +1 -0
  34. package/dist/decorators/list.decorator.js +99 -0
  35. package/dist/decorators/list.decorator.js.map +1 -0
  36. package/dist/decorators/middleware.decorator.d.ts +4 -0
  37. package/dist/decorators/middleware.decorator.d.ts.map +1 -0
  38. package/dist/decorators/middleware.decorator.js +34 -0
  39. package/dist/decorators/middleware.decorator.js.map +1 -0
  40. package/dist/decorators/response.decorator.d.ts +8 -0
  41. package/dist/decorators/response.decorator.d.ts.map +1 -0
  42. package/dist/decorators/response.decorator.js +44 -0
  43. package/dist/decorators/response.decorator.js.map +1 -0
  44. package/dist/decorators/route-options.d.ts +14 -0
  45. package/dist/decorators/route-options.d.ts.map +1 -0
  46. package/dist/decorators/route-options.js +22 -0
  47. package/dist/decorators/route-options.js.map +1 -0
  48. package/dist/decorators/schema.decorator.d.ts +82 -0
  49. package/dist/decorators/schema.decorator.d.ts.map +1 -0
  50. package/dist/decorators/schema.decorator.js +123 -0
  51. package/dist/decorators/schema.decorator.js.map +1 -0
  52. package/dist/decorators/update.decorator.d.ts +8 -0
  53. package/dist/decorators/update.decorator.d.ts.map +1 -0
  54. package/dist/decorators/update.decorator.js +63 -0
  55. package/dist/decorators/update.decorator.js.map +1 -0
  56. package/dist/index.d.ts +11 -13
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +18 -637
  59. package/dist/index.js.map +1 -1
  60. package/dist/metadata/metadata-storage.d.ts +38 -0
  61. package/dist/metadata/metadata-storage.d.ts.map +1 -0
  62. package/dist/metadata/metadata-storage.js +102 -0
  63. package/dist/metadata/metadata-storage.js.map +1 -0
  64. package/dist/metal-orm-integration/dto-helper.d.ts +5 -0
  65. package/dist/metal-orm-integration/dto-helper.d.ts.map +1 -0
  66. package/dist/metal-orm-integration/dto-helper.js +48 -0
  67. package/dist/metal-orm-integration/dto-helper.js.map +1 -0
  68. package/dist/metal-orm-integration/dto-response.decorator.d.ts +4 -0
  69. package/dist/metal-orm-integration/dto-response.decorator.d.ts.map +1 -0
  70. package/dist/metal-orm-integration/dto-response.decorator.js +69 -0
  71. package/dist/metal-orm-integration/dto-response.decorator.js.map +1 -0
  72. package/dist/metal-orm-integration/entity-schema-builder.d.ts +20 -0
  73. package/dist/metal-orm-integration/entity-schema-builder.d.ts.map +1 -0
  74. package/dist/metal-orm-integration/entity-schema-builder.js +356 -0
  75. package/dist/metal-orm-integration/entity-schema-builder.js.map +1 -0
  76. package/dist/metal-orm-integration/index.d.ts +5 -0
  77. package/dist/metal-orm-integration/index.d.ts.map +1 -0
  78. package/dist/metal-orm-integration/index.js +5 -0
  79. package/dist/metal-orm-integration/index.js.map +1 -0
  80. package/dist/metal-orm-integration/schema-modifier.d.ts +11 -0
  81. package/dist/metal-orm-integration/schema-modifier.d.ts.map +1 -0
  82. package/dist/metal-orm-integration/schema-modifier.js +62 -0
  83. package/dist/metal-orm-integration/schema-modifier.js.map +1 -0
  84. package/dist/openapi/index.d.ts +4 -0
  85. package/dist/openapi/index.d.ts.map +1 -0
  86. package/dist/openapi/index.js +4 -0
  87. package/dist/openapi/index.js.map +1 -0
  88. package/dist/openapi/openapi-generator.d.ts +22 -0
  89. package/dist/openapi/openapi-generator.d.ts.map +1 -0
  90. package/dist/openapi/openapi-generator.js +428 -0
  91. package/dist/openapi/openapi-generator.js.map +1 -0
  92. package/dist/openapi/swagger-ui.d.ts +11 -0
  93. package/dist/openapi/swagger-ui.d.ts.map +1 -0
  94. package/dist/openapi/swagger-ui.js +20 -0
  95. package/dist/openapi/swagger-ui.js.map +1 -0
  96. package/dist/openapi/zod-to-openapi.d.ts +4 -0
  97. package/dist/openapi/zod-to-openapi.d.ts.map +1 -0
  98. package/dist/openapi/zod-to-openapi.js +184 -0
  99. package/dist/openapi/zod-to-openapi.js.map +1 -0
  100. package/dist/types/common.d.ts +4 -0
  101. package/dist/types/common.d.ts.map +1 -0
  102. package/dist/types/common.js +2 -0
  103. package/dist/types/common.js.map +1 -0
  104. package/dist/types/controller.d.ts +14 -0
  105. package/dist/types/controller.d.ts.map +1 -0
  106. package/dist/types/controller.js +2 -0
  107. package/dist/types/controller.js.map +1 -0
  108. package/dist/types/metadata.d.ts +48 -0
  109. package/dist/types/metadata.d.ts.map +1 -0
  110. package/dist/types/metadata.js +2 -0
  111. package/dist/types/metadata.js.map +1 -0
  112. package/dist/types/openapi.d.ts +30 -0
  113. package/dist/types/openapi.d.ts.map +1 -0
  114. package/dist/types/openapi.js +2 -0
  115. package/dist/types/openapi.js.map +1 -0
  116. package/dist/validation/zod-adapter.d.ts +15 -0
  117. package/dist/validation/zod-adapter.d.ts.map +1 -0
  118. package/dist/validation/zod-adapter.js +61 -0
  119. package/dist/validation/zod-adapter.js.map +1 -0
  120. package/examples/basic/app.ts +15 -0
  121. package/examples/basic/index.ts +6 -0
  122. package/examples/basic/user.controller.ts +35 -0
  123. package/examples/basic/user.dtos.ts +23 -0
  124. package/examples/metal-orm-sqlite/app.ts +18 -0
  125. package/examples/metal-orm-sqlite/db.ts +90 -0
  126. package/examples/metal-orm-sqlite/index.ts +6 -0
  127. package/examples/metal-orm-sqlite/post.controller.ts +209 -0
  128. package/examples/metal-orm-sqlite/post.dtos.ts +78 -0
  129. package/examples/metal-orm-sqlite/post.entity.ts +24 -0
  130. package/examples/metal-orm-sqlite/user.controller.helpers.ts +305 -0
  131. package/examples/metal-orm-sqlite/user.controller.ts +231 -0
  132. package/examples/metal-orm-sqlite/user.dtos.ts +88 -0
  133. package/examples/metal-orm-sqlite/user.entity.ts +21 -0
  134. package/examples/metal-orm-sqlite-music/album.controller.ts +278 -0
  135. package/examples/metal-orm-sqlite-music/album.dtos.ts +85 -0
  136. package/examples/metal-orm-sqlite-music/album.entity.ts +28 -0
  137. package/examples/metal-orm-sqlite-music/app.ts +19 -0
  138. package/examples/metal-orm-sqlite-music/artist.controller.ts +272 -0
  139. package/examples/metal-orm-sqlite-music/artist.dtos.ts +68 -0
  140. package/examples/metal-orm-sqlite-music/artist.entity.ts +27 -0
  141. package/examples/metal-orm-sqlite-music/db.ts +148 -0
  142. package/examples/metal-orm-sqlite-music/index.ts +6 -0
  143. package/examples/metal-orm-sqlite-music/track.controller.ts +221 -0
  144. package/examples/metal-orm-sqlite-music/track.dtos.ts +82 -0
  145. package/examples/metal-orm-sqlite-music/track.entity.ts +27 -0
  146. package/examples/openapi/health.controller.ts +11 -0
  147. package/examples/openapi/health.dto.ts +7 -0
  148. package/examples/openapi/index.ts +12 -0
  149. package/examples/restful/app.ts +15 -0
  150. package/examples/restful/index.ts +9 -0
  151. package/examples/restful/task.controller.ts +118 -0
  152. package/examples/restful/task.dtos.ts +66 -0
  153. package/examples/restful/task.store.ts +95 -0
  154. package/examples/tsconfig.json +8 -0
  155. package/examples/utils/start-server.ts +56 -0
  156. package/package.json +33 -97
  157. package/scripts/run-example.js +29 -0
  158. package/src/adapter/express.ts +589 -0
  159. package/src/adapter/metal-orm/convention-overrides.ts +115 -0
  160. package/src/adapter/metal-orm/crud-dtos.ts +141 -0
  161. package/src/adapter/metal-orm/dto.ts +20 -0
  162. package/src/adapter/metal-orm/error-dtos.ts +51 -0
  163. package/src/adapter/metal-orm/field-builder.ts +185 -0
  164. package/src/adapter/metal-orm/filters.ts +52 -0
  165. package/src/adapter/metal-orm/index.ts +66 -0
  166. package/src/adapter/metal-orm/paged-dtos.ts +94 -0
  167. package/src/adapter/metal-orm/pagination.ts +28 -0
  168. package/src/adapter/metal-orm/types.ts +250 -0
  169. package/src/adapter/metal-orm/utils.ts +36 -0
  170. package/src/adapter/metal-orm.test.ts +439 -0
  171. package/src/core/__tests__/coerce.test.ts +39 -0
  172. package/src/core/__tests__/dto-compose.test.ts +68 -0
  173. package/src/core/__tests__/schema-builder.test.ts +82 -0
  174. package/src/core/coerce.ts +190 -0
  175. package/src/core/decorators.ts +645 -0
  176. package/src/core/errors.ts +55 -0
  177. package/src/core/metadata.ts +110 -0
  178. package/src/core/openapi.ts +282 -0
  179. package/src/core/schema-builder.ts +287 -0
  180. package/src/core/schema.ts +400 -0
  181. package/src/core/types.ts +14 -0
  182. package/src/e2e/http-error.e2e.test.ts +52 -0
  183. package/src/e2e/sqlite-metal-orm.e2e.test.ts +174 -0
  184. package/src/e2e/sqlite.e2e.test.ts +126 -0
  185. package/src/index.ts +8 -0
  186. package/tsconfig.eslint.json +7 -0
  187. package/tsconfig.json +18 -0
  188. package/vitest.config.ts +8 -0
  189. package/dist/adapter/express/auth.d.ts +0 -13
  190. package/dist/adapter/express/auth.d.ts.map +0 -1
  191. package/dist/adapter/express/bootstrap.d.ts +0 -40
  192. package/dist/adapter/express/bootstrap.d.ts.map +0 -1
  193. package/dist/adapter/express/coercion.d.ts +0 -102
  194. package/dist/adapter/express/coercion.d.ts.map +0 -1
  195. package/dist/adapter/express/index.d.ts +0 -6
  196. package/dist/adapter/express/index.d.ts.map +0 -1
  197. package/dist/adapter/express/merge.d.ts +0 -45
  198. package/dist/adapter/express/merge.d.ts.map +0 -1
  199. package/dist/adapter/express/openapi.d.ts +0 -66
  200. package/dist/adapter/express/openapi.d.ts.map +0 -1
  201. package/dist/adapter/express/router.d.ts +0 -10
  202. package/dist/adapter/express/router.d.ts.map +0 -1
  203. package/dist/adapter/express/swagger.d.ts +0 -18
  204. package/dist/adapter/express/swagger.d.ts.map +0 -1
  205. package/dist/adapter/express/types.d.ts +0 -110
  206. package/dist/adapter/express/types.d.ts.map +0 -1
  207. package/dist/adapter/express/validation.d.ts +0 -27
  208. package/dist/adapter/express/validation.d.ts.map +0 -1
  209. package/dist/cli/progress.d.ts +0 -122
  210. package/dist/cli/progress.d.ts.map +0 -1
  211. package/dist/cli.cjs +0 -4390
  212. package/dist/cli.cjs.map +0 -1
  213. package/dist/cli.d.ts +0 -3
  214. package/dist/cli.d.ts.map +0 -1
  215. package/dist/cli.js +0 -4371
  216. package/dist/cli.js.map +0 -1
  217. package/dist/compiler/analyze/index.d.ts +0 -5
  218. package/dist/compiler/analyze/index.d.ts.map +0 -1
  219. package/dist/compiler/analyze/scanControllers.d.ts +0 -88
  220. package/dist/compiler/analyze/scanControllers.d.ts.map +0 -1
  221. package/dist/compiler/cache/isStale.d.ts +0 -46
  222. package/dist/compiler/cache/isStale.d.ts.map +0 -1
  223. package/dist/compiler/cache/loadArtifacts.d.ts +0 -149
  224. package/dist/compiler/cache/loadArtifacts.d.ts.map +0 -1
  225. package/dist/compiler/cache/schema.d.ts +0 -32
  226. package/dist/compiler/cache/schema.d.ts.map +0 -1
  227. package/dist/compiler/cache/writeCache.d.ts +0 -14
  228. package/dist/compiler/cache/writeCache.d.ts.map +0 -1
  229. package/dist/compiler/gems.d.ts +0 -75
  230. package/dist/compiler/gems.d.ts.map +0 -1
  231. package/dist/compiler/generator/index.d.ts +0 -7
  232. package/dist/compiler/generator/index.d.ts.map +0 -1
  233. package/dist/compiler/generator/manifest.d.ts +0 -23
  234. package/dist/compiler/generator/manifest.d.ts.map +0 -1
  235. package/dist/compiler/generator/openapi.d.ts +0 -118
  236. package/dist/compiler/generator/openapi.d.ts.map +0 -1
  237. package/dist/compiler/graph/builder.d.ts +0 -24
  238. package/dist/compiler/graph/builder.d.ts.map +0 -1
  239. package/dist/compiler/graph/index.d.ts +0 -7
  240. package/dist/compiler/graph/index.d.ts.map +0 -1
  241. package/dist/compiler/graph/schemaGraph.d.ts +0 -67
  242. package/dist/compiler/graph/schemaGraph.d.ts.map +0 -1
  243. package/dist/compiler/graph/types.d.ts +0 -203
  244. package/dist/compiler/graph/types.d.ts.map +0 -1
  245. package/dist/compiler/index.d.ts +0 -12
  246. package/dist/compiler/index.d.ts.map +0 -1
  247. package/dist/compiler/ir/index.d.ts +0 -7
  248. package/dist/compiler/ir/index.d.ts.map +0 -1
  249. package/dist/compiler/ir/pipeline.d.ts +0 -82
  250. package/dist/compiler/ir/pipeline.d.ts.map +0 -1
  251. package/dist/compiler/ir/stages.d.ts +0 -40
  252. package/dist/compiler/ir/stages.d.ts.map +0 -1
  253. package/dist/compiler/ir/visitor.d.ts +0 -98
  254. package/dist/compiler/ir/visitor.d.ts.map +0 -1
  255. package/dist/compiler/manifest/emit.d.ts +0 -21
  256. package/dist/compiler/manifest/emit.d.ts.map +0 -1
  257. package/dist/compiler/manifest/format.d.ts +0 -119
  258. package/dist/compiler/manifest/format.d.ts.map +0 -1
  259. package/dist/compiler/manifest/index.d.ts +0 -6
  260. package/dist/compiler/manifest/index.d.ts.map +0 -1
  261. package/dist/compiler/runner/createProgram.d.ts +0 -24
  262. package/dist/compiler/runner/createProgram.d.ts.map +0 -1
  263. package/dist/compiler/runner/index.d.ts +0 -5
  264. package/dist/compiler/runner/index.d.ts.map +0 -1
  265. package/dist/compiler/schema/extractAnnotations.d.ts +0 -57
  266. package/dist/compiler/schema/extractAnnotations.d.ts.map +0 -1
  267. package/dist/compiler/schema/index.d.ts +0 -8
  268. package/dist/compiler/schema/index.d.ts.map +0 -1
  269. package/dist/compiler/schema/intersectionHandler.d.ts +0 -44
  270. package/dist/compiler/schema/intersectionHandler.d.ts.map +0 -1
  271. package/dist/compiler/schema/objectHandler.d.ts +0 -146
  272. package/dist/compiler/schema/objectHandler.d.ts.map +0 -1
  273. package/dist/compiler/schema/openapi.d.ts +0 -71
  274. package/dist/compiler/schema/openapi.d.ts.map +0 -1
  275. package/dist/compiler/schema/parameters.d.ts +0 -90
  276. package/dist/compiler/schema/parameters.d.ts.map +0 -1
  277. package/dist/compiler/schema/partitioner.d.ts +0 -85
  278. package/dist/compiler/schema/partitioner.d.ts.map +0 -1
  279. package/dist/compiler/schema/primitives.d.ts +0 -68
  280. package/dist/compiler/schema/primitives.d.ts.map +0 -1
  281. package/dist/compiler/schema/queryBuilderAnalyzer.d.ts +0 -76
  282. package/dist/compiler/schema/queryBuilderAnalyzer.d.ts.map +0 -1
  283. package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts +0 -13
  284. package/dist/compiler/schema/queryBuilderSchemaBuilder.d.ts.map +0 -1
  285. package/dist/compiler/schema/splitOpenapi.d.ts +0 -46
  286. package/dist/compiler/schema/splitOpenapi.d.ts.map +0 -1
  287. package/dist/compiler/schema/typeToJsonSchema.d.ts +0 -26
  288. package/dist/compiler/schema/typeToJsonSchema.d.ts.map +0 -1
  289. package/dist/compiler/schema/types.d.ts +0 -70
  290. package/dist/compiler/schema/types.d.ts.map +0 -1
  291. package/dist/compiler/schema/unionHandler.d.ts +0 -70
  292. package/dist/compiler/schema/unionHandler.d.ts.map +0 -1
  293. package/dist/compiler/transform/dedup.d.ts +0 -35
  294. package/dist/compiler/transform/dedup.d.ts.map +0 -1
  295. package/dist/compiler/transform/flatten.d.ts +0 -50
  296. package/dist/compiler/transform/flatten.d.ts.map +0 -1
  297. package/dist/compiler/transform/index.d.ts +0 -7
  298. package/dist/compiler/transform/index.d.ts.map +0 -1
  299. package/dist/compiler/transform/inline.d.ts +0 -46
  300. package/dist/compiler/transform/inline.d.ts.map +0 -1
  301. package/dist/compiler/validation/emitPrecompiledValidators.d.ts +0 -62
  302. package/dist/compiler/validation/emitPrecompiledValidators.d.ts.map +0 -1
  303. package/dist/compiler/validation/index.d.ts +0 -5
  304. package/dist/compiler/validation/index.d.ts.map +0 -1
  305. package/dist/decorators/Auth.d.ts +0 -22
  306. package/dist/decorators/Auth.d.ts.map +0 -1
  307. package/dist/decorators/Controller.d.ts +0 -17
  308. package/dist/decorators/Controller.d.ts.map +0 -1
  309. package/dist/decorators/Public.d.ts +0 -15
  310. package/dist/decorators/Public.d.ts.map +0 -1
  311. package/dist/decorators/Use.d.ts +0 -23
  312. package/dist/decorators/Use.d.ts.map +0 -1
  313. package/dist/decorators/methods.d.ts +0 -26
  314. package/dist/decorators/methods.d.ts.map +0 -1
  315. package/dist/express.cjs +0 -1186
  316. package/dist/express.cjs.map +0 -1
  317. package/dist/express.d.ts +0 -8
  318. package/dist/express.d.ts.map +0 -1
  319. package/dist/express.js +0 -1150
  320. package/dist/express.js.map +0 -1
  321. package/dist/http.d.ts +0 -33
  322. package/dist/http.d.ts.map +0 -1
  323. package/dist/index.cjs +0 -724
  324. package/dist/index.cjs.map +0 -1
  325. package/dist/metal/applyListQuery.d.ts +0 -100
  326. package/dist/metal/applyListQuery.d.ts.map +0 -1
  327. package/dist/metal/index.cjs +0 -278
  328. package/dist/metal/index.cjs.map +0 -1
  329. package/dist/metal/index.d.ts +0 -15
  330. package/dist/metal/index.d.ts.map +0 -1
  331. package/dist/metal/index.js +0 -243
  332. package/dist/metal/index.js.map +0 -1
  333. package/dist/metal/listQuery.d.ts +0 -26
  334. package/dist/metal/listQuery.d.ts.map +0 -1
  335. package/dist/metal/queryOptions.d.ts +0 -16
  336. package/dist/metal/queryOptions.d.ts.map +0 -1
  337. package/dist/metal/readMetalBag.d.ts +0 -69
  338. package/dist/metal/readMetalBag.d.ts.map +0 -1
  339. package/dist/metal/registerMetalEntities.d.ts +0 -26
  340. package/dist/metal/registerMetalEntities.d.ts.map +0 -1
  341. package/dist/metal/schemaFromEntity.d.ts +0 -41
  342. package/dist/metal/schemaFromEntity.d.ts.map +0 -1
  343. package/dist/metal/searchWhere.d.ts +0 -97
  344. package/dist/metal/searchWhere.d.ts.map +0 -1
  345. package/dist/metal/symbolMetadata.d.ts +0 -8
  346. package/dist/metal/symbolMetadata.d.ts.map +0 -1
  347. package/dist/runtime/auth/runtime.d.ts +0 -183
  348. package/dist/runtime/auth/runtime.d.ts.map +0 -1
  349. package/dist/runtime/metadata/bucket.d.ts +0 -2
  350. package/dist/runtime/metadata/bucket.d.ts.map +0 -1
  351. package/dist/runtime/metadata/key.d.ts +0 -2
  352. package/dist/runtime/metadata/key.d.ts.map +0 -1
  353. package/dist/runtime/metadata/read.d.ts +0 -2
  354. package/dist/runtime/metadata/read.d.ts.map +0 -1
  355. package/dist/runtime/metadata/types.d.ts +0 -95
  356. package/dist/runtime/metadata/types.d.ts.map +0 -1
  357. package/dist/runtime/polyfill.d.ts +0 -2
  358. package/dist/runtime/polyfill.d.ts.map +0 -1
  359. package/dist/runtime/upload.d.ts +0 -44
  360. package/dist/runtime/upload.d.ts.map +0 -1
  361. package/dist/runtime/validation/ajv.d.ts +0 -120
  362. package/dist/runtime/validation/ajv.d.ts.map +0 -1
  363. package/dist/runtime/validation/index.d.ts +0 -11
  364. package/dist/runtime/validation/index.d.ts.map +0 -1
  365. package/dist/schema/decorators.d.ts +0 -37
  366. package/dist/schema/decorators.d.ts.map +0 -1
  367. package/dist/schema/index.cjs +0 -214
  368. package/dist/schema/index.cjs.map +0 -1
  369. package/dist/schema/index.d.ts +0 -2
  370. package/dist/schema/index.d.ts.map +0 -1
  371. package/dist/schema/index.js +0 -163
  372. package/dist/schema/index.js.map +0 -1
  373. package/dist/scripts/adorn-example.cjs +0 -404
  374. package/dist/scripts/adorn-example.cjs.map +0 -1
  375. package/dist/utils/operationId.d.ts +0 -2
  376. package/dist/utils/operationId.d.ts.map +0 -1
  377. package/dist/utils/path.d.ts +0 -2
  378. package/dist/utils/path.d.ts.map +0 -1
  379. package/dist/utils/port.d.ts +0 -9
  380. package/dist/utils/port.d.ts.map +0 -1
@@ -0,0 +1,589 @@
1
+ import express, { type Request, type Response, type NextFunction } from "express";
2
+ import { buildOpenApi, type OpenApiInfo, type OpenApiServer } from "../core/openapi";
3
+ import type { Constructor, DtoConstructor } from "../core/types";
4
+ import { getControllerMeta, getDtoMeta, type InputMeta } from "../core/metadata";
5
+ import type {
6
+ SchemaNode,
7
+ SchemaSource,
8
+ ArraySchema,
9
+ NumberSchema,
10
+ ObjectSchema,
11
+ RecordSchema,
12
+ RefSchema,
13
+ UnionSchema
14
+ } from "../core/schema";
15
+ import { coerce } from "../core/coerce";
16
+ import { HttpError, isHttpError } from "../core/errors";
17
+
18
+ /**
19
+ * Request context provided to route handlers.
20
+ */
21
+ export interface RequestContext<
22
+ TBody = unknown,
23
+ TQuery extends object | undefined = Record<string, unknown>,
24
+ TParams extends object | undefined = Record<string, string | number | boolean | undefined>,
25
+ THeaders extends object | undefined = Record<string, string | string[] | undefined>
26
+ > {
27
+ /** Express request object */
28
+ req: Request;
29
+ /** Express response object */
30
+ res: Response;
31
+ /** Parsed request body */
32
+ body: TBody;
33
+ /** Parsed query parameters */
34
+ query: TQuery;
35
+ /** Parsed path parameters */
36
+ params: TParams;
37
+ /** Request headers */
38
+ headers: THeaders;
39
+ }
40
+
41
+ /**
42
+ * Input coercion modes.
43
+ */
44
+ export type InputCoercionMode = "safe" | "strict";
45
+
46
+ /**
47
+ * Input coercion setting - can be a mode or disabled.
48
+ */
49
+ export type InputCoercionSetting = InputCoercionMode | false;
50
+
51
+ /**
52
+ * Options for creating an Express application adapter.
53
+ */
54
+ export interface ExpressAdapterOptions {
55
+ /** Array of controller classes */
56
+ controllers: Constructor[];
57
+ /** Whether to enable JSON body parsing */
58
+ jsonBody?: boolean;
59
+ /** OpenAPI configuration */
60
+ openApi?: OpenApiExpressOptions;
61
+ /** Input coercion setting */
62
+ inputCoercion?: InputCoercionSetting;
63
+ }
64
+
65
+ /**
66
+ * Creates an Express application with Adorn controllers.
67
+ * @param options - Express adapter options
68
+ * @returns Configured Express application
69
+ */
70
+ export function createExpressApp(options: ExpressAdapterOptions): express.Express {
71
+ const app = express();
72
+ if (options.jsonBody ?? true) {
73
+ app.use(express.json());
74
+ }
75
+ const inputCoercion = options.inputCoercion ?? "safe";
76
+ attachControllers(app, options.controllers, inputCoercion);
77
+ if (options.openApi) {
78
+ attachOpenApi(app, options.controllers, options.openApi);
79
+ }
80
+ return app;
81
+ }
82
+
83
+ /**
84
+ * Options for OpenAPI documentation UI.
85
+ */
86
+ export interface OpenApiDocsOptions {
87
+ /** Path for documentation UI */
88
+ path?: string;
89
+ /** Title for documentation page */
90
+ title?: string;
91
+ /** URL for Swagger UI assets */
92
+ swaggerUiUrl?: string;
93
+ }
94
+
95
+ /**
96
+ * OpenAPI configuration for Express adapter.
97
+ */
98
+ export interface OpenApiExpressOptions {
99
+ /** OpenAPI document info */
100
+ info: OpenApiInfo;
101
+ /** Array of servers */
102
+ servers?: OpenApiServer[];
103
+ /** Path for OpenAPI JSON endpoint */
104
+ path?: string;
105
+ /** Documentation UI configuration */
106
+ docs?: boolean | OpenApiDocsOptions;
107
+ }
108
+
109
+ /**
110
+ * Attaches controllers to an Express application.
111
+ * @param app - Express application instance
112
+ * @param controllers - Array of controller classes
113
+ * @param inputCoercion - Input coercion setting
114
+ */
115
+ export function attachControllers(
116
+ app: express.Express,
117
+ controllers: Constructor[],
118
+ inputCoercion: InputCoercionSetting = "safe"
119
+ ): void {
120
+ for (const controller of controllers) {
121
+ const meta = getControllerMeta(controller);
122
+ if (!meta) {
123
+ throw new Error(`Controller "${controller.name}" is missing @Controller decorator.`);
124
+ }
125
+ const instance = new controller();
126
+ for (const route of meta.routes) {
127
+ const path = joinPaths(meta.basePath, route.path);
128
+ const handler = instance[route.handlerName as keyof typeof instance];
129
+ if (typeof handler !== "function") {
130
+ throw new Error(`Handler "${String(route.handlerName)}" is not a function on ${controller.name}.`);
131
+ }
132
+ const coerceParams = inputCoercion === false
133
+ ? undefined
134
+ : createInputCoercer<Record<string, string | number | boolean | undefined>>(
135
+ route.params,
136
+ { mode: inputCoercion, location: "params" }
137
+ );
138
+ const coerceQuery = inputCoercion === false
139
+ ? undefined
140
+ : createInputCoercer(route.query, { mode: inputCoercion, location: "query" });
141
+ app[route.httpMethod](path, async (req: Request, res: Response, next: NextFunction) => {
142
+ try {
143
+ const ctx: RequestContext = {
144
+ req,
145
+ res,
146
+ body: req.body,
147
+ query: coerceQuery ? coerceQuery(req.query) : req.query,
148
+ params: coerceParams ? coerceParams(req.params) : req.params,
149
+ headers: req.headers
150
+ };
151
+ const result = await handler.call(instance, ctx);
152
+ if (res.headersSent) {
153
+ return;
154
+ }
155
+ if (result === undefined) {
156
+ res.status(defaultStatus(route)).end();
157
+ return;
158
+ }
159
+ res.status(defaultStatus(route)).json(result);
160
+ } catch (error) {
161
+ if (isHttpError(error)) {
162
+ sendHttpError(res, error);
163
+ return;
164
+ }
165
+ next(error);
166
+ }
167
+ });
168
+ }
169
+ }
170
+ }
171
+
172
+ function attachOpenApi(
173
+ app: express.Express,
174
+ controllers: Constructor[],
175
+ options: OpenApiExpressOptions
176
+ ): void {
177
+ const openApiPath = normalizePath(options.path, "/openapi.json");
178
+ const document = buildOpenApi({
179
+ info: options.info,
180
+ servers: options.servers,
181
+ controllers
182
+ });
183
+
184
+ app.get(openApiPath, (_req, res) => {
185
+ res.json(document);
186
+ });
187
+
188
+ if (!options.docs) {
189
+ return;
190
+ }
191
+
192
+ const docsOptions = typeof options.docs === "object" ? options.docs : {};
193
+ const docsPath = normalizePath(docsOptions.path, "/docs");
194
+ const title = docsOptions.title ?? `${options.info.title} Docs`;
195
+ const swaggerUiUrl = (docsOptions.swaggerUiUrl ?? "https://unpkg.com/swagger-ui-dist@5").replace(
196
+ /\/+$/,
197
+ ""
198
+ );
199
+
200
+ const html = buildSwaggerUiHtml({ title, swaggerUiUrl, openApiPath });
201
+ app.get(docsPath, (_req, res) => {
202
+ res.type("html").send(html);
203
+ });
204
+ }
205
+
206
+ function buildSwaggerUiHtml(options: {
207
+ title: string;
208
+ swaggerUiUrl: string;
209
+ openApiPath: string;
210
+ }): string {
211
+ return `<!doctype html>
212
+ <html lang="en">
213
+ <head>
214
+ <meta charset="utf-8" />
215
+ <title>${options.title}</title>
216
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
217
+ <link rel="stylesheet" href="${options.swaggerUiUrl}/swagger-ui.css" />
218
+ <style>
219
+ body {
220
+ margin: 0;
221
+ background: #f6f6f6;
222
+ }
223
+ </style>
224
+ </head>
225
+ <body>
226
+ <div id="swagger-ui"></div>
227
+ <script src="${options.swaggerUiUrl}/swagger-ui-bundle.js"></script>
228
+ <script>
229
+ window.onload = () => {
230
+ window.ui = SwaggerUIBundle({
231
+ url: "${options.openApiPath}",
232
+ dom_id: "#swagger-ui",
233
+ deepLinking: true,
234
+ presets: [SwaggerUIBundle.presets.apis],
235
+ layout: "BaseLayout"
236
+ });
237
+ };
238
+ </script>
239
+ </body>
240
+ </html>`;
241
+ }
242
+
243
+ function defaultStatus(route: {
244
+ responses?: Array<{ status: number; error?: boolean }>;
245
+ }): number {
246
+ const responses = route.responses ?? [];
247
+ const success = responses.find(
248
+ (response) => !response.error && response.status < 400
249
+ );
250
+ return success?.status ?? 200;
251
+ }
252
+
253
+ function joinPaths(basePath: string, routePath: string): string {
254
+ const base = basePath ? basePath.replace(/\/+$/, "") : "";
255
+ const route = routePath ? routePath.replace(/^\/+/, "") : "";
256
+ if (!base && !route) {
257
+ return "/";
258
+ }
259
+ if (!base) {
260
+ return `/${route}`;
261
+ }
262
+ if (!route) {
263
+ return base.startsWith("/") ? base : `/${base}`;
264
+ }
265
+ return `${base.startsWith("/") ? base : `/${base}`}/${route}`;
266
+ }
267
+
268
+ function normalizePath(path: string | undefined, fallback: string): string {
269
+ const value = path && path.trim().length ? path.trim() : fallback;
270
+ return value.startsWith("/") ? value : `/${value}`;
271
+ }
272
+
273
+ function sendHttpError(res: Response, error: HttpError): void {
274
+ if (res.headersSent) {
275
+ return;
276
+ }
277
+ if (error.headers) {
278
+ for (const [key, value] of Object.entries(error.headers)) {
279
+ res.setHeader(key, value);
280
+ }
281
+ }
282
+ const body = error.body ?? { message: error.message };
283
+ if (body === undefined) {
284
+ res.status(error.status).end();
285
+ return;
286
+ }
287
+ res.status(error.status).json(body);
288
+ }
289
+
290
+ type CoerceField = { name: string; schema: SchemaNode };
291
+ type InputLocation = "params" | "query";
292
+
293
+ interface InputCoercionOptions {
294
+ mode: InputCoercionMode;
295
+ location: InputLocation;
296
+ }
297
+
298
+ interface CoerceOutcome {
299
+ value: unknown;
300
+ ok: boolean;
301
+ changed: boolean;
302
+ }
303
+
304
+ function createInputCoercer<T extends Record<string, unknown> = Record<string, unknown>>(
305
+ input: InputMeta | undefined,
306
+ options: InputCoercionOptions
307
+ ): ((value: T) => T) | undefined {
308
+ if (!input) {
309
+ return undefined;
310
+ }
311
+ const fields = extractFields(input.schema);
312
+ if (!fields.length) {
313
+ return undefined;
314
+ }
315
+ return (value: T) => {
316
+ const result = coerceRecord(value, fields, options.mode);
317
+ if (options.mode === "strict" && result.invalidFields.length) {
318
+ throw new HttpError(400, buildInvalidMessage(options.location, result.invalidFields));
319
+ }
320
+ return result.value as T;
321
+ };
322
+ }
323
+
324
+ function coerceRecord(
325
+ value: unknown,
326
+ fields: CoerceField[],
327
+ mode: InputCoercionMode
328
+ ): { value: unknown; invalidFields: string[] } {
329
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
330
+ return { value, invalidFields: [] };
331
+ }
332
+ const input = value as Record<string, unknown>;
333
+ let changed = false;
334
+ const output: Record<string, unknown> = { ...input };
335
+ const invalidFields: string[] = [];
336
+ for (const field of fields) {
337
+ if (!(field.name in input)) {
338
+ continue;
339
+ }
340
+ const original = input[field.name];
341
+ const result = coerceValue(original, field.schema, mode);
342
+ if (!result.ok && mode === "strict") {
343
+ invalidFields.push(field.name);
344
+ }
345
+ if (result.changed) {
346
+ output[field.name] = result.value;
347
+ changed = true;
348
+ }
349
+ }
350
+ return { value: changed ? output : value, invalidFields };
351
+ }
352
+
353
+ function coerceValue(
354
+ value: unknown,
355
+ schema: SchemaNode,
356
+ mode: InputCoercionMode
357
+ ): CoerceOutcome {
358
+ switch (schema.kind) {
359
+ case "integer":
360
+ return coerceNumber(value, schema, true);
361
+ case "number":
362
+ return coerceNumber(value, schema, false);
363
+ case "boolean": {
364
+ return coerceBoolean(value);
365
+ }
366
+ case "string": {
367
+ return coerceString(value);
368
+ }
369
+ case "array":
370
+ return coerceArrayValue(value, schema, mode);
371
+ case "object":
372
+ return coerceObjectValue(value, schema, mode);
373
+ case "record":
374
+ return coerceRecordValue(value, schema, mode);
375
+ case "ref":
376
+ return coerceRefValue(value, schema, mode);
377
+ case "union":
378
+ return coerceUnionValue(value, schema, mode);
379
+ default:
380
+ return { value, ok: true, changed: false };
381
+ }
382
+ }
383
+
384
+ function coerceNumber(value: unknown, schema: NumberSchema, integer: boolean): CoerceOutcome {
385
+ if (!isPresent(value)) {
386
+ return { value, ok: true, changed: false };
387
+ }
388
+ const parsed = integer
389
+ ? coerce.integer(value, { min: schema.minimum, max: schema.maximum })
390
+ : coerce.number(value, { min: schema.minimum, max: schema.maximum });
391
+ if (parsed === undefined) {
392
+ return { value, ok: false, changed: false };
393
+ }
394
+ if (schema.exclusiveMinimum !== undefined && parsed <= schema.exclusiveMinimum) {
395
+ return { value, ok: false, changed: false };
396
+ }
397
+ if (schema.exclusiveMaximum !== undefined && parsed >= schema.exclusiveMaximum) {
398
+ return { value, ok: false, changed: false };
399
+ }
400
+ return { value: parsed, ok: true, changed: parsed !== value };
401
+ }
402
+
403
+ function coerceBoolean(value: unknown): CoerceOutcome {
404
+ if (!isPresent(value)) {
405
+ return { value, ok: true, changed: false };
406
+ }
407
+ const parsed = coerce.boolean(value);
408
+ if (parsed === undefined) {
409
+ return { value, ok: false, changed: false };
410
+ }
411
+ return { value: parsed, ok: true, changed: parsed !== value };
412
+ }
413
+
414
+ function coerceString(value: unknown): CoerceOutcome {
415
+ const parsed = coerce.string(value);
416
+ if (parsed === undefined) {
417
+ return { value, ok: true, changed: false };
418
+ }
419
+ return { value: parsed, ok: true, changed: parsed !== value };
420
+ }
421
+
422
+ function coerceArrayValue(
423
+ value: unknown,
424
+ schema: ArraySchema,
425
+ mode: InputCoercionMode
426
+ ): CoerceOutcome {
427
+ if (value === undefined || value === null) {
428
+ return { value, ok: true, changed: false };
429
+ }
430
+ const input = Array.isArray(value) ? value : [value];
431
+ let changed = !Array.isArray(value);
432
+ let ok = true;
433
+ const output = input.map((entry) => {
434
+ const result = coerceValue(entry, schema.items, mode);
435
+ if (!result.ok) {
436
+ ok = false;
437
+ }
438
+ if (result.changed) {
439
+ changed = true;
440
+ }
441
+ return result.value;
442
+ });
443
+ return { value: changed ? output : value, ok, changed };
444
+ }
445
+
446
+ function coerceObjectValue(
447
+ value: unknown,
448
+ schema: ObjectSchema,
449
+ mode: InputCoercionMode
450
+ ): CoerceOutcome {
451
+ if (value === undefined || value === null) {
452
+ return { value, ok: true, changed: false };
453
+ }
454
+ if (typeof value !== "object" || Array.isArray(value)) {
455
+ return { value, ok: mode === "safe", changed: false };
456
+ }
457
+ const properties = schema.properties ?? {};
458
+ const fields = Object.entries(properties).map(([name, fieldSchema]) => ({
459
+ name,
460
+ schema: fieldSchema
461
+ }));
462
+ if (!fields.length) {
463
+ return { value, ok: true, changed: false };
464
+ }
465
+ const result = coerceRecord(value, fields, mode);
466
+ return {
467
+ value: result.value,
468
+ ok: result.invalidFields.length === 0,
469
+ changed: result.value !== value
470
+ };
471
+ }
472
+
473
+ function coerceRecordValue(
474
+ value: unknown,
475
+ schema: RecordSchema,
476
+ mode: InputCoercionMode
477
+ ): CoerceOutcome {
478
+ if (value === undefined || value === null) {
479
+ return { value, ok: true, changed: false };
480
+ }
481
+ if (typeof value !== "object" || Array.isArray(value)) {
482
+ return { value, ok: mode === "safe", changed: false };
483
+ }
484
+ const input = value as Record<string, unknown>;
485
+ let changed = false;
486
+ let ok = true;
487
+ const output: Record<string, unknown> = { ...input };
488
+ for (const [key, entry] of Object.entries(input)) {
489
+ const result = coerceValue(entry, schema.values, mode);
490
+ if (!result.ok) {
491
+ ok = false;
492
+ }
493
+ if (result.changed) {
494
+ output[key] = result.value;
495
+ changed = true;
496
+ }
497
+ }
498
+ return { value: changed ? output : value, ok, changed };
499
+ }
500
+
501
+ function coerceRefValue(
502
+ value: unknown,
503
+ schema: RefSchema,
504
+ mode: InputCoercionMode
505
+ ): CoerceOutcome {
506
+ if (value === undefined || value === null) {
507
+ return { value, ok: true, changed: false };
508
+ }
509
+ if (typeof value !== "object" || Array.isArray(value)) {
510
+ return { value, ok: mode === "safe", changed: false };
511
+ }
512
+ const meta = getDtoMetaSafe(schema.dto);
513
+ const fields = Object.entries(meta.fields).map(([name, field]) => ({
514
+ name,
515
+ schema: field.schema
516
+ }));
517
+ if (!fields.length) {
518
+ return { value, ok: true, changed: false };
519
+ }
520
+ const result = coerceRecord(value, fields, mode);
521
+ return {
522
+ value: result.value,
523
+ ok: result.invalidFields.length === 0,
524
+ changed: result.value !== value
525
+ };
526
+ }
527
+
528
+ function coerceUnionValue(
529
+ value: unknown,
530
+ schema: UnionSchema,
531
+ mode: InputCoercionMode
532
+ ): CoerceOutcome {
533
+ let fallback: CoerceOutcome | undefined;
534
+ for (const option of schema.anyOf) {
535
+ const result = coerceValue(value, option, mode);
536
+ if (!result.ok) {
537
+ continue;
538
+ }
539
+ if (result.changed) {
540
+ return result;
541
+ }
542
+ fallback ??= result;
543
+ }
544
+ if (fallback) {
545
+ return fallback;
546
+ }
547
+ return { value, ok: mode === "safe", changed: false };
548
+ }
549
+
550
+ function extractFields(schema: SchemaSource): CoerceField[] {
551
+ if (isSchemaNode(schema)) {
552
+ if (schema.kind === "object" && schema.properties) {
553
+ return Object.entries(schema.properties).map(([name, fieldSchema]) => ({
554
+ name,
555
+ schema: fieldSchema
556
+ }));
557
+ }
558
+ return [];
559
+ }
560
+ const meta = getDtoMetaSafe(schema);
561
+ return Object.entries(meta.fields).map(([name, field]) => ({
562
+ name,
563
+ schema: field.schema
564
+ }));
565
+ }
566
+
567
+ function getDtoMetaSafe(dto: DtoConstructor): {
568
+ fields: Record<string, { schema: SchemaNode }>;
569
+ } {
570
+ const meta = getDtoMeta(dto);
571
+ if (!meta) {
572
+ throw new Error(`DTO "${dto.name}" is missing @Dto decorator.`);
573
+ }
574
+ return meta;
575
+ }
576
+
577
+ function isSchemaNode(value: unknown): value is SchemaNode {
578
+ return !!value && typeof value === "object" && "kind" in (value as SchemaNode);
579
+ }
580
+
581
+ function isPresent(value: unknown): boolean {
582
+ return coerce.string(value) !== undefined;
583
+ }
584
+
585
+ function buildInvalidMessage(location: InputLocation, fields: string[]): string {
586
+ const label = location === "params" ? "path parameter" : "query parameter";
587
+ const suffix = fields.length > 1 ? "s" : "";
588
+ return `Invalid ${label}${suffix}: ${fields.join(", ")}.`;
589
+ }
@@ -0,0 +1,115 @@
1
+ import { getColumnMap, type ColumnDef } from "metal-orm";
2
+ import { t, type SchemaNode } from "../../core/schema";
3
+
4
+ export interface CreateMetalDtoOverridesOptions {
5
+ overrides?: Record<string, SchemaNode>;
6
+ exclude?: string[];
7
+ entityName?: string;
8
+ timestampDescription?: string;
9
+ }
10
+
11
+ export function createMetalDtoOverrides(
12
+ target: any,
13
+ options: CreateMetalDtoOverridesOptions = {}
14
+ ): Record<string, SchemaNode> {
15
+ const columns = getColumnMap(target);
16
+ const {
17
+ overrides = {},
18
+ exclude = [],
19
+ entityName = target.name,
20
+ timestampDescription = "Creation timestamp."
21
+ } = options;
22
+
23
+ const result: Record<string, SchemaNode> = {};
24
+
25
+ for (const [name, col] of Object.entries(columns)) {
26
+ if (exclude.includes(name)) continue;
27
+
28
+ if (overrides[name]) {
29
+ result[name] = overrides[name];
30
+ continue;
31
+ }
32
+
33
+ const convention = inferFromMetadata(name, col, entityName, timestampDescription);
34
+ if (convention) {
35
+ result[name] = convention;
36
+ }
37
+ }
38
+
39
+ return result;
40
+ }
41
+
42
+ function inferFromMetadata(
43
+ name: string,
44
+ col: ColumnDef,
45
+ entityName: string,
46
+ timestampDescription: string
47
+ ): SchemaNode | null {
48
+ const normalizedType = col.type.toUpperCase();
49
+
50
+ if (col.primary || col.autoIncrement) {
51
+ return t.integer({
52
+ minimum: 1,
53
+ description: `${entityName} id.`
54
+ });
55
+ }
56
+
57
+ if (col.references) {
58
+ const targetEntity = extractEntityName(col.references.table);
59
+ return t.integer({
60
+ minimum: 1,
61
+ description: `${targetEntity} id.`
62
+ });
63
+ }
64
+
65
+ if (normalizedType === "DATETIME" || normalizedType === "TIMESTAMP") {
66
+ return t.dateTime({ description: timestampDescription });
67
+ }
68
+
69
+ if (col.notNull && isTextColumn(col)) {
70
+ return t.string({ minLength: 1 });
71
+ }
72
+
73
+ if (!col.notNull && isTextColumn(col)) {
74
+ return t.nullable(t.string());
75
+ }
76
+
77
+ if (col.notNull && isIntegerColumn(col)) {
78
+ return t.integer({ minimum: 1 });
79
+ }
80
+
81
+ if (!col.notNull && isIntegerColumn(col)) {
82
+ return t.nullable(t.integer());
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ function extractEntityName(tableName: string): string {
89
+ const pascalCase = tableName
90
+ .split("_")
91
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
92
+ .join("");
93
+
94
+ return singularize(pascalCase);
95
+ }
96
+
97
+ function singularize(word: string): string {
98
+ if (word.endsWith("ies")) {
99
+ return word.slice(0, -3) + "y";
100
+ }
101
+ if (word.endsWith("s") && !word.endsWith("ss")) {
102
+ return word.slice(0, -1);
103
+ }
104
+ return word;
105
+ }
106
+
107
+ function isTextColumn(col: ColumnDef): boolean {
108
+ const type = col.type.toUpperCase();
109
+ return type === "TEXT" || type === "VARCHAR" || type === "CHAR";
110
+ }
111
+
112
+ function isIntegerColumn(col: ColumnDef): boolean {
113
+ const type = col.type.toUpperCase();
114
+ return type === "INT" || type === "INTEGER" || type === "BIGINT";
115
+ }