sovrium 0.0.2

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 (527) hide show
  1. package/CHANGELOG.md +3497 -0
  2. package/LICENSE.md +147 -0
  3. package/LICENSE_EE.md +297 -0
  4. package/README.md +321 -0
  5. package/drizzle/0000_melted_kabuki.sql +163 -0
  6. package/drizzle/meta/0000_snapshot.json +1216 -0
  7. package/drizzle/meta/_journal.json +13 -0
  8. package/package.json +167 -0
  9. package/schemas/0.0.1/app.openapi.json +70 -0
  10. package/schemas/0.0.1/app.schema.json +7961 -0
  11. package/schemas/0.0.2/app.openapi.json +80 -0
  12. package/schemas/0.0.2/app.schema.json +8829 -0
  13. package/schemas/development/app.openapi.json +70 -0
  14. package/schemas/development/app.schema.json +7456 -0
  15. package/src/application/errors/app-validation-error.ts +14 -0
  16. package/src/application/errors/static-generation-error.ts +16 -0
  17. package/src/application/metadata/favicon-transformer.ts +127 -0
  18. package/src/application/models/server.ts +27 -0
  19. package/src/application/ports/models/user-metadata.ts +36 -0
  20. package/src/application/ports/models/user-session.ts +34 -0
  21. package/src/application/ports/repositories/activity-log-repository.ts +68 -0
  22. package/src/application/ports/repositories/activity-repository.ts +49 -0
  23. package/src/application/ports/repositories/analytics-repository.ts +164 -0
  24. package/src/application/ports/repositories/auth-repository.ts +33 -0
  25. package/src/application/ports/repositories/batch-repository.ts +86 -0
  26. package/src/application/ports/repositories/comment-repository.ts +150 -0
  27. package/src/application/ports/repositories/index.ts +41 -0
  28. package/src/application/ports/repositories/table-repository.ts +139 -0
  29. package/src/application/ports/services/css-compiler.ts +55 -0
  30. package/src/application/ports/services/index.ts +16 -0
  31. package/src/application/ports/services/page-renderer.ts +79 -0
  32. package/src/application/ports/services/server-factory.ts +80 -0
  33. package/src/application/ports/services/static-site-generator.ts +82 -0
  34. package/src/application/use-cases/activity/programs.ts +66 -0
  35. package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
  36. package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
  37. package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
  38. package/src/application/use-cases/analytics/query-devices.ts +36 -0
  39. package/src/application/use-cases/analytics/query-overview.ts +50 -0
  40. package/src/application/use-cases/analytics/query-pages.ts +40 -0
  41. package/src/application/use-cases/analytics/query-referrers.ts +43 -0
  42. package/src/application/use-cases/analytics/ua-parser.ts +89 -0
  43. package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
  44. package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
  45. package/src/application/use-cases/list-activity-logs.ts +123 -0
  46. package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
  47. package/src/application/use-cases/server/generate-static.ts +287 -0
  48. package/src/application/use-cases/server/start-server.ts +118 -0
  49. package/src/application/use-cases/server/startup-error-handler.ts +69 -0
  50. package/src/application/use-cases/server/static-content-generators.ts +182 -0
  51. package/src/application/use-cases/server/static-language-generators.ts +181 -0
  52. package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
  53. package/src/application/use-cases/server/translation-replacer.ts +164 -0
  54. package/src/application/use-cases/tables/activity-programs.ts +93 -0
  55. package/src/application/use-cases/tables/batch-operations.ts +156 -0
  56. package/src/application/use-cases/tables/comment-programs.ts +436 -0
  57. package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
  58. package/src/application/use-cases/tables/programs.ts +435 -0
  59. package/src/application/use-cases/tables/table-operations.ts +412 -0
  60. package/src/application/use-cases/tables/user-role.ts +52 -0
  61. package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
  62. package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
  63. package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
  64. package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
  65. package/src/cli.ts +370 -0
  66. package/src/domain/errors/create-tagged-error.ts +36 -0
  67. package/src/domain/errors/index.ts +78 -0
  68. package/src/domain/models/api/analytics.ts +179 -0
  69. package/src/domain/models/api/auth.ts +231 -0
  70. package/src/domain/models/api/common.ts +60 -0
  71. package/src/domain/models/api/error.ts +89 -0
  72. package/src/domain/models/api/health.ts +38 -0
  73. package/src/domain/models/api/index.ts +42 -0
  74. package/src/domain/models/api/request.ts +132 -0
  75. package/src/domain/models/api/tables.ts +444 -0
  76. package/src/domain/models/app/analytics/index.ts +129 -0
  77. package/src/domain/models/app/auth/config.ts +116 -0
  78. package/src/domain/models/app/auth/index.ts +230 -0
  79. package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
  80. package/src/domain/models/app/auth/methods/index.ts +11 -0
  81. package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
  82. package/src/domain/models/app/auth/oauth/index.ts +8 -0
  83. package/src/domain/models/app/auth/oauth/providers.ts +105 -0
  84. package/src/domain/models/app/auth/plugins/admin.ts +130 -0
  85. package/src/domain/models/app/auth/plugins/index.ts +74 -0
  86. package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
  87. package/src/domain/models/app/auth/roles.ts +179 -0
  88. package/src/domain/models/app/auth/strategies.ts +191 -0
  89. package/src/domain/models/app/auth/validation.ts +127 -0
  90. package/src/domain/models/app/common/branded-ids.ts +200 -0
  91. package/src/domain/models/app/common/definitions.ts +187 -0
  92. package/src/domain/models/app/component/common/component-children.ts +119 -0
  93. package/src/domain/models/app/component/common/component-props.ts +89 -0
  94. package/src/domain/models/app/component/common/component-reference.ts +170 -0
  95. package/src/domain/models/app/component/component.ts +81 -0
  96. package/src/domain/models/app/components.ts +65 -0
  97. package/src/domain/models/app/description.ts +83 -0
  98. package/src/domain/models/app/index.ts +258 -0
  99. package/src/domain/models/app/language/language-config.ts +200 -0
  100. package/src/domain/models/app/languages.ts +205 -0
  101. package/src/domain/models/app/name.ts +66 -0
  102. package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
  103. package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
  104. package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
  105. package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
  106. package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
  107. package/src/domain/models/app/page/common/responsive.ts +114 -0
  108. package/src/domain/models/app/page/common/url.ts +35 -0
  109. package/src/domain/models/app/page/common/variable-reference.ts +53 -0
  110. package/src/domain/models/app/page/id.ts +44 -0
  111. package/src/domain/models/app/page/index.ts +270 -0
  112. package/src/domain/models/app/page/meta/analytics.ts +248 -0
  113. package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
  114. package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
  115. package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
  116. package/src/domain/models/app/page/meta/favicon.ts +50 -0
  117. package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
  118. package/src/domain/models/app/page/meta/index.ts +278 -0
  119. package/src/domain/models/app/page/meta/open-graph.ts +166 -0
  120. package/src/domain/models/app/page/meta/preload.ts +190 -0
  121. package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
  122. package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
  123. package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
  124. package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
  125. package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
  126. package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
  127. package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
  128. package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
  129. package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
  130. package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
  131. package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
  132. package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
  133. package/src/domain/models/app/page/name.ts +38 -0
  134. package/src/domain/models/app/page/path.ts +21 -0
  135. package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
  136. package/src/domain/models/app/page/scripts/features.ts +135 -0
  137. package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
  138. package/src/domain/models/app/page/scripts/scripts.ts +102 -0
  139. package/src/domain/models/app/page/sections.ts +298 -0
  140. package/src/domain/models/app/pages.ts +61 -0
  141. package/src/domain/models/app/permissions/index.ts +61 -0
  142. package/src/domain/models/app/permissions/resource-action.ts +114 -0
  143. package/src/domain/models/app/permissions/roles.ts +120 -0
  144. package/src/domain/models/app/table/check-constraints.ts +105 -0
  145. package/src/domain/models/app/table/cycle-detection.ts +124 -0
  146. package/src/domain/models/app/table/database-identifier.ts +153 -0
  147. package/src/domain/models/app/table/field-name.ts +36 -0
  148. package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
  149. package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
  150. package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
  151. package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
  152. package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
  153. package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
  154. package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
  155. package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
  156. package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
  157. package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
  158. package/src/domain/models/app/table/field-types/base-field.ts +42 -0
  159. package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
  160. package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
  161. package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
  162. package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
  163. package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
  164. package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
  165. package/src/domain/models/app/table/field-types/index.ts +19 -0
  166. package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
  167. package/src/domain/models/app/table/field-types/media/index.ts +10 -0
  168. package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
  169. package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
  170. package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
  171. package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
  172. package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
  173. package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
  174. package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
  175. package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
  176. package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
  177. package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
  178. package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
  179. package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
  180. package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
  181. package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
  182. package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
  183. package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
  184. package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
  185. package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
  186. package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
  187. package/src/domain/models/app/table/field-types/text/index.ts +13 -0
  188. package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
  189. package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
  190. package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
  191. package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
  192. package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
  193. package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
  194. package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
  195. package/src/domain/models/app/table/field-types/user/index.ts +11 -0
  196. package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
  197. package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
  198. package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
  199. package/src/domain/models/app/table/fields.ts +216 -0
  200. package/src/domain/models/app/table/foreign-keys.ts +111 -0
  201. package/src/domain/models/app/table/formula-keywords.ts +326 -0
  202. package/src/domain/models/app/table/id.ts +31 -0
  203. package/src/domain/models/app/table/index.ts +290 -0
  204. package/src/domain/models/app/table/indexes.ts +80 -0
  205. package/src/domain/models/app/table/name.ts +37 -0
  206. package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
  207. package/src/domain/models/app/table/permissions/index.ts +167 -0
  208. package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
  209. package/src/domain/models/app/table/permissions/permission.ts +49 -0
  210. package/src/domain/models/app/table/primary-key.ts +62 -0
  211. package/src/domain/models/app/table/table-formula-validation.ts +168 -0
  212. package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
  213. package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
  214. package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
  215. package/src/domain/models/app/table/table-views-validation.ts +408 -0
  216. package/src/domain/models/app/table/unique-constraints.ts +79 -0
  217. package/src/domain/models/app/table/views/fields.ts +28 -0
  218. package/src/domain/models/app/table/views/filters.ts +162 -0
  219. package/src/domain/models/app/table/views/group-by.ts +32 -0
  220. package/src/domain/models/app/table/views/id.ts +50 -0
  221. package/src/domain/models/app/table/views/index.ts +177 -0
  222. package/src/domain/models/app/table/views/name.ts +32 -0
  223. package/src/domain/models/app/table/views/permissions.ts +98 -0
  224. package/src/domain/models/app/table/views/sorts.ts +31 -0
  225. package/src/domain/models/app/tables.ts +695 -0
  226. package/src/domain/models/app/theme/animations.ts +208 -0
  227. package/src/domain/models/app/theme/border-radius.ts +58 -0
  228. package/src/domain/models/app/theme/breakpoints.ts +62 -0
  229. package/src/domain/models/app/theme/colors.ts +110 -0
  230. package/src/domain/models/app/theme/fonts.ts +164 -0
  231. package/src/domain/models/app/theme/shadows.ts +61 -0
  232. package/src/domain/models/app/theme/spacing.ts +115 -0
  233. package/src/domain/models/app/theme.ts +66 -0
  234. package/src/domain/models/app/version.ts +87 -0
  235. package/src/domain/models/record-comment.ts +91 -0
  236. package/src/domain/utils/content-parsing.ts +49 -0
  237. package/src/domain/utils/format-detection.ts +69 -0
  238. package/src/domain/utils/index.ts +9 -0
  239. package/src/domain/utils/route-matcher.ts +184 -0
  240. package/src/domain/utils/translation-resolver.ts +170 -0
  241. package/src/index.ts +208 -0
  242. package/src/infrastructure/analytics/tracking-script.ts +48 -0
  243. package/src/infrastructure/auth/better-auth/auth.ts +216 -0
  244. package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
  245. package/src/infrastructure/auth/better-auth/index.ts +16 -0
  246. package/src/infrastructure/auth/better-auth/layer.ts +97 -0
  247. package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
  248. package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
  249. package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
  250. package/src/infrastructure/auth/better-auth/schema.ts +152 -0
  251. package/src/infrastructure/auth/index.ts +27 -0
  252. package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
  253. package/src/infrastructure/css/compiler.ts +210 -0
  254. package/src/infrastructure/css/css-compiler-live.ts +20 -0
  255. package/src/infrastructure/css/index.ts +25 -0
  256. package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
  257. package/src/infrastructure/css/styles/click-animations.ts +147 -0
  258. package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
  259. package/src/infrastructure/css/theme/theme-generators.ts +130 -0
  260. package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
  261. package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
  262. package/src/infrastructure/database/activity-queries.ts +111 -0
  263. package/src/infrastructure/database/auth/auth-validation.ts +101 -0
  264. package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
  265. package/src/infrastructure/database/drizzle/db.ts +17 -0
  266. package/src/infrastructure/database/drizzle/index.ts +16 -0
  267. package/src/infrastructure/database/drizzle/layer.ts +34 -0
  268. package/src/infrastructure/database/drizzle/migrate.ts +77 -0
  269. package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
  270. package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
  271. package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
  272. package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
  273. package/src/infrastructure/database/drizzle/schema.ts +12 -0
  274. package/src/infrastructure/database/field-utils.ts +87 -0
  275. package/src/infrastructure/database/filter-operators.ts +136 -0
  276. package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
  277. package/src/infrastructure/database/formula/formula-utils.ts +440 -0
  278. package/src/infrastructure/database/generators/index-generators.ts +152 -0
  279. package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
  280. package/src/infrastructure/database/index.ts +35 -0
  281. package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
  282. package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
  283. package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
  284. package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
  285. package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
  286. package/src/infrastructure/database/migration-audit-trail.ts +375 -0
  287. package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
  288. package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
  289. package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
  290. package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
  291. package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
  292. package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
  293. package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
  294. package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
  295. package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
  296. package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
  297. package/src/infrastructure/database/schema-migration/constants.ts +31 -0
  298. package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
  299. package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
  300. package/src/infrastructure/database/schema-migration/index.ts +66 -0
  301. package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
  302. package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
  303. package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
  304. package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
  305. package/src/infrastructure/database/schema-migration/types.ts +14 -0
  306. package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
  307. package/src/infrastructure/database/session-context.ts +20 -0
  308. package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
  309. package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
  310. package/src/infrastructure/database/sql/sql-execution.ts +245 -0
  311. package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
  312. package/src/infrastructure/database/sql/sql-generators.ts +91 -0
  313. package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
  314. package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
  315. package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
  316. package/src/infrastructure/database/sql/sql-utils.ts +53 -0
  317. package/src/infrastructure/database/table-live-layers.ts +30 -0
  318. package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
  319. package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
  320. package/src/infrastructure/database/table-operations/index.ts +55 -0
  321. package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
  322. package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
  323. package/src/infrastructure/database/table-operations/table-features.ts +96 -0
  324. package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
  325. package/src/infrastructure/database/table-operations.ts +47 -0
  326. package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
  327. package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
  328. package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
  329. package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
  330. package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
  331. package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
  332. package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
  333. package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
  334. package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
  335. package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
  336. package/src/infrastructure/database/table-queries/index.ts +11 -0
  337. package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
  338. package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
  339. package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
  340. package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
  341. package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
  342. package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
  343. package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
  344. package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
  345. package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
  346. package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
  347. package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
  348. package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
  349. package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
  350. package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
  351. package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
  352. package/src/infrastructure/database/views/view-generators.ts +258 -0
  353. package/src/infrastructure/devtools/devtools-layer.ts +43 -0
  354. package/src/infrastructure/devtools/index.ts +8 -0
  355. package/src/infrastructure/email/email-config.ts +103 -0
  356. package/src/infrastructure/email/email-service.ts +152 -0
  357. package/src/infrastructure/email/index.ts +107 -0
  358. package/src/infrastructure/email/nodemailer.ts +125 -0
  359. package/src/infrastructure/email/templates.ts +244 -0
  360. package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
  361. package/src/infrastructure/errors/auth-error.ts +16 -0
  362. package/src/infrastructure/errors/css-compilation-error.ts +14 -0
  363. package/src/infrastructure/errors/index.ts +26 -0
  364. package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
  365. package/src/infrastructure/errors/server-creation-error.ts +14 -0
  366. package/src/infrastructure/filesystem/copy-directory.ts +136 -0
  367. package/src/infrastructure/layers/app-layer.ts +61 -0
  368. package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
  369. package/src/infrastructure/logging/index.ts +8 -0
  370. package/src/infrastructure/logging/logger.ts +204 -0
  371. package/src/infrastructure/schema/file-loader.ts +53 -0
  372. package/src/infrastructure/schema/index.ts +15 -0
  373. package/src/infrastructure/schema/remote-loader.ts +48 -0
  374. package/src/infrastructure/server/index.ts +26 -0
  375. package/src/infrastructure/server/language-detection.ts +87 -0
  376. package/src/infrastructure/server/lifecycle.ts +67 -0
  377. package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
  378. package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
  379. package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
  380. package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
  381. package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
  382. package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
  383. package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
  384. package/src/infrastructure/server/server-factory-live.ts +45 -0
  385. package/src/infrastructure/server/server.ts +275 -0
  386. package/src/infrastructure/server/ssg-adapter.ts +196 -0
  387. package/src/infrastructure/server/static-site-generator-live.ts +20 -0
  388. package/src/infrastructure/utils/accept-language-parser.ts +106 -0
  389. package/src/infrastructure/utils/glob-matcher.ts +50 -0
  390. package/src/presentation/api/client.ts +114 -0
  391. package/src/presentation/api/middleware/auth.ts +233 -0
  392. package/src/presentation/api/middleware/table.ts +155 -0
  393. package/src/presentation/api/middleware/validation.ts +88 -0
  394. package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
  395. package/src/presentation/api/routes/activity/index.ts +28 -0
  396. package/src/presentation/api/routes/activity.ts +339 -0
  397. package/src/presentation/api/routes/analytics.ts +328 -0
  398. package/src/presentation/api/routes/auth.ts +169 -0
  399. package/src/presentation/api/routes/index.ts +11 -0
  400. package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
  401. package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
  402. package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
  403. package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
  404. package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
  405. package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
  406. package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
  407. package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
  408. package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
  409. package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
  410. package/src/presentation/api/routes/tables/index.ts +113 -0
  411. package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
  412. package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
  413. package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
  414. package/src/presentation/api/routes/tables/record-routes.ts +53 -0
  415. package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
  416. package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
  417. package/src/presentation/api/routes/tables/table-routes.ts +76 -0
  418. package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
  419. package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
  420. package/src/presentation/api/routes/tables/utils.ts +159 -0
  421. package/src/presentation/api/routes/tables/view-routes.ts +51 -0
  422. package/src/presentation/api/routes/tables.ts +9 -0
  423. package/src/presentation/api/utils/context-helpers.ts +43 -0
  424. package/src/presentation/api/utils/error-sanitizer.ts +235 -0
  425. package/src/presentation/api/utils/field-permission-validator.ts +53 -0
  426. package/src/presentation/api/utils/filter-field-validator.ts +90 -0
  427. package/src/presentation/api/utils/index.ts +13 -0
  428. package/src/presentation/api/utils/run-effect.ts +94 -0
  429. package/src/presentation/api/utils/validate-request.ts +89 -0
  430. package/src/presentation/api/validation/index.ts +29 -0
  431. package/src/presentation/api/validation/rules/field-rules.ts +158 -0
  432. package/src/presentation/api/validation/rules/record-rules.ts +73 -0
  433. package/src/presentation/cli/index.ts +19 -0
  434. package/src/presentation/cli/schema-loader.ts +172 -0
  435. package/src/presentation/hooks/use-breakpoint.ts +155 -0
  436. package/src/presentation/rendering/render-error-pages.tsx +60 -0
  437. package/src/presentation/rendering/render-homepage.tsx +137 -0
  438. package/src/presentation/scripts/script-renderers.ts +112 -0
  439. package/src/presentation/styling/animation-composer.ts +117 -0
  440. package/src/presentation/styling/index.ts +13 -0
  441. package/src/presentation/styling/parse-style.ts +243 -0
  442. package/src/presentation/styling/style-utils.ts +50 -0
  443. package/src/presentation/styling/theme-colors.ts +53 -0
  444. package/src/presentation/translations/component-utils.ts +54 -0
  445. package/src/presentation/translations/index.ts +16 -0
  446. package/src/presentation/translations/translation-resolver.ts +22 -0
  447. package/src/presentation/ui/languages/language-switcher.tsx +119 -0
  448. package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
  449. package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
  450. package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
  451. package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
  452. package/src/presentation/ui/metadata/head-elements.tsx +164 -0
  453. package/src/presentation/ui/metadata/index.tsx +35 -0
  454. package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
  455. package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
  456. package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
  457. package/src/presentation/ui/metadata/structured-data.tsx +88 -0
  458. package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
  459. package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
  460. package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
  461. package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
  462. package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
  463. package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
  464. package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
  465. package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
  466. package/src/presentation/ui/pages/PageHead.tsx +380 -0
  467. package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
  468. package/src/presentation/ui/pages/PageMain.tsx +58 -0
  469. package/src/presentation/ui/pages/PageMetadata.ts +168 -0
  470. package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
  471. package/src/presentation/ui/pages/PageScripts.ts +78 -0
  472. package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
  473. package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
  474. package/src/presentation/ui/sections/component-renderer.tsx +426 -0
  475. package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
  476. package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
  477. package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
  478. package/src/presentation/ui/sections/components/index.ts +9 -0
  479. package/src/presentation/ui/sections/hero.tsx +394 -0
  480. package/src/presentation/ui/sections/props/component-builder.ts +183 -0
  481. package/src/presentation/ui/sections/props/element-props.ts +179 -0
  482. package/src/presentation/ui/sections/props/index.ts +9 -0
  483. package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
  484. package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
  485. package/src/presentation/ui/sections/props/props-builder.ts +296 -0
  486. package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
  487. package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
  488. package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
  489. package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
  490. package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
  491. package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
  492. package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
  493. package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
  494. package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
  495. package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
  496. package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
  497. package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
  498. package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
  499. package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
  500. package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
  501. package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
  502. package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
  503. package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
  504. package/src/presentation/ui/sections/rendering/index.ts +9 -0
  505. package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
  506. package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
  507. package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
  508. package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
  509. package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
  510. package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
  511. package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
  512. package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
  513. package/src/presentation/ui/sections/styling/index.ts +9 -0
  514. package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
  515. package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
  516. package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
  517. package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
  518. package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
  519. package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
  520. package/src/presentation/ui/sections/translations/index.ts +9 -0
  521. package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
  522. package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
  523. package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
  524. package/src/presentation/utils/link-attributes.ts +50 -0
  525. package/src/presentation/utils/string-utils.ts +58 -0
  526. package/src/presentation/utils/styles.ts +50 -0
  527. package/tsconfig.json +46 -0
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import {
9
+ createCommentProgram,
10
+ deleteCommentProgram,
11
+ getCommentProgram,
12
+ listCommentsProgram,
13
+ updateCommentProgram,
14
+ } from '@/application/use-cases/tables/comment-programs'
15
+ import { hasReadPermission } from '@/application/use-cases/tables/permissions/permissions'
16
+ import { getTableContext } from '@/presentation/api/utils/context-helpers'
17
+ import { runTableProgram } from './effect-runner'
18
+ import { handleRouteError } from './error-handlers'
19
+ import { isAuthorizationError } from './utils'
20
+ import type { App } from '@/domain/models/app'
21
+ import type { Context } from 'hono'
22
+
23
+ /**
24
+ * Create comment request validation
25
+ */
26
+ interface CreateCommentBody {
27
+ readonly content: string
28
+ }
29
+
30
+ /**
31
+ * Validate create comment request body
32
+ */
33
+ function validateCreateCommentBody(body: unknown): CreateCommentBody | undefined {
34
+ if (typeof body !== 'object' || body === undefined) {
35
+ return undefined
36
+ }
37
+
38
+ const { content } = body as Record<string, unknown>
39
+
40
+ if (typeof content !== 'string') {
41
+ return undefined
42
+ }
43
+
44
+ if (content.length === 0) {
45
+ return undefined
46
+ }
47
+
48
+ if (content.length > 10_000) {
49
+ return undefined
50
+ }
51
+
52
+ return { content }
53
+ }
54
+
55
+ /**
56
+ * Handle comment creation errors
57
+ *
58
+ * Authorization errors return 404 (prevents resource enumeration),
59
+ * all other errors are sanitized via the shared route error handler.
60
+ */
61
+ function handleCommentError(c: Context, error: unknown) {
62
+ if (isAuthorizationError(error)) {
63
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
64
+ }
65
+ return handleRouteError(c, error)
66
+ }
67
+
68
+ /**
69
+ * Handle create comment on a record
70
+ */
71
+ export async function handleCreateComment(c: Context, app: App) {
72
+ const { session, userRole } = getTableContext(c)
73
+ const tableId = c.req.param('tableId')
74
+ const recordId = c.req.param('recordId')
75
+
76
+ // Find table by ID
77
+ const table = app.tables?.find((t) => String(t.id) === String(tableId))
78
+ if (!table) {
79
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
80
+ }
81
+
82
+ // Check read permission (must be able to read the record to comment on it)
83
+ if (!hasReadPermission(table, userRole, app.tables)) {
84
+ return c.json(
85
+ {
86
+ success: false,
87
+ message: 'You do not have permission to perform this action',
88
+ code: 'FORBIDDEN',
89
+ },
90
+ 403
91
+ )
92
+ }
93
+
94
+ // Parse and validate request body
95
+ const body = await c.req.json().catch(() => undefined)
96
+ const validated = validateCreateCommentBody(body)
97
+
98
+ if (!validated) {
99
+ return c.json(
100
+ { success: false, message: 'Invalid request body', code: 'VALIDATION_ERROR' },
101
+ 400
102
+ )
103
+ }
104
+
105
+ // Create comment
106
+ const program = createCommentProgram({
107
+ session,
108
+ tableId,
109
+ recordId,
110
+ tableName: table.name,
111
+ content: validated.content,
112
+ })
113
+
114
+ const result = await runTableProgram(program)
115
+
116
+ if (result._tag === 'Left') {
117
+ return handleCommentError(c, result.left)
118
+ }
119
+
120
+ return c.json(result.right, 201)
121
+ }
122
+
123
+ /**
124
+ * Handle delete comment error
125
+ */
126
+ function handleDeleteCommentError(c: Context, error: unknown) {
127
+ // Check for authorization errors
128
+ if (isAuthorizationError(error)) {
129
+ const errorMessage = error instanceof Error ? error.message : String(error)
130
+
131
+ // Forbidden error (user is not author and not admin)
132
+ if (errorMessage.includes('Forbidden')) {
133
+ return c.json({ success: false, code: 'FORBIDDEN' }, 403)
134
+ }
135
+
136
+ // Not found error (comment doesn't exist, already deleted, or no record access)
137
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
138
+ }
139
+
140
+ // All other errors - use shared sanitization
141
+ return handleRouteError(c, error)
142
+ }
143
+
144
+ /**
145
+ * Handle delete comment
146
+ */
147
+ export async function handleDeleteComment(c: Context, app: App) {
148
+ const { session } = getTableContext(c)
149
+ const tableId = c.req.param('tableId')
150
+ const commentId = c.req.param('commentId')
151
+
152
+ // Find table by ID
153
+ const table = app.tables?.find((t) => String(t.id) === String(tableId))
154
+ if (!table) {
155
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
156
+ }
157
+
158
+ // Delete comment
159
+ const program = deleteCommentProgram({
160
+ session,
161
+ commentId,
162
+ tableName: table.name,
163
+ })
164
+
165
+ const result = await runTableProgram(program)
166
+
167
+ if (result._tag === 'Left') {
168
+ return handleDeleteCommentError(c, result.left)
169
+ }
170
+
171
+ // Return 204 No Content on success
172
+ // eslint-disable-next-line unicorn/no-null -- Hono's c.body() requires null for 204 No Content
173
+ return c.body(null, 204)
174
+ }
175
+
176
+ /**
177
+ * Handle get comment by ID
178
+ */
179
+ export async function handleGetComment(c: Context, app: App) {
180
+ const { session, userRole } = getTableContext(c)
181
+ const tableId = c.req.param('tableId')
182
+ const commentId = c.req.param('commentId')
183
+
184
+ // Find table by ID
185
+ const table = app.tables?.find((t) => String(t.id) === String(tableId))
186
+ if (!table) {
187
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
188
+ }
189
+
190
+ // Check read permission
191
+ if (!hasReadPermission(table, userRole, app.tables)) {
192
+ return c.json(
193
+ {
194
+ success: false,
195
+ message: 'You do not have permission to perform this action',
196
+ code: 'FORBIDDEN',
197
+ },
198
+ 403
199
+ )
200
+ }
201
+
202
+ // Get comment
203
+ const program = getCommentProgram({
204
+ session,
205
+ commentId,
206
+ tableName: table.name,
207
+ })
208
+
209
+ const result = await runTableProgram(program)
210
+
211
+ if (result._tag === 'Left') {
212
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
213
+ }
214
+
215
+ return c.json(result.right, 200)
216
+ }
217
+
218
+ /**
219
+ * Validate update comment request body
220
+ */
221
+ function validateUpdateCommentBody(body: unknown): CreateCommentBody | undefined {
222
+ if (typeof body !== 'object' || body === undefined) {
223
+ return undefined
224
+ }
225
+
226
+ const { content } = body as Record<string, unknown>
227
+
228
+ if (typeof content !== 'string') {
229
+ return undefined
230
+ }
231
+
232
+ if (content.length === 0) {
233
+ return undefined
234
+ }
235
+
236
+ if (content.length > 10_000) {
237
+ return undefined
238
+ }
239
+
240
+ return { content }
241
+ }
242
+
243
+ /**
244
+ * Handle update comment error
245
+ */
246
+ function handleUpdateCommentError(c: Context, error: unknown) {
247
+ // Check for authorization errors
248
+ if (isAuthorizationError(error)) {
249
+ const errorMessage = error instanceof Error ? error.message : String(error)
250
+
251
+ // Forbidden error (user is not author)
252
+ if (errorMessage.includes('Forbidden')) {
253
+ return c.json({ success: false, code: 'FORBIDDEN' }, 403)
254
+ }
255
+
256
+ // Not found error (comment doesn't exist, already deleted, or no record access)
257
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
258
+ }
259
+
260
+ // Internal server error
261
+ return c.json(
262
+ { success: false, message: 'Failed to update comment', code: 'INTERNAL_ERROR' },
263
+ 500
264
+ )
265
+ }
266
+
267
+ /**
268
+ * Handle update comment
269
+ */
270
+ export async function handleUpdateComment(c: Context, app: App) {
271
+ const { session } = getTableContext(c)
272
+ const tableId = c.req.param('tableId')
273
+ const commentId = c.req.param('commentId')
274
+
275
+ // Find table by ID
276
+ const table = app.tables?.find((t) => String(t.id) === String(tableId))
277
+ if (!table) {
278
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
279
+ }
280
+
281
+ // Parse and validate request body
282
+ const body = await c.req.json().catch(() => undefined)
283
+ const validated = validateUpdateCommentBody(body)
284
+
285
+ if (!validated) {
286
+ return c.json(
287
+ { success: false, message: 'Invalid request body', code: 'VALIDATION_ERROR' },
288
+ 400
289
+ )
290
+ }
291
+
292
+ // Update comment
293
+ const program = updateCommentProgram({
294
+ session,
295
+ commentId,
296
+ tableName: table.name,
297
+ content: validated.content,
298
+ })
299
+
300
+ const result = await runTableProgram(program)
301
+
302
+ if (result._tag === 'Left') {
303
+ return handleUpdateCommentError(c, result.left)
304
+ }
305
+
306
+ return c.json(result.right, 200)
307
+ }
308
+
309
+ /**
310
+ * Parse sort query parameter (e.g., "createdAt:asc" or "createdAt:desc")
311
+ */
312
+ function parseSortOrder(sortParam: string | undefined): 'asc' | 'desc' | undefined {
313
+ if (!sortParam) {
314
+ return undefined
315
+ }
316
+
317
+ const [field, order] = sortParam.split(':')
318
+ if (field === 'createdAt' && (order === 'asc' || order === 'desc')) {
319
+ return order
320
+ }
321
+
322
+ return undefined
323
+ }
324
+
325
+ /**
326
+ * Handle list comments for a record
327
+ */
328
+ export async function handleListComments(c: Context, app: App) {
329
+ const { session, userRole } = getTableContext(c)
330
+ const tableId = c.req.param('tableId')
331
+ const recordId = c.req.param('recordId')
332
+
333
+ // Find table by ID
334
+ const table = app.tables?.find((t) => String(t.id) === String(tableId))
335
+ if (!table) {
336
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
337
+ }
338
+
339
+ // Check read permission
340
+ if (!hasReadPermission(table, userRole, app.tables)) {
341
+ return c.json(
342
+ {
343
+ success: false,
344
+ message: 'You do not have permission to perform this action',
345
+ code: 'FORBIDDEN',
346
+ },
347
+ 403
348
+ )
349
+ }
350
+
351
+ // Parse query parameters
352
+ const limitParam = c.req.query('limit')
353
+ const offsetParam = c.req.query('offset')
354
+ const sortParam = c.req.query('sort')
355
+
356
+ const limit = limitParam ? Number(limitParam) : undefined
357
+ const offset = offsetParam ? Number(offsetParam) : undefined
358
+ const sortOrder = parseSortOrder(sortParam)
359
+
360
+ // List comments
361
+ const program = listCommentsProgram({
362
+ session,
363
+ recordId,
364
+ tableName: table.name,
365
+ limit,
366
+ offset,
367
+ sortOrder,
368
+ })
369
+
370
+ const result = await runTableProgram(program)
371
+
372
+ if (result._tag === 'Left') {
373
+ return c.json({ success: false, message: 'Resource not found', code: 'NOT_FOUND' }, 404)
374
+ }
375
+
376
+ return c.json(result.right, 200)
377
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import type { Context } from 'hono'
9
+
10
+ /**
11
+ * Validate required fields for a single record
12
+ * Returns array of missing field names
13
+ */
14
+ export function validateRequiredFieldsForRecord(
15
+ table:
16
+ | {
17
+ readonly name: string
18
+ readonly fields: ReadonlyArray<{
19
+ readonly name: string
20
+ readonly required?: boolean
21
+ }>
22
+ readonly primaryKey?: {
23
+ readonly type: string
24
+ readonly fields?: ReadonlyArray<string>
25
+ readonly field?: string
26
+ }
27
+ }
28
+ | undefined,
29
+ fields: Record<string, unknown>
30
+ ): readonly string[] {
31
+ if (!table) return []
32
+
33
+ // Get primary key field names to exclude from validation
34
+ const primaryKeyFields = new Set(
35
+ table.primaryKey?.fields ?? (table.primaryKey?.field ? [table.primaryKey.field] : [])
36
+ )
37
+
38
+ // Auto-injected fields that should be excluded from required field validation
39
+ const autoInjectedFields = new Set<string>([])
40
+
41
+ return table.fields
42
+ .filter(
43
+ (field) =>
44
+ field.required &&
45
+ !(field.name in fields) &&
46
+ !primaryKeyFields.has(field.name) && // Skip primary key fields
47
+ !autoInjectedFields.has(field.name) // Skip auto-injected fields
48
+ )
49
+ .map((field) => field.name)
50
+ }
51
+
52
+ /**
53
+ * Validate required fields for record creation
54
+ * Returns error response if required fields are missing, undefined otherwise
55
+ */
56
+ export function validateRequiredFields(
57
+ table:
58
+ | {
59
+ readonly name: string
60
+ readonly fields: ReadonlyArray<{
61
+ readonly name: string
62
+ readonly required?: boolean
63
+ }>
64
+ readonly primaryKey?: {
65
+ readonly type: string
66
+ readonly fields?: ReadonlyArray<string>
67
+ readonly field?: string
68
+ }
69
+ }
70
+ | undefined,
71
+ fields: Record<string, unknown>,
72
+ c: Context
73
+ ) {
74
+ if (!table) return undefined
75
+
76
+ // Get primary key field names to exclude from validation
77
+ const primaryKeyFields = new Set(
78
+ table.primaryKey?.fields ?? (table.primaryKey?.field ? [table.primaryKey.field] : [])
79
+ )
80
+
81
+ // Auto-injected fields that should be excluded from required field validation
82
+ const autoInjectedFields = new Set<string>([])
83
+
84
+ const missingRequiredFields = table.fields
85
+ .filter(
86
+ (field) =>
87
+ field.required &&
88
+ !(field.name in fields) &&
89
+ !primaryKeyFields.has(field.name) && // Skip primary key fields
90
+ !autoInjectedFields.has(field.name) // Skip auto-injected fields
91
+ )
92
+ .map((field) => field.name)
93
+
94
+ if (missingRequiredFields.length > 0) {
95
+ return c.json(
96
+ {
97
+ success: false,
98
+ message: 'Missing required fields',
99
+ code: 'VALIDATION_ERROR',
100
+ details: missingRequiredFields.map((field, index) => ({
101
+ record: index,
102
+ field,
103
+ error: 'Required field is missing',
104
+ })),
105
+ },
106
+ 400
107
+ )
108
+ }
109
+
110
+ return undefined
111
+ }
112
+
113
+ /**
114
+ * Check if user is trying to set readonly 'id' field
115
+ */
116
+ export function checkReadonlyIdField(requestedFields: Record<string, unknown>, c: Context) {
117
+ if ('id' in requestedFields) {
118
+ return c.json(
119
+ {
120
+ success: false,
121
+ message: "Cannot write to readonly field 'id'",
122
+ code: 'FORBIDDEN',
123
+ },
124
+ 403
125
+ )
126
+ }
127
+ return undefined
128
+ }
129
+
130
+ /**
131
+ * Check if user is trying to set fields with default values (system-managed)
132
+ */
133
+ export function checkDefaultFields(
134
+ table:
135
+ | {
136
+ readonly fields?: ReadonlyArray<{
137
+ readonly name: string
138
+ readonly default?: unknown
139
+ }>
140
+ }
141
+ | undefined,
142
+ requestedFields: Record<string, unknown>,
143
+ c: Context
144
+ ) {
145
+ const fieldsWithDefaults =
146
+ table?.fields?.filter((f) => 'default' in f && f.default !== undefined) ?? []
147
+ const attemptedDefaultField = fieldsWithDefaults.find((f) => f.name in requestedFields)
148
+
149
+ if (attemptedDefaultField) {
150
+ return c.json(
151
+ {
152
+ success: false,
153
+ message: `Cannot write to readonly field '${attemptedDefaultField.name}'`,
154
+ code: 'FORBIDDEN',
155
+ },
156
+ 403
157
+ )
158
+ }
159
+ return undefined
160
+ }
161
+
162
+ /**
163
+ * Check field-level write permissions and return forbidden field error if needed
164
+ */
165
+ export function checkFieldWritePermissions(forbiddenFields: readonly string[], c: Context) {
166
+ if (forbiddenFields.length > 0) {
167
+ const firstForbiddenField = forbiddenFields[0]
168
+ return c.json(
169
+ {
170
+ success: false,
171
+ message: `Cannot write to field '${firstForbiddenField}': insufficient permissions`,
172
+ code: 'FORBIDDEN',
173
+ field: firstForbiddenField,
174
+ },
175
+ 403
176
+ )
177
+ }
178
+ return undefined
179
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { Effect } from 'effect'
9
+ import { TableLive } from '@/infrastructure/database/table-live-layers'
10
+
11
+ /**
12
+ * Run an Effect program with TableLive layer
13
+ *
14
+ * This utility consolidates the common pattern of providing TableLive, converting to Either,
15
+ * and running as Promise. It's used by all table-related route handlers.
16
+ *
17
+ * TableLive provides: TableRepository, BatchRepository, CommentRepository, ActivityRepository
18
+ *
19
+ * @param program - The Effect program to run (may require repository services from TableLive)
20
+ * @returns Promise resolving to Either (Left for errors, Right for success)
21
+ *
22
+ * @example
23
+ * const result = await runTableProgram(createCommentProgram({ session, tableId, content }))
24
+ * if (result._tag === 'Left') {
25
+ * return handleError(c, result.left)
26
+ * }
27
+ * return c.json(result.right, 201)
28
+ */
29
+ export async function runTableProgram<A, E, R>(
30
+ program: Effect.Effect<A, E, R>
31
+ ): Promise<
32
+ { readonly _tag: 'Left'; readonly left: E } | { readonly _tag: 'Right'; readonly right: A }
33
+ > {
34
+ // Type assertion: TableLive provides all required repositories, so remaining requirements are never
35
+ const provided = Effect.provide(program, TableLive) as Effect.Effect<A, E, never>
36
+ return Effect.runPromise(Effect.either(provided))
37
+ }
38
+
39
+ /**
40
+ * Provide TableLive layer to an Effect program
41
+ *
42
+ * This is a simpler utility for cases where the program will be passed to runEffect
43
+ * (which handles the Either conversion and error handling itself).
44
+ *
45
+ * TableLive provides: TableRepository, BatchRepository, CommentRepository, ActivityRepository
46
+ *
47
+ * @param program - The Effect program to provide TableLive to (may require repository services from TableLive)
48
+ * @returns Effect program with TableLive provided (requirements resolved)
49
+ *
50
+ * @example
51
+ * return runEffect(c, provideTableLive(batchCreateProgram({ ... })), responseSchema, 201)
52
+ */
53
+ export function provideTableLive<A, E, R>(
54
+ program: Effect.Effect<A, E, R>
55
+ ): Effect.Effect<A, E, never> {
56
+ // Type assertion: TableLive provides all required repositories, so remaining requirements are never
57
+ return Effect.provide(program, TableLive) as Effect.Effect<A, E, never>
58
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { sanitizeError, getStatusCode } from '@/presentation/api/utils/error-sanitizer'
9
+ import type { Context } from 'hono'
10
+
11
+ /**
12
+ * Shared route error handler
13
+ *
14
+ * Uses centralized error sanitization to prevent information disclosure.
15
+ * Automatically detects not-found/authorization errors and returns appropriate status codes.
16
+ *
17
+ * @param c - Hono context
18
+ * @param error - The error to handle
19
+ * @returns JSON response with sanitized error details
20
+ */
21
+ export function handleRouteError(c: Context, error: unknown): Response {
22
+ const requestId = crypto.randomUUID()
23
+ const sanitized = sanitizeError(error, requestId)
24
+ const statusCode = getStatusCode(sanitized.code)
25
+
26
+ return c.json(
27
+ {
28
+ success: false,
29
+ message: sanitized.message,
30
+ code: sanitized.code,
31
+ },
32
+ statusCode
33
+ )
34
+ }
35
+
36
+ /**
37
+ * Handle errors from record restore operations
38
+ *
39
+ * Adds restore-specific "Record is not deleted" check, then delegates
40
+ * to the shared route error handler.
41
+ */
42
+ export function handleRestoreRecordError(c: Context, error: unknown): Response {
43
+ // Check for "Record is not deleted" error (safe to expose)
44
+ const errorMessage = error instanceof Error ? error.message : String(error)
45
+ if (errorMessage === 'Record is not deleted') {
46
+ return c.json(
47
+ { success: false, message: 'Record is not deleted', code: 'VALIDATION_ERROR' },
48
+ 400
49
+ )
50
+ }
51
+
52
+ return handleRouteError(c, error)
53
+ }