velocious 1.0.449 → 1.0.451

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 (636) hide show
  1. package/README.md +1 -1
  2. package/build/application.js +4 -4
  3. package/build/authorization/ability.js +8 -8
  4. package/build/authorization/base-resource.js +4 -4
  5. package/build/background-jobs/cron-expression.js +1 -1
  6. package/build/background-jobs/forked-runner-child.js +1 -1
  7. package/build/background-jobs/job-registry.js +1 -1
  8. package/build/background-jobs/job-runner.js +1 -1
  9. package/build/background-jobs/job.js +1 -1
  10. package/build/background-jobs/json-socket.js +4 -4
  11. package/build/background-jobs/main.js +21 -21
  12. package/build/background-jobs/scheduler.js +7 -7
  13. package/build/background-jobs/store.js +7 -7
  14. package/build/background-jobs/web/controller.js +2 -2
  15. package/build/background-jobs/worker.js +12 -12
  16. package/build/beacon/client.js +11 -8
  17. package/build/beacon/in-process-broker.js +2 -2
  18. package/build/beacon/in-process-client.js +2 -2
  19. package/build/beacon/server.js +3 -3
  20. package/build/cli/browser-cli.js +1 -1
  21. package/build/cli/commands/db/base-command.js +2 -2
  22. package/build/cli/index.js +2 -2
  23. package/build/cli/tenant-database-command-helper.js +3 -3
  24. package/build/cli/use-browser-cli.js +1 -1
  25. package/build/configuration-types.js +3 -1
  26. package/build/configuration.js +38 -38
  27. package/build/controller.js +8 -8
  28. package/build/current-configuration.js +1 -1
  29. package/build/database/annotations-async-hooks.js +2 -2
  30. package/build/database/annotations.js +3 -3
  31. package/build/database/drivers/base-column.js +1 -1
  32. package/build/database/drivers/base-foreign-key.js +1 -1
  33. package/build/database/drivers/base-table.js +1 -1
  34. package/build/database/drivers/base.js +10 -10
  35. package/build/database/drivers/mssql/index.js +1 -1
  36. package/build/database/drivers/mssql/table.js +1 -1
  37. package/build/database/drivers/mysql/index.js +3 -3
  38. package/build/database/drivers/mysql/query.js +1 -1
  39. package/build/database/drivers/mysql/table.js +1 -1
  40. package/build/database/drivers/pgsql/index.js +2 -2
  41. package/build/database/drivers/sqlite/base.js +16 -16
  42. package/build/database/drivers/sqlite/index.js +11 -11
  43. package/build/database/drivers/sqlite/index.native.js +1 -1
  44. package/build/database/drivers/sqlite/index.web.js +3 -3
  45. package/build/database/drivers/sqlite/query.js +1 -1
  46. package/build/database/drivers/sqlite/query.web.js +1 -1
  47. package/build/database/drivers/sqlite/sql/alter-table.js +2 -2
  48. package/build/database/drivers/sqlite/table.js +1 -1
  49. package/build/database/migrator/files-finder.js +1 -1
  50. package/build/database/migrator.js +6 -6
  51. package/build/database/pool/async-tracked-multi-connection.js +27 -27
  52. package/build/database/pool/base-methods-forward.js +4 -4
  53. package/build/database/pool/base.js +8 -8
  54. package/build/database/query/from-base.js +1 -1
  55. package/build/database/query/index.js +10 -10
  56. package/build/database/query/join-object.js +3 -3
  57. package/build/database/query/model-class-query.js +39 -39
  58. package/build/database/query/preloader/belongs-to.js +22 -22
  59. package/build/database/query/preloader/has-many.js +20 -20
  60. package/build/database/query/preloader/has-one.js +8 -8
  61. package/build/database/query/preloader/selection.js +3 -3
  62. package/build/database/query/preloader.js +8 -8
  63. package/build/database/query/query-data.js +9 -9
  64. package/build/database/query/where-model-class-hash.js +7 -7
  65. package/build/database/query/with-count.js +9 -9
  66. package/build/database/record/acts-as-list.js +15 -15
  67. package/build/database/record/attachments/handle.js +2 -2
  68. package/build/database/record/attachments/normalize-input.js +12 -12
  69. package/build/database/record/attachments/storage-drivers/s3.js +6 -6
  70. package/build/database/record/attachments/store.js +13 -13
  71. package/build/database/record/index.js +115 -121
  72. package/build/database/record/instance-relationships/base.js +11 -11
  73. package/build/database/record/instance-relationships/belongs-to.js +3 -3
  74. package/build/database/record/instance-relationships/has-many.js +8 -8
  75. package/build/database/record/instance-relationships/has-one.js +7 -7
  76. package/build/database/record/relationships/base.js +2 -2
  77. package/build/database/record/state-machine.js +7 -7
  78. package/build/database/record/validators/presence.js +1 -1
  79. package/build/database/record/validators/uniqueness.js +5 -5
  80. package/build/database/table-data/index.js +4 -4
  81. package/build/environment-handlers/base.js +2 -2
  82. package/build/environment-handlers/browser.js +7 -7
  83. package/build/environment-handlers/node/cli/commands/cli-command-context.js +1 -1
  84. package/build/environment-handlers/node/cli/commands/console.js +4 -4
  85. package/build/environment-handlers/node/cli/commands/db/schema/dump.js +1 -1
  86. package/build/environment-handlers/node/cli/commands/db/schema/load.js +3 -3
  87. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +836 -60
  88. package/build/environment-handlers/node.js +6 -6
  89. package/build/frontend-model-controller.js +81 -82
  90. package/build/frontend-model-resource/base-resource.js +78 -115
  91. package/build/frontend-models/base.js +162 -149
  92. package/build/frontend-models/clear-pending-debounced-callback.js +1 -1
  93. package/build/frontend-models/event-hook-models.js +2 -2
  94. package/build/frontend-models/outgoing-event-buffer.js +2 -2
  95. package/build/frontend-models/preloader.js +5 -5
  96. package/build/frontend-models/query.js +43 -38
  97. package/build/frontend-models/resource-definition.js +6 -6
  98. package/build/frontend-models/transport-serialization.js +11 -11
  99. package/build/frontend-models/use-destroyed-event.js +10 -10
  100. package/build/frontend-models/use-model-class-event.js +11 -11
  101. package/build/frontend-models/use-updated-event.js +11 -11
  102. package/build/frontend-models/websocket-channel.js +26 -17
  103. package/build/frontend-models/websocket-publishers.js +9 -9
  104. package/build/http-client/response.js +2 -2
  105. package/build/http-server/client/index.js +8 -8
  106. package/build/http-server/client/params-to-object.js +10 -10
  107. package/build/http-server/client/request-buffer/form-data-part.js +2 -2
  108. package/build/http-server/client/request-buffer/index.js +8 -8
  109. package/build/http-server/client/request-parser.js +3 -3
  110. package/build/http-server/client/request-runner.js +6 -6
  111. package/build/http-server/client/request-timing.js +5 -5
  112. package/build/http-server/client/request.js +1 -1
  113. package/build/http-server/client/response.js +5 -5
  114. package/build/http-server/client/websocket-request.js +4 -4
  115. package/build/http-server/client/websocket-session.js +18 -18
  116. package/build/http-server/development-reloader.js +5 -5
  117. package/build/http-server/index.js +12 -12
  118. package/build/http-server/remote-address.js +4 -4
  119. package/build/http-server/server-client.js +3 -3
  120. package/build/http-server/server-lock.js +4 -4
  121. package/build/http-server/websocket-channel-subscribers.js +3 -3
  122. package/build/http-server/websocket-channel.js +1 -1
  123. package/build/http-server/websocket-connection.js +1 -1
  124. package/build/http-server/websocket-event-log-store.js +6 -6
  125. package/build/http-server/websocket-events-host.js +2 -2
  126. package/build/http-server/worker-handler/in-process.js +7 -7
  127. package/build/http-server/worker-handler/index.js +4 -4
  128. package/build/http-server/worker-handler/worker-thread.js +3 -3
  129. package/build/logger/outputs/array-output.js +3 -3
  130. package/build/logger/outputs/console-output.js +1 -1
  131. package/build/logger/outputs/file-output.js +4 -4
  132. package/build/logger/outputs/stdout-output.js +1 -1
  133. package/build/logger.js +10 -10
  134. package/build/mailer/backends/smtp.js +3 -3
  135. package/build/mailer/base.js +8 -8
  136. package/build/mailer/delivery.js +4 -4
  137. package/build/plugins/sqljs-wasm-route-controller.js +1 -1
  138. package/build/routes/base-route.js +6 -6
  139. package/build/routes/basic-route.js +1 -1
  140. package/build/routes/hooks/frontend-model-command-route-hook.js +1 -1
  141. package/build/routes/index.js +1 -1
  142. package/build/routes/plugin-routes.js +1 -1
  143. package/build/routes/resolver.js +11 -11
  144. package/build/routes/resource-route.js +1 -1
  145. package/build/src/application.d.ts +3 -3
  146. package/build/src/application.d.ts.map +1 -1
  147. package/build/src/application.js +5 -5
  148. package/build/src/authorization/ability.d.ts +7 -7
  149. package/build/src/authorization/ability.d.ts.map +1 -1
  150. package/build/src/authorization/ability.js +9 -9
  151. package/build/src/authorization/base-resource.d.ts +1 -1
  152. package/build/src/authorization/base-resource.d.ts.map +1 -1
  153. package/build/src/authorization/base-resource.js +5 -5
  154. package/build/src/background-jobs/cron-expression.js +2 -2
  155. package/build/src/background-jobs/forked-runner-child.js +2 -2
  156. package/build/src/background-jobs/job-registry.d.ts +1 -1
  157. package/build/src/background-jobs/job-registry.d.ts.map +1 -1
  158. package/build/src/background-jobs/job-registry.js +2 -2
  159. package/build/src/background-jobs/job-runner.js +2 -2
  160. package/build/src/background-jobs/job.js +2 -2
  161. package/build/src/background-jobs/json-socket.d.ts +4 -4
  162. package/build/src/background-jobs/json-socket.d.ts.map +1 -1
  163. package/build/src/background-jobs/json-socket.js +5 -5
  164. package/build/src/background-jobs/main.d.ts +18 -18
  165. package/build/src/background-jobs/main.d.ts.map +1 -1
  166. package/build/src/background-jobs/main.js +22 -22
  167. package/build/src/background-jobs/scheduler.d.ts +5 -5
  168. package/build/src/background-jobs/scheduler.d.ts.map +1 -1
  169. package/build/src/background-jobs/scheduler.js +8 -8
  170. package/build/src/background-jobs/store.js +8 -8
  171. package/build/src/background-jobs/web/controller.js +3 -3
  172. package/build/src/background-jobs/worker.d.ts +6 -6
  173. package/build/src/background-jobs/worker.d.ts.map +1 -1
  174. package/build/src/background-jobs/worker.js +13 -13
  175. package/build/src/beacon/client.d.ts +4 -4
  176. package/build/src/beacon/client.d.ts.map +1 -1
  177. package/build/src/beacon/client.js +12 -9
  178. package/build/src/beacon/in-process-broker.d.ts.map +1 -1
  179. package/build/src/beacon/in-process-broker.js +3 -3
  180. package/build/src/beacon/in-process-client.d.ts +1 -1
  181. package/build/src/beacon/in-process-client.d.ts.map +1 -1
  182. package/build/src/beacon/in-process-client.js +3 -3
  183. package/build/src/beacon/server.d.ts +2 -2
  184. package/build/src/beacon/server.d.ts.map +1 -1
  185. package/build/src/beacon/server.js +4 -4
  186. package/build/src/cli/browser-cli.js +2 -2
  187. package/build/src/cli/commands/db/base-command.d.ts +2 -2
  188. package/build/src/cli/commands/db/base-command.d.ts.map +1 -1
  189. package/build/src/cli/commands/db/base-command.js +3 -3
  190. package/build/src/cli/index.js +3 -3
  191. package/build/src/cli/tenant-database-command-helper.d.ts +1 -1
  192. package/build/src/cli/tenant-database-command-helper.d.ts.map +1 -1
  193. package/build/src/cli/tenant-database-command-helper.js +4 -4
  194. package/build/src/cli/use-browser-cli.js +2 -2
  195. package/build/src/configuration-types.d.ts +12 -2
  196. package/build/src/configuration-types.d.ts.map +1 -1
  197. package/build/src/configuration-types.js +4 -2
  198. package/build/src/configuration.d.ts +14 -14
  199. package/build/src/configuration.d.ts.map +1 -1
  200. package/build/src/configuration.js +39 -39
  201. package/build/src/controller.js +10 -10
  202. package/build/src/current-configuration.js +2 -2
  203. package/build/src/database/annotations-async-hooks.js +3 -3
  204. package/build/src/database/annotations.d.ts.map +1 -1
  205. package/build/src/database/annotations.js +4 -4
  206. package/build/src/database/drivers/base-column.d.ts +1 -1
  207. package/build/src/database/drivers/base-column.d.ts.map +1 -1
  208. package/build/src/database/drivers/base-column.js +2 -2
  209. package/build/src/database/drivers/base-foreign-key.d.ts +1 -1
  210. package/build/src/database/drivers/base-foreign-key.d.ts.map +1 -1
  211. package/build/src/database/drivers/base-foreign-key.js +2 -2
  212. package/build/src/database/drivers/base-table.d.ts +1 -1
  213. package/build/src/database/drivers/base-table.d.ts.map +1 -1
  214. package/build/src/database/drivers/base-table.js +2 -2
  215. package/build/src/database/drivers/base.d.ts +6 -6
  216. package/build/src/database/drivers/base.d.ts.map +1 -1
  217. package/build/src/database/drivers/base.js +11 -11
  218. package/build/src/database/drivers/mssql/index.js +2 -2
  219. package/build/src/database/drivers/mssql/table.js +2 -2
  220. package/build/src/database/drivers/mysql/index.js +4 -4
  221. package/build/src/database/drivers/mysql/query.js +2 -2
  222. package/build/src/database/drivers/mysql/table.js +2 -2
  223. package/build/src/database/drivers/pgsql/index.js +3 -3
  224. package/build/src/database/drivers/sqlite/base.d.ts +3 -3
  225. package/build/src/database/drivers/sqlite/base.d.ts.map +1 -1
  226. package/build/src/database/drivers/sqlite/base.js +17 -17
  227. package/build/src/database/drivers/sqlite/index.d.ts +4 -4
  228. package/build/src/database/drivers/sqlite/index.d.ts.map +1 -1
  229. package/build/src/database/drivers/sqlite/index.js +12 -12
  230. package/build/src/database/drivers/sqlite/index.native.js +2 -2
  231. package/build/src/database/drivers/sqlite/index.web.d.ts +2 -2
  232. package/build/src/database/drivers/sqlite/index.web.d.ts.map +1 -1
  233. package/build/src/database/drivers/sqlite/index.web.js +4 -4
  234. package/build/src/database/drivers/sqlite/query.js +2 -2
  235. package/build/src/database/drivers/sqlite/query.web.js +2 -2
  236. package/build/src/database/drivers/sqlite/sql/alter-table.js +3 -3
  237. package/build/src/database/drivers/sqlite/table.js +2 -2
  238. package/build/src/database/migrator/files-finder.js +2 -2
  239. package/build/src/database/migrator.d.ts +1 -1
  240. package/build/src/database/migrator.d.ts.map +1 -1
  241. package/build/src/database/migrator.js +7 -7
  242. package/build/src/database/pool/async-tracked-multi-connection.d.ts +9 -9
  243. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  244. package/build/src/database/pool/async-tracked-multi-connection.js +28 -28
  245. package/build/src/database/pool/base-methods-forward.js +7 -7
  246. package/build/src/database/pool/base.d.ts +1 -1
  247. package/build/src/database/pool/base.d.ts.map +1 -1
  248. package/build/src/database/pool/base.js +9 -9
  249. package/build/src/database/query/from-base.d.ts +1 -1
  250. package/build/src/database/query/from-base.d.ts.map +1 -1
  251. package/build/src/database/query/from-base.js +2 -2
  252. package/build/src/database/query/index.d.ts +4 -4
  253. package/build/src/database/query/index.d.ts.map +1 -1
  254. package/build/src/database/query/index.js +11 -11
  255. package/build/src/database/query/join-object.js +4 -4
  256. package/build/src/database/query/model-class-query.d.ts +4 -4
  257. package/build/src/database/query/model-class-query.d.ts.map +1 -1
  258. package/build/src/database/query/model-class-query.js +40 -40
  259. package/build/src/database/query/preloader/belongs-to.js +23 -23
  260. package/build/src/database/query/preloader/has-many.js +21 -21
  261. package/build/src/database/query/preloader/has-one.js +9 -9
  262. package/build/src/database/query/preloader/selection.js +4 -4
  263. package/build/src/database/query/preloader.js +9 -9
  264. package/build/src/database/query/query-data.js +10 -10
  265. package/build/src/database/query/where-model-class-hash.js +8 -8
  266. package/build/src/database/query/with-count.js +10 -10
  267. package/build/src/database/record/acts-as-list.js +20 -20
  268. package/build/src/database/record/attachments/handle.d.ts +1 -1
  269. package/build/src/database/record/attachments/handle.d.ts.map +1 -1
  270. package/build/src/database/record/attachments/handle.js +3 -3
  271. package/build/src/database/record/attachments/normalize-input.js +15 -15
  272. package/build/src/database/record/attachments/storage-drivers/s3.js +7 -7
  273. package/build/src/database/record/attachments/store.d.ts +2 -2
  274. package/build/src/database/record/attachments/store.d.ts.map +1 -1
  275. package/build/src/database/record/attachments/store.js +14 -14
  276. package/build/src/database/record/index.d.ts +18 -25
  277. package/build/src/database/record/index.d.ts.map +1 -1
  278. package/build/src/database/record/index.js +135 -141
  279. package/build/src/database/record/instance-relationships/base.d.ts +3 -3
  280. package/build/src/database/record/instance-relationships/base.d.ts.map +1 -1
  281. package/build/src/database/record/instance-relationships/base.js +13 -13
  282. package/build/src/database/record/instance-relationships/belongs-to.js +4 -4
  283. package/build/src/database/record/instance-relationships/has-many.js +9 -9
  284. package/build/src/database/record/instance-relationships/has-one.d.ts +1 -1
  285. package/build/src/database/record/instance-relationships/has-one.d.ts.map +1 -1
  286. package/build/src/database/record/instance-relationships/has-one.js +8 -8
  287. package/build/src/database/record/relationships/base.js +4 -4
  288. package/build/src/database/record/state-machine.js +8 -8
  289. package/build/src/database/record/validators/presence.js +2 -2
  290. package/build/src/database/record/validators/uniqueness.js +6 -6
  291. package/build/src/database/table-data/index.d.ts +4 -4
  292. package/build/src/database/table-data/index.d.ts.map +1 -1
  293. package/build/src/database/table-data/index.js +5 -5
  294. package/build/src/environment-handlers/base.js +3 -3
  295. package/build/src/environment-handlers/browser.d.ts +3 -3
  296. package/build/src/environment-handlers/browser.d.ts.map +1 -1
  297. package/build/src/environment-handlers/browser.js +8 -8
  298. package/build/src/environment-handlers/node/cli/commands/cli-command-context.js +2 -2
  299. package/build/src/environment-handlers/node/cli/commands/console.d.ts.map +1 -1
  300. package/build/src/environment-handlers/node/cli/commands/console.js +5 -5
  301. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.d.ts +1 -1
  302. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.d.ts.map +1 -1
  303. package/build/src/environment-handlers/node/cli/commands/db/schema/dump.js +2 -2
  304. package/build/src/environment-handlers/node/cli/commands/db/schema/load.d.ts +1 -1
  305. package/build/src/environment-handlers/node/cli/commands/db/schema/load.d.ts.map +1 -1
  306. package/build/src/environment-handlers/node/cli/commands/db/schema/load.js +4 -4
  307. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +285 -12
  308. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  309. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +712 -61
  310. package/build/src/environment-handlers/node.d.ts +2 -2
  311. package/build/src/environment-handlers/node.d.ts.map +1 -1
  312. package/build/src/environment-handlers/node.js +7 -7
  313. package/build/src/frontend-model-controller.d.ts +5 -6
  314. package/build/src/frontend-model-controller.d.ts.map +1 -1
  315. package/build/src/frontend-model-controller.js +88 -89
  316. package/build/src/frontend-model-resource/base-resource.d.ts +36 -27
  317. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  318. package/build/src/frontend-model-resource/base-resource.js +67 -120
  319. package/build/src/frontend-models/base.d.ts +67 -59
  320. package/build/src/frontend-models/base.d.ts.map +1 -1
  321. package/build/src/frontend-models/base.js +171 -158
  322. package/build/src/frontend-models/clear-pending-debounced-callback.js +2 -2
  323. package/build/src/frontend-models/event-hook-models.d.ts.map +1 -1
  324. package/build/src/frontend-models/event-hook-models.js +3 -3
  325. package/build/src/frontend-models/outgoing-event-buffer.d.ts +1 -1
  326. package/build/src/frontend-models/outgoing-event-buffer.d.ts.map +1 -1
  327. package/build/src/frontend-models/outgoing-event-buffer.js +3 -3
  328. package/build/src/frontend-models/preloader.js +6 -6
  329. package/build/src/frontend-models/query.d.ts +16 -8
  330. package/build/src/frontend-models/query.d.ts.map +1 -1
  331. package/build/src/frontend-models/query.js +43 -39
  332. package/build/src/frontend-models/resource-definition.js +7 -7
  333. package/build/src/frontend-models/transport-serialization.js +12 -12
  334. package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
  335. package/build/src/frontend-models/use-destroyed-event.js +11 -11
  336. package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
  337. package/build/src/frontend-models/use-model-class-event.js +12 -12
  338. package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
  339. package/build/src/frontend-models/use-updated-event.js +12 -12
  340. package/build/src/frontend-models/websocket-channel.d.ts +6 -1
  341. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  342. package/build/src/frontend-models/websocket-channel.js +26 -18
  343. package/build/src/frontend-models/websocket-publishers.js +11 -11
  344. package/build/src/http-client/response.d.ts +2 -2
  345. package/build/src/http-client/response.d.ts.map +1 -1
  346. package/build/src/http-client/response.js +3 -3
  347. package/build/src/http-server/client/index.d.ts +1 -1
  348. package/build/src/http-server/client/index.d.ts.map +1 -1
  349. package/build/src/http-server/client/index.js +9 -9
  350. package/build/src/http-server/client/params-to-object.js +11 -11
  351. package/build/src/http-server/client/request-buffer/form-data-part.d.ts +2 -2
  352. package/build/src/http-server/client/request-buffer/form-data-part.d.ts.map +1 -1
  353. package/build/src/http-server/client/request-buffer/form-data-part.js +3 -3
  354. package/build/src/http-server/client/request-buffer/index.d.ts +4 -4
  355. package/build/src/http-server/client/request-buffer/index.d.ts.map +1 -1
  356. package/build/src/http-server/client/request-buffer/index.js +9 -9
  357. package/build/src/http-server/client/request-parser.d.ts +1 -1
  358. package/build/src/http-server/client/request-parser.d.ts.map +1 -1
  359. package/build/src/http-server/client/request-parser.js +4 -4
  360. package/build/src/http-server/client/request-runner.js +7 -7
  361. package/build/src/http-server/client/request-timing.d.ts +5 -5
  362. package/build/src/http-server/client/request-timing.d.ts.map +1 -1
  363. package/build/src/http-server/client/request-timing.js +6 -6
  364. package/build/src/http-server/client/request.js +2 -2
  365. package/build/src/http-server/client/response.d.ts +3 -3
  366. package/build/src/http-server/client/response.d.ts.map +1 -1
  367. package/build/src/http-server/client/response.js +6 -6
  368. package/build/src/http-server/client/websocket-request.d.ts +3 -3
  369. package/build/src/http-server/client/websocket-request.d.ts.map +1 -1
  370. package/build/src/http-server/client/websocket-request.js +5 -5
  371. package/build/src/http-server/client/websocket-session.d.ts +6 -6
  372. package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
  373. package/build/src/http-server/client/websocket-session.js +19 -19
  374. package/build/src/http-server/development-reloader.d.ts +3 -3
  375. package/build/src/http-server/development-reloader.d.ts.map +1 -1
  376. package/build/src/http-server/development-reloader.js +6 -6
  377. package/build/src/http-server/index.d.ts +7 -7
  378. package/build/src/http-server/index.d.ts.map +1 -1
  379. package/build/src/http-server/index.js +13 -13
  380. package/build/src/http-server/remote-address.js +6 -6
  381. package/build/src/http-server/server-client.js +4 -4
  382. package/build/src/http-server/server-lock.js +5 -5
  383. package/build/src/http-server/websocket-channel-subscribers.d.ts +1 -1
  384. package/build/src/http-server/websocket-channel-subscribers.d.ts.map +1 -1
  385. package/build/src/http-server/websocket-channel-subscribers.js +4 -4
  386. package/build/src/http-server/websocket-channel.d.ts +1 -1
  387. package/build/src/http-server/websocket-channel.d.ts.map +1 -1
  388. package/build/src/http-server/websocket-channel.js +2 -2
  389. package/build/src/http-server/websocket-connection.d.ts +1 -1
  390. package/build/src/http-server/websocket-connection.d.ts.map +1 -1
  391. package/build/src/http-server/websocket-connection.js +2 -2
  392. package/build/src/http-server/websocket-event-log-store.d.ts +1 -1
  393. package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
  394. package/build/src/http-server/websocket-event-log-store.js +7 -7
  395. package/build/src/http-server/websocket-events-host.d.ts +1 -1
  396. package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
  397. package/build/src/http-server/websocket-events-host.js +3 -3
  398. package/build/src/http-server/worker-handler/in-process.d.ts +3 -3
  399. package/build/src/http-server/worker-handler/in-process.d.ts.map +1 -1
  400. package/build/src/http-server/worker-handler/in-process.js +9 -9
  401. package/build/src/http-server/worker-handler/index.d.ts +4 -4
  402. package/build/src/http-server/worker-handler/index.d.ts.map +1 -1
  403. package/build/src/http-server/worker-handler/index.js +5 -5
  404. package/build/src/http-server/worker-handler/worker-thread.d.ts +2 -2
  405. package/build/src/http-server/worker-handler/worker-thread.d.ts.map +1 -1
  406. package/build/src/http-server/worker-handler/worker-thread.js +4 -4
  407. package/build/src/logger/outputs/array-output.d.ts +3 -3
  408. package/build/src/logger/outputs/array-output.d.ts.map +1 -1
  409. package/build/src/logger/outputs/array-output.js +4 -4
  410. package/build/src/logger/outputs/console-output.d.ts +1 -1
  411. package/build/src/logger/outputs/console-output.d.ts.map +1 -1
  412. package/build/src/logger/outputs/console-output.js +2 -2
  413. package/build/src/logger/outputs/file-output.d.ts +4 -4
  414. package/build/src/logger/outputs/file-output.d.ts.map +1 -1
  415. package/build/src/logger/outputs/file-output.js +5 -5
  416. package/build/src/logger/outputs/stdout-output.d.ts.map +1 -1
  417. package/build/src/logger/outputs/stdout-output.js +2 -2
  418. package/build/src/logger.d.ts.map +1 -1
  419. package/build/src/logger.js +11 -11
  420. package/build/src/mailer/backends/smtp.d.ts.map +1 -1
  421. package/build/src/mailer/backends/smtp.js +5 -5
  422. package/build/src/mailer/base.js +11 -11
  423. package/build/src/mailer/delivery.d.ts +3 -3
  424. package/build/src/mailer/delivery.d.ts.map +1 -1
  425. package/build/src/mailer/delivery.js +5 -5
  426. package/build/src/plugins/sqljs-wasm-route-controller.js +2 -2
  427. package/build/src/routes/base-route.d.ts +6 -6
  428. package/build/src/routes/base-route.d.ts.map +1 -1
  429. package/build/src/routes/base-route.js +7 -7
  430. package/build/src/routes/basic-route.js +2 -2
  431. package/build/src/routes/hooks/frontend-model-command-route-hook.js +2 -2
  432. package/build/src/routes/index.js +2 -2
  433. package/build/src/routes/plugin-routes.js +2 -2
  434. package/build/src/routes/resolver.d.ts +1 -1
  435. package/build/src/routes/resolver.d.ts.map +1 -1
  436. package/build/src/routes/resolver.js +13 -13
  437. package/build/src/routes/resource-route.d.ts +1 -1
  438. package/build/src/routes/resource-route.d.ts.map +1 -1
  439. package/build/src/routes/resource-route.js +2 -2
  440. package/build/src/testing/browser-frontend-model-event-hook-scenarios.d.ts.map +1 -1
  441. package/build/src/testing/browser-frontend-model-event-hook-scenarios.js +8 -8
  442. package/build/src/testing/expect-utils.js +13 -13
  443. package/build/src/testing/expect.d.ts +1 -1
  444. package/build/src/testing/expect.d.ts.map +1 -1
  445. package/build/src/testing/expect.js +9 -9
  446. package/build/src/testing/test-files-finder.d.ts +8 -8
  447. package/build/src/testing/test-files-finder.d.ts.map +1 -1
  448. package/build/src/testing/test-files-finder.js +9 -9
  449. package/build/src/testing/test-filter-parser.js +3 -3
  450. package/build/src/testing/test-runner.d.ts +2 -2
  451. package/build/src/testing/test-runner.d.ts.map +1 -1
  452. package/build/src/testing/test-runner.js +17 -17
  453. package/build/src/testing/test-suite-splitter.js +2 -2
  454. package/build/src/testing/test.d.ts +2 -2
  455. package/build/src/testing/test.d.ts.map +1 -1
  456. package/build/src/testing/test.js +16 -16
  457. package/build/src/utils/backtrace-cleaner.d.ts +1 -1
  458. package/build/src/utils/backtrace-cleaner.d.ts.map +1 -1
  459. package/build/src/utils/backtrace-cleaner.js +2 -2
  460. package/build/src/utils/format-value.js +2 -2
  461. package/build/src/utils/model-scope.js +2 -2
  462. package/build/src/utils/ransack.js +27 -27
  463. package/build/src/utils/with-tracked-stack-async-hooks.js +8 -8
  464. package/build/src/utils/with-tracked-stack.js +4 -4
  465. package/build/testing/browser-frontend-model-event-hook-scenarios.js +7 -7
  466. package/build/testing/expect-utils.js +11 -11
  467. package/build/testing/expect.js +7 -7
  468. package/build/testing/test-files-finder.js +8 -8
  469. package/build/testing/test-filter-parser.js +2 -2
  470. package/build/testing/test-runner.js +16 -16
  471. package/build/testing/test-suite-splitter.js +1 -1
  472. package/build/testing/test.js +15 -15
  473. package/build/utils/backtrace-cleaner.js +1 -1
  474. package/build/utils/format-value.js +1 -1
  475. package/build/utils/model-scope.js +1 -1
  476. package/build/utils/ransack.js +26 -26
  477. package/build/utils/with-tracked-stack-async-hooks.js +7 -7
  478. package/build/utils/with-tracked-stack.js +3 -3
  479. package/package.json +2 -1
  480. package/src/application.js +4 -4
  481. package/src/authorization/ability.js +8 -8
  482. package/src/authorization/base-resource.js +4 -4
  483. package/src/background-jobs/cron-expression.js +1 -1
  484. package/src/background-jobs/forked-runner-child.js +1 -1
  485. package/src/background-jobs/job-registry.js +1 -1
  486. package/src/background-jobs/job-runner.js +1 -1
  487. package/src/background-jobs/job.js +1 -1
  488. package/src/background-jobs/json-socket.js +4 -4
  489. package/src/background-jobs/main.js +21 -21
  490. package/src/background-jobs/scheduler.js +7 -7
  491. package/src/background-jobs/store.js +7 -7
  492. package/src/background-jobs/web/controller.js +2 -2
  493. package/src/background-jobs/worker.js +12 -12
  494. package/src/beacon/client.js +11 -8
  495. package/src/beacon/in-process-broker.js +2 -2
  496. package/src/beacon/in-process-client.js +2 -2
  497. package/src/beacon/server.js +3 -3
  498. package/src/cli/browser-cli.js +1 -1
  499. package/src/cli/commands/db/base-command.js +2 -2
  500. package/src/cli/index.js +2 -2
  501. package/src/cli/tenant-database-command-helper.js +3 -3
  502. package/src/cli/use-browser-cli.js +1 -1
  503. package/src/configuration-types.js +3 -1
  504. package/src/configuration.js +38 -38
  505. package/src/controller.js +8 -8
  506. package/src/current-configuration.js +1 -1
  507. package/src/database/annotations-async-hooks.js +2 -2
  508. package/src/database/annotations.js +3 -3
  509. package/src/database/drivers/base-column.js +1 -1
  510. package/src/database/drivers/base-foreign-key.js +1 -1
  511. package/src/database/drivers/base-table.js +1 -1
  512. package/src/database/drivers/base.js +10 -10
  513. package/src/database/drivers/mssql/index.js +1 -1
  514. package/src/database/drivers/mssql/table.js +1 -1
  515. package/src/database/drivers/mysql/index.js +3 -3
  516. package/src/database/drivers/mysql/query.js +1 -1
  517. package/src/database/drivers/mysql/table.js +1 -1
  518. package/src/database/drivers/pgsql/index.js +2 -2
  519. package/src/database/drivers/sqlite/base.js +16 -16
  520. package/src/database/drivers/sqlite/index.js +11 -11
  521. package/src/database/drivers/sqlite/index.native.js +1 -1
  522. package/src/database/drivers/sqlite/index.web.js +3 -3
  523. package/src/database/drivers/sqlite/query.js +1 -1
  524. package/src/database/drivers/sqlite/query.web.js +1 -1
  525. package/src/database/drivers/sqlite/sql/alter-table.js +2 -2
  526. package/src/database/drivers/sqlite/table.js +1 -1
  527. package/src/database/migrator/files-finder.js +1 -1
  528. package/src/database/migrator.js +6 -6
  529. package/src/database/pool/async-tracked-multi-connection.js +27 -27
  530. package/src/database/pool/base-methods-forward.js +4 -4
  531. package/src/database/pool/base.js +8 -8
  532. package/src/database/query/from-base.js +1 -1
  533. package/src/database/query/index.js +10 -10
  534. package/src/database/query/join-object.js +3 -3
  535. package/src/database/query/model-class-query.js +39 -39
  536. package/src/database/query/preloader/belongs-to.js +22 -22
  537. package/src/database/query/preloader/has-many.js +20 -20
  538. package/src/database/query/preloader/has-one.js +8 -8
  539. package/src/database/query/preloader/selection.js +3 -3
  540. package/src/database/query/preloader.js +8 -8
  541. package/src/database/query/query-data.js +9 -9
  542. package/src/database/query/where-model-class-hash.js +7 -7
  543. package/src/database/query/with-count.js +9 -9
  544. package/src/database/record/acts-as-list.js +15 -15
  545. package/src/database/record/attachments/handle.js +2 -2
  546. package/src/database/record/attachments/normalize-input.js +12 -12
  547. package/src/database/record/attachments/storage-drivers/s3.js +6 -6
  548. package/src/database/record/attachments/store.js +13 -13
  549. package/src/database/record/index.js +115 -121
  550. package/src/database/record/instance-relationships/base.js +11 -11
  551. package/src/database/record/instance-relationships/belongs-to.js +3 -3
  552. package/src/database/record/instance-relationships/has-many.js +8 -8
  553. package/src/database/record/instance-relationships/has-one.js +7 -7
  554. package/src/database/record/relationships/base.js +2 -2
  555. package/src/database/record/state-machine.js +7 -7
  556. package/src/database/record/validators/presence.js +1 -1
  557. package/src/database/record/validators/uniqueness.js +5 -5
  558. package/src/database/table-data/index.js +4 -4
  559. package/src/environment-handlers/base.js +2 -2
  560. package/src/environment-handlers/browser.js +7 -7
  561. package/src/environment-handlers/node/cli/commands/cli-command-context.js +1 -1
  562. package/src/environment-handlers/node/cli/commands/console.js +4 -4
  563. package/src/environment-handlers/node/cli/commands/db/schema/dump.js +1 -1
  564. package/src/environment-handlers/node/cli/commands/db/schema/load.js +3 -3
  565. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +836 -60
  566. package/src/environment-handlers/node.js +6 -6
  567. package/src/frontend-model-controller.js +81 -82
  568. package/src/frontend-model-resource/base-resource.js +78 -115
  569. package/src/frontend-models/base.js +162 -149
  570. package/src/frontend-models/clear-pending-debounced-callback.js +1 -1
  571. package/src/frontend-models/event-hook-models.js +2 -2
  572. package/src/frontend-models/outgoing-event-buffer.js +2 -2
  573. package/src/frontend-models/preloader.js +5 -5
  574. package/src/frontend-models/query.js +43 -38
  575. package/src/frontend-models/resource-definition.js +6 -6
  576. package/src/frontend-models/transport-serialization.js +11 -11
  577. package/src/frontend-models/use-destroyed-event.js +10 -10
  578. package/src/frontend-models/use-model-class-event.js +11 -11
  579. package/src/frontend-models/use-updated-event.js +11 -11
  580. package/src/frontend-models/websocket-channel.js +26 -17
  581. package/src/frontend-models/websocket-publishers.js +9 -9
  582. package/src/http-client/response.js +2 -2
  583. package/src/http-server/client/index.js +8 -8
  584. package/src/http-server/client/params-to-object.js +10 -10
  585. package/src/http-server/client/request-buffer/form-data-part.js +2 -2
  586. package/src/http-server/client/request-buffer/index.js +8 -8
  587. package/src/http-server/client/request-parser.js +3 -3
  588. package/src/http-server/client/request-runner.js +6 -6
  589. package/src/http-server/client/request-timing.js +5 -5
  590. package/src/http-server/client/request.js +1 -1
  591. package/src/http-server/client/response.js +5 -5
  592. package/src/http-server/client/websocket-request.js +4 -4
  593. package/src/http-server/client/websocket-session.js +18 -18
  594. package/src/http-server/development-reloader.js +5 -5
  595. package/src/http-server/index.js +12 -12
  596. package/src/http-server/remote-address.js +4 -4
  597. package/src/http-server/server-client.js +3 -3
  598. package/src/http-server/server-lock.js +4 -4
  599. package/src/http-server/websocket-channel-subscribers.js +3 -3
  600. package/src/http-server/websocket-channel.js +1 -1
  601. package/src/http-server/websocket-connection.js +1 -1
  602. package/src/http-server/websocket-event-log-store.js +6 -6
  603. package/src/http-server/websocket-events-host.js +2 -2
  604. package/src/http-server/worker-handler/in-process.js +7 -7
  605. package/src/http-server/worker-handler/index.js +4 -4
  606. package/src/http-server/worker-handler/worker-thread.js +3 -3
  607. package/src/logger/outputs/array-output.js +3 -3
  608. package/src/logger/outputs/console-output.js +1 -1
  609. package/src/logger/outputs/file-output.js +4 -4
  610. package/src/logger/outputs/stdout-output.js +1 -1
  611. package/src/logger.js +10 -10
  612. package/src/mailer/backends/smtp.js +3 -3
  613. package/src/mailer/base.js +8 -8
  614. package/src/mailer/delivery.js +4 -4
  615. package/src/plugins/sqljs-wasm-route-controller.js +1 -1
  616. package/src/routes/base-route.js +6 -6
  617. package/src/routes/basic-route.js +1 -1
  618. package/src/routes/hooks/frontend-model-command-route-hook.js +1 -1
  619. package/src/routes/index.js +1 -1
  620. package/src/routes/plugin-routes.js +1 -1
  621. package/src/routes/resolver.js +11 -11
  622. package/src/routes/resource-route.js +1 -1
  623. package/src/testing/browser-frontend-model-event-hook-scenarios.js +7 -7
  624. package/src/testing/expect-utils.js +11 -11
  625. package/src/testing/expect.js +7 -7
  626. package/src/testing/test-files-finder.js +8 -8
  627. package/src/testing/test-filter-parser.js +2 -2
  628. package/src/testing/test-runner.js +16 -16
  629. package/src/testing/test-suite-splitter.js +1 -1
  630. package/src/testing/test.js +15 -15
  631. package/src/utils/backtrace-cleaner.js +1 -1
  632. package/src/utils/format-value.js +1 -1
  633. package/src/utils/model-scope.js +1 -1
  634. package/src/utils/ransack.js +26 -26
  635. package/src/utils/with-tracked-stack-async-hooks.js +7 -7
  636. package/src/utils/with-tracked-stack.js +3 -3
@@ -11,18 +11,26 @@ import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigura
11
11
  * @property {string} [columnType] - Column type.
12
12
  * @property {string} [sqlType] - SQL type.
13
13
  * @property {string} [dataType] - Data type.
14
+ * @property {string} [jsDocType] - Exact JSDoc type.
14
15
  * @property {string} [name] - Attribute name when configured as an array entry.
15
16
  * @property {boolean} [null] - Whether null is allowed.
17
+ * @property {boolean} [selectedByDefault] - Whether the attribute is selected by default.
16
18
  * @property {() => string} [getType] - Returns column type.
17
19
  * @property {() => boolean} [getNull] - Returns whether null is allowed.
18
20
  */
19
21
  /**
20
22
  * Permit spec returned by frontend-model resources during generation.
21
- * @typedef {Array<string | Record<string, object>>} FrontendModelGeneratorPermitSpec
23
+ * @typedef {Array<string | Record<string, FrontendModelGeneratorPermitSpec>>} FrontendModelGeneratorPermitSpec
22
24
  */
23
25
 
24
26
  /** Node CLI command that generates frontend model classes from backend project resource config. */
25
27
  export default class DbGenerateFrontendModels extends BaseCommand {
28
+ /** @type {Map<string, string> | null} */
29
+ _resourceMethodReturnTypes = null
30
+
31
+ /** @type {Map<string, string[]> | null} */
32
+ _resourceMethodParameterTypes = null
33
+
26
34
  /**
27
35
  * Runs execute.
28
36
  * @returns {Promise<void>} - Resolves when files are generated.
@@ -45,15 +53,15 @@ export default class DbGenerateFrontendModels extends BaseCommand {
45
53
 
46
54
  /**
47
55
  * Ensured directories.
48
- @type {Set<string>} */
56
+ * @type {Set<string>} */
49
57
  const ensuredDirectories = new Set()
50
58
  /**
51
59
  * Generated model names by directory.
52
- @type {Map<string, Set<string>>} */
60
+ * @type {Map<string, Set<string>>} */
53
61
  const generatedModelNamesByDirectory = new Map()
54
62
  /**
55
63
  * Generated files by directory.
56
- @type {Map<string, Array<{className: string, fileName: string}>>} */
64
+ * @type {Map<string, Array<{className: string, fileName: string}>>} */
57
65
  const generatedFilesByDirectory = new Map()
58
66
 
59
67
  for (const backendProject of backendProjects) {
@@ -97,7 +105,9 @@ export default class DbGenerateFrontendModels extends BaseCommand {
97
105
  throw new Error(`Invalid frontend model resource definition for '${className}'`)
98
106
  }
99
107
 
100
- this.validateModelConfig({availableFrontendModelClassNames, className, modelConfig, resourceClass: frontendModelResourceClassFromDefinition(resources[modelClassName])})
108
+ const resourceClass = frontendModelResourceClassFromDefinition(resources[modelClassName])
109
+
110
+ this.validateModelConfig({availableFrontendModelClassNames, className, modelConfig, resourceClass})
101
111
 
102
112
  if (generatedModelNames.has(className)) {
103
113
  throw new Error(`Duplicate frontend model definition for '${className}'`)
@@ -105,12 +115,12 @@ export default class DbGenerateFrontendModels extends BaseCommand {
105
115
 
106
116
  generatedModelNames.add(className)
107
117
 
108
- const fileContent = this.buildModelFileContent({
118
+ const fileContent = await this.buildModelFileContent({
109
119
  className,
110
120
  importPath,
111
- modelClass: configuration.getModelClasses()[className],
121
+ modelClass: resourceClass?.ModelClass || configuration.getModelClasses()[className],
112
122
  modelConfig,
113
- resourceClass: frontendModelResourceClassFromDefinition(resources[modelClassName])
123
+ resourceClass
114
124
  })
115
125
 
116
126
  await fs.writeFile(filePath, fileContent)
@@ -192,7 +202,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
192
202
  availableFrontendModelClassNames(resources) {
193
203
  /**
194
204
  * Class names.
195
- @type {Set<string>} */
205
+ * @type {Set<string>} */
196
206
  const classNames = new Set()
197
207
 
198
208
  for (const resourceModelName in resources) {
@@ -236,10 +246,10 @@ export default class DbGenerateFrontendModels extends BaseCommand {
236
246
  * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
237
247
  * @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
238
248
  * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
239
- * @returns {string} - Generated file content.
249
+ * @returns {Promise<string>} - Generated file content.
240
250
  */
241
- buildModelFileContent({className, importPath, modelClass, modelConfig, resourceClass}) {
242
- const attributes = this.attributeDefinitionsForModel({modelClass, modelConfig})
251
+ async buildModelFileContent({className, importPath, modelClass, modelConfig, resourceClass}) {
252
+ const attributes = await this.attributeDefinitionsForModel({className, modelClass, modelConfig, resourceClass})
243
253
  const relationships = this.relationshipsForModel({className, modelConfig, resourceClass})
244
254
  const attachments = modelConfig.attachments && typeof modelConfig.attachments === "object"
245
255
  ? modelConfig.attachments
@@ -311,8 +321,8 @@ export default class DbGenerateFrontendModels extends BaseCommand {
311
321
  }
312
322
  fileContent += " */\n"
313
323
  }
314
- fileContent += this.writeAttributesTypedef({attributes, attributesTypeName, nestedWriteTypes, permittedParams: permittedCreateParams, typeName: createAttributesTypeName})
315
- fileContent += this.writeAttributesTypedef({attributes, attributesTypeName, nestedWriteTypes, permittedParams: permittedUpdateParams, typeName: updateAttributesTypeName})
324
+ fileContent += await this.writeAttributesTypedef({attributes, attributesTypeName, modelClass, nestedWriteTypes, permittedParams: permittedCreateParams, resourceClass, typeName: createAttributesTypeName})
325
+ fileContent += await this.writeAttributesTypedef({attributes, attributesTypeName, modelClass, nestedWriteTypes, permittedParams: permittedUpdateParams, resourceClass, typeName: updateAttributesTypeName})
316
326
  fileContent += "/**\n"
317
327
  fileContent += ` * Frontend model for ${className}.\n`
318
328
  fileContent += ` * @augments {FrontendModelBase<${attributesTypeName}, ${createAttributesTypeName}, ${updateAttributesTypeName}>}\n`
@@ -418,17 +428,24 @@ export default class DbGenerateFrontendModels extends BaseCommand {
418
428
  for (const attribute of attributes) {
419
429
  const camelizedAttribute = inflection.camelize(attribute.name, true)
420
430
  const camelizedAttributeUpper = inflection.camelize(attribute.name)
431
+ const attributeType = `${attributesTypeName}[${JSON.stringify(attribute.name)}]`
432
+ const setterAttributeType = await this.frontendWriteAttributeType({
433
+ attribute,
434
+ attributeName: attribute.name,
435
+ attributesTypeName,
436
+ resourceClass
437
+ })
421
438
 
422
439
  fileContent += "\n"
423
- fileContent += ` /** @returns {${attributesTypeName}[${JSON.stringify(attribute.name)}]} - Attribute value. */\n`
424
- fileContent += ` ${camelizedAttribute}() { return /** @type {${attributesTypeName}[${JSON.stringify(attribute.name)}]} */ (this.readAttribute(${JSON.stringify(attribute.name)})) }\n`
440
+ fileContent += ` /** @returns {${attributeType}} - Attribute value. */\n`
441
+ fileContent += ` ${camelizedAttribute}() { return /** @type {${attributeType}} */ (this.readAttribute(${JSON.stringify(attribute.name)})) }\n`
425
442
 
426
443
  fileContent += "\n"
427
444
  fileContent += " /**\n"
428
- fileContent += ` * @param {${attributesTypeName}[${JSON.stringify(attribute.name)}]} newValue - New attribute value.\n`
429
- fileContent += ` * @returns {${attributesTypeName}[${JSON.stringify(attribute.name)}]} - Assigned value.\n`
445
+ fileContent += ` * @param {${setterAttributeType}} newValue - New attribute value.\n`
446
+ fileContent += ` * @returns {${setterAttributeType}} - Assigned value.\n`
430
447
  fileContent += " */\n"
431
- fileContent += ` set${camelizedAttributeUpper}(newValue) { return /** @type {${attributesTypeName}[${JSON.stringify(attribute.name)}]} */ (this.setAttribute(${JSON.stringify(attribute.name)}, newValue)) }\n`
448
+ fileContent += ` set${camelizedAttributeUpper}(newValue) { return /** @type {${setterAttributeType}} */ (this.setAttribute(${JSON.stringify(attribute.name)}, newValue)) }\n`
432
449
  }
433
450
 
434
451
  for (const methodName of Object.keys(collectionCommands)) {
@@ -553,7 +570,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
553
570
  fileContent += "\n"
554
571
  fileContent += `export {${className}}\n`
555
572
  fileContent += "\n"
556
- fileContent += `export default /** @type {import(${JSON.stringify(importPath)}).FrontendModelClass<${className}, ${attributesTypeName}, ${createAttributesTypeName}>} */ (${className})\n`
573
+ fileContent += `export default ${className}\n`
557
574
 
558
575
  return fileContent
559
576
  }
@@ -595,41 +612,135 @@ export default class DbGenerateFrontendModels extends BaseCommand {
595
612
  * @param {object} args - Arguments.
596
613
  * @param {Array<{jsDocType: string, name: string}>} args.attributes - Generated read attributes.
597
614
  * @param {string} args.attributesTypeName - Generated read attributes typedef name.
615
+ * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
598
616
  * @param {Array<{attributes: Array<{name: string, type: string}>, relationshipName: string, typeName: string}>} args.nestedWriteTypes - Nested write typedefs.
599
- * @param {Array<string | Record<string, object>>} args.permittedParams - Resource permitted params spec.
617
+ * @param {FrontendModelGeneratorPermitSpec} args.permittedParams - Resource permitted params spec.
618
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
600
619
  * @param {string} args.typeName - Typedef name.
601
- * @returns {string} - Generated typedef source.
620
+ * @returns {Promise<string>} - Generated typedef source.
602
621
  */
603
- writeAttributesTypedef({attributes, attributesTypeName, nestedWriteTypes, permittedParams, typeName}) {
604
- let output = "/**\n"
622
+ async writeAttributesTypedef({attributes, attributesTypeName, modelClass, nestedWriteTypes, permittedParams, resourceClass, typeName}) {
623
+ const attributeLines = []
605
624
 
606
- output += ` * Attributes accepted by ${typeName}.\n`
607
- output += ` * @typedef {object} ${typeName}\n`
625
+ let output = "/**\n"
608
626
 
609
627
  const attributesByName = new Map(attributes.map((attribute) => [attribute.name, attribute]))
610
628
  const nestedWriteTypesByKey = new Map(nestedWriteTypes.map((nestedWriteType) => [`${nestedWriteType.relationshipName}Attributes`, nestedWriteType]))
629
+ const emittedAttributeNames = new Set()
611
630
 
612
631
  for (const entry of permittedParams) {
613
632
  if (typeof entry == "string") {
614
- const attribute = attributesByName.get(entry)
615
- const type = attribute ? `${attributesTypeName}[${JSON.stringify(attribute.name)}]` : "FrontendModelAttributeValue"
633
+ const attributeName = this.frontendWriteAttributeName({attributeName: entry, attributesByName, modelClass})
634
+
635
+ if (emittedAttributeNames.has(attributeName)) continue
636
+
637
+ emittedAttributeNames.add(attributeName)
638
+
639
+ const type = await this.frontendWriteAttributeType({
640
+ attribute: attributesByName.get(attributeName),
641
+ attributeName,
642
+ attributesTypeName,
643
+ resourceClass
644
+ })
616
645
 
617
- output += ` * @property {${type}} [${entry}] - Permitted ${entry} value.\n`
646
+ attributeLines.push(` * @property {${type}} [${attributeName}] - Permitted ${attributeName} value.\n`)
618
647
  } else if (entry && typeof entry == "object" && !Array.isArray(entry)) {
619
648
  for (const key of Object.keys(entry)) {
620
649
  const nestedWriteType = nestedWriteTypesByKey.get(key)
621
650
  const type = nestedWriteType ? `Array<${nestedWriteType.typeName}>` : "Array<object>"
622
651
 
623
- output += ` * @property {${type}} [${key}] - Permitted nested ${key} values.\n`
652
+ attributeLines.push(` * @property {${type}} [${key}] - Permitted nested ${key} values.\n`)
624
653
  }
625
654
  }
626
655
  }
627
656
 
657
+ output += ` * Attributes accepted by ${typeName}.\n`
658
+ if (attributeLines.length === 0) {
659
+ output += ` * @typedef {Record<string, never>} ${typeName}\n`
660
+ } else {
661
+ output += ` * @typedef {object} ${typeName}\n`
662
+ output += attributeLines.join("")
663
+ }
628
664
  output += " */\n"
629
665
 
630
666
  return output
631
667
  }
632
668
 
669
+ /**
670
+ * Runs frontend write attribute type.
671
+ * @param {{attribute: {jsDocType: string, name: string} | undefined, attributeName: string, attributesTypeName: string, resourceClass: import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined}} args - Arguments.
672
+ * @returns {Promise<string>} - JSDoc type for the permitted write field.
673
+ */
674
+ async frontendWriteAttributeType({attribute, attributeName, attributesTypeName, resourceClass}) {
675
+ const setterParameterType = await this.frontendWriteAttributeSetterParameterType({attributeName, resourceClass})
676
+
677
+ if (setterParameterType) return `${setterParameterType} | null`
678
+
679
+ if (!attribute) return "FrontendModelAttributeValue"
680
+
681
+ if (attribute.jsDocType.trim() === "null") return "FrontendModelAttributeValue"
682
+
683
+ return `${attributesTypeName}[${JSON.stringify(attribute.name)}] | null`
684
+ }
685
+
686
+ /**
687
+ * Runs frontend write attribute setter parameter type.
688
+ * @param {{attributeName: string, resourceClass: import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined}} args - Arguments.
689
+ * @returns {Promise<string | null>} - Setter value parameter type when it is useful for generation.
690
+ */
691
+ async frontendWriteAttributeSetterParameterType({attributeName, resourceClass}) {
692
+ if (!resourceClass?.name) return null
693
+
694
+ const methodName = `set${inflection.camelize(attributeName)}Attribute`
695
+ const parameterType = await this.resourceMethodParameterType({
696
+ methodName,
697
+ parameterIndex: 1,
698
+ sourceClassName: resourceClass.name
699
+ })
700
+
701
+ if (!parameterType) return null
702
+ if (this.isBroadGeneratedType(parameterType)) return null
703
+
704
+ return parameterType
705
+ }
706
+
707
+ /**
708
+ * Runs is broad generated type.
709
+ * @param {string} jsDocType - JSDoc type.
710
+ * @returns {boolean} - Whether the type is too broad to improve generated write typing.
711
+ */
712
+ isBroadGeneratedType(jsDocType) {
713
+ const normalizedType = jsDocType.trim()
714
+
715
+ return normalizedType === "?"
716
+ || normalizedType === "any"
717
+ || normalizedType === "object"
718
+ || normalizedType === "unknown"
719
+ }
720
+
721
+ /**
722
+ * Resolves a permitted write attribute to the generated frontend attribute name.
723
+ * @param {{attributeName: string, attributesByName: Map<string, {jsDocType: string, name: string}>, modelClass: typeof import("../../../../../database/record/index.js").default | undefined}} args - Arguments.
724
+ * @returns {string} - Frontend attribute name used by generated accessors.
725
+ */
726
+ frontendWriteAttributeName({attributeName, attributesByName, modelClass}) {
727
+ if (attributesByName.has(attributeName)) return attributeName
728
+
729
+ if (modelClass) {
730
+ const resolvedAttributeName = modelClass.resolveAttributeName(attributeName)
731
+
732
+ if (resolvedAttributeName && attributesByName.has(resolvedAttributeName)) return resolvedAttributeName
733
+ }
734
+
735
+ const normalizedAttributeName = inflection.camelize(attributeName, true).toLowerCase()
736
+ const matchingAttributeName = Array.from(attributesByName.keys()).find((candidateName) => candidateName.toLowerCase() === normalizedAttributeName)
737
+
738
+ if (matchingAttributeName) return matchingAttributeName
739
+
740
+ // Write-only virtual params are valid permitted params even when they have no read attribute.
741
+ return attributeName
742
+ }
743
+
633
744
  /**
634
745
  * Runs nested write types for model.
635
746
  * @param {object} args - Arguments.
@@ -684,10 +795,11 @@ export default class DbGenerateFrontendModels extends BaseCommand {
684
795
  if (!Array.isArray(nestedSpec)) return []
685
796
 
686
797
  return nestedSpec.filter((entry) => typeof entry == "string").map((attributeName) => {
687
- const attributeConfig = this.frontendAttributeConfigForModelAttribute({attributeName, modelClass: targetModelClass})
798
+ const resolvedAttributeName = targetModelClass?.resolveAttributeName(attributeName) || attributeName
799
+ const attributeConfig = this.frontendAttributeConfigForModelAttribute({attributeName: resolvedAttributeName, modelClass: targetModelClass})
688
800
 
689
801
  return {
690
- name: attributeName,
802
+ name: resolvedAttributeName,
691
803
  type: attributeConfig ? this.jsDocTypeForFrontendAttribute({attributeConfig}) : "FrontendModelAttributeValue"
692
804
  }
693
805
  })
@@ -776,7 +888,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
776
888
 
777
889
  /**
778
890
  * Relationship names.
779
- @type {string[]} */
891
+ * @type {string[]} */
780
892
  const relationshipNames = []
781
893
 
782
894
  for (const entry of spec) {
@@ -856,64 +968,172 @@ export default class DbGenerateFrontendModels extends BaseCommand {
856
968
  /**
857
969
  * Runs attribute definitions for model.
858
970
  * @param {object} args - Arguments.
971
+ * @param {string} args.className - Frontend model class name.
859
972
  * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
860
973
  * @param {import("../../../../../configuration-types.js").NormalizedFrontendModelResourceConfiguration} args.modelConfig - Model configuration.
861
- * @returns {Array<{jsDocType: string, name: string}>} - Attribute definitions.
974
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null} [args.resourceClass] - Resource class.
975
+ * @returns {Promise<Array<{jsDocType: string, name: string}>>} - Attribute definitions.
862
976
  */
863
- attributeDefinitionsForModel({modelClass, modelConfig}) {
977
+ async attributeDefinitionsForModel({className, modelClass, modelConfig, resourceClass}) {
864
978
  let attributes = modelConfig.attributes
865
979
 
866
980
  // Auto-derive attributes from model columns when not explicitly defined
867
981
  if ((!attributes || (Array.isArray(attributes) && attributes.length === 0)) && modelClass) {
868
- try {
869
- const columns = modelClass.getColumns()
982
+ const columns = modelClass.getColumns()
870
983
 
871
- if (Array.isArray(columns)) {
872
- attributes = columns.map((column) => inflection.camelize(column.getName(), true))
873
- }
874
- } catch {
875
- // Model may not be initialized yet
984
+ if (Array.isArray(columns)) {
985
+ attributes = columns.map((column) => inflection.camelize(column.getName(), true))
876
986
  }
877
987
  }
878
988
 
879
989
  if (Array.isArray(attributes)) {
880
- return attributes.map((attributeDefinition) => {
990
+ const attributeDefinitions = []
991
+
992
+ for (const attributeDefinition of attributes) {
881
993
  /** @type {FrontendAttributeConfig | null} */
882
- let attributeConfig = null
994
+ let configuredAttributeConfig = null
883
995
  let attributeName
884
996
 
885
997
  if (typeof attributeDefinition == "string") {
886
998
  attributeName = attributeDefinition
887
- attributeConfig = this.frontendAttributeConfigForModelAttribute({attributeName, modelClass})
888
999
  } else if (attributeDefinition && typeof attributeDefinition == "object" && !Array.isArray(attributeDefinition)) {
889
- attributeConfig = /** @type {FrontendAttributeConfig} */ (attributeDefinition)
890
- attributeName = attributeConfig.name
1000
+ configuredAttributeConfig = /** @type {FrontendAttributeConfig} */ (attributeDefinition)
1001
+ attributeName = configuredAttributeConfig.name
891
1002
  }
892
1003
 
893
1004
  if (typeof attributeName != "string" || attributeName.length < 1) {
894
1005
  throw new Error(`Expected frontend model attribute array entries to be strings or objects with a name, got: ${JSON.stringify(attributeDefinition)}`)
895
1006
  }
896
1007
 
897
- return {
898
- jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig}),
1008
+ const attributeConfig = await this.resolvedFrontendAttributeConfig({
1009
+ attributeName,
1010
+ className,
1011
+ configuredAttributeConfig,
1012
+ modelClass,
1013
+ resourceClass
1014
+ })
1015
+
1016
+ const frontendAttributeConfig = this.frontendAttributeConfigForGeneratedAttribute({
1017
+ attributeConfig,
1018
+ attributeName,
1019
+ modelClass
1020
+ })
1021
+
1022
+ attributeDefinitions.push({
1023
+ jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: frontendAttributeConfig}),
899
1024
  name: attributeName
900
- }
901
- })
1025
+ })
1026
+ }
1027
+
1028
+ return attributeDefinitions
902
1029
  }
903
1030
 
904
1031
  if (!attributes || typeof attributes !== "object") {
905
1032
  throw new Error(`Expected 'attributes' as array or object but got: ${attributes}`)
906
1033
  }
907
1034
 
908
- return Object.keys(attributes).map((attributeName) => {
1035
+ const attributeDefinitions = []
1036
+
1037
+ for (const attributeName of Object.keys(attributes)) {
909
1038
  const attributeConfig = attributes[attributeName]
910
- const normalizedAttributeConfig = attributeConfig && typeof attributeConfig === "object" ? attributeConfig : null
1039
+ const configuredAttributeConfig = attributeConfig && typeof attributeConfig === "object"
1040
+ ? /** @type {FrontendAttributeConfig} */ (attributeConfig)
1041
+ : null
1042
+ const normalizedAttributeConfig = await this.resolvedFrontendAttributeConfig({
1043
+ attributeName,
1044
+ className,
1045
+ configuredAttributeConfig,
1046
+ modelClass,
1047
+ resourceClass
1048
+ })
1049
+ const frontendAttributeConfig = this.frontendAttributeConfigForGeneratedAttribute({
1050
+ attributeConfig: normalizedAttributeConfig,
1051
+ attributeName,
1052
+ modelClass
1053
+ })
911
1054
 
912
- return {
913
- jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: normalizedAttributeConfig}),
1055
+ attributeDefinitions.push({
1056
+ jsDocType: this.jsDocTypeForFrontendAttribute({attributeConfig: frontendAttributeConfig}),
914
1057
  name: attributeName
915
- }
916
- })
1058
+ })
1059
+ }
1060
+
1061
+ return attributeDefinitions
1062
+ }
1063
+
1064
+ /**
1065
+ * Runs frontend attribute config for generated attribute.
1066
+ * @param {{attributeConfig: FrontendAttributeConfig, attributeName: string, modelClass: typeof import("../../../../../database/record/index.js").default | undefined}} args - Arguments.
1067
+ * @returns {FrontendAttributeConfig} - Attribute config used for generated JSDoc.
1068
+ */
1069
+ frontendAttributeConfigForGeneratedAttribute({attributeConfig, attributeName, modelClass}) {
1070
+ if (!this.frontendAttributeIsModelPrimaryKey({attributeName, modelClass})) return attributeConfig
1071
+
1072
+ return {...attributeConfig, null: false}
1073
+ }
1074
+
1075
+ /**
1076
+ * Runs frontend attribute is model primary key.
1077
+ * @param {{attributeName: string, modelClass: typeof import("../../../../../database/record/index.js").default | undefined}} args - Arguments.
1078
+ * @returns {boolean} - Whether the attribute is the model primary key.
1079
+ */
1080
+ frontendAttributeIsModelPrimaryKey({attributeName, modelClass}) {
1081
+ if (!modelClass) return false
1082
+
1083
+ const primaryKey = modelClass.primaryKey()
1084
+
1085
+ if (typeof primaryKey != "string" || primaryKey.length < 1) return false
1086
+ if (attributeName === primaryKey) return true
1087
+
1088
+ return modelClass.resolveAttributeName(primaryKey) === attributeName
1089
+ }
1090
+
1091
+ /**
1092
+ * Resolves frontend attribute config from explicit metadata, resource methods, model columns, translated columns, or model accessor JSDoc.
1093
+ * @param {object} args - Arguments.
1094
+ * @param {string} args.attributeName - Frontend attribute name.
1095
+ * @param {string} args.className - Frontend model class name.
1096
+ * @param {FrontendAttributeConfig | null} args.configuredAttributeConfig - Resource-provided attribute config.
1097
+ * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
1098
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
1099
+ * @returns {Promise<FrontendAttributeConfig>} - Resolved frontend attribute config.
1100
+ */
1101
+ async resolvedFrontendAttributeConfig({attributeName, className, configuredAttributeConfig, modelClass, resourceClass}) {
1102
+ const inferredResourceConfig = await this.frontendAttributeConfigForResourceAttribute({attributeName, resourceClass})
1103
+ const inferredColumnConfig = inferredResourceConfig
1104
+ ? null
1105
+ : this.frontendAttributeConfigForModelAttribute({attributeName, modelClass})
1106
+ const inferredTranslatedConfig = inferredResourceConfig || inferredColumnConfig
1107
+ ? null
1108
+ : this.frontendAttributeConfigForTranslatedAttribute({attributeName, modelClass, resourceClass})
1109
+ const inferredModelAccessorConfig = inferredResourceConfig || inferredColumnConfig || inferredTranslatedConfig
1110
+ ? null
1111
+ : await this.frontendAttributeConfigForModelAccessor({attributeName, modelClass})
1112
+ const inferredConfig = inferredResourceConfig || inferredColumnConfig || inferredTranslatedConfig || inferredModelAccessorConfig
1113
+
1114
+ if (configuredAttributeConfig && this.frontendAttributeConfigHasType(configuredAttributeConfig)) {
1115
+ return inferredConfig
1116
+ ? {...inferredConfig, ...configuredAttributeConfig}
1117
+ : configuredAttributeConfig
1118
+ }
1119
+
1120
+ if (inferredConfig) {
1121
+ return configuredAttributeConfig
1122
+ ? {...inferredConfig, ...configuredAttributeConfig}
1123
+ : inferredConfig
1124
+ }
1125
+
1126
+ throw new Error(`Could not infer JSDoc type for frontend model attribute '${className}#${attributeName}'. Add a backend model column, translation table column, explicit resource metadata, or a @returns JSDoc type on ${resourceClass?.name || "the resource"}.${attributeName}Attribute().`)
1127
+ }
1128
+
1129
+ /**
1130
+ * Runs frontend attribute config has type.
1131
+ * @param {FrontendAttributeConfig | null | undefined} attributeConfig - Attribute config.
1132
+ * @returns {boolean} - Whether the config declares a type source.
1133
+ */
1134
+ frontendAttributeConfigHasType(attributeConfig) {
1135
+ return typeof this.frontendAttributeTypeValue(attributeConfig) == "string"
1136
+ || typeof attributeConfig?.jsDocType == "string"
917
1137
  }
918
1138
 
919
1139
  /**
@@ -923,6 +1143,10 @@ export default class DbGenerateFrontendModels extends BaseCommand {
923
1143
  * @returns {string} - JSDoc type.
924
1144
  */
925
1145
  jsDocTypeForFrontendAttribute({attributeConfig}) {
1146
+ if (attributeConfig && typeof attributeConfig.jsDocType == "string" && attributeConfig.jsDocType.length > 0) {
1147
+ return attributeConfig.jsDocType
1148
+ }
1149
+
926
1150
  const jsDocType = this.jsDocTypeForFrontendAttributeBaseType(attributeConfig)
927
1151
 
928
1152
  if (!this.frontendAttributeCanBeNull(attributeConfig)) {
@@ -948,7 +1172,7 @@ export default class DbGenerateFrontendModels extends BaseCommand {
948
1172
  return "boolean"
949
1173
  } else if (type == "json" || type == "jsonb") {
950
1174
  return "FrontendModelTransportValue"
951
- } else if (type && ["blob", "char", "nvarchar", "varchar", "text", "longtext", "uuid", "character varying"].includes(type)) {
1175
+ } else if (type && ["blob", "char", "nvarchar", "varchar", "text", "longtext", "mediumtext", "tinytext", "uuid", "character varying"].includes(type)) {
952
1176
  return "string"
953
1177
  } else if (type && ["bit", "bigint", "decimal", "double", "double precision", "float", "int", "integer", "numeric", "real", "smallint", "tinyint"].includes(type)) {
954
1178
  return "number"
@@ -999,6 +1223,517 @@ export default class DbGenerateFrontendModels extends BaseCommand {
999
1223
  return typeValue
1000
1224
  }
1001
1225
 
1226
+ /**
1227
+ * Runs frontend attribute config for resource attribute.
1228
+ * @param {object} args - Arguments.
1229
+ * @param {string} args.attributeName - Frontend model attribute name.
1230
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
1231
+ * @returns {Promise<FrontendAttributeConfig | null>} - Attribute config inferred from resource method JSDoc.
1232
+ */
1233
+ async frontendAttributeConfigForResourceAttribute({attributeName, resourceClass}) {
1234
+ if (!resourceClass) return null
1235
+
1236
+ const methodName = `${attributeName}Attribute`
1237
+ const ownerClassName = this.methodOwnerClassName({methodName, targetClass: resourceClass})
1238
+
1239
+ if (!ownerClassName) return null
1240
+
1241
+ const jsDocType = await this.resourceMethodReturnType({
1242
+ methodName,
1243
+ sourceClassName: ownerClassName
1244
+ })
1245
+
1246
+ return jsDocType ? {jsDocType: this.unwrappedPromiseJsDocType({jsDocType})} : null
1247
+ }
1248
+
1249
+ /**
1250
+ * Runs frontend attribute config for translated attribute.
1251
+ * @param {object} args - Arguments.
1252
+ * @param {string} args.attributeName - Frontend model attribute name.
1253
+ * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
1254
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
1255
+ * @returns {FrontendAttributeConfig | null} - Attribute config inferred from translated attribute columns.
1256
+ */
1257
+ frontendAttributeConfigForTranslatedAttribute({attributeName, modelClass, resourceClass}) {
1258
+ if (!modelClass) return null
1259
+ if (!this.frontendAttributeIsTranslated({attributeName, modelClass, resourceClass})) return null
1260
+
1261
+ const TranslationClass = modelClass.getTranslationClass()
1262
+ const columnName = inflection.underscore(attributeName)
1263
+
1264
+ let column
1265
+
1266
+ try {
1267
+ column = TranslationClass.getColumnsHash()[columnName]
1268
+ } catch (error) {
1269
+ if (error instanceof Error && (error.message.includes("hasn't been initialized yet") || error.message.includes("used before initialization"))) return null
1270
+
1271
+ throw error
1272
+ }
1273
+
1274
+ return column ? this.frontendAttributeConfigForColumn({column}) : null
1275
+ }
1276
+
1277
+ /**
1278
+ * Runs frontend attribute is translated.
1279
+ * @param {object} args - Arguments.
1280
+ * @param {string} args.attributeName - Frontend model attribute name.
1281
+ * @param {typeof import("../../../../../database/record/index.js").default} args.modelClass - Backend model class.
1282
+ * @param {import("../../../../../configuration-types.js").FrontendModelResourceClassType | null | undefined} args.resourceClass - Resource class.
1283
+ * @returns {boolean} - Whether the frontend attribute is translated.
1284
+ */
1285
+ frontendAttributeIsTranslated({attributeName, modelClass, resourceClass}) {
1286
+ if (resourceClass) {
1287
+ const translatedAttributes = resourceClass.translatedAttributes
1288
+
1289
+ if (Array.isArray(translatedAttributes) && translatedAttributes.includes(attributeName)) return true
1290
+ }
1291
+
1292
+ const translations = modelClass._translations
1293
+
1294
+ return Boolean(translations && typeof translations == "object" && Object.prototype.hasOwnProperty.call(translations, attributeName))
1295
+ }
1296
+
1297
+ /**
1298
+ * Runs frontend attribute config for model accessor.
1299
+ * @param {object} args - Arguments.
1300
+ * @param {string} args.attributeName - Frontend model attribute name.
1301
+ * @param {typeof import("../../../../../database/record/index.js").default | undefined} args.modelClass - Backend model class.
1302
+ * @returns {Promise<FrontendAttributeConfig | null>} - Attribute config inferred from model accessor JSDoc.
1303
+ */
1304
+ async frontendAttributeConfigForModelAccessor({attributeName, modelClass}) {
1305
+ if (!modelClass) return null
1306
+
1307
+ const ownerClassName = this.methodOwnerClassName({methodName: attributeName, targetClass: modelClass})
1308
+
1309
+ if (!ownerClassName) return null
1310
+
1311
+ const jsDocType = await this.resourceMethodReturnType({
1312
+ methodName: attributeName,
1313
+ sourceClassName: ownerClassName
1314
+ })
1315
+
1316
+ return jsDocType ? {jsDocType} : null
1317
+ }
1318
+
1319
+ /**
1320
+ * Runs unwrapped promise js doc type.
1321
+ * @param {object} args - Arguments.
1322
+ * @param {string} args.jsDocType - JSDoc type to normalize.
1323
+ * @returns {string} - The resolved value type for serialized frontend attributes.
1324
+ */
1325
+ unwrappedPromiseJsDocType({jsDocType}) {
1326
+ const promisePrefix = "Promise<"
1327
+
1328
+ if (!jsDocType.startsWith(promisePrefix)) return jsDocType
1329
+
1330
+ if (!jsDocType.endsWith(">")) {
1331
+ throw new Error(`Expected Promise JSDoc type to end with '>': ${jsDocType}`)
1332
+ }
1333
+
1334
+ const resolvedType = jsDocType.slice(promisePrefix.length, -1).trim()
1335
+
1336
+ if (resolvedType.length < 1) {
1337
+ throw new Error(`Expected Promise JSDoc type to contain a resolved type: ${jsDocType}`)
1338
+ }
1339
+
1340
+ return resolvedType
1341
+ }
1342
+
1343
+ /**
1344
+ * Runs method owner class name.
1345
+ * @param {object} args - Arguments.
1346
+ * @param {string} args.methodName - Method name.
1347
+ * @param {typeof import("../../../../../database/record/index.js").default | import("../../../../../configuration-types.js").FrontendModelResourceClassType} args.targetClass - Target class.
1348
+ * @returns {string | null} - Class name that declares the method.
1349
+ */
1350
+ methodOwnerClassName({methodName, targetClass}) {
1351
+ let prototype = targetClass.prototype
1352
+
1353
+ while (prototype && prototype !== Object.prototype) {
1354
+ if (Object.prototype.hasOwnProperty.call(prototype, methodName)) {
1355
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName)
1356
+
1357
+ if (typeof descriptor?.value != "function") return null
1358
+
1359
+ const constructorName = prototype.constructor?.name
1360
+
1361
+ if (typeof constructorName == "string" && constructorName.length > 0) return constructorName
1362
+
1363
+ return null
1364
+ }
1365
+
1366
+ prototype = Object.getPrototypeOf(prototype)
1367
+ }
1368
+
1369
+ return null
1370
+ }
1371
+
1372
+ /**
1373
+ * Runs resource method return type.
1374
+ * @param {object} args - Arguments.
1375
+ * @param {string} args.methodName - Method name.
1376
+ * @param {string} args.sourceClassName - Source class name.
1377
+ * @returns {Promise<string | null>} - JSDoc return type when documented.
1378
+ */
1379
+ async resourceMethodReturnType({methodName, sourceClassName}) {
1380
+ const resourceMethodReturnTypes = await this.resourceMethodReturnTypes()
1381
+ const returnTypeKey = `${sourceClassName}.${methodName}`
1382
+
1383
+ if (!resourceMethodReturnTypes.has(returnTypeKey)) return null
1384
+
1385
+ const returnType = resourceMethodReturnTypes.get(returnTypeKey)
1386
+
1387
+ if (typeof returnType != "string" || returnType.length < 1) {
1388
+ throw new Error(`Expected non-empty JSDoc return type for ${returnTypeKey}`)
1389
+ }
1390
+
1391
+ return returnType
1392
+ }
1393
+
1394
+ /**
1395
+ * Runs resource method parameter type.
1396
+ * @param {{methodName: string, parameterIndex: number, sourceClassName: string}} args - Arguments.
1397
+ * @returns {Promise<string | null>} - JSDoc parameter type when documented.
1398
+ */
1399
+ async resourceMethodParameterType({methodName, parameterIndex, sourceClassName}) {
1400
+ const resourceMethodParameterTypes = await this.resourceMethodParameterTypes()
1401
+ const parameterTypesKey = `${sourceClassName}.${methodName}`
1402
+
1403
+ if (!resourceMethodParameterTypes.has(parameterTypesKey)) return null
1404
+
1405
+ const parameterTypes = resourceMethodParameterTypes.get(parameterTypesKey)
1406
+
1407
+ if (!parameterTypes) {
1408
+ throw new Error(`Expected JSDoc parameter types for ${parameterTypesKey}`)
1409
+ }
1410
+
1411
+ const parameterType = parameterTypes[parameterIndex]
1412
+
1413
+ if (parameterType === undefined) return null
1414
+
1415
+ if (parameterType.length < 1) {
1416
+ throw new Error(`Expected non-empty JSDoc parameter type for ${parameterTypesKey} parameter ${parameterIndex}`)
1417
+ }
1418
+
1419
+ return parameterType
1420
+ }
1421
+
1422
+ /**
1423
+ * Runs resource method return types.
1424
+ * @returns {Promise<Map<string, string>>} - Resource method return types keyed by ClassName.methodName.
1425
+ */
1426
+ async resourceMethodReturnTypes() {
1427
+ if (this._resourceMethodReturnTypes) return this._resourceMethodReturnTypes
1428
+
1429
+ const sourceFiles = await this.frontendModelJsDocSourceFiles()
1430
+ const returnTypes = new Map()
1431
+
1432
+ for (const sourceFile of sourceFiles) {
1433
+ const sourceText = await fs.readFile(sourceFile, "utf8")
1434
+
1435
+ this.addResourceMethodReturnTypesFromSource({returnTypes, sourceText})
1436
+ }
1437
+
1438
+ this._resourceMethodReturnTypes = returnTypes
1439
+
1440
+ return returnTypes
1441
+ }
1442
+
1443
+ /**
1444
+ * Runs resource method parameter types.
1445
+ * @returns {Promise<Map<string, string[]>>} - Resource method parameter types keyed by ClassName.methodName.
1446
+ */
1447
+ async resourceMethodParameterTypes() {
1448
+ if (this._resourceMethodParameterTypes) return this._resourceMethodParameterTypes
1449
+
1450
+ const sourceFiles = await this.frontendModelJsDocSourceFiles()
1451
+ const parameterTypes = new Map()
1452
+
1453
+ for (const sourceFile of sourceFiles) {
1454
+ const sourceText = await fs.readFile(sourceFile, "utf8")
1455
+
1456
+ this.addResourceMethodParameterTypesFromSource({parameterTypes, sourceText})
1457
+ }
1458
+
1459
+ this._resourceMethodParameterTypes = parameterTypes
1460
+
1461
+ return parameterTypes
1462
+ }
1463
+
1464
+ /**
1465
+ * Runs frontend model JSDoc source files.
1466
+ * @returns {Promise<string[]>} - JavaScript source files that can define frontend-model resources and model accessors.
1467
+ */
1468
+ async frontendModelJsDocSourceFiles() {
1469
+ const sourceFiles = []
1470
+
1471
+ for (const sourceDirectory of this.frontendModelJsDocSourceDirectories()) {
1472
+ sourceFiles.push(...await this.javascriptFilesInDirectory(sourceDirectory))
1473
+ }
1474
+
1475
+ return sourceFiles
1476
+ }
1477
+
1478
+ /**
1479
+ * Runs frontend model JSDoc source directories.
1480
+ * @returns {string[]} - Source directories to scan for generated frontend-model JSDoc.
1481
+ */
1482
+ frontendModelJsDocSourceDirectories() {
1483
+ const sourceDirectories = new Set([path.join(this.directory(), "src")])
1484
+
1485
+ for (const backendProject of this.getConfiguration().getBackendProjects()) {
1486
+ if (typeof backendProject.path == "string" && backendProject.path.length > 0) {
1487
+ sourceDirectories.add(path.join(backendProject.path, "src"))
1488
+ }
1489
+ }
1490
+
1491
+ return Array.from(sourceDirectories)
1492
+ }
1493
+
1494
+ /**
1495
+ * Adds resource method return types from source.
1496
+ * @param {object} args - Arguments.
1497
+ * @param {Map<string, string>} args.returnTypes - Mutable return types map.
1498
+ * @param {string} args.sourceText - Source text.
1499
+ * @returns {void}
1500
+ */
1501
+ addResourceMethodReturnTypesFromSource({returnTypes, sourceText}) {
1502
+ const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
1503
+ let classMatch
1504
+
1505
+ while ((classMatch = classRegex.exec(sourceText))) {
1506
+ const className = classMatch[1]
1507
+ const classBodyStart = classRegex.lastIndex
1508
+ const classBodyEnd = this.matchingBraceIndex({openIndex: classBodyStart - 1, sourceText})
1509
+
1510
+ if (classBodyEnd == null) {
1511
+ throw new Error(`Could not find closing brace for resource class '${className}' while reading frontend attribute JSDoc`)
1512
+ }
1513
+
1514
+ const classBody = sourceText.slice(classBodyStart, classBodyEnd)
1515
+ const jsDocRegex = /\/\*\*([\s\S]*?)\*\//g
1516
+ let jsDocMatch
1517
+
1518
+ while ((jsDocMatch = jsDocRegex.exec(classBody))) {
1519
+ const sourceAfterJsDoc = classBody.slice(jsDocRegex.lastIndex)
1520
+ const methodMatch = sourceAfterJsDoc.match(/^\s*(?:async\s+)?([A-Za-z_$][\w$]*)\s*\(/)
1521
+
1522
+ if (!methodMatch) continue
1523
+
1524
+ const methodName = methodMatch[1]
1525
+
1526
+ const returnType = this.jsDocReturnType(jsDocMatch[1])
1527
+
1528
+ if (returnType) {
1529
+ returnTypes.set(`${className}.${methodName}`, returnType)
1530
+ }
1531
+ }
1532
+
1533
+ classRegex.lastIndex = classBodyEnd + 1
1534
+ }
1535
+ }
1536
+
1537
+ /**
1538
+ * Adds resource method parameter types from source.
1539
+ * @param {{parameterTypes: Map<string, string[]>, sourceText: string}} args - Arguments.
1540
+ * @returns {void}
1541
+ */
1542
+ addResourceMethodParameterTypesFromSource({parameterTypes, sourceText}) {
1543
+ const classRegex = /class\s+([A-Za-z_$][\w$]*)\s+(?:extends\s+[^{]+)?\{/g
1544
+ let classMatch
1545
+
1546
+ while ((classMatch = classRegex.exec(sourceText))) {
1547
+ const className = classMatch[1]
1548
+ const classBodyStart = classRegex.lastIndex
1549
+ const classBodyEnd = this.matchingBraceIndex({openIndex: classBodyStart - 1, sourceText})
1550
+
1551
+ if (classBodyEnd == null) {
1552
+ throw new Error(`Could not find closing brace for resource class '${className}' while reading frontend attribute JSDoc`)
1553
+ }
1554
+
1555
+ const classBody = sourceText.slice(classBodyStart, classBodyEnd)
1556
+ const jsDocRegex = /\/\*\*([\s\S]*?)\*\//g
1557
+ let jsDocMatch
1558
+
1559
+ while ((jsDocMatch = jsDocRegex.exec(classBody))) {
1560
+ const sourceAfterJsDoc = classBody.slice(jsDocRegex.lastIndex)
1561
+ const methodMatch = sourceAfterJsDoc.match(/^\s*(?:async\s+)?([A-Za-z_$][\w$]*)\s*\(/)
1562
+
1563
+ if (!methodMatch) continue
1564
+
1565
+ const methodName = methodMatch[1]
1566
+ const jsDocParameterTypes = this.jsDocParameterTypes(jsDocMatch[1])
1567
+
1568
+ if (jsDocParameterTypes.length > 0) {
1569
+ parameterTypes.set(`${className}.${methodName}`, jsDocParameterTypes)
1570
+ }
1571
+ }
1572
+
1573
+ classRegex.lastIndex = classBodyEnd + 1
1574
+ }
1575
+ }
1576
+
1577
+ /**
1578
+ * Runs js doc return type.
1579
+ * @param {string} jsDocText - JSDoc text inside comment markers.
1580
+ * @returns {string | null} - JSDoc return type when present.
1581
+ */
1582
+ jsDocReturnType(jsDocText) {
1583
+ const returnsMatch = jsDocText.match(/@returns?\s*\{/)
1584
+
1585
+ if (!returnsMatch || returnsMatch.index == null) return null
1586
+
1587
+ const typeOpenIndex = returnsMatch.index + returnsMatch[0].length - 1
1588
+ const typeCloseIndex = this.matchingBraceIndex({openIndex: typeOpenIndex, sourceText: jsDocText})
1589
+
1590
+ if (typeCloseIndex == null) {
1591
+ throw new Error(`Could not parse JSDoc return type from: ${jsDocText}`)
1592
+ }
1593
+
1594
+ const returnType = jsDocText.slice(typeOpenIndex + 1, typeCloseIndex).trim()
1595
+
1596
+ if (returnType.length < 1) {
1597
+ throw new Error(`Expected non-empty JSDoc return type in: ${jsDocText}`)
1598
+ }
1599
+
1600
+ return returnType
1601
+ }
1602
+
1603
+ /**
1604
+ * Runs js doc parameter types.
1605
+ * @param {string} jsDocText - JSDoc text inside comment markers.
1606
+ * @returns {string[]} - JSDoc parameter types in declaration order.
1607
+ */
1608
+ jsDocParameterTypes(jsDocText) {
1609
+ const parameterTypes = []
1610
+ const paramRegex = /@param\s*\{/g
1611
+ let _paramMatch
1612
+
1613
+ while ((_paramMatch = paramRegex.exec(jsDocText))) {
1614
+ const typeOpenIndex = paramRegex.lastIndex - 1
1615
+ const typeCloseIndex = this.matchingBraceIndex({openIndex: typeOpenIndex, sourceText: jsDocText})
1616
+
1617
+ if (typeCloseIndex == null) {
1618
+ throw new Error(`Could not parse JSDoc parameter type from: ${jsDocText}`)
1619
+ }
1620
+
1621
+ const parameterType = jsDocText.slice(typeOpenIndex + 1, typeCloseIndex).trim()
1622
+
1623
+ if (parameterType.length < 1) {
1624
+ throw new Error(`Expected non-empty JSDoc parameter type in: ${jsDocText}`)
1625
+ }
1626
+
1627
+ parameterTypes.push(parameterType)
1628
+ paramRegex.lastIndex = typeCloseIndex + 1
1629
+ }
1630
+
1631
+ return parameterTypes
1632
+ }
1633
+
1634
+ /**
1635
+ * Runs javascript files in directory.
1636
+ * @param {string} directory - Directory path.
1637
+ * @returns {Promise<string[]>} - JavaScript source file paths.
1638
+ */
1639
+ async javascriptFilesInDirectory(directory) {
1640
+ let entries
1641
+
1642
+ try {
1643
+ entries = await fs.readdir(directory, {withFileTypes: true})
1644
+ } catch (error) {
1645
+ if (error && typeof error == "object" && "code" in error && error.code === "ENOENT") return []
1646
+
1647
+ throw error
1648
+ }
1649
+
1650
+ const filePaths = []
1651
+
1652
+ for (const entry of entries) {
1653
+ const entryPath = path.join(directory, entry.name)
1654
+
1655
+ if (entry.isDirectory()) {
1656
+ filePaths.push(...await this.javascriptFilesInDirectory(entryPath))
1657
+ } else if (entry.isFile() && /\.(mjs|js|jsx|ts)$/.test(entry.name)) {
1658
+ filePaths.push(entryPath)
1659
+ }
1660
+ }
1661
+
1662
+ return filePaths
1663
+ }
1664
+
1665
+ /**
1666
+ * Finds a matching closing brace while respecting JavaScript strings and comments.
1667
+ * @param {object} args - Arguments.
1668
+ * @param {number} args.openIndex - Opening brace index.
1669
+ * @param {string} args.sourceText - Source text.
1670
+ * @returns {number | null} - Closing brace index when found.
1671
+ */
1672
+ matchingBraceIndex({openIndex, sourceText}) {
1673
+ if (sourceText[openIndex] !== "{") {
1674
+ throw new Error(`Expected opening brace at index ${openIndex}`)
1675
+ }
1676
+
1677
+ let depth = 0
1678
+ let inBlockComment = false
1679
+ let inLineComment = false
1680
+ let inString = ""
1681
+
1682
+ for (let index = openIndex; index < sourceText.length; index++) {
1683
+ const char = sourceText[index]
1684
+ const nextChar = sourceText[index + 1]
1685
+ const previousChar = sourceText[index - 1]
1686
+
1687
+ if (inLineComment) {
1688
+ if (char === "\n") inLineComment = false
1689
+
1690
+ continue
1691
+ }
1692
+
1693
+ if (inBlockComment) {
1694
+ if (char === "*" && nextChar === "/") {
1695
+ inBlockComment = false
1696
+ index++
1697
+ }
1698
+
1699
+ continue
1700
+ }
1701
+
1702
+ if (inString) {
1703
+ if (char === inString && previousChar !== "\\") inString = ""
1704
+
1705
+ continue
1706
+ }
1707
+
1708
+ if (char === "/" && nextChar === "/") {
1709
+ inLineComment = true
1710
+ index++
1711
+ continue
1712
+ }
1713
+
1714
+ if (char === "/" && nextChar === "*") {
1715
+ inBlockComment = true
1716
+ index++
1717
+ continue
1718
+ }
1719
+
1720
+ if (char === "\"" || char === "'" || char === "`") {
1721
+ inString = char
1722
+ continue
1723
+ }
1724
+
1725
+ if (char === "{") {
1726
+ depth++
1727
+ } else if (char === "}") {
1728
+ depth--
1729
+
1730
+ if (depth === 0) return index
1731
+ }
1732
+ }
1733
+
1734
+ return null
1735
+ }
1736
+
1002
1737
  /**
1003
1738
  * Runs frontend attribute config for model attribute.
1004
1739
  * @param {object} args - Arguments.
@@ -1011,13 +1746,54 @@ export default class DbGenerateFrontendModels extends BaseCommand {
1011
1746
  return null
1012
1747
  }
1013
1748
 
1014
- const columnName = modelClass.getAttributeNameToColumnNameMap()[attributeName]
1749
+ const resolvedAttributeName = modelClass.resolveAttributeName(attributeName)
1750
+
1751
+ if (!resolvedAttributeName) return null
1752
+
1753
+ let columnName
1754
+
1755
+ try {
1756
+ columnName = modelClass.getAttributeNameToColumnNameMap()[resolvedAttributeName]
1757
+ } catch (error) {
1758
+ if (error instanceof Error && error.message.includes("used before initialization")) return null
1759
+
1760
+ throw error
1761
+ }
1015
1762
 
1016
1763
  if (!columnName) {
1017
1764
  return null
1018
1765
  }
1019
1766
 
1020
- return modelClass.getColumnsHash()[columnName] || null
1767
+ let column
1768
+
1769
+ try {
1770
+ column = modelClass.getColumnsHash()[columnName]
1771
+ } catch (error) {
1772
+ if (error instanceof Error && error.message.includes("used before initialization")) return null
1773
+
1774
+ throw error
1775
+ }
1776
+
1777
+ return column ? this.frontendAttributeConfigForColumn({column}) : null
1778
+ }
1779
+
1780
+ /**
1781
+ * Runs frontend attribute config for column.
1782
+ * @param {object} args - Arguments.
1783
+ * @param {import("../../../../../database/drivers/base-column.js").default} args.column - Database column.
1784
+ * @returns {FrontendAttributeConfig} - Attribute config inferred from the database column.
1785
+ */
1786
+ frontendAttributeConfigForColumn({column}) {
1787
+ const type = column.getType()
1788
+
1789
+ if (typeof type != "string" || type.length < 1) {
1790
+ throw new Error(`Expected non-empty column type for frontend model attribute inference, got: ${type}`)
1791
+ }
1792
+
1793
+ return {
1794
+ null: column.getNull(),
1795
+ type
1796
+ }
1021
1797
  }
1022
1798
 
1023
1799
  /**