@warlock.js/core 5.20.0 → 5.22.0

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 (295) hide show
  1. package/CHANGELOG.md +1130 -1086
  2. package/esm/benchmark/benchmark.mjs +34 -25
  3. package/esm/benchmark/benchmark.mjs.map +1 -1
  4. package/esm/benchmark/profiler.d.mts.map +1 -1
  5. package/esm/benchmark/profiler.mjs +8 -3
  6. package/esm/benchmark/profiler.mjs.map +1 -1
  7. package/esm/bootstrap.d.mts.map +1 -1
  8. package/esm/bootstrap.mjs +3 -0
  9. package/esm/bootstrap.mjs.map +1 -1
  10. package/esm/cache/database-cache-driver.d.mts +2 -2
  11. package/esm/cache/database-cache-driver.d.mts.map +1 -1
  12. package/esm/cache/database-cache-driver.mjs +3 -2
  13. package/esm/cache/database-cache-driver.mjs.map +1 -1
  14. package/esm/cli/cli-commands.manager.mjs +16 -14
  15. package/esm/cli/cli-commands.manager.mjs.map +1 -1
  16. package/esm/cli/commands/add.command.mjs +1 -1
  17. package/esm/cli/commands/add.command.mjs.map +1 -1
  18. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs +3 -2
  19. package/esm/cli/commands/doctor/checks/optional-peers.check.mjs.map +1 -1
  20. package/esm/cli/commands/generate/generate.command.mjs +20 -0
  21. package/esm/cli/commands/generate/generate.command.mjs.map +1 -1
  22. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs +4 -2
  23. package/esm/cli/commands/generate/generators/column-dsl-parser.mjs.map +1 -1
  24. package/esm/cli/commands/generate/generators/controller.generator.mjs +1 -1
  25. package/esm/cli/commands/generate/generators/controller.generator.mjs.map +1 -1
  26. package/esm/cli/commands/generate/generators/migration.generator.mjs +22 -6
  27. package/esm/cli/commands/generate/generators/migration.generator.mjs.map +1 -1
  28. package/esm/cli/commands/generate/generators/model.generator.mjs +22 -7
  29. package/esm/cli/commands/generate/generators/model.generator.mjs.map +1 -1
  30. package/esm/cli/commands/generate/generators/module.generator.mjs +8 -6
  31. package/esm/cli/commands/generate/generators/module.generator.mjs.map +1 -1
  32. package/esm/cli/commands/generate/generators/repository.generator.mjs +1 -1
  33. package/esm/cli/commands/generate/generators/repository.generator.mjs.map +1 -1
  34. package/esm/cli/commands/generate/generators/resource.generator.mjs +1 -1
  35. package/esm/cli/commands/generate/generators/resource.generator.mjs.map +1 -1
  36. package/esm/cli/commands/generate/generators/service.generator.mjs +1 -1
  37. package/esm/cli/commands/generate/generators/service.generator.mjs.map +1 -1
  38. package/esm/cli/commands/generate/templates/stubs.mjs +7 -7
  39. package/esm/cli/commands/generate/templates/stubs.mjs.map +1 -1
  40. package/esm/cli/commands/generate/utils/writer.mjs +25 -1
  41. package/esm/cli/commands/generate/utils/writer.mjs.map +1 -1
  42. package/esm/cli/commands/routes/routes-command.action.mjs +5 -1
  43. package/esm/cli/commands/routes/routes-command.action.mjs.map +1 -1
  44. package/esm/cli/commands/routes/with-console-on-stderr.mjs +24 -0
  45. package/esm/cli/commands/routes/with-console-on-stderr.mjs.map +1 -0
  46. package/esm/cli/commands/routes.command.mjs +3 -4
  47. package/esm/cli/commands/routes.command.mjs.map +1 -1
  48. package/esm/cli/commands/start-production.command.mjs +5 -5
  49. package/esm/cli/commands/start-production.command.mjs.map +1 -1
  50. package/esm/cli/commands/storage-put.action.mjs +2 -1
  51. package/esm/cli/commands/storage-put.action.mjs.map +1 -1
  52. package/esm/cli/commands/update.command.mjs +1 -0
  53. package/esm/cli/commands/update.command.mjs.map +1 -1
  54. package/esm/cli/parse-cli-args.mjs +13 -1
  55. package/esm/cli/parse-cli-args.mjs.map +1 -1
  56. package/esm/commands/cli-command.d.mts.map +1 -1
  57. package/esm/commands/cli-command.mjs +4 -2
  58. package/esm/commands/cli-command.mjs.map +1 -1
  59. package/esm/commands/index.mjs +3 -0
  60. package/esm/config/config-key-from-path.mjs +20 -0
  61. package/esm/config/config-key-from-path.mjs.map +1 -0
  62. package/esm/config/config-loader.d.mts.map +1 -1
  63. package/esm/config/config-loader.mjs +2 -3
  64. package/esm/config/config-loader.mjs.map +1 -1
  65. package/esm/connectors/connectors-manager.d.mts +4 -0
  66. package/esm/connectors/connectors-manager.d.mts.map +1 -1
  67. package/esm/connectors/connectors-manager.mjs +30 -2
  68. package/esm/connectors/connectors-manager.mjs.map +1 -1
  69. package/esm/connectors/socket-connector.d.mts +29 -0
  70. package/esm/connectors/socket-connector.d.mts.map +1 -1
  71. package/esm/connectors/socket-connector.mjs +73 -9
  72. package/esm/connectors/socket-connector.mjs.map +1 -1
  73. package/esm/connectors/types.d.mts +1 -1
  74. package/esm/connectors/types.mjs.map +1 -1
  75. package/esm/database/models/database-log/database-log.d.mts +47 -24
  76. package/esm/database/models/database-log/database-log.d.mts.map +1 -1
  77. package/esm/database/models/database-log/database-log.mjs +36 -4
  78. package/esm/database/models/database-log/database-log.mjs.map +1 -1
  79. package/esm/database/seed-command-action.mjs +10 -2
  80. package/esm/database/seed-command-action.mjs.map +1 -1
  81. package/esm/dev-server/development-server.mjs +11 -4
  82. package/esm/dev-server/development-server.mjs.map +1 -1
  83. package/esm/dev-server/file-event-handler.mjs +26 -6
  84. package/esm/dev-server/file-event-handler.mjs.map +1 -1
  85. package/esm/dev-server/file-manager.d.mts.map +1 -1
  86. package/esm/dev-server/file-manager.mjs +2 -2
  87. package/esm/dev-server/file-manager.mjs.map +1 -1
  88. package/esm/dev-server/files-orchestrator.mjs +3 -1
  89. package/esm/dev-server/files-orchestrator.mjs.map +1 -1
  90. package/esm/dev-server/files-watcher.mjs +64 -5
  91. package/esm/dev-server/files-watcher.mjs.map +1 -1
  92. package/esm/dev-server/health-checker/files-healthcare.manager.mjs +31 -9
  93. package/esm/dev-server/health-checker/files-healthcare.manager.mjs.map +1 -1
  94. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs +11 -7
  95. package/esm/dev-server/health-checker/workers/eslint-health.worker.mjs.map +1 -1
  96. package/esm/dev-server/loader/load-hook.mjs +1 -1
  97. package/esm/dev-server/loader/register-loader.mjs +1 -1
  98. package/esm/dev-server/manifest-manager.d.mts.map +1 -1
  99. package/esm/dev-server/manifest-manager.mjs +12 -6
  100. package/esm/dev-server/manifest-manager.mjs.map +1 -1
  101. package/esm/dev-server/parse-imports.mjs +9 -0
  102. package/esm/dev-server/parse-imports.mjs.map +1 -1
  103. package/esm/dev-server/register-dev-shortcuts.mjs +2 -1
  104. package/esm/dev-server/register-dev-shortcuts.mjs.map +1 -1
  105. package/esm/dev-server/shortcuts.mjs +2 -2
  106. package/esm/dev-server/shortcuts.mjs.map +1 -1
  107. package/esm/dev-server/special-file-patterns.mjs +18 -0
  108. package/esm/dev-server/special-file-patterns.mjs.map +1 -0
  109. package/esm/dev-server/special-files-collector.d.mts.map +1 -1
  110. package/esm/dev-server/special-files-collector.mjs +2 -5
  111. package/esm/dev-server/special-files-collector.mjs.map +1 -1
  112. package/esm/dev-server/start-development-server.mjs +1 -2
  113. package/esm/dev-server/start-development-server.mjs.map +1 -1
  114. package/esm/dev-server/supervisor.mjs +23 -3
  115. package/esm/dev-server/supervisor.mjs.map +1 -1
  116. package/esm/dev-server/tsconfig-manager.mjs +22 -12
  117. package/esm/dev-server/tsconfig-manager.mjs.map +1 -1
  118. package/esm/dev-server/type-generator.mjs +10 -9
  119. package/esm/dev-server/type-generator.mjs.map +1 -1
  120. package/esm/encryption/encrypt.d.mts.map +1 -1
  121. package/esm/encryption/encrypt.mjs +16 -5
  122. package/esm/encryption/encrypt.mjs.map +1 -1
  123. package/esm/encryption/hash.d.mts.map +1 -1
  124. package/esm/encryption/hash.mjs +2 -0
  125. package/esm/encryption/hash.mjs.map +1 -1
  126. package/esm/encryption/password.d.mts.map +1 -1
  127. package/esm/encryption/password.mjs +3 -1
  128. package/esm/encryption/password.mjs.map +1 -1
  129. package/esm/generations/features/index.mjs +1 -1
  130. package/esm/generations/features/mysql.feature.mjs +9 -2
  131. package/esm/generations/features/mysql.feature.mjs.map +1 -1
  132. package/esm/generations/features/test.feature.mjs +2 -1
  133. package/esm/generations/features/test.feature.mjs.map +1 -1
  134. package/esm/generations/features/web.feature.mjs +4 -2
  135. package/esm/generations/features/web.feature.mjs.map +1 -1
  136. package/esm/http/csrf-origin-policy.d.mts.map +1 -1
  137. package/esm/http/csrf-origin-policy.mjs +5 -2
  138. package/esm/http/csrf-origin-policy.mjs.map +1 -1
  139. package/esm/http/flush-pending-cookies.mjs +52 -0
  140. package/esm/http/flush-pending-cookies.mjs.map +1 -0
  141. package/esm/http/middleware/cache-response-middleware.d.mts.map +1 -1
  142. package/esm/http/middleware/cache-response-middleware.mjs +4 -2
  143. package/esm/http/middleware/cache-response-middleware.mjs.map +1 -1
  144. package/esm/http/middleware/inject-request-context.d.mts.map +1 -1
  145. package/esm/http/middleware/inject-request-context.mjs +14 -3
  146. package/esm/http/middleware/inject-request-context.mjs.map +1 -1
  147. package/esm/http/middleware/utils/cidr-match.d.mts +1 -1
  148. package/esm/http/middleware/utils/cidr-match.d.mts.map +1 -1
  149. package/esm/http/middleware/utils/cidr-match.mjs +11 -2
  150. package/esm/http/middleware/utils/cidr-match.mjs.map +1 -1
  151. package/esm/http/middleware/utils/idempotency-key.mjs +1 -1
  152. package/esm/http/middleware/utils/idempotency-key.mjs.map +1 -1
  153. package/esm/http/plugins.d.mts.map +1 -1
  154. package/esm/http/plugins.mjs +10 -1
  155. package/esm/http/plugins.mjs.map +1 -1
  156. package/esm/http/request.d.mts +12 -1
  157. package/esm/http/request.d.mts.map +1 -1
  158. package/esm/http/request.mjs +35 -7
  159. package/esm/http/request.mjs.map +1 -1
  160. package/esm/http/response.d.mts +16 -0
  161. package/esm/http/response.d.mts.map +1 -1
  162. package/esm/http/response.mjs +75 -37
  163. package/esm/http/response.mjs.map +1 -1
  164. package/esm/http/types.d.mts +24 -0
  165. package/esm/http/types.d.mts.map +1 -1
  166. package/esm/http/uploads/uploaded-file.controller.mjs +1 -1
  167. package/esm/http/uploads-config.mjs +2 -1
  168. package/esm/http/uploads-config.mjs.map +1 -1
  169. package/esm/http/uploads-types.d.mts +3 -3
  170. package/esm/image/image.d.mts +1 -1
  171. package/esm/image/image.d.mts.map +1 -1
  172. package/esm/image/image.mjs +25 -15
  173. package/esm/image/image.mjs.map +1 -1
  174. package/esm/index.d.mts +4 -3
  175. package/esm/index.mjs +5 -3
  176. package/esm/logger/logger.mjs +6 -1
  177. package/esm/logger/logger.mjs.map +1 -1
  178. package/esm/mail/config.d.mts.map +1 -1
  179. package/esm/mail/config.mjs +31 -21
  180. package/esm/mail/config.mjs.map +1 -1
  181. package/esm/mail/react-mail.mjs +6 -3
  182. package/esm/mail/react-mail.mjs.map +1 -1
  183. package/esm/mail/send-mail.d.mts.map +1 -1
  184. package/esm/mail/send-mail.mjs +40 -19
  185. package/esm/mail/send-mail.mjs.map +1 -1
  186. package/esm/production/build-contributions.mjs +3 -0
  187. package/esm/production/build-contributions.mjs.map +1 -1
  188. package/esm/production/esbuild-plugins.mjs +3 -2
  189. package/esm/production/esbuild-plugins.mjs.map +1 -1
  190. package/esm/production/production-builder.mjs +32 -36
  191. package/esm/production/production-builder.mjs.map +1 -1
  192. package/esm/production/production-supervisor.mjs +12 -5
  193. package/esm/production/production-supervisor.mjs.map +1 -1
  194. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts +3 -1
  195. package/esm/repositories/adapters/cascade/cascade-adapter.d.mts.map +1 -1
  196. package/esm/repositories/adapters/cascade/cascade-adapter.mjs +17 -5
  197. package/esm/repositories/adapters/cascade/cascade-adapter.mjs.map +1 -1
  198. package/esm/repositories/adapters/cascade/cascade-query-builder.d.mts +3 -3
  199. package/esm/repositories/adapters/cascade/cascade-query-builder.mjs.map +1 -1
  200. package/esm/repositories/adapters/cascade/filter-applicator.mjs +88 -61
  201. package/esm/repositories/adapters/cascade/filter-applicator.mjs.map +1 -1
  202. package/esm/repositories/adapters/cascade/index.d.mts +1 -1
  203. package/esm/repositories/adapters/cascade/index.mjs +1 -1
  204. package/esm/repositories/contracts/query-builder.contract.d.mts +3 -3
  205. package/esm/repositories/contracts/types.d.mts +5 -0
  206. package/esm/repositories/contracts/types.d.mts.map +1 -1
  207. package/esm/repositories/index.d.mts +1 -1
  208. package/esm/repositories/index.mjs +1 -1
  209. package/esm/repositories/repository.manager.d.mts +65 -3
  210. package/esm/repositories/repository.manager.d.mts.map +1 -1
  211. package/esm/repositories/repository.manager.mjs +146 -57
  212. package/esm/repositories/repository.manager.mjs.map +1 -1
  213. package/esm/resource/resource-field-builder.d.mts.map +1 -1
  214. package/esm/resource/resource-field-builder.mjs +1 -0
  215. package/esm/resource/resource-field-builder.mjs.map +1 -1
  216. package/esm/resource/resource.d.mts +10 -3
  217. package/esm/resource/resource.d.mts.map +1 -1
  218. package/esm/resource/resource.mjs +19 -5
  219. package/esm/resource/resource.mjs.map +1 -1
  220. package/esm/restful/restful.d.mts +12 -5
  221. package/esm/restful/restful.d.mts.map +1 -1
  222. package/esm/restful/restful.mjs +47 -16
  223. package/esm/restful/restful.mjs.map +1 -1
  224. package/esm/router/build-route-rate-limit.mjs +22 -0
  225. package/esm/router/build-route-rate-limit.mjs.map +1 -0
  226. package/esm/router/dev-dispatch.mjs +60 -0
  227. package/esm/router/dev-dispatch.mjs.map +1 -0
  228. package/esm/router/route-builder.mjs +4 -1
  229. package/esm/router/route-builder.mjs.map +1 -1
  230. package/esm/router/route-registry.d.mts.map +1 -1
  231. package/esm/router/route-registry.mjs +2 -2
  232. package/esm/router/route-registry.mjs.map +1 -1
  233. package/esm/router/router.d.mts +1 -1
  234. package/esm/router/router.d.mts.map +1 -1
  235. package/esm/router/router.mjs +57 -39
  236. package/esm/router/router.mjs.map +1 -1
  237. package/esm/socket/types.d.mts +9 -2
  238. package/esm/socket/types.d.mts.map +1 -1
  239. package/esm/storage/drivers/cloud-driver.d.mts +17 -0
  240. package/esm/storage/drivers/cloud-driver.d.mts.map +1 -1
  241. package/esm/storage/drivers/cloud-driver.mjs +53 -17
  242. package/esm/storage/drivers/cloud-driver.mjs.map +1 -1
  243. package/esm/storage/drivers/local-driver.d.mts +7 -5
  244. package/esm/storage/drivers/local-driver.d.mts.map +1 -1
  245. package/esm/storage/drivers/local-driver.mjs +36 -31
  246. package/esm/storage/drivers/local-driver.mjs.map +1 -1
  247. package/esm/storage/index.d.mts +2 -1
  248. package/esm/storage/index.mjs +2 -1
  249. package/esm/storage/scoped-storage.d.mts +13 -1
  250. package/esm/storage/scoped-storage.d.mts.map +1 -1
  251. package/esm/storage/scoped-storage.mjs +18 -8
  252. package/esm/storage/scoped-storage.mjs.map +1 -1
  253. package/esm/storage/storage.d.mts +0 -5
  254. package/esm/storage/storage.d.mts.map +1 -1
  255. package/esm/storage/storage.mjs +7 -16
  256. package/esm/storage/storage.mjs.map +1 -1
  257. package/esm/storage/types.d.mts +22 -4
  258. package/esm/storage/types.d.mts.map +1 -1
  259. package/esm/storage/utils/classify-ip-address.d.mts +13 -0
  260. package/esm/storage/utils/classify-ip-address.d.mts.map +1 -0
  261. package/esm/storage/utils/classify-ip-address.mjs +98 -0
  262. package/esm/storage/utils/classify-ip-address.mjs.map +1 -0
  263. package/esm/storage/utils/safe-fetch.d.mts +3 -12
  264. package/esm/storage/utils/safe-fetch.d.mts.map +1 -1
  265. package/esm/storage/utils/safe-fetch.mjs +3 -48
  266. package/esm/storage/utils/safe-fetch.mjs.map +1 -1
  267. package/esm/updater/update-warlock-packages.mjs +11 -1
  268. package/esm/updater/update-warlock-packages.mjs.map +1 -1
  269. package/esm/use-cases/use-case-broadcast.mjs +7 -1
  270. package/esm/use-cases/use-case-broadcast.mjs.map +1 -1
  271. package/esm/use-cases/use-cases-registry.mjs +21 -2
  272. package/esm/use-cases/use-cases-registry.mjs.map +1 -1
  273. package/esm/utils/database-log.mjs +0 -3
  274. package/esm/utils/database-log.mjs.map +1 -1
  275. package/esm/utils/queue.d.mts +6 -4
  276. package/esm/utils/queue.d.mts.map +1 -1
  277. package/esm/utils/queue.mjs +20 -9
  278. package/esm/utils/queue.mjs.map +1 -1
  279. package/esm/validation/database/exists-except-current-id.mjs +6 -2
  280. package/esm/validation/database/exists-except-current-id.mjs.map +1 -1
  281. package/esm/validation/database/unique-except-current-id.mjs +6 -2
  282. package/esm/validation/database/unique-except-current-id.mjs.map +1 -1
  283. package/esm/validation/init.mjs +6 -2
  284. package/esm/validation/init.mjs.map +1 -1
  285. package/esm/validation/validateAll.mjs +3 -2
  286. package/esm/validation/validateAll.mjs.map +1 -1
  287. package/esm/warlock-config/types.d.mts +14 -1
  288. package/esm/warlock-config/types.d.mts.map +1 -1
  289. package/esm/warlock-config/warlock-config.manager.d.mts.map +1 -1
  290. package/esm/warlock-config/warlock-config.manager.mjs +3 -1
  291. package/esm/warlock-config/warlock-config.manager.mjs.map +1 -1
  292. package/esm/warlock-config/warn-deprecated-config.mjs +25 -0
  293. package/esm/warlock-config/warn-deprecated-config.mjs.map +1 -0
  294. package/package.json +16 -16
  295. package/esm/_virtual/_rolldown/runtime.mjs +0 -7
@@ -1 +1 @@
1
- {"version":3,"file":"resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"sourcesContent":["import { get, isLazy, set, type GenericObject } from \"@mongez/reinforcements\";\r\nimport { Model } from \"@warlock.js/cascade\";\r\nimport { useRequestStore } from \"../http/context/request-context\";\r\nimport { ResourceFieldBuilder } from \"./resource-field-builder\";\r\nimport {\r\n type ResourceArraySchema,\r\n type ResourceFieldConfig,\r\n type ResourceOutputValueCastType,\r\n type ResourceSchema,\r\n} from \"./types\";\r\n\r\n/**\r\n * Maximum recursion depth for self-referencing fields.\r\n * Prevents runaway serialization on deep trees or circular data.\r\n */\r\nconst MAX_SELF_DEPTH = 10;\r\n\r\n/**\r\n * Resource contract\r\n */\r\nexport interface ResourceContract {\r\n /**\r\n * Resource data\r\n */\r\n resource: GenericObject;\r\n\r\n /**\r\n * Resource final output\r\n */\r\n data: GenericObject;\r\n\r\n /**\r\n * Original data\r\n */\r\n originalData: GenericObject;\r\n\r\n /**\r\n * Convert resource to JSON\r\n */\r\n toJSON(): GenericObject;\r\n\r\n /**\r\n * Transform the given value with given type\r\n */\r\n transform(value: any, type: ResourceOutputValueCastType, locale?: string): any;\r\n\r\n /**\r\n * Get a input value for the given key\r\n */\r\n get(key: string, defaultValue?: any): any;\r\n\r\n /**\r\n * Set the given value for the given field\r\n */\r\n set(key: string, value: any): ResourceContract;\r\n\r\n /**\r\n * Create an array schema for transforming array items\r\n */\r\n arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema;\r\n\r\n /**\r\n * Get a string field builder\r\n */\r\n string(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a date field builder\r\n */\r\n date(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a localized field builder\r\n */\r\n localized(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a url field builder\r\n */\r\n url(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a uploadsUrl field builder\r\n */\r\n uploadsUrl(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a number field builder\r\n */\r\n number(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a boolean field builder\r\n */\r\n boolean(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a float field builder\r\n */\r\n float(inputKey?: string): ResourceFieldBuilder;\r\n\r\n /**\r\n * Get a int field builder\r\n */\r\n int(inputKey?: string): ResourceFieldBuilder;\r\n}\r\n\r\n/**\r\n * Resource constructor\r\n */\r\nexport interface ResourceConstructor {\r\n new (originalData: GenericObject | Resource | Model): ResourceContract;\r\n}\r\n\r\nexport class Resource implements ResourceContract {\r\n /**\r\n * Resource data\r\n */\r\n public resource: GenericObject = {};\r\n\r\n /**\r\n * Resource final output\r\n */\r\n public data: GenericObject = {};\r\n\r\n /**\r\n * Tracks visited object identities during self-reference recursion.\r\n * Prevents infinite loops on circular data (e.g. A.parent → B, B.parent → A).\r\n */\r\n protected _selfSeen?: Set<unknown>;\r\n\r\n /**\r\n * Raw resource schema — field declarations as written by the developer.\r\n * Used by doc generators for introspection.\r\n */\r\n public static schema: ResourceSchema = {};\r\n\r\n /**\r\n * Normalized schema — all string cast types and tuples converted to\r\n * ResourceFieldBuilder instances at definition time. Used by transformOutput at runtime.\r\n */\r\n public static parsedSchema: Record<string, ResourceFieldConfig> = {};\r\n\r\n /**\r\n * Normalize a raw schema into parsedSchema.\r\n * Converts string cast types (including suffixes) and tuples into pre-built builders.\r\n * Other entry types (ResourceConstructor, resolver functions, ResourceArraySchema) are kept as-is.\r\n */\r\n public static normalizeSchema(schema: ResourceSchema): Record<string, ResourceFieldConfig> {\r\n const parsed: Record<string, ResourceFieldConfig> = {};\r\n\r\n for (const [key, value] of Object.entries(schema)) {\r\n if (value === \"self\" || value === \"self[]\") {\r\n // Keep self-references as-is — resolved lazily in transformValue\r\n parsed[key] = value;\r\n } else if (isLazy<ResourceConstructor>(value)) {\r\n // Keep Lazy<ResourceConstructor> as-is — resolved at transform\r\n // time so cyclic resource refs (A embeds B embeds A) compose\r\n // without import-graph cycles.\r\n parsed[key] = value;\r\n } else if (typeof value === \"string\") {\r\n parsed[key] = ResourceFieldBuilder.fromCastType(value);\r\n } else if (Array.isArray(value) && value.length === 2 && typeof value[0] === \"string\") {\r\n const builder = ResourceFieldBuilder.fromCastType(value[1] as string);\r\n builder.setInputKey(value[0]);\r\n parsed[key] = builder;\r\n } else {\r\n parsed[key] = value;\r\n }\r\n }\r\n\r\n return parsed;\r\n }\r\n\r\n /**\r\n * Constructor\r\n */\r\n public constructor(public originalData: GenericObject | Resource | Model) {\r\n if (this.originalData instanceof Model) {\r\n this.resource = this.originalData.data;\r\n } else if (this.originalData instanceof Resource) {\r\n this.resource = this.originalData.data;\r\n } else {\r\n this.resource = this.originalData;\r\n }\r\n }\r\n\r\n /**\r\n * Convert resource to JSON\r\n */\r\n public toJSON() {\r\n this.boot();\r\n this.transformOutput();\r\n this.extend();\r\n\r\n return this.data;\r\n }\r\n\r\n /**\r\n * Boot method\r\n * Called before transforming the resource\r\n */\r\n protected boot() {\r\n //\r\n }\r\n\r\n /**\r\n * Transform resource to output using the pre-normalized parsedSchema.\r\n * Builders handle their own array/nullable logic internally.\r\n * ResourceConstructor and ResourceArraySchema handle arrays in transformValue.\r\n */\r\n protected transformOutput() {\r\n const localeCode = useRequestStore()?.request?.locale;\r\n const parsedSchema = (this.constructor as typeof Resource).parsedSchema;\r\n\r\n for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {\r\n const inputValue = this.get(outputKey);\r\n const outputValue = this.transformValue(inputValue, outputSettings, localeCode);\r\n\r\n if (outputValue !== undefined) {\r\n this.set(outputKey, outputValue);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Transform the given value with given type\r\n */\r\n public transform(value: any, type: ResourceOutputValueCastType, locale?: string) {\r\n return new ResourceFieldBuilder(type).transform(value, locale);\r\n }\r\n\r\n /**\r\n * Transform the given value for the given output setting.\r\n * After normalization, string cast types no longer reach here — they are pre-converted to builders.\r\n */\r\n protected transformValue(value: any, outputSettings: ResourceFieldConfig, locale?: string): any {\r\n let outputValue: any;\r\n\r\n if (isLazy<ResourceConstructor>(outputSettings)) {\r\n // Resolve the lazy ref now (cyclic resource imports are fully loaded\r\n // by serialization time) and recurse — the resolved constructor is\r\n // handled by the ResourceConstructor branch below.\r\n return this.transformValue(value, outputSettings.resolve(), locale);\r\n }\r\n\r\n if (outputSettings === \"self\" || outputSettings === \"self[]\") {\r\n // Self-reference — resolve using the same resource class with cycle detection\r\n outputValue = this.transformSelfReference(value, outputSettings === \"self[]\");\r\n } else if (\r\n typeof outputSettings === \"function\" &&\r\n outputSettings.prototype instanceof Resource\r\n ) {\r\n if (!value) return;\r\n // Nested resource — handle both single and array values\r\n if (Array.isArray(value)) {\r\n if (value.length === 0) return;\r\n outputValue = value\r\n .map((item) => new (outputSettings as typeof Resource)(item).toJSON())\r\n .filter((v) => v !== undefined);\r\n } else {\r\n outputValue = new (outputSettings as typeof Resource)(value).toJSON();\r\n }\r\n } else if (typeof outputSettings === \"function\") {\r\n // Resolver function - bind to Resource instance for access to this.get(), etc.\r\n outputValue = (outputSettings as Function).call(this, value, this);\r\n } else if (outputSettings instanceof ResourceFieldBuilder) {\r\n // Builder — handles array mapping and nullable internally via isArrayField\r\n const inputKey = outputSettings.getInputKey();\r\n outputValue = outputSettings.transform(inputKey ? this.get(inputKey) : value, locale);\r\n } else if (\r\n typeof outputSettings === \"object\" &&\r\n outputSettings !== null &&\r\n \"__type\" in outputSettings &&\r\n outputSettings.__type === \"arrayOf\"\r\n ) {\r\n // ResourceArraySchema — structured array items with their own sub-schema\r\n if (Array.isArray(value)) {\r\n outputValue = value\r\n .map((item) => this.transformArrayItem(item, outputSettings.schema, locale))\r\n .filter((v) => v !== undefined);\r\n } else {\r\n outputValue = this.transformArrayItem(value, outputSettings.schema, locale);\r\n }\r\n }\r\n\r\n return outputValue;\r\n }\r\n\r\n /**\r\n * Extend the resource output\r\n */\r\n protected extend() {\r\n //\r\n }\r\n\r\n /**\r\n * Transform a self-referencing field value.\r\n *\r\n * @example\r\n * // Single: parent: \"self\"\r\n * // Array: children: \"self[]\"\r\n */\r\n protected transformSelfReference(value: any, isArray: boolean): any {\r\n if (isArray) {\r\n return Array.isArray(value)\r\n ? value.map((item) => this.resolveSelf(item)).filter((v) => v !== undefined)\r\n : undefined;\r\n }\r\n\r\n return this.resolveSelf(value);\r\n }\r\n\r\n /**\r\n * Resolve a single self-reference value.\r\n * Uses identity-based cycle detection (id/_id) and a max depth guard.\r\n */\r\n protected resolveSelf(value: any): any {\r\n if (!value) return undefined;\r\n\r\n const identity = value.id ?? value._id ?? value;\r\n const seen = this._selfSeen ?? new Set();\r\n\r\n // Circular reference or depth limit reached — stop recursion\r\n if (seen.has(identity) || seen.size >= MAX_SELF_DEPTH) {\r\n return undefined;\r\n }\r\n\r\n seen.add(identity);\r\n\r\n const SelfConstructor = this.constructor as typeof Resource;\r\n const child = new SelfConstructor(value);\r\n child._selfSeen = seen;\r\n\r\n return child.toJSON();\r\n }\r\n\r\n /**\r\n * Transform a single array item according to the given schema\r\n */\r\n protected transformArrayItem(\r\n item: any,\r\n schema: Record<string, ResourceFieldConfig>,\r\n locale?: string,\r\n ) {\r\n const transformedItem: GenericObject = {};\r\n\r\n for (const [outputKey, outputSettings] of Object.entries(schema)) {\r\n let fieldKey = outputKey;\r\n let valueTransformType = outputSettings as ResourceFieldConfig;\r\n\r\n if (Array.isArray(outputSettings)) {\r\n fieldKey = outputSettings[0];\r\n valueTransformType = outputSettings[1];\r\n }\r\n\r\n const inputValue = get(item, fieldKey);\r\n const outputValue = this.transformValue(inputValue, valueTransformType, locale);\r\n\r\n if (outputValue !== undefined) {\r\n set(transformedItem, outputKey, outputValue);\r\n }\r\n }\r\n\r\n return transformedItem;\r\n }\r\n\r\n /**\r\n * Get a input value for the given key\r\n */\r\n public get(key: string, defaultValue?: any) {\r\n return get(this.resource, key, defaultValue);\r\n }\r\n\r\n /**\r\n * Set the given value for the given field\r\n */\r\n public set(key: string, value: any): ResourceContract {\r\n set(this.data, key, value);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Create an array schema for transforming array items\r\n */\r\n public arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema {\r\n return {\r\n __type: \"arrayOf\",\r\n schema,\r\n };\r\n }\r\n\r\n /**\r\n * Get a string field builder\r\n */\r\n public string(inputKey?: string) {\r\n return this.fieldBuilder(\"string\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a date field builder\r\n */\r\n public date(inputKey?: string) {\r\n return this.fieldBuilder(\"date\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a localized field builder\r\n */\r\n public localized(inputKey?: string) {\r\n return this.fieldBuilder(\"localized\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a url field builder\r\n */\r\n public url(inputKey?: string) {\r\n return this.fieldBuilder(\"url\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a uploadsUrl field builder\r\n */\r\n public uploadsUrl(inputKey?: string) {\r\n return this.fieldBuilder(\"uploadsUrl\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a number field builder\r\n */\r\n public number(inputKey?: string) {\r\n return this.fieldBuilder(\"number\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a boolean field builder\r\n */\r\n public boolean(inputKey?: string) {\r\n return this.fieldBuilder(\"boolean\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a float field builder\r\n */\r\n public float(inputKey?: string) {\r\n return this.fieldBuilder(\"float\", inputKey);\r\n }\r\n\r\n /**\r\n * Get a int field builder\r\n */\r\n public int(inputKey?: string) {\r\n return this.fieldBuilder(\"int\", inputKey);\r\n }\r\n\r\n /**\r\n * New field builder\r\n */\r\n protected fieldBuilder(type: ResourceOutputValueCastType, inputKey?: string) {\r\n const builder = new ResourceFieldBuilder(type);\r\n\r\n if (inputKey) {\r\n builder.setInputKey(inputKey);\r\n }\r\n\r\n return builder;\r\n }\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"resource.mjs","names":[],"sources":["../../../../../../../core/src/resource/resource.ts"],"sourcesContent":["import { get, isLazy, set, type GenericObject } from \"@mongez/reinforcements\";\nimport { Model } from \"@warlock.js/cascade\";\nimport { useRequestStore } from \"../http/context/request-context\";\nimport { ResourceFieldBuilder } from \"./resource-field-builder\";\nimport {\n type ResourceArraySchema,\n type ResourceFieldConfig,\n type ResourceOutputValueCastType,\n type ResourceSchema,\n} from \"./types\";\n\n/**\n * Maximum recursion depth for self-referencing fields.\n * Prevents runaway serialization on deep trees or circular data.\n */\nconst MAX_SELF_DEPTH = 10;\n\n/**\n * Resource contract\n */\nexport interface ResourceContract {\n /**\n * Resource data\n */\n resource: GenericObject;\n\n /**\n * Resource final output\n */\n data: GenericObject;\n\n /**\n * Original data\n */\n originalData: GenericObject;\n\n /**\n * Convert resource to JSON\n */\n toJSON(): GenericObject;\n\n /**\n * Transform the given value with given type\n */\n transform(value: any, type: ResourceOutputValueCastType, locale?: string): any;\n\n /**\n * Get a input value for the given key\n */\n get(key: string, defaultValue?: any): any;\n\n /**\n * Set the given value for the given field\n */\n set(key: string, value: any): ResourceContract;\n\n /**\n * Create an array schema for transforming array items\n */\n arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema;\n\n /**\n * Get a string field builder\n */\n string(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a date field builder\n */\n date(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a localized field builder\n */\n localized(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a url field builder\n */\n url(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a uploadsUrl field builder\n */\n uploadsUrl(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a number field builder\n */\n number(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a boolean field builder\n */\n boolean(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a float field builder\n */\n float(inputKey?: string): ResourceFieldBuilder;\n\n /**\n * Get a int field builder\n */\n int(inputKey?: string): ResourceFieldBuilder;\n}\n\n/**\n * Resource constructor\n */\nexport interface ResourceConstructor {\n new (originalData: GenericObject | Resource | Model): ResourceContract;\n}\n\nexport class Resource implements ResourceContract {\n /**\n * Resource data\n */\n public resource: GenericObject = {};\n\n /**\n * Resource final output\n */\n public data: GenericObject = {};\n\n /**\n * Tracks visited object identities during self-reference recursion.\n * Prevents infinite loops on circular data (e.g. A.parent → B, B.parent → A).\n */\n protected _selfSeen?: Set<unknown>;\n\n /**\n * Raw resource schema — field declarations as written by the developer.\n * Used by doc generators for introspection.\n */\n public static schema: Record<string, ResourceFieldConfig | string> = {};\n\n /**\n * Normalized schema — all string cast types and tuples converted to\n * ResourceFieldBuilder instances at definition time. Used by transformOutput at runtime.\n */\n public static parsedSchema: Record<string, ResourceFieldConfig> = {};\n\n /**\n * Normalize a raw schema into parsedSchema.\n * Converts string cast types (including suffixes) and tuples into pre-built builders.\n * Other entry types (ResourceConstructor, resolver functions, ResourceArraySchema) are kept as-is.\n */\n public static normalizeSchema(schema: Record<string, ResourceFieldConfig | string>): Record<string, ResourceFieldConfig> {\n const parsed: Record<string, ResourceFieldConfig> = {};\n\n for (const [key, value] of Object.entries(schema)) {\n if (value === \"self\" || value === \"self[]\") {\n // Keep self-references as-is — resolved lazily in transformValue\n parsed[key] = value;\n } else if (isLazy<ResourceConstructor>(value)) {\n // Keep Lazy<ResourceConstructor> as-is — resolved at transform\n // time so cyclic resource refs (A embeds B embeds A) compose\n // without import-graph cycles.\n parsed[key] = value;\n } else if (typeof value === \"string\") {\n parsed[key] = ResourceFieldBuilder.fromCastType(value);\n } else if (Array.isArray(value) && value.length === 2 && typeof value[0] === \"string\") {\n const builder = ResourceFieldBuilder.fromCastType(value[1] as string);\n builder.setInputKey(value[0]);\n parsed[key] = builder;\n } else {\n parsed[key] = value;\n }\n }\n\n return parsed;\n }\n\n /**\n * Constructor\n */\n public constructor(public originalData: GenericObject | Resource | Model) {\n if (this.originalData instanceof Model) {\n this.resource = this.originalData.data;\n } else if (this.originalData instanceof Resource) {\n this.resource = this.originalData.data;\n } else {\n this.resource = this.originalData;\n }\n }\n\n /**\n * Convert resource to JSON\n */\n public toJSON() {\n this.boot();\n this.transformOutput();\n this.extend();\n\n return this.data;\n }\n\n /**\n * Boot method\n * Called before transforming the resource\n */\n protected boot() {\n //\n }\n\n /**\n * Transform resource to output using the pre-normalized parsedSchema.\n * Builders handle their own array/nullable logic internally.\n * ResourceConstructor and ResourceArraySchema handle arrays in transformValue.\n */\n protected transformOutput() {\n const localeCode = useRequestStore()?.request?.locale;\n const parsedSchema = this.resolveParsedSchema();\n\n for (const [outputKey, outputSettings] of Object.entries(parsedSchema)) {\n const inputValue = this.get(outputKey);\n const outputValue = this.transformValue(inputValue, outputSettings, localeCode);\n\n if (outputValue !== undefined) {\n this.set(outputKey, outputValue);\n }\n }\n }\n\n /**\n * Resolve the normalized schema of THIS class.\n * `@RegisterResource()` is optional: when the class has no own `parsedSchema`\n * (a static field is inherited from the base as `{}`), normalize its own\n * static `schema` (or an instance `schema` field) once and cache it on the class.\n */\n protected resolveParsedSchema(): Record<string, ResourceFieldConfig> {\n const ctor = this.constructor as typeof Resource;\n\n if (Object.hasOwn(ctor, \"parsedSchema\")) {\n return ctor.parsedSchema;\n }\n\n const instanceSchema = (this as unknown as { schema?: Record<string, ResourceFieldConfig | string> }).schema;\n const schema = instanceSchema ?? ctor.schema;\n\n if (!schema || Object.keys(schema).length === 0) {\n throw new Error(\n `Resource \"${ctor.name}\" has no schema. Declare \\`static schema = { ... }\\` on the class.`,\n );\n }\n\n const parsed = Resource.normalizeSchema(schema);\n\n // Instance schemas are per-class by convention, so caching on the class is safe\n ctor.parsedSchema = parsed;\n\n return parsed;\n }\n\n /**\n * Transform the given value with given type\n */\n public transform(value: any, type: ResourceOutputValueCastType, locale?: string) {\n return new ResourceFieldBuilder(type).transform(value, locale);\n }\n\n /**\n * Transform the given value for the given output setting.\n * After normalization, string cast types no longer reach here — they are pre-converted to builders.\n */\n protected transformValue(value: any, outputSettings: ResourceFieldConfig, locale?: string): any {\n let outputValue: any;\n\n if (isLazy<ResourceConstructor>(outputSettings)) {\n // Resolve the lazy ref now (cyclic resource imports are fully loaded\n // by serialization time) and recurse — the resolved constructor is\n // handled by the ResourceConstructor branch below.\n return this.transformValue(value, outputSettings.resolve(), locale);\n }\n\n if (outputSettings === \"self\" || outputSettings === \"self[]\") {\n // Self-reference — resolve using the same resource class with cycle detection\n outputValue = this.transformSelfReference(value, outputSettings === \"self[]\");\n } else if (\n typeof outputSettings === \"function\" &&\n outputSettings.prototype instanceof Resource\n ) {\n if (!value) return;\n // Nested resource — handle both single and array values\n if (Array.isArray(value)) {\n if (value.length === 0) return;\n outputValue = value\n .map((item) => new (outputSettings as typeof Resource)(item).toJSON())\n .filter((v) => v !== undefined);\n } else {\n outputValue = new (outputSettings as typeof Resource)(value).toJSON();\n }\n } else if (typeof outputSettings === \"function\") {\n // Resolver function - bind to Resource instance for access to this.get(), etc.\n outputValue = (outputSettings as Function).call(this, value, this);\n } else if (outputSettings instanceof ResourceFieldBuilder) {\n // Builder — handles array mapping and nullable internally via isArrayField\n const inputKey = outputSettings.getInputKey();\n outputValue = outputSettings.transform(inputKey ? this.get(inputKey) : value, locale);\n } else if (\n typeof outputSettings === \"object\" &&\n outputSettings !== null &&\n \"__type\" in outputSettings &&\n outputSettings.__type === \"arrayOf\"\n ) {\n // ResourceArraySchema — structured array items with their own sub-schema\n if (Array.isArray(value)) {\n outputValue = value\n .map((item) => this.transformArrayItem(item, outputSettings.schema, locale))\n .filter((v) => v !== undefined);\n } else {\n outputValue = this.transformArrayItem(value, outputSettings.schema, locale);\n }\n }\n\n return outputValue;\n }\n\n /**\n * Extend the resource output\n */\n protected extend() {\n //\n }\n\n /**\n * Transform a self-referencing field value.\n *\n * @example\n * // Single: parent: \"self\"\n * // Array: children: \"self[]\"\n */\n protected transformSelfReference(value: any, isArray: boolean): any {\n if (isArray) {\n return Array.isArray(value)\n ? value.map((item) => this.resolveSelf(item)).filter((v) => v !== undefined)\n : undefined;\n }\n\n return this.resolveSelf(value);\n }\n\n /**\n * Resolve a single self-reference value.\n * Uses identity-based cycle detection (id/_id) and a max depth guard.\n */\n protected resolveSelf(value: any): any {\n if (!value) return undefined;\n\n const identity = value.id ?? value._id ?? value;\n // Ancestors of the current path only: siblings never see each other,\n // so the set size is the depth.\n const ancestors = this._selfSeen ?? new Set();\n\n // Circular reference or depth limit reached — stop recursion\n if (ancestors.has(identity) || ancestors.size >= MAX_SELF_DEPTH) {\n return undefined;\n }\n\n const SelfConstructor = this.constructor as typeof Resource;\n const child = new SelfConstructor(value);\n child._selfSeen = new Set(ancestors).add(identity);\n\n return child.toJSON();\n }\n\n /**\n * Transform a single array item according to the given schema\n */\n protected transformArrayItem(\n item: any,\n schema: Record<string, ResourceFieldConfig>,\n locale?: string,\n ) {\n const transformedItem: GenericObject = {};\n\n for (const [outputKey, outputSettings] of Object.entries(schema)) {\n let fieldKey = outputKey;\n let valueTransformType = outputSettings as ResourceFieldConfig;\n\n if (Array.isArray(outputSettings)) {\n fieldKey = outputSettings[0];\n valueTransformType = outputSettings[1];\n }\n\n const inputValue = get(item, fieldKey);\n const outputValue = this.transformValue(inputValue, valueTransformType, locale);\n\n if (outputValue !== undefined) {\n set(transformedItem, outputKey, outputValue);\n }\n }\n\n return transformedItem;\n }\n\n /**\n * Get a input value for the given key\n */\n public get(key: string, defaultValue?: any) {\n return get(this.resource, key, defaultValue);\n }\n\n /**\n * Set the given value for the given field\n */\n public set(key: string, value: any): ResourceContract {\n set(this.data, key, value);\n\n return this;\n }\n\n /**\n * Create an array schema for transforming array items\n */\n public arrayOf(schema: Record<string, ResourceFieldConfig>): ResourceArraySchema {\n return {\n __type: \"arrayOf\",\n schema,\n };\n }\n\n /**\n * Get a string field builder\n */\n public string(inputKey?: string) {\n return this.fieldBuilder(\"string\", inputKey);\n }\n\n /**\n * Get a date field builder\n */\n public date(inputKey?: string) {\n return this.fieldBuilder(\"date\", inputKey);\n }\n\n /**\n * Get a localized field builder\n */\n public localized(inputKey?: string) {\n return this.fieldBuilder(\"localized\", inputKey);\n }\n\n /**\n * Get a url field builder\n */\n public url(inputKey?: string) {\n return this.fieldBuilder(\"url\", inputKey);\n }\n\n /**\n * Get a uploadsUrl field builder\n */\n public uploadsUrl(inputKey?: string) {\n return this.fieldBuilder(\"uploadsUrl\", inputKey);\n }\n\n /**\n * Get a number field builder\n */\n public number(inputKey?: string) {\n return this.fieldBuilder(\"number\", inputKey);\n }\n\n /**\n * Get a boolean field builder\n */\n public boolean(inputKey?: string) {\n return this.fieldBuilder(\"boolean\", inputKey);\n }\n\n /**\n * Get a float field builder\n */\n public float(inputKey?: string) {\n return this.fieldBuilder(\"float\", inputKey);\n }\n\n /**\n * Get a int field builder\n */\n public int(inputKey?: string) {\n return this.fieldBuilder(\"int\", inputKey);\n }\n\n /**\n * New field builder\n */\n protected fieldBuilder(type: ResourceOutputValueCastType, inputKey?: string) {\n const builder = new ResourceFieldBuilder(type);\n\n if (inputKey) {\n builder.setInputKey(inputKey);\n }\n\n return builder;\n }\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { Request } from "../http/request.mjs";
3
3
  import { HttpContext, RestfulMiddleware, RouteResource } from "../router/types.mjs";
4
4
  import { RepositoryManager } from "../repositories/repository.manager.mjs";
5
5
  import { Model } from "@warlock.js/cascade";
6
+ import { GenericObject } from "@mongez/reinforcements";
6
7
 
7
8
  //#region ../core/src/restful/restful.d.ts
8
9
  declare abstract class Restful<T extends Model> implements RouteResource {
@@ -42,14 +43,14 @@ declare abstract class Restful<T extends Model> implements RouteResource {
42
43
  list({
43
44
  request,
44
45
  response
45
- }: HttpContext): Promise<Response | undefined>;
46
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
46
47
  /**
47
48
  * Get single record
48
49
  */
49
50
  get({
50
51
  request,
51
52
  response
52
- }: HttpContext): Promise<Response | undefined>;
53
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
53
54
  /**
54
55
  * Create a new record
55
56
  */
@@ -70,21 +71,21 @@ declare abstract class Restful<T extends Model> implements RouteResource {
70
71
  patch({
71
72
  request,
72
73
  response
73
- }: HttpContext): Promise<Response | undefined>;
74
+ }: HttpContext): Promise<any>;
74
75
  /**
75
76
  * Delete record
76
77
  */
77
78
  delete({
78
79
  request,
79
80
  response
80
- }: HttpContext): Promise<Response | undefined>;
81
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
81
82
  /**
82
83
  * Bulk delete records
83
84
  */
84
85
  bulkDelete({
85
86
  request,
86
87
  response
87
- }: HttpContext): Promise<Response | undefined>;
88
+ }: HttpContext): Promise<any[] | Record<string, any> | Response>;
88
89
  /**
89
90
  * Before create
90
91
  */
@@ -125,6 +126,12 @@ declare abstract class Restful<T extends Model> implements RouteResource {
125
126
  * On save
126
127
  */
127
128
  protected onSave(_request: Request, _response: Response, _record: T, _oldRecord?: T): Promise<any>;
129
+ /**
130
+ * Data to persist: only the validated input.
131
+ * Without a validation schema the raw input is kept (legacy behaviour)
132
+ * and a one-time warning names the resource.
133
+ */
134
+ protected payload(action: "create" | "update" | "patch", request: Request): GenericObject;
128
135
  /**
129
136
  * Call middleware for the given method
130
137
  *
@@ -1 +1 @@
1
- {"version":3,"file":"restful.d.mts","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"mappings":";;;;;;;uBAOsB,OAAA,WAAkB,KAAA,aAAkB,aAAA;;;;YAI9C,UAAA,EAAY,iBAAA;;AAJxB;;YASY,UAAA;EAT4B;;;EAAA,UAc5B,eAAA;EAUU;;;EAAA,mBALD,UAAA,EAAY,iBAAA,CAAkB,CAAA;EA8BpB;;;EAAA,UAzBnB,QAAA,EAAU,MAAA;EAyDD;;;;;EA7CZ,KAAA;EAkEwB;;;EA7DlB,IAAA,CAAK,EAAA,WAAU,OAAA,CAAA,CAAA;EA6GG;;;EArGlB,IAAA;IAAO,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA,CAAA,QAAA;EAiJtB;;;EAjHjB,GAAA;IAAM,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA,CAAA,QAAA;EAoJ7B;;;EA/HT,MAAA;IAAS,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA;EA+HA;;;EA/EzC,MAAA;IAAS,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA;EAgHI;;;EApE7C,KAAA;IAAQ,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA,CAAA,QAAA;EAoHyB;;;EAjFjE,MAAA;IAAS,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA,CAAA,QAAA;EAwFoB;;;EAvD7D,UAAA;IAAa,OAAA;IAAS;EAAA,GAAY,WAAA,GAAW,OAAA,CAAA,QAAA;EAiE/C;;;EAAA,UAjBK,YAAA,CAAa,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,OAAA,EAAS,CAAA,GAAI,OAAA;EA4BrE;;;EAAA,UArBG,QAAA,CAAS,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,OAAA,EAAS,CAAA,GAAI,OAAA;EA+BvC;;;EAAA,UAxBvB,YAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,EAAS,CAAA,EACT,UAAA,GAAa,CAAA,GACZ,OAAA;EA0BgC;;;EAAA,UAnBnB,QAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,EAAS,CAAA,EACT,UAAA,EAAY,CAAA,GACX,OAAA;EAsBS;;;EAAA,UAfI,YAAA,CAAa,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,OAAA,EAAS,CAAA,GAAI,OAAA;EAmB/E;;;EAAA,UAZa,QAAA,CAAS,QAAA,EAAU,OAAA,EAAS,SAAA,EAAW,QAAA,EAAU,OAAA,EAAS,CAAA,GAAI,OAAA;EAuBhE;;;EAAA,UAhBE,WAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,EAAS,CAAA,EACT,UAAA,GAAa,CAAA,GACZ,OAAA;EAsBS;;;EAAA,UAfI,OAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,EAAS,CAAA,EACT,UAAA,EAAY,CAAA,GACX,OAAA;EAqBU;;;EAAA,UAdG,UAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,GAAU,CAAA,EACV,UAAA,GAAa,CAAA,GACZ,OAAA;EAsBQ;;;EAAA,UAfK,MAAA,CACd,QAAA,EAAU,OAAA,EACV,SAAA,EAAW,QAAA,EACX,OAAA,EAAS,CAAA,EACT,UAAA,GAAa,CAAA,GACZ,OAAA;EAYY;;;;EAAA,UAJC,cAAA,CACd,MAAA,UACA,OAAA,EAAS,OAAA,EACT,QAAA,EAAU,QAAA,EACV,OAAA,SAAa,OAAA,SAAA,MAAA,gBAAA,QAAA;AAAA"}
1
+ {"version":3,"file":"restful.d.mts","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import { log } from "@warlock.js/logger";
2
2
 
3
3
  //#region ../core/src/restful/restful.ts
4
+ const warnedUnvalidated = /* @__PURE__ */ new WeakSet();
4
5
  var Restful = class {
5
6
  constructor() {
6
7
  this.middleware = {};
@@ -26,7 +27,8 @@ var Restful = class {
26
27
  */
27
28
  async list({ request, response }) {
28
29
  try {
29
- if (await this.callMiddleware("list", request, response)) return;
30
+ const middlewareOutput = await this.callMiddleware("list", request, response);
31
+ if (middlewareOutput) return middlewareOutput;
30
32
  const responseDocument = {};
31
33
  const data = request.heavy();
32
34
  if (data.paginate === "false") data.paginate = false;
@@ -45,12 +47,14 @@ var Restful = class {
45
47
  */
46
48
  async get({ request, response }) {
47
49
  try {
48
- if (await this.callMiddleware("get", request, response)) return;
50
+ const middlewareOutput = await this.callMiddleware("get", request, response);
51
+ if (middlewareOutput) return middlewareOutput;
49
52
  const record = await this.find(request.input("id"));
50
53
  if (!record) return response.notFound();
51
54
  return response.success({ [this.recordName]: record });
52
55
  } catch (error) {
53
56
  log.error("restful", "get", error);
57
+ throw error;
54
58
  }
55
59
  }
56
60
  /**
@@ -58,12 +62,15 @@ var Restful = class {
58
62
  */
59
63
  async create({ request, response }) {
60
64
  try {
65
+ const middlewareOutput = await this.callMiddleware("create", request, response);
66
+ if (middlewareOutput) return middlewareOutput;
61
67
  const model = this.repository.newModel();
62
68
  const beforeCreate = await this.beforeCreate(request, response, model);
63
69
  if (beforeCreate) return beforeCreate;
64
70
  const beforeSave = await this.beforeSave(request, response, model);
65
71
  if (beforeSave) return beforeSave;
66
- const record = await this.repository.create(request.all());
72
+ await model.save(this.payload("create", request));
73
+ const record = model;
67
74
  const createOutput = await this.onCreate(request, response, record);
68
75
  if (createOutput) return createOutput;
69
76
  const saveOutput = await this.onSave(request, response, record);
@@ -75,7 +82,7 @@ var Restful = class {
75
82
  return response.successCreate({ [this.recordName]: record });
76
83
  } catch (error) {
77
84
  log.error("restful", "create", error);
78
- return response.badRequest({ error: error.message });
85
+ throw error;
79
86
  }
80
87
  }
81
88
  /**
@@ -83,6 +90,8 @@ var Restful = class {
83
90
  */
84
91
  async update({ request, response }) {
85
92
  try {
93
+ const middlewareOutput = await this.callMiddleware("update", request, response);
94
+ if (middlewareOutput) return middlewareOutput;
86
95
  const record = await this.find(request.input("id"));
87
96
  if (!record) return response.notFound({ error: "Record not found" });
88
97
  const beforeOutput = await this.beforeUpdate(request, response, record);
@@ -90,9 +99,9 @@ var Restful = class {
90
99
  const beforeSafe = await this.beforeSave(request, response, record);
91
100
  if (beforeSafe) return beforeSafe;
92
101
  const oldRecord = record.clone();
93
- await record.save(request.allExceptParams());
94
- this.onUpdate(request, response, record, oldRecord);
95
- this.onSave(request, response, record, oldRecord);
102
+ await record.save(this.payload("update", request));
103
+ await this.onUpdate(request, response, record, oldRecord);
104
+ await this.onSave(request, response, record, oldRecord);
96
105
  if (this.returnOn.update === "records") return this.list({
97
106
  request,
98
107
  response
@@ -100,6 +109,7 @@ var Restful = class {
100
109
  return response.success({ [this.recordName]: record });
101
110
  } catch (error) {
102
111
  log.error("restful", "update", error);
112
+ throw error;
103
113
  }
104
114
  }
105
115
  /**
@@ -107,14 +117,18 @@ var Restful = class {
107
117
  */
108
118
  async patch({ request, response }) {
109
119
  try {
120
+ const middlewareOutput = await this.callMiddleware("patch", request, response);
121
+ if (middlewareOutput) return middlewareOutput;
110
122
  const record = await this.find(request.input("id"));
111
123
  if (!record) return response.notFound({ error: "Record not found" });
112
124
  const oldRecord = record.clone();
113
- await this.beforePatch(request, response, record, oldRecord);
114
- await this.beforeSave(request, response, record, oldRecord);
115
- await record.save(request.heavyExceptParams());
116
- this.onPatch(request, response, record, oldRecord);
117
- this.onSave(request, response, record, oldRecord);
125
+ const beforePatch = await this.beforePatch(request, response, record, oldRecord);
126
+ if (beforePatch) return beforePatch;
127
+ const beforeSave = await this.beforeSave(request, response, record, oldRecord);
128
+ if (beforeSave) return beforeSave;
129
+ await record.save(this.payload("patch", request));
130
+ await this.onPatch(request, response, record, oldRecord);
131
+ await this.onSave(request, response, record, oldRecord);
118
132
  if (this.returnOn.patch === "records") return this.list({
119
133
  request,
120
134
  response
@@ -122,6 +136,7 @@ var Restful = class {
122
136
  return response.success({ [this.recordName]: record });
123
137
  } catch (error) {
124
138
  log.error("restful", "patch", error);
139
+ throw error;
125
140
  }
126
141
  }
127
142
  /**
@@ -131,7 +146,8 @@ var Restful = class {
131
146
  try {
132
147
  const record = await this.find(request.input("id"));
133
148
  if (!record) return response.notFound();
134
- if (await this.callMiddleware("delete", request, response, record)) return;
149
+ const middlewareOutput = await this.callMiddleware("delete", request, response, record);
150
+ if (middlewareOutput) return middlewareOutput;
135
151
  await this.beforeDelete(request, response, record);
136
152
  await record.destroy();
137
153
  this.onDelete(request, response, record);
@@ -142,7 +158,7 @@ var Restful = class {
142
158
  return response.success();
143
159
  } catch (error) {
144
160
  log.error("restful", "delete", error);
145
- return response.badRequest({ error: error.message });
161
+ throw error;
146
162
  }
147
163
  }
148
164
  /**
@@ -152,7 +168,7 @@ var Restful = class {
152
168
  try {
153
169
  const ids = request.input("id");
154
170
  if (!Array.isArray(ids)) return response.badRequest({ error: "id must be an array" });
155
- const records = await this.repository.all({ perform: (query) => query.whereIn("id", ids.map((id) => parseInt(id))) });
171
+ const records = await this.repository.all({ perform: (query) => query.whereIn("id", ids.map((id) => /^\d+$/.test(String(id)) ? Number(id) : id)) });
156
172
  await Promise.all(records.map(async (record) => {
157
173
  if (await this.callMiddleware("delete", request, response, record)) return;
158
174
  await this.beforeDelete(request, response, record);
@@ -166,7 +182,7 @@ var Restful = class {
166
182
  return response.success({ deleted: records.length });
167
183
  } catch (error) {
168
184
  log.error("restful", "bulkDelete", error);
169
- return response.badRequest({ error: error.message });
185
+ throw error;
170
186
  }
171
187
  }
172
188
  /**
@@ -210,6 +226,21 @@ var Restful = class {
210
226
  */
211
227
  async onSave(_request, _response, _record, _oldRecord) {}
212
228
  /**
229
+ * Data to persist: only the validated input.
230
+ * Without a validation schema the raw input is kept (legacy behaviour)
231
+ * and a one-time warning names the resource.
232
+ */
233
+ payload(action, request) {
234
+ const validation = this.validation;
235
+ if (validation?.all || validation?.[action]) return request.validated();
236
+ if (!warnedUnvalidated.has(this.constructor)) {
237
+ warnedUnvalidated.add(this.constructor);
238
+ log.warn("restful", "validation", this.constructor.name + " has no validation schema: " + action + " persists unvalidated request input. Define a validation schema to persist only validated fields.");
239
+ }
240
+ if (action === "create") return request.all();
241
+ return action === "update" ? request.allExceptParams() : request.heavyExceptParams();
242
+ }
243
+ /**
213
244
  * Call middleware for the given method
214
245
  *
215
246
  */
@@ -1 +1 @@
1
- {"version":3,"file":"restful.mjs","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\nimport type { Model } from \"@warlock.js/cascade\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Request, Response } from \"../http\";\nimport type { QueryBuilderContract, RepositoryManager } from \"../repositories\";\nimport type { HttpContext, RestfulMiddleware, RouteResource } from \"../router\";\n\nexport abstract class Restful<T extends Model> implements RouteResource {\n /**\n * Middleware for each method\n */\n protected middleware: RestfulMiddleware = {};\n\n /**\n * Record name\n */\n protected recordName = \"record\";\n\n /**\n * Records list name\n */\n protected recordsListName = \"records\";\n\n /**\n * Repository\n */\n protected abstract repository: RepositoryManager<T>;\n\n /**\n * Define what to be returned when a record is created|updated|deleted|patched\n */\n protected returnOn: Record<string, \"record\" | \"records\"> = {\n create: \"record\",\n update: \"record\",\n delete: \"record\",\n patch: \"record\",\n };\n\n /**\n * Enable fetching cache\n *\n * @default true\n */\n public cache = true;\n\n /**\n * Find record instance by id\n */\n public async find(id: number) {\n const findMethod = this.cache ? \"getCached\" : \"find\";\n return this.repository[findMethod](id);\n }\n\n /**\n * List records\n */\n public async list({ request, response }: HttpContext) {\n try {\n if (await this.callMiddleware(\"list\", request, response)) return;\n\n const responseDocument: GenericObject = {};\n\n const data = request.heavy();\n\n if (data.paginate === \"false\") {\n data.paginate = false;\n }\n\n const listMethod = this.cache ? \"listCached\" : \"list\";\n\n const { data: documents, pagination } = await this.repository[listMethod](data);\n\n responseDocument[this.recordsListName] = documents;\n\n if (pagination) {\n responseDocument.pagination = pagination;\n }\n\n return response.success(responseDocument);\n } catch (error: any) {\n log.error(\"restful\", \"list\", error);\n return response.serverError(error);\n }\n }\n\n /**\n * Get single record\n */\n public async get({ request, response }: HttpContext) {\n try {\n if (await this.callMiddleware(\"get\", request, response)) return;\n\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound();\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"get\", error);\n }\n }\n\n /**\n * Create a new record\n */\n public async create({ request, response }: HttpContext) {\n try {\n const model = this.repository.newModel();\n const beforeCreate = await this.beforeCreate(request, response, model);\n\n if (beforeCreate) {\n return beforeCreate;\n }\n\n const beforeSave = await this.beforeSave(request, response, model);\n\n if (beforeSave) {\n return beforeSave;\n }\n\n const record = await this.repository.create(request.all());\n\n const createOutput = await this.onCreate(request, response, record);\n\n if (createOutput) {\n return createOutput;\n }\n\n const saveOutput = await this.onSave(request, response, record);\n\n if (saveOutput) {\n return saveOutput;\n }\n\n if (this.returnOn.create === \"records\") {\n return this.list({ request, response });\n }\n\n return response.successCreate({\n [this.recordName]: record,\n });\n } catch (error: Error | any) {\n log.error(\"restful\", \"create\", error);\n\n return response.badRequest({\n error: error.message,\n });\n }\n }\n\n /**\n * Update record\n */\n public async update({ request, response }: HttpContext) {\n try {\n // Find record\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound({\n error: \"Record not found\",\n });\n }\n\n const beforeOutput = await this.beforeUpdate(request, response, record);\n if (beforeOutput) {\n return beforeOutput;\n }\n\n const beforeSafe = await this.beforeSave(request, response, record);\n\n if (beforeSafe) {\n return beforeSafe;\n }\n\n const oldRecord = record.clone();\n\n await record.save(request.allExceptParams());\n\n this.onUpdate(request, response, record, oldRecord);\n this.onSave(request, response, record, oldRecord);\n\n if (this.returnOn.update === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"update\", error);\n }\n }\n\n /**\n * Patch record\n */\n public async patch({ request, response }: HttpContext) {\n try {\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound({\n error: \"Record not found\",\n });\n }\n\n const oldRecord = record.clone();\n\n await this.beforePatch(request, response, record, oldRecord);\n await this.beforeSave(request, response, record, oldRecord);\n\n await record.save(request.heavyExceptParams());\n\n this.onPatch(request, response, record, oldRecord);\n this.onSave(request, response, record, oldRecord);\n\n if (this.returnOn.patch === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"patch\", error);\n }\n }\n\n /**\n * Delete record\n */\n public async delete({ request, response }: HttpContext) {\n try {\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound();\n }\n\n if (await this.callMiddleware(\"delete\", request, response, record)) return;\n\n await this.beforeDelete(request, response, record);\n\n await record.destroy();\n\n this.onDelete(request, response, record);\n\n if (this.returnOn.delete === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success();\n } catch (error: Error | any) {\n log.error(\"restful\", \"delete\", error);\n\n return response.badRequest({\n error: error.message,\n });\n }\n }\n\n /**\n * Bulk delete records\n */\n public async bulkDelete({ request, response }: HttpContext) {\n try {\n const ids = request.input(\"id\");\n\n if (!Array.isArray(ids)) {\n return response.badRequest({\n error: \"id must be an array\",\n });\n }\n\n const records = await this.repository.all({\n perform: (query: QueryBuilderContract<T>) =>\n query.whereIn(\n \"id\",\n ids.map((id) => parseInt(id)),\n ),\n });\n\n await Promise.all(\n records.map(async (record) => {\n if (await this.callMiddleware(\"delete\", request, response, record)) {\n return;\n }\n\n await this.beforeDelete(request, response, record);\n await record.destroy();\n this.onDelete(request, response, record);\n }),\n );\n\n if (this.returnOn.delete === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n deleted: records.length,\n });\n } catch (error: Error | any) {\n log.error(\"restful\", \"bulkDelete\", error);\n return response.badRequest({\n error: error.message,\n });\n }\n }\n\n /**\n * Before create\n */\n protected async beforeCreate(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * On create\n */\n protected async onCreate(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * Before update\n */\n protected async beforeUpdate(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On update\n */\n protected async onUpdate(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Before delete\n */\n protected async beforeDelete(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * On delete\n */\n protected async onDelete(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * Before patch\n */\n protected async beforePatch(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On patch\n */\n protected async onPatch(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Before save\n */\n protected async beforeSave(\n _request: Request,\n _response: Response,\n _record?: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On save\n */\n protected async onSave(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Call middleware for the given method\n *\n */\n protected async callMiddleware(\n method: string,\n request: Request,\n response: Response,\n _record?: any,\n ) {\n if (!this.middleware[method]) return;\n\n for (const middleware of this.middleware[method]) {\n const output = await middleware({ request, response });\n\n if (output) {\n return output;\n }\n }\n\n return;\n }\n}\n"],"mappings":";;;AAOA,IAAsB,UAAtB,MAAwE;;oBAI5B,CAAC;oBAKpB;yBAKK;kBAU+B;GACzD,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,OAAO;EACT;eAOe;;;;;CAKf,MAAa,KAAK,IAAY;EAC5B,MAAM,aAAa,KAAK,QAAQ,cAAc;EAC9C,OAAO,KAAK,WAAW,WAAW,CAAC,EAAE;CACvC;;;;CAKA,MAAa,KAAK,EAAE,SAAS,YAAyB;EACpD,IAAI;GACF,IAAI,MAAM,KAAK,eAAe,QAAQ,SAAS,QAAQ,GAAG;GAE1D,MAAM,mBAAkC,CAAC;GAEzC,MAAM,OAAO,QAAQ,MAAM;GAE3B,IAAI,KAAK,aAAa,SACpB,KAAK,WAAW;GAGlB,MAAM,aAAa,KAAK,QAAQ,eAAe;GAE/C,MAAM,EAAE,MAAM,WAAW,eAAe,MAAM,KAAK,WAAW,WAAW,CAAC,IAAI;GAE9E,iBAAiB,KAAK,mBAAmB;GAEzC,IAAI,YACF,iBAAiB,aAAa;GAGhC,OAAO,SAAS,QAAQ,gBAAgB;EAC1C,SAAS,OAAY;GACnB,IAAI,MAAM,WAAW,QAAQ,KAAK;GAClC,OAAO,SAAS,YAAY,KAAK;EACnC;CACF;;;;CAKA,MAAa,IAAI,EAAE,SAAS,YAAyB;EACnD,IAAI;GACF,IAAI,MAAM,KAAK,eAAe,OAAO,SAAS,QAAQ,GAAG;GAEzD,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS;GAG3B,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,OAAO,KAAK;EACnC;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GACF,MAAM,QAAQ,KAAK,WAAW,SAAS;GACvC,MAAM,eAAe,MAAM,KAAK,aAAa,SAAS,UAAU,KAAK;GAErE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,WAAW,SAAS,UAAU,KAAK;GAEjE,IAAI,YACF,OAAO;GAGT,MAAM,SAAS,MAAM,KAAK,WAAW,OAAO,QAAQ,IAAI,CAAC;GAEzD,MAAM,eAAe,MAAM,KAAK,SAAS,SAAS,UAAU,MAAM;GAElE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,OAAO,SAAS,UAAU,MAAM;GAE9D,IAAI,YACF,OAAO;GAGT,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,cAAc,GAC3B,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,UAAU,KAAK;GAEpC,OAAO,SAAS,WAAW,EACzB,OAAO,MAAM,QACf,CAAC;EACH;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GAEF,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS,EACvB,OAAO,mBACT,CAAC;GAGH,MAAM,eAAe,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;GACtE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,WAAW,SAAS,UAAU,MAAM;GAElE,IAAI,YACF,OAAO;GAGT,MAAM,YAAY,OAAO,MAAM;GAE/B,MAAM,OAAO,KAAK,QAAQ,gBAAgB,CAAC;GAE3C,KAAK,SAAS,SAAS,UAAU,QAAQ,SAAS;GAClD,KAAK,OAAO,SAAS,UAAU,QAAQ,SAAS;GAEhD,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,UAAU,KAAK;EACtC;CACF;;;;CAKA,MAAa,MAAM,EAAE,SAAS,YAAyB;EACrD,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS,EACvB,OAAO,mBACT,CAAC;GAGH,MAAM,YAAY,OAAO,MAAM;GAE/B,MAAM,KAAK,YAAY,SAAS,UAAU,QAAQ,SAAS;GAC3D,MAAM,KAAK,WAAW,SAAS,UAAU,QAAQ,SAAS;GAE1D,MAAM,OAAO,KAAK,QAAQ,kBAAkB,CAAC;GAE7C,KAAK,QAAQ,SAAS,UAAU,QAAQ,SAAS;GACjD,KAAK,OAAO,SAAS,UAAU,QAAQ,SAAS;GAEhD,IAAI,KAAK,SAAS,UAAU,WAC1B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,SAAS,KAAK;EACrC;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS;GAG3B,IAAI,MAAM,KAAK,eAAe,UAAU,SAAS,UAAU,MAAM,GAAG;GAEpE,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;GAEjD,MAAM,OAAO,QAAQ;GAErB,KAAK,SAAS,SAAS,UAAU,MAAM;GAEvC,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ;EAC1B,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,UAAU,KAAK;GAEpC,OAAO,SAAS,WAAW,EACzB,OAAO,MAAM,QACf,CAAC;EACH;CACF;;;;CAKA,MAAa,WAAW,EAAE,SAAS,YAAyB;EAC1D,IAAI;GACF,MAAM,MAAM,QAAQ,MAAM,IAAI;GAE9B,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,OAAO,SAAS,WAAW,EACzB,OAAO,sBACT,CAAC;GAGH,MAAM,UAAU,MAAM,KAAK,WAAW,IAAI,EACxC,UAAU,UACR,MAAM,QACJ,MACA,IAAI,KAAK,OAAO,SAAS,EAAE,CAAC,CAC9B,EACJ,CAAC;GAED,MAAM,QAAQ,IACZ,QAAQ,IAAI,OAAO,WAAW;IAC5B,IAAI,MAAM,KAAK,eAAe,UAAU,SAAS,UAAU,MAAM,GAC/D;IAGF,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;IACjD,MAAM,OAAO,QAAQ;IACrB,KAAK,SAAS,SAAS,UAAU,MAAM;GACzC,CAAC,CACH;GAEA,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,EACtB,SAAS,QAAQ,OACnB,CAAC;EACH,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,cAAc,KAAK;GACxC,OAAO,SAAS,WAAW,EACzB,OAAO,MAAM,QACf,CAAC;EACH;CACF;;;;CAKA,MAAgB,aAAa,UAAmB,WAAqB,SAA0B,CAE/F;;;;CAKA,MAAgB,SAAS,UAAmB,WAAqB,SAA0B,CAE3F;;;;CAKA,MAAgB,aACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,SACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,aAAa,UAAmB,WAAqB,SAA0B,CAE/F;;;;CAKA,MAAgB,SAAS,UAAmB,WAAqB,SAA0B,CAE3F;;;;CAKA,MAAgB,YACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,QACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,WACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,OACd,UACA,WACA,SACA,YACc,CAEhB;;;;;CAMA,MAAgB,eACd,QACA,SACA,UACA,SACA;EACA,IAAI,CAAC,KAAK,WAAW,SAAS;EAE9B,KAAK,MAAM,cAAc,KAAK,WAAW,SAAS;GAChD,MAAM,SAAS,MAAM,WAAW;IAAE;IAAS;GAAS,CAAC;GAErD,IAAI,QACF,OAAO;EAEX;CAGF;AACF"}
1
+ {"version":3,"file":"restful.mjs","names":[],"sources":["../../../../../../../core/src/restful/restful.ts"],"sourcesContent":["import type { GenericObject } from \"@mongez/reinforcements\";\nimport type { Model } from \"@warlock.js/cascade\";\nimport { log } from \"@warlock.js/logger\";\nimport type { Request, Response } from \"../http\";\nimport type { QueryBuilderContract, RepositoryManager } from \"../repositories\";\nimport type { HttpContext, RestfulMiddleware, RouteResource } from \"../router\";\n\nconst warnedUnvalidated = new WeakSet<object>();\n\nexport abstract class Restful<T extends Model> implements RouteResource {\n /**\n * Middleware for each method\n */\n protected middleware: RestfulMiddleware = {};\n\n /**\n * Record name\n */\n protected recordName = \"record\";\n\n /**\n * Records list name\n */\n protected recordsListName = \"records\";\n\n /**\n * Repository\n */\n protected abstract repository: RepositoryManager<T>;\n\n /**\n * Define what to be returned when a record is created|updated|deleted|patched\n */\n protected returnOn: Record<string, \"record\" | \"records\"> = {\n create: \"record\",\n update: \"record\",\n delete: \"record\",\n patch: \"record\",\n };\n\n /**\n * Enable fetching cache\n *\n * @default true\n */\n public cache = true;\n\n /**\n * Find record instance by id\n */\n public async find(id: number) {\n const findMethod = this.cache ? \"getCached\" : \"find\";\n return this.repository[findMethod](id);\n }\n\n /**\n * List records\n */\n public async list({ request, response }: HttpContext) {\n try {\n const middlewareOutput = await this.callMiddleware(\"list\", request, response);\n\n if (middlewareOutput) return middlewareOutput;\n\n const responseDocument: GenericObject = {};\n\n const data = request.heavy();\n\n if (data.paginate === \"false\") {\n data.paginate = false;\n }\n\n const listMethod = this.cache ? \"listCached\" : \"list\";\n\n const { data: documents, pagination } = await this.repository[listMethod](data);\n\n responseDocument[this.recordsListName] = documents;\n\n if (pagination) {\n responseDocument.pagination = pagination;\n }\n\n return response.success(responseDocument);\n } catch (error: any) {\n log.error(\"restful\", \"list\", error);\n return response.serverError(error);\n }\n }\n\n /**\n * Get single record\n */\n public async get({ request, response }: HttpContext) {\n try {\n const middlewareOutput = await this.callMiddleware(\"get\", request, response);\n\n if (middlewareOutput) return middlewareOutput;\n\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound();\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"get\", error);\n\n throw error;\n }\n }\n\n /**\n * Create a new record\n */\n public async create({ request, response }: HttpContext) {\n try {\n const middlewareOutput = await this.callMiddleware(\"create\", request, response);\n\n if (middlewareOutput) return middlewareOutput;\n\n const model = this.repository.newModel();\n const beforeCreate = await this.beforeCreate(request, response, model);\n\n if (beforeCreate) {\n return beforeCreate;\n }\n\n const beforeSave = await this.beforeSave(request, response, model);\n\n if (beforeSave) {\n return beforeSave;\n }\n\n // Save the model the hooks received, so their changes are not discarded\n await model.save(this.payload(\"create\", request));\n\n const record = model;\n\n const createOutput = await this.onCreate(request, response, record);\n\n if (createOutput) {\n return createOutput;\n }\n\n const saveOutput = await this.onSave(request, response, record);\n\n if (saveOutput) {\n return saveOutput;\n }\n\n if (this.returnOn.create === \"records\") {\n return this.list({ request, response });\n }\n\n return response.successCreate({\n [this.recordName]: record,\n });\n } catch (error: Error | any) {\n log.error(\"restful\", \"create\", error);\n\n // let the request error handler decide the status; never leak DB messages as 400\n throw error;\n }\n }\n\n /**\n * Update record\n */\n public async update({ request, response }: HttpContext) {\n try {\n const middlewareOutput = await this.callMiddleware(\"update\", request, response);\n\n if (middlewareOutput) return middlewareOutput;\n\n // Find record\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound({\n error: \"Record not found\",\n });\n }\n\n const beforeOutput = await this.beforeUpdate(request, response, record);\n if (beforeOutput) {\n return beforeOutput;\n }\n\n const beforeSafe = await this.beforeSave(request, response, record);\n\n if (beforeSafe) {\n return beforeSafe;\n }\n\n const oldRecord = record.clone();\n\n await record.save(this.payload(\"update\", request));\n\n await this.onUpdate(request, response, record, oldRecord);\n await this.onSave(request, response, record, oldRecord);\n\n if (this.returnOn.update === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"update\", error);\n\n throw error;\n }\n }\n\n /**\n * Patch record\n */\n public async patch({ request, response }: HttpContext) {\n try {\n const middlewareOutput = await this.callMiddleware(\"patch\", request, response);\n\n if (middlewareOutput) return middlewareOutput;\n\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound({\n error: \"Record not found\",\n });\n }\n\n const oldRecord = record.clone();\n\n const beforePatch = await this.beforePatch(request, response, record, oldRecord);\n\n if (beforePatch) {\n return beforePatch;\n }\n\n const beforeSave = await this.beforeSave(request, response, record, oldRecord);\n\n if (beforeSave) {\n return beforeSave;\n }\n\n await record.save(this.payload(\"patch\", request));\n\n await this.onPatch(request, response, record, oldRecord);\n await this.onSave(request, response, record, oldRecord);\n\n if (this.returnOn.patch === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n [this.recordName]: record,\n });\n } catch (error) {\n log.error(\"restful\", \"patch\", error);\n\n throw error;\n }\n }\n\n /**\n * Delete record\n */\n public async delete({ request, response }: HttpContext) {\n try {\n const record = await this.find(request.input(\"id\"));\n\n if (!record) {\n return response.notFound();\n }\n\n const middlewareOutput = await this.callMiddleware(\"delete\", request, response, record);\n\n if (middlewareOutput) return middlewareOutput;\n\n await this.beforeDelete(request, response, record);\n\n await record.destroy();\n\n this.onDelete(request, response, record);\n\n if (this.returnOn.delete === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success();\n } catch (error: Error | any) {\n log.error(\"restful\", \"delete\", error);\n\n throw error;\n }\n }\n\n /**\n * Bulk delete records\n */\n public async bulkDelete({ request, response }: HttpContext) {\n try {\n const ids = request.input(\"id\");\n\n if (!Array.isArray(ids)) {\n return response.badRequest({\n error: \"id must be an array\",\n });\n }\n\n const records = await this.repository.all({\n perform: (query: QueryBuilderContract<T>) =>\n query.whereIn(\n \"id\",\n ids.map((id) => (/^\\d+$/.test(String(id)) ? Number(id) : id)),\n ),\n });\n\n await Promise.all(\n records.map(async (record) => {\n if (await this.callMiddleware(\"delete\", request, response, record)) {\n return;\n }\n\n await this.beforeDelete(request, response, record);\n await record.destroy();\n this.onDelete(request, response, record);\n }),\n );\n\n if (this.returnOn.delete === \"records\") {\n return this.list({ request, response });\n }\n\n return response.success({\n deleted: records.length,\n });\n } catch (error: Error | any) {\n log.error(\"restful\", \"bulkDelete\", error);\n\n throw error;\n }\n }\n\n /**\n * Before create\n */\n protected async beforeCreate(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * On create\n */\n protected async onCreate(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * Before update\n */\n protected async beforeUpdate(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On update\n */\n protected async onUpdate(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Before delete\n */\n protected async beforeDelete(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * On delete\n */\n protected async onDelete(_request: Request, _response: Response, _record: T): Promise<any> {\n //\n }\n\n /**\n * Before patch\n */\n protected async beforePatch(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On patch\n */\n protected async onPatch(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Before save\n */\n protected async beforeSave(\n _request: Request,\n _response: Response,\n _record?: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * On save\n */\n protected async onSave(\n _request: Request,\n _response: Response,\n _record: T,\n _oldRecord?: T,\n ): Promise<any> {\n //\n }\n\n /**\n * Data to persist: only the validated input.\n * Without a validation schema the raw input is kept (legacy behaviour)\n * and a one-time warning names the resource.\n */\n protected payload(action: \"create\" | \"update\" | \"patch\", request: Request): GenericObject {\n const validation = (this as any).validation;\n\n if (validation?.all || validation?.[action]) {\n return request.validated();\n }\n\n if (!warnedUnvalidated.has(this.constructor)) {\n warnedUnvalidated.add(this.constructor);\n\n log.warn(\n \"restful\",\n \"validation\",\n this.constructor.name + \" has no validation schema: \" + action + \" persists unvalidated request input. Define a validation schema to persist only validated fields.\",\n );\n }\n\n if (action === \"create\") return request.all();\n\n return action === \"update\" ? request.allExceptParams() : request.heavyExceptParams();\n }\n\n /**\n * Call middleware for the given method\n *\n */\n protected async callMiddleware(\n method: string,\n request: Request,\n response: Response,\n _record?: any,\n ) {\n if (!this.middleware[method]) return;\n\n for (const middleware of this.middleware[method]) {\n const output = await middleware({ request, response });\n\n if (output) {\n return output;\n }\n }\n\n return;\n }\n}\n"],"mappings":";;;AAOA,MAAM,oCAAoB,IAAI,QAAgB;AAE9C,IAAsB,UAAtB,MAAwE;;oBAI5B,CAAC;oBAKpB;yBAKK;kBAU+B;GACzD,QAAQ;GACR,QAAQ;GACR,QAAQ;GACR,OAAO;EACT;eAOe;;;;;CAKf,MAAa,KAAK,IAAY;EAC5B,MAAM,aAAa,KAAK,QAAQ,cAAc;EAC9C,OAAO,KAAK,WAAW,WAAW,CAAC,EAAE;CACvC;;;;CAKA,MAAa,KAAK,EAAE,SAAS,YAAyB;EACpD,IAAI;GACF,MAAM,mBAAmB,MAAM,KAAK,eAAe,QAAQ,SAAS,QAAQ;GAE5E,IAAI,kBAAkB,OAAO;GAE7B,MAAM,mBAAkC,CAAC;GAEzC,MAAM,OAAO,QAAQ,MAAM;GAE3B,IAAI,KAAK,aAAa,SACpB,KAAK,WAAW;GAGlB,MAAM,aAAa,KAAK,QAAQ,eAAe;GAE/C,MAAM,EAAE,MAAM,WAAW,eAAe,MAAM,KAAK,WAAW,WAAW,CAAC,IAAI;GAE9E,iBAAiB,KAAK,mBAAmB;GAEzC,IAAI,YACF,iBAAiB,aAAa;GAGhC,OAAO,SAAS,QAAQ,gBAAgB;EAC1C,SAAS,OAAY;GACnB,IAAI,MAAM,WAAW,QAAQ,KAAK;GAClC,OAAO,SAAS,YAAY,KAAK;EACnC;CACF;;;;CAKA,MAAa,IAAI,EAAE,SAAS,YAAyB;EACnD,IAAI;GACF,MAAM,mBAAmB,MAAM,KAAK,eAAe,OAAO,SAAS,QAAQ;GAE3E,IAAI,kBAAkB,OAAO;GAE7B,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS;GAG3B,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,OAAO,KAAK;GAEjC,MAAM;EACR;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GACF,MAAM,mBAAmB,MAAM,KAAK,eAAe,UAAU,SAAS,QAAQ;GAE9E,IAAI,kBAAkB,OAAO;GAE7B,MAAM,QAAQ,KAAK,WAAW,SAAS;GACvC,MAAM,eAAe,MAAM,KAAK,aAAa,SAAS,UAAU,KAAK;GAErE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,WAAW,SAAS,UAAU,KAAK;GAEjE,IAAI,YACF,OAAO;GAIT,MAAM,MAAM,KAAK,KAAK,QAAQ,UAAU,OAAO,CAAC;GAEhD,MAAM,SAAS;GAEf,MAAM,eAAe,MAAM,KAAK,SAAS,SAAS,UAAU,MAAM;GAElE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,OAAO,SAAS,UAAU,MAAM;GAE9D,IAAI,YACF,OAAO;GAGT,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,cAAc,GAC3B,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,UAAU,KAAK;GAGpC,MAAM;EACR;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GACF,MAAM,mBAAmB,MAAM,KAAK,eAAe,UAAU,SAAS,QAAQ;GAE9E,IAAI,kBAAkB,OAAO;GAG7B,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS,EACvB,OAAO,mBACT,CAAC;GAGH,MAAM,eAAe,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;GACtE,IAAI,cACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,WAAW,SAAS,UAAU,MAAM;GAElE,IAAI,YACF,OAAO;GAGT,MAAM,YAAY,OAAO,MAAM;GAE/B,MAAM,OAAO,KAAK,KAAK,QAAQ,UAAU,OAAO,CAAC;GAEjD,MAAM,KAAK,SAAS,SAAS,UAAU,QAAQ,SAAS;GACxD,MAAM,KAAK,OAAO,SAAS,UAAU,QAAQ,SAAS;GAEtD,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,UAAU,KAAK;GAEpC,MAAM;EACR;CACF;;;;CAKA,MAAa,MAAM,EAAE,SAAS,YAAyB;EACrD,IAAI;GACF,MAAM,mBAAmB,MAAM,KAAK,eAAe,SAAS,SAAS,QAAQ;GAE7E,IAAI,kBAAkB,OAAO;GAE7B,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS,EACvB,OAAO,mBACT,CAAC;GAGH,MAAM,YAAY,OAAO,MAAM;GAE/B,MAAM,cAAc,MAAM,KAAK,YAAY,SAAS,UAAU,QAAQ,SAAS;GAE/E,IAAI,aACF,OAAO;GAGT,MAAM,aAAa,MAAM,KAAK,WAAW,SAAS,UAAU,QAAQ,SAAS;GAE7E,IAAI,YACF,OAAO;GAGT,MAAM,OAAO,KAAK,KAAK,QAAQ,SAAS,OAAO,CAAC;GAEhD,MAAM,KAAK,QAAQ,SAAS,UAAU,QAAQ,SAAS;GACvD,MAAM,KAAK,OAAO,SAAS,UAAU,QAAQ,SAAS;GAEtD,IAAI,KAAK,SAAS,UAAU,WAC1B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,GACrB,KAAK,aAAa,OACrB,CAAC;EACH,SAAS,OAAO;GACd,IAAI,MAAM,WAAW,SAAS,KAAK;GAEnC,MAAM;EACR;CACF;;;;CAKA,MAAa,OAAO,EAAE,SAAS,YAAyB;EACtD,IAAI;GACF,MAAM,SAAS,MAAM,KAAK,KAAK,QAAQ,MAAM,IAAI,CAAC;GAElD,IAAI,CAAC,QACH,OAAO,SAAS,SAAS;GAG3B,MAAM,mBAAmB,MAAM,KAAK,eAAe,UAAU,SAAS,UAAU,MAAM;GAEtF,IAAI,kBAAkB,OAAO;GAE7B,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;GAEjD,MAAM,OAAO,QAAQ;GAErB,KAAK,SAAS,SAAS,UAAU,MAAM;GAEvC,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ;EAC1B,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,UAAU,KAAK;GAEpC,MAAM;EACR;CACF;;;;CAKA,MAAa,WAAW,EAAE,SAAS,YAAyB;EAC1D,IAAI;GACF,MAAM,MAAM,QAAQ,MAAM,IAAI;GAE9B,IAAI,CAAC,MAAM,QAAQ,GAAG,GACpB,OAAO,SAAS,WAAW,EACzB,OAAO,sBACT,CAAC;GAGH,MAAM,UAAU,MAAM,KAAK,WAAW,IAAI,EACxC,UAAU,UACR,MAAM,QACJ,MACA,IAAI,KAAK,OAAQ,QAAQ,KAAK,OAAO,EAAE,CAAC,IAAI,OAAO,EAAE,IAAI,EAAG,CAC9D,EACJ,CAAC;GAED,MAAM,QAAQ,IACZ,QAAQ,IAAI,OAAO,WAAW;IAC5B,IAAI,MAAM,KAAK,eAAe,UAAU,SAAS,UAAU,MAAM,GAC/D;IAGF,MAAM,KAAK,aAAa,SAAS,UAAU,MAAM;IACjD,MAAM,OAAO,QAAQ;IACrB,KAAK,SAAS,SAAS,UAAU,MAAM;GACzC,CAAC,CACH;GAEA,IAAI,KAAK,SAAS,WAAW,WAC3B,OAAO,KAAK,KAAK;IAAE;IAAS;GAAS,CAAC;GAGxC,OAAO,SAAS,QAAQ,EACtB,SAAS,QAAQ,OACnB,CAAC;EACH,SAAS,OAAoB;GAC3B,IAAI,MAAM,WAAW,cAAc,KAAK;GAExC,MAAM;EACR;CACF;;;;CAKA,MAAgB,aAAa,UAAmB,WAAqB,SAA0B,CAE/F;;;;CAKA,MAAgB,SAAS,UAAmB,WAAqB,SAA0B,CAE3F;;;;CAKA,MAAgB,aACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,SACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,aAAa,UAAmB,WAAqB,SAA0B,CAE/F;;;;CAKA,MAAgB,SAAS,UAAmB,WAAqB,SAA0B,CAE3F;;;;CAKA,MAAgB,YACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,QACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,WACd,UACA,WACA,SACA,YACc,CAEhB;;;;CAKA,MAAgB,OACd,UACA,WACA,SACA,YACc,CAEhB;;;;;;CAOA,AAAU,QAAQ,QAAuC,SAAiC;EACxF,MAAM,aAAc,KAAa;EAEjC,IAAI,YAAY,OAAO,aAAa,SAClC,OAAO,QAAQ,UAAU;EAG3B,IAAI,CAAC,kBAAkB,IAAI,KAAK,WAAW,GAAG;GAC5C,kBAAkB,IAAI,KAAK,WAAW;GAEtC,IAAI,KACF,WACA,cACA,KAAK,YAAY,OAAO,gCAAgC,SAAS,mGACnE;EACF;EAEA,IAAI,WAAW,UAAU,OAAO,QAAQ,IAAI;EAE5C,OAAO,WAAW,WAAW,QAAQ,gBAAgB,IAAI,QAAQ,kBAAkB;CACrF;;;;;CAMA,MAAgB,eACd,QACA,SACA,UACA,SACA;EACA,IAAI,CAAC,KAAK,WAAW,SAAS;EAE9B,KAAK,MAAM,cAAc,KAAK,WAAW,SAAS;GAChD,MAAM,SAAS,MAAM,WAAW;IAAE;IAAS;GAAS,CAAC;GAErD,IAAI,QACF,OAAO;EAEX;CAGF;AACF"}
@@ -0,0 +1,22 @@
1
+ //#region ../core/src/router/build-route-rate-limit.ts
2
+ /**
3
+ * Turn a route's `rateLimit` option into `@fastify/rate-limit` route config.
4
+ * `errorMessage` is not a plugin option, so it is mapped to
5
+ * `errorResponseBuilder`; the rest passes through unchanged.
6
+ */
7
+ function buildRouteRateLimit(rateLimit) {
8
+ const { errorMessage, ...rest } = rateLimit;
9
+ if (!errorMessage) return rest;
10
+ return {
11
+ ...rest,
12
+ errorResponseBuilder: () => ({
13
+ statusCode: 429,
14
+ error: "Too Many Requests",
15
+ message: errorMessage
16
+ })
17
+ };
18
+ }
19
+
20
+ //#endregion
21
+ export { buildRouteRateLimit };
22
+ //# sourceMappingURL=build-route-rate-limit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-route-rate-limit.mjs","names":[],"sources":["../../../../../../../core/src/router/build-route-rate-limit.ts"],"sourcesContent":["import type { RouteOptions } from \"./types\";\n\n/**\n * Turn a route's `rateLimit` option into `@fastify/rate-limit` route config.\n * `errorMessage` is not a plugin option, so it is mapped to\n * `errorResponseBuilder`; the rest passes through unchanged.\n */\nexport function buildRouteRateLimit(rateLimit: NonNullable<RouteOptions[\"rateLimit\"]>) {\n const { errorMessage, ...rest } = rateLimit;\n\n if (!errorMessage) {\n return rest;\n }\n\n return {\n ...rest,\n errorResponseBuilder: () => ({\n statusCode: 429,\n error: \"Too Many Requests\",\n message: errorMessage,\n }),\n };\n}\n"],"mappings":";;;;;;AAOA,SAAgB,oBAAoB,WAAmD;CACrF,MAAM,EAAE,cAAc,GAAG,SAAS;CAElC,IAAI,CAAC,cACH,OAAO;CAGT,OAAO;EACL,GAAG;EACH,6BAA6B;GAC3B,YAAY;GACZ,OAAO;GACP,SAAS;EACX;CACF;AACF"}
@@ -0,0 +1,60 @@
1
+ import { buildRouteRateLimit } from "./build-route-rate-limit.mjs";
2
+
3
+ //#region ../core/src/router/dev-dispatch.ts
4
+ /**
5
+ * Every verb the dev wildcard answers. Mirrors what production registers per
6
+ * route: `head`/`options` routes are ordinary registrations there, so the dev
7
+ * dispatcher must be reachable for them too.
8
+ */
9
+ const DEV_DISPATCH_METHODS = [
10
+ "GET",
11
+ "HEAD",
12
+ "POST",
13
+ "PUT",
14
+ "DELETE",
15
+ "PATCH",
16
+ "OPTIONS"
17
+ ];
18
+ /**
19
+ * The body of an unmatched request — Fastify's own default 404 shape, so an
20
+ * unknown URL looks the same in dev as it does in production.
21
+ */
22
+ function buildNotFoundBody(method, url) {
23
+ return {
24
+ message: `Route ${method}:${url} not found`,
25
+ error: "Not Found",
26
+ statusCode: 404
27
+ };
28
+ }
29
+ /**
30
+ * Per-route rate limiting for the dev wildcard dispatcher.
31
+ *
32
+ * `@fastify/rate-limit` reads `config.rateLimit` in its `onRoute` hook, which
33
+ * never sees individual routes behind a wildcard. It exposes the same limiter
34
+ * as `server.rateLimit(options)`, built here from the identical
35
+ * `buildRouteRateLimit` options `scan()` passes in production. Limiters are
36
+ * cached per rate-limit config + method so counters survive across requests.
37
+ */
38
+ function createDevRateLimiter(server) {
39
+ const limiters = /* @__PURE__ */ new WeakMap();
40
+ return async (route, request, reply) => {
41
+ const options = route.rateLimit;
42
+ const factory = server.rateLimit;
43
+ if (!options || typeof factory !== "function") return;
44
+ let byMethod = limiters.get(options);
45
+ if (!byMethod) {
46
+ byMethod = /* @__PURE__ */ new Map();
47
+ limiters.set(options, byMethod);
48
+ }
49
+ let limiter = byMethod.get(request.method);
50
+ if (!limiter) {
51
+ limiter = factory.call(server, buildRouteRateLimit(options));
52
+ byMethod.set(request.method, limiter);
53
+ }
54
+ return limiter(request, reply);
55
+ };
56
+ }
57
+
58
+ //#endregion
59
+ export { DEV_DISPATCH_METHODS, buildNotFoundBody, createDevRateLimiter };
60
+ //# sourceMappingURL=dev-dispatch.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-dispatch.mjs","names":[],"sources":["../../../../../../../core/src/router/dev-dispatch.ts"],"sourcesContent":["import type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { buildRouteRateLimit } from \"./build-route-rate-limit\";\nimport type { Route } from \"./types\";\n\n/**\n * Every verb the dev wildcard answers. Mirrors what production registers per\n * route: `head`/`options` routes are ordinary registrations there, so the dev\n * dispatcher must be reachable for them too.\n */\nexport const DEV_DISPATCH_METHODS = [\n \"GET\",\n \"HEAD\",\n \"POST\",\n \"PUT\",\n \"DELETE\",\n \"PATCH\",\n \"OPTIONS\",\n] as const;\n\n/**\n * The body of an unmatched request — Fastify's own default 404 shape, so an\n * unknown URL looks the same in dev as it does in production.\n */\nexport function buildNotFoundBody(method: string, url: string) {\n return {\n message: `Route ${method}:${url} not found`,\n error: \"Not Found\",\n statusCode: 404,\n };\n}\n\ntype RateLimitHandler = (request: FastifyRequest, reply: FastifyReply) => Promise<unknown>;\n\n/**\n * Per-route rate limiting for the dev wildcard dispatcher.\n *\n * `@fastify/rate-limit` reads `config.rateLimit` in its `onRoute` hook, which\n * never sees individual routes behind a wildcard. It exposes the same limiter\n * as `server.rateLimit(options)`, built here from the identical\n * `buildRouteRateLimit` options `scan()` passes in production. Limiters are\n * cached per rate-limit config + method so counters survive across requests.\n */\nexport function createDevRateLimiter(server: FastifyInstance) {\n const limiters = new WeakMap<object, Map<string, RateLimitHandler>>();\n\n return async (route: Route, request: FastifyRequest, reply: FastifyReply) => {\n const options = route.rateLimit;\n\n const factory = (server as any).rateLimit;\n\n if (!options || typeof factory !== \"function\") {\n return undefined;\n }\n\n let byMethod = limiters.get(options);\n\n if (!byMethod) {\n byMethod = new Map();\n limiters.set(options, byMethod);\n }\n\n // `all` routes are expanded per verb in production, each with its own counter.\n let limiter = byMethod.get(request.method);\n\n if (!limiter) {\n limiter = factory.call(server, buildRouteRateLimit(options)) as RateLimitHandler;\n byMethod.set(request.method, limiter);\n }\n\n return limiter(request, reply);\n };\n}\n"],"mappings":";;;;;;;;AASA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;AAMA,SAAgB,kBAAkB,QAAgB,KAAa;CAC7D,OAAO;EACL,SAAS,SAAS,OAAO,GAAG,IAAI;EAChC,OAAO;EACP,YAAY;CACd;AACF;;;;;;;;;;AAaA,SAAgB,qBAAqB,QAAyB;CAC5D,MAAM,2BAAW,IAAI,QAA+C;CAEpE,OAAO,OAAO,OAAc,SAAyB,UAAwB;EAC3E,MAAM,UAAU,MAAM;EAEtB,MAAM,UAAW,OAAe;EAEhC,IAAI,CAAC,WAAW,OAAO,YAAY,YACjC;EAGF,IAAI,WAAW,SAAS,IAAI,OAAO;EAEnC,IAAI,CAAC,UAAU;GACb,2BAAW,IAAI,IAAI;GACnB,SAAS,IAAI,SAAS,QAAQ;EAChC;EAGA,IAAI,UAAU,SAAS,IAAI,QAAQ,MAAM;EAEzC,IAAI,CAAC,SAAS;GACZ,UAAU,QAAQ,KAAK,QAAQ,oBAAoB,OAAO,CAAC;GAC3D,SAAS,IAAI,QAAQ,QAAQ,OAAO;EACtC;EAEA,OAAO,QAAQ,SAAS,KAAK;CAC/B;AACF"}
@@ -173,7 +173,10 @@ var RouteBuilder = class RouteBuilder {
173
173
  * Merge options with moreOptions
174
174
  */
175
175
  withOptions(options = {}) {
176
- return merge(this.moreOptions, options);
176
+ const merged = merge(this.moreOptions, options);
177
+ const middleware = [...this.moreOptions.middleware || [], ...options.middleware || []];
178
+ if (middleware.length > 0) merged.middleware = [...new Set(middleware)];
179
+ return merged;
177
180
  }
178
181
  };
179
182
 
@@ -1 +1 @@
1
- {"version":3,"file":"route-builder.mjs","names":[],"sources":["../../../../../../../core/src/router/route-builder.ts"],"sourcesContent":["import { merge } from \"@mongez/reinforcements\";\nimport { type Router } from \"./router\";\nimport type { RequestHandlerType, RouteOptions } from \"./types\";\n\nexport class RouteBuilder {\n protected addedRoutes = {\n get: false,\n post: false,\n put: false,\n delete: false,\n patch: false,\n options: false,\n head: false,\n };\n\n public constructor(\n private readonly router: Router,\n private readonly path: string,\n private readonly moreOptions: RouteOptions = {},\n ) {\n //\n }\n\n /**\n * Add a get method to the route\n */\n public get(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.get) {\n throw new Error(`Route ${this.path} already has a GET method`);\n }\n\n this.addedRoutes.get = true;\n this.router.get(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Get one resource, appends /:id to the path\n * For example: /posts/:id\n */\n public getOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.get(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a post method to the route\n */\n public post(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.post) {\n throw new Error(`Route ${this.path} already has a POST method`);\n }\n\n this.addedRoutes.post = true;\n this.router.post(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Post one resource, appends /:id to the path\n * For example: /posts/:id\n */\n public postOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.post(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a PUT request handler for current path\n */\n public put(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.put) {\n throw new Error(`Route ${this.path} already has a PUT method`);\n }\n\n this.addedRoutes.put = true;\n this.router.put(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Update one resource, appends /:id to the path\n */\n public updateOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.put(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a PATCH request handler for current path\n */\n public patch(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.patch) {\n throw new Error(`Route ${this.path} already has a PATCH method`);\n }\n\n this.addedRoutes.patch = true;\n this.router.patch(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Patch one resource, appends /:id to the path\n */\n public patchOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.patch(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a DELETE request handler for current path\n */\n public delete(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.delete) {\n throw new Error(`Route ${this.path} already has a DELETE method`);\n }\n\n this.addedRoutes.delete = true;\n this.router.delete(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Delete one resource, appends /:id to the path\n */\n public deleteOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.delete(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n // ============================================================================\n // RESTful Semantic Aliases\n // ============================================================================\n\n /**\n * List all resources (RESTful alias for GET collection)\n * @example router.route(\"/posts\").list(listPosts)\n */\n public list(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.get(handler, options);\n }\n\n /**\n * Create a new resource (RESTful alias for POST)\n * @example router.route(\"/posts\").create(createPost)\n */\n public create(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.post(handler, options);\n }\n\n /**\n * Show a single resource (RESTful alias for GET one)\n * @example router.route(\"/posts\").show(showPost) // GET /posts/:id\n */\n public show(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.getOne(handler, options);\n }\n\n /**\n * Update a resource (RESTful alias for PUT one)\n * @example router.route(\"/posts\").update(updatePost) // PUT /posts/:id\n */\n public update(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.updateOne(handler, options);\n }\n\n /**\n * Destroy a resource (RESTful alias for DELETE one)\n * @example router.route(\"/posts\").destroy(deletePost) // DELETE /posts/:id\n */\n public destroy(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.deleteOne(handler, options);\n }\n\n // ============================================================================\n // Route Nesting\n // ============================================================================\n\n /**\n * Create a nested route builder\n * Useful for building nested resources like /posts/:id/comments\n * @example\n * router.route(\"/posts/:id\")\n * .getOne(showPost)\n * .nest(\"/comments\")\n * .list(listComments) // GET /posts/:id/comments\n * .create(createComment); // POST /posts/:id/comments\n */\n public nest(path: string, options: RouteOptions = {}): RouteBuilder {\n const nestedPath = `${this.path}${path}`;\n const mergedOptions = this.withOptions(options);\n return new RouteBuilder(this.router, nestedPath, mergedOptions);\n }\n\n /**\n * Set up common RESTful CRUD routes in one call\n * @example\n * router.route(\"/posts\").crud({\n * list: listPosts, // GET /posts\n * create: createPost, // POST /posts\n * show: showPost, // GET /posts/:id\n * update: updatePost, // PUT /posts/:id\n * destroy: deletePost, // DELETE /posts/:id\n * patch: patchPost, // PATCH /posts/:id\n * });\n */\n public crud(\n handlers: {\n list?: RequestHandlerType;\n create?: RequestHandlerType;\n show?: RequestHandlerType;\n update?: RequestHandlerType;\n destroy?: RequestHandlerType;\n patch?: RequestHandlerType;\n },\n options: RouteOptions = {},\n ) {\n if (handlers.list) this.get(handlers.list, options);\n if (handlers.create) this.post(handlers.create, options);\n if (handlers.show) this.getOne(handlers.show, options);\n if (handlers.update) this.updateOne(handlers.update, options);\n if (handlers.destroy) this.deleteOne(handlers.destroy, options);\n if (handlers.patch) this.patchOne(handlers.patch, options);\n return this;\n }\n\n /**\n * Merge options with moreOptions\n */\n protected withOptions(options: RouteOptions = {}) {\n return merge(this.moreOptions, options);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"route-builder.mjs","names":[],"sources":["../../../../../../../core/src/router/route-builder.ts"],"sourcesContent":["import { merge } from \"@mongez/reinforcements\";\nimport { type Router } from \"./router\";\nimport type { RequestHandlerType, RouteOptions } from \"./types\";\n\nexport class RouteBuilder {\n protected addedRoutes = {\n get: false,\n post: false,\n put: false,\n delete: false,\n patch: false,\n options: false,\n head: false,\n };\n\n public constructor(\n private readonly router: Router,\n private readonly path: string,\n private readonly moreOptions: RouteOptions = {},\n ) {\n //\n }\n\n /**\n * Add a get method to the route\n */\n public get(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.get) {\n throw new Error(`Route ${this.path} already has a GET method`);\n }\n\n this.addedRoutes.get = true;\n this.router.get(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Get one resource, appends /:id to the path\n * For example: /posts/:id\n */\n public getOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.get(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a post method to the route\n */\n public post(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.post) {\n throw new Error(`Route ${this.path} already has a POST method`);\n }\n\n this.addedRoutes.post = true;\n this.router.post(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Post one resource, appends /:id to the path\n * For example: /posts/:id\n */\n public postOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.post(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a PUT request handler for current path\n */\n public put(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.put) {\n throw new Error(`Route ${this.path} already has a PUT method`);\n }\n\n this.addedRoutes.put = true;\n this.router.put(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Update one resource, appends /:id to the path\n */\n public updateOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.put(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a PATCH request handler for current path\n */\n public patch(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.patch) {\n throw new Error(`Route ${this.path} already has a PATCH method`);\n }\n\n this.addedRoutes.patch = true;\n this.router.patch(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Patch one resource, appends /:id to the path\n */\n public patchOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.patch(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Add a DELETE request handler for current path\n */\n public delete(handler: RequestHandlerType, options: RouteOptions = {}) {\n if (this.addedRoutes.delete) {\n throw new Error(`Route ${this.path} already has a DELETE method`);\n }\n\n this.addedRoutes.delete = true;\n this.router.delete(this.path, handler, this.withOptions(options));\n return this;\n }\n\n /**\n * Delete one resource, appends /:id to the path\n */\n public deleteOne(handler: RequestHandlerType, options: RouteOptions = {}) {\n this.router.delete(`${this.path}/:id`, handler, this.withOptions(options));\n return this;\n }\n\n // ============================================================================\n // RESTful Semantic Aliases\n // ============================================================================\n\n /**\n * List all resources (RESTful alias for GET collection)\n * @example router.route(\"/posts\").list(listPosts)\n */\n public list(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.get(handler, options);\n }\n\n /**\n * Create a new resource (RESTful alias for POST)\n * @example router.route(\"/posts\").create(createPost)\n */\n public create(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.post(handler, options);\n }\n\n /**\n * Show a single resource (RESTful alias for GET one)\n * @example router.route(\"/posts\").show(showPost) // GET /posts/:id\n */\n public show(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.getOne(handler, options);\n }\n\n /**\n * Update a resource (RESTful alias for PUT one)\n * @example router.route(\"/posts\").update(updatePost) // PUT /posts/:id\n */\n public update(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.updateOne(handler, options);\n }\n\n /**\n * Destroy a resource (RESTful alias for DELETE one)\n * @example router.route(\"/posts\").destroy(deletePost) // DELETE /posts/:id\n */\n public destroy(handler: RequestHandlerType, options: RouteOptions = {}) {\n return this.deleteOne(handler, options);\n }\n\n // ============================================================================\n // Route Nesting\n // ============================================================================\n\n /**\n * Create a nested route builder\n * Useful for building nested resources like /posts/:id/comments\n * @example\n * router.route(\"/posts/:id\")\n * .getOne(showPost)\n * .nest(\"/comments\")\n * .list(listComments) // GET /posts/:id/comments\n * .create(createComment); // POST /posts/:id/comments\n */\n public nest(path: string, options: RouteOptions = {}): RouteBuilder {\n const nestedPath = `${this.path}${path}`;\n const mergedOptions = this.withOptions(options);\n return new RouteBuilder(this.router, nestedPath, mergedOptions);\n }\n\n /**\n * Set up common RESTful CRUD routes in one call\n * @example\n * router.route(\"/posts\").crud({\n * list: listPosts, // GET /posts\n * create: createPost, // POST /posts\n * show: showPost, // GET /posts/:id\n * update: updatePost, // PUT /posts/:id\n * destroy: deletePost, // DELETE /posts/:id\n * patch: patchPost, // PATCH /posts/:id\n * });\n */\n public crud(\n handlers: {\n list?: RequestHandlerType;\n create?: RequestHandlerType;\n show?: RequestHandlerType;\n update?: RequestHandlerType;\n destroy?: RequestHandlerType;\n patch?: RequestHandlerType;\n },\n options: RouteOptions = {},\n ) {\n if (handlers.list) this.get(handlers.list, options);\n if (handlers.create) this.post(handlers.create, options);\n if (handlers.show) this.getOne(handlers.show, options);\n if (handlers.update) this.updateOne(handlers.update, options);\n if (handlers.destroy) this.deleteOne(handlers.destroy, options);\n if (handlers.patch) this.patchOne(handlers.patch, options);\n return this;\n }\n\n /**\n * Merge options with moreOptions\n */\n protected withOptions(options: RouteOptions = {}) {\n const merged = merge(this.moreOptions, options);\n\n // builder middleware runs first, then the verb/nested ones (deduplicated by reference)\n const middleware = [...(this.moreOptions.middleware || []), ...(options.middleware || [])];\n\n if (middleware.length > 0) {\n merged.middleware = [...new Set(middleware)];\n }\n\n return merged;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"route-registry.d.mts","names":[],"sources":["../../../../../../../core/src/router/route-registry.ts"],"mappings":";;;;;AAuBA;;cAAa,aAAA;EAAA,QACH,MAAA;;EAgDI;;;EAjCL,QAAA,CAAS,MAAA,EAAQ,KAAA;EAfhB;;;EAkCD,aAAA,CAAc,KAAA,EAAO,KAAA;EAnBZ;;;;EA8BT,IAAA,CACL,MAAA,UACA,GAAA;IACG,KAAA,EAAO,KAAA;IAAO,MAAA,EAAQ,MAAA;EAAA;EAAf;;;EAkBL,aAAA;AAAA"}
1
+ {"version":3,"file":"route-registry.d.mts","names":[],"sources":["../../../../../../../core/src/router/route-registry.ts"],"mappings":";;;;;AAuBA;;cAAa,aAAA;EAAA,QACH,MAAA;;EAgDI;;;EAjCL,QAAA,CAAS,MAAA,EAAQ,KAAA;EAfhB;;;EAkCD,aAAA,CAAc,KAAA,EAAO,KAAA;EAnBZ;;;;EA8BT,IAAA,CACL,MAAA,UACA,GAAA;IACG,KAAA,EAAO,KAAA;IAAO,MAAA,EAAQ,MAAA;EAAA;EAAf;;;EAqBL,aAAA;AAAA"}
@@ -24,7 +24,7 @@ var RouteRegistry = class {
24
24
  constructor() {
25
25
  this.router = FindMyWay({
26
26
  ignoreTrailingSlash: false,
27
- caseSensitive: false
27
+ caseSensitive: true
28
28
  });
29
29
  }
30
30
  /**
@@ -54,7 +54,7 @@ var RouteRegistry = class {
54
54
  */
55
55
  find(method, url) {
56
56
  const path = normalizeRequestPath(url.split("?")[0] ?? "/");
57
- const match = this.router.find(method, path);
57
+ const match = this.router.find(method, path) ?? (method === "HEAD" ? this.router.find("GET", path) : null);
58
58
  if (!match) return null;
59
59
  return match.handler(null, null, match.params, match.store, {});
60
60
  }