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,237 @@
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
+ /* eslint-disable max-params, max-lines-per-function -- Complex URL rewriting functions require explicit dependencies */
9
+
10
+ import { Effect, Console } from 'effect'
11
+ import { StaticGenerationError } from '@/application/errors/static-generation-error'
12
+ import type { FileSystemLike, PathModuleLike } from './generate-static-helpers'
13
+
14
+ /**
15
+ * Check if a URL is a GitHub Pages URL
16
+ * Security: Use proper hostname checking to avoid substring matching vulnerabilities
17
+ */
18
+ export const isGitHubPagesUrl = (url: string): boolean => {
19
+ try {
20
+ const { hostname } = new URL(url)
21
+ return hostname === 'github.io' || hostname.endsWith('.github.io')
22
+ } catch {
23
+ return false
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Rewrite URLs in HTML content with base path prefix
29
+ *
30
+ * Rewrites:
31
+ * - href="/..." → href="/basePath/..."
32
+ * - src="/..." → src="/basePath/..."
33
+ * - Adds canonical link if missing (when baseUrl is provided)
34
+ * - Skips URLs that already have base path
35
+ * - Skips external URLs (http://, https://, //, etc.)
36
+ */
37
+ export const rewriteUrlsWithBasePath = (
38
+ html: string,
39
+ basePath: string,
40
+ baseUrl?: string,
41
+ pagePath?: string
42
+ ): string => {
43
+ // Remove trailing slash from basePath if present
44
+ const normalizedBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath
45
+
46
+ // Extract base path without language code for assets
47
+ // If basePath is "/my-project/en", extract "/my-project" for assets
48
+ const basePathWithoutLang = normalizedBasePath.replace(/\/[a-z]{2}$/, '')
49
+
50
+ // Rewrite href="/..." to href="/basePath/..."
51
+ // But for /assets/*, use only the base path without language code
52
+ // Also handle hreflang alternate links specially (they should link to other languages)
53
+ const hrefRewritten = html.replace(
54
+ /(hrefLang|hreflang)="([^"]*)"[^>]*href="(\/[^"]*)"/g,
55
+ (_match, _hreflangAttr, langCode, url) => {
56
+ // For hreflang links, extract language from URL and construct correct path
57
+ // Example: href="/en/" → href="https://baseUrl/my-project/en/"
58
+ // Convert hrefLang to lowercase hreflang for standards compliance
59
+ const normalizedAttr = 'hreflang'
60
+ // Extract short code from full locale (e.g., "en-US" → "en")
61
+ const shortCode = langCode.split('-')[0]
62
+ // hreflang URLs should include full base URL
63
+ const fullUrl = baseUrl ? `${baseUrl}${url}` : `${basePathWithoutLang}${url}`
64
+ return `${normalizedAttr}="${shortCode}" href="${fullUrl}"`
65
+ }
66
+ )
67
+
68
+ // Now rewrite remaining href attributes (non-hreflang)
69
+ const allHrefRewritten = hrefRewritten.replace(
70
+ /(?<!hreflang=")href="(\/[^"]*)"/g,
71
+ (_match, url) => {
72
+ if (url.startsWith('/assets/')) {
73
+ // Assets are shared across languages - use base path only
74
+ return `href="${basePathWithoutLang}${url}"`
75
+ }
76
+ return `href="${normalizedBasePath}${url}"`
77
+ }
78
+ )
79
+
80
+ // Rewrite src="/..." to src="/basePath/..."
81
+ // Assets paths should not include language prefix
82
+ const srcRewritten = allHrefRewritten.replace(/src="(\/[^"]*)"/g, (_match, url) => {
83
+ if (url.startsWith('/assets/')) {
84
+ // Assets are shared across languages - use base path only
85
+ return `src="${basePathWithoutLang}${url}"`
86
+ }
87
+ return `src="${normalizedBasePath}${url}"`
88
+ })
89
+
90
+ // Add canonical link if missing and baseUrl is provided
91
+ if (baseUrl && pagePath && !srcRewritten.includes('rel="canonical"')) {
92
+ // Construct canonical URL from baseUrl + pagePath
93
+ // Remove trailing slash from baseUrl for consistent URLs
94
+ const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl
95
+ const canonicalPath = pagePath === '/' ? '/' : pagePath
96
+ const canonicalUrl = `${normalizedBaseUrl}${canonicalPath}`
97
+
98
+ // Insert canonical link in <head> before </head>
99
+ return srcRewritten.replace('</head>', `<link rel="canonical" href="${canonicalUrl}"></head>`)
100
+ }
101
+
102
+ return srcRewritten
103
+ }
104
+
105
+ /**
106
+ * Rewrite URLs in HTML files with base path
107
+ */
108
+ export const rewriteBasePathInHtml = (
109
+ generatedFiles: readonly string[],
110
+ outputDir: string,
111
+ basePath: string,
112
+ baseUrl: string | undefined,
113
+ fsModule: FileSystemLike
114
+ ): Effect.Effect<void, StaticGenerationError, never> =>
115
+ Effect.gen(function* () {
116
+ yield* Console.log(`🔗 Rewriting URLs with base path: ${basePath}...`)
117
+ // Only rewrite actual page HTML files, not assets with .html extension
118
+ const htmlFiles = generatedFiles.filter(
119
+ (f) =>
120
+ f.endsWith('.html') &&
121
+ !f.startsWith('assets/') &&
122
+ !f.includes('/assets/') &&
123
+ !f.endsWith('.js.html')
124
+ )
125
+
126
+ yield* Effect.forEach(
127
+ htmlFiles,
128
+ (file) =>
129
+ Effect.gen(function* () {
130
+ // file might be absolute or relative - handle both cases
131
+ const filePath =
132
+ file.startsWith('/') || file.startsWith(outputDir) ? file : `${outputDir}/${file}`
133
+
134
+ const content = yield* Effect.tryPromise({
135
+ try: () => fsModule.readFile(filePath, 'utf-8'),
136
+ catch: (error) =>
137
+ new StaticGenerationError({
138
+ message: `Failed to read HTML file: ${file}`,
139
+ cause: error,
140
+ }),
141
+ })
142
+
143
+ // Determine page path from filename (index.html → /, about.html → /about)
144
+ const relativeFile = file.startsWith(outputDir) ? file.slice(outputDir.length + 1) : file
145
+
146
+ // Check if this file is in a language subdirectory (e.g., en/index.html, fr/about.html)
147
+ const langMatch = relativeFile.match(/^([a-z]{2})\//)
148
+ const langPrefix = langMatch ? `/${langMatch[1]}` : ''
149
+
150
+ // Extract page path without language prefix
151
+ const fileWithoutLang = langPrefix ? relativeFile.slice(3) : relativeFile // Skip "en/"
152
+ const basePagePath =
153
+ fileWithoutLang === 'index.html' ? '/' : `/${fileWithoutLang.replace('.html', '')}`
154
+
155
+ // Full page path includes basePath + language prefix
156
+ const fullBasePath = `${basePath}${langPrefix}`
157
+
158
+ // For canonical URL, use the page path relative to baseUrl
159
+ // If baseUrl includes the basePath (e.g., https://example.com/myapp),
160
+ // we should use basePagePath directly
161
+ const canonicalPagePath =
162
+ basePagePath === '/'
163
+ ? langPrefix === ''
164
+ ? '/'
165
+ : langPrefix
166
+ : `${langPrefix}${basePagePath}`
167
+
168
+ const rewrittenContent = rewriteUrlsWithBasePath(
169
+ content,
170
+ fullBasePath,
171
+ baseUrl,
172
+ canonicalPagePath
173
+ )
174
+
175
+ yield* Effect.tryPromise({
176
+ try: () => fsModule.writeFile(filePath, rewrittenContent, 'utf-8'),
177
+ catch: (error) =>
178
+ new StaticGenerationError({
179
+ message: `Failed to write rewritten HTML file: ${file}`,
180
+ cause: error,
181
+ }),
182
+ })
183
+ }),
184
+ { concurrency: 'unbounded' }
185
+ )
186
+ })
187
+
188
+ /**
189
+ * Inject client-side hydration script into HTML files
190
+ */
191
+ export const injectHydrationScript = (
192
+ generatedFiles: readonly string[],
193
+ outputDir: string,
194
+ basePath: string,
195
+ fsModule: FileSystemLike,
196
+ pathModule: PathModuleLike
197
+ ): Effect.Effect<void, StaticGenerationError, never> =>
198
+ Effect.gen(function* () {
199
+ yield* Console.log('💧 Injecting hydration script into HTML files...')
200
+ const htmlFiles = generatedFiles.filter(
201
+ (f) =>
202
+ f.endsWith('.html') &&
203
+ !f.endsWith('.js.html') &&
204
+ !f.startsWith('assets/') &&
205
+ !f.includes('/assets/')
206
+ )
207
+
208
+ yield* Effect.forEach(
209
+ htmlFiles,
210
+ (file) =>
211
+ Effect.gen(function* () {
212
+ const filePath = file.startsWith('/') ? file : pathModule.join(outputDir, file)
213
+ const content = yield* Effect.tryPromise({
214
+ try: () => fsModule.readFile(filePath, 'utf-8'),
215
+ catch: (error) =>
216
+ new StaticGenerationError({
217
+ message: `Failed to read HTML file for hydration injection: ${file}`,
218
+ cause: error,
219
+ }),
220
+ })
221
+
222
+ // Inject hydration script before </body>
223
+ const hydrationScript = `<script src="${basePath}/assets/client.js" defer=""></script>`
224
+ const updatedContent = content.replace('</body>', `${hydrationScript}\n</body>`)
225
+
226
+ yield* Effect.tryPromise({
227
+ try: () => fsModule.writeFile(filePath, updatedContent, 'utf-8'),
228
+ catch: (error) =>
229
+ new StaticGenerationError({
230
+ message: `Failed to write HTML file with hydration script: ${file}`,
231
+ cause: error,
232
+ }),
233
+ })
234
+ }),
235
+ { concurrency: 'unbounded' }
236
+ )
237
+ })
@@ -0,0 +1,164 @@
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 { resolveTranslation } from '@/domain/utils/translation-resolver'
9
+ import type { App, Page } from '@/domain/models/app'
10
+ import type { LanguageConfig } from '@/domain/models/app/language/language-config'
11
+ import type { Languages } from '@/domain/models/app/languages'
12
+
13
+ /**
14
+ * Context for token replacement operations
15
+ */
16
+ type TokenReplacementContext = {
17
+ readonly langCode: string
18
+ readonly langConfig: LanguageConfig
19
+ readonly languages: Languages | undefined
20
+ readonly translations: Record<string, string>
21
+ readonly currentPath?: string // Optional current page path for {{currentPath}} replacement
22
+ }
23
+
24
+ /**
25
+ * Replace translation tokens in a string
26
+ *
27
+ * Replaces $t:key patterns with translations from the centralized translations dictionary.
28
+ * Also replaces {{currentPath}} with the current page path.
29
+ * Uses the same resolution logic as dynamic rendering (with fallback support).
30
+ *
31
+ * @param str - String potentially containing $t:key or {{currentPath}} patterns
32
+ * @param context - Token replacement context
33
+ * @returns String with all patterns resolved
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * replaceTokens('$t:welcome', context) // 'Welcome' (or 'Bienvenue' for fr)
38
+ * replaceTokens('$t:goodbye', context) // Falls back to default if missing
39
+ * replaceTokens('/en{{currentPath}}', { ...context, currentPath: '/about' }) // '/en/about'
40
+ * replaceTokens('Hello world', context) // 'Hello world' (no pattern)
41
+ * ```
42
+ */
43
+ function replaceTokens(str: string, context: TokenReplacementContext): string {
44
+ // Replace $t: translation pattern
45
+ const translatedStr = str.startsWith('$t:')
46
+ ? resolveTranslation(str.slice(3), context.langCode, context.languages)
47
+ : str
48
+
49
+ // Replace {{currentPath}} pattern (for language switcher hrefs)
50
+ const currentPath = context.currentPath || '/'
51
+ return translatedStr.replace(/\{\{currentPath\}\}/g, currentPath)
52
+ }
53
+
54
+ /**
55
+ * Replace tokens in any value (recursively handles objects, arrays, strings)
56
+ */
57
+ function replaceTokensInValue(value: unknown, context: TokenReplacementContext): unknown {
58
+ if (typeof value === 'string') {
59
+ return replaceTokens(value, context)
60
+ }
61
+
62
+ if (Array.isArray(value)) {
63
+ return value.map((item) => replaceTokensInValue(item, context))
64
+ }
65
+
66
+ if (value !== null && typeof value === 'object') {
67
+ return Object.fromEntries(
68
+ Object.entries(value).map(([key, val]) => [key, replaceTokensInValue(val, context)])
69
+ )
70
+ }
71
+
72
+ return value
73
+ }
74
+
75
+ /**
76
+ * Replace translation tokens in page meta and set lang attribute programmatically
77
+ *
78
+ * Sets meta.lang to the full locale (e.g., 'en-US', 'fr-FR') without using tokens.
79
+ * Then resolves any $t: patterns in the remaining meta fields.
80
+ */
81
+ function replaceMetaTokens(meta: Page['meta'], context: TokenReplacementContext): Page['meta'] {
82
+ if (!meta) return meta
83
+
84
+ // Set lang to full locale programmatically (not via token)
85
+ const locale = context.langConfig.locale || context.langCode
86
+ const metaWithLang = {
87
+ ...meta,
88
+ lang: locale,
89
+ }
90
+
91
+ // Resolve $t: patterns in remaining meta fields
92
+ return replaceTokensInValue(metaWithLang, context) as Page['meta']
93
+ }
94
+
95
+ /**
96
+ * Replace translation tokens in a page configuration
97
+ *
98
+ * Resolves $t:key patterns throughout the page and sets meta.lang programmatically.
99
+ * Also replaces {{currentPath}} patterns in language switcher hrefs.
100
+ *
101
+ * @param page - Page with potential $t:key translation tokens
102
+ * @param context - Token replacement context
103
+ * @returns Page with all $t: patterns resolved
104
+ */
105
+ export function replacePageTokens(page: Page, context: TokenReplacementContext): Page {
106
+ const pageContext: TokenReplacementContext = {
107
+ ...context,
108
+ currentPath: page.path, // Pass current page path for {{currentPath}} replacement
109
+ }
110
+
111
+ // Replace tokens in everything except meta
112
+ const { meta, ...restOfPage } = page
113
+ const replacedRest = replaceTokensInValue(restOfPage, pageContext) as Omit<Page, 'meta'>
114
+
115
+ // Replace meta separately with special lang handling
116
+ const replacedMeta = replaceMetaTokens(meta, pageContext)
117
+
118
+ return {
119
+ ...replacedRest,
120
+ meta: replacedMeta,
121
+ } as Page
122
+ }
123
+
124
+ /**
125
+ * Replace translation tokens in app configuration for a specific language
126
+ *
127
+ * Resolves all $t:key patterns throughout the app for static site generation.
128
+ * This is a pure function that performs token replacement without throwing exceptions.
129
+ * Callers should validate language codes before calling this function.
130
+ *
131
+ * @param app - App configuration (may contain $t:key tokens in pages)
132
+ * @param langCode - Language code to generate for (e.g., 'en', 'fr')
133
+ * @returns App with all $t: patterns resolved for the language
134
+ * @internal This function assumes langCode exists in supported languages
135
+ */
136
+ export function replaceAppTokens(app: App, langCode: string): App {
137
+ // If no languages configured, return app as-is
138
+ if (!app.languages) {
139
+ return app
140
+ }
141
+
142
+ // Find language config - caller must ensure langCode is valid
143
+ const langConfig = app.languages.supported.find((lang) => lang.code === langCode)!
144
+
145
+ // Get translations for this language
146
+ const translations = app.languages.translations?.[langCode] || {}
147
+
148
+ // Create context for token replacement
149
+ const context: TokenReplacementContext = {
150
+ langCode,
151
+ langConfig,
152
+ languages: app.languages,
153
+ translations,
154
+ }
155
+
156
+ // Replace tokens in pages (with currentPath resolution)
157
+ const pages = app.pages?.map((page) => replacePageTokens(page, context))
158
+
159
+ // Return app with replaced pages
160
+ return {
161
+ ...app,
162
+ pages,
163
+ }
164
+ }
@@ -0,0 +1,93 @@
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 { ActivityRepository } from '@/application/ports/repositories/activity-repository'
10
+ import { SessionContextError } from '@/domain/errors'
11
+ import type { UserMetadataWithImage } from '@/application/ports/models/user-metadata'
12
+ import type { UserSession } from '@/application/ports/models/user-session'
13
+ import type { ActivityHistoryEntry } from '@/application/ports/repositories/activity-repository'
14
+
15
+ /**
16
+ * Get record history configuration
17
+ */
18
+ interface GetRecordHistoryConfig {
19
+ readonly session: Readonly<UserSession>
20
+ readonly tableName: string
21
+ readonly recordId: string
22
+ readonly limit?: number
23
+ readonly offset?: number
24
+ }
25
+
26
+ /**
27
+ * Format activity history entry for API response
28
+ */
29
+ function formatActivityEntry(entry: ActivityHistoryEntry) {
30
+ return {
31
+ action: entry.action,
32
+ createdAt: entry.createdAt.toISOString(),
33
+ changes: entry.changes,
34
+ user: entry.user,
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Get record history program
40
+ */
41
+ export function getRecordHistoryProgram(config: GetRecordHistoryConfig): Effect.Effect<
42
+ {
43
+ readonly history: readonly {
44
+ readonly action: string
45
+ readonly createdAt: string
46
+ readonly changes: unknown
47
+ readonly user: UserMetadataWithImage | undefined
48
+ }[]
49
+ readonly pagination: {
50
+ readonly limit: number
51
+ readonly offset: number
52
+ readonly total: number
53
+ }
54
+ },
55
+ SessionContextError,
56
+ ActivityRepository
57
+ > {
58
+ return Effect.gen(function* () {
59
+ const activityRepo = yield* ActivityRepository
60
+ const { session, tableName, recordId, limit, offset } = config
61
+
62
+ // Check if record exists in the table (handles live records)
63
+ const recordExists = yield* activityRepo.checkRecordExists({ session, tableName, recordId })
64
+
65
+ // Fetch activity history with pagination (needed even for deleted records)
66
+ const { entries, total } = yield* activityRepo.getRecordHistory({
67
+ session,
68
+ tableName,
69
+ recordId,
70
+ limit,
71
+ offset,
72
+ })
73
+
74
+ // If record doesn't exist in table AND has no activity logs, it truly doesn't exist
75
+ if (!recordExists && total === 0) {
76
+ return yield* Effect.fail(new SessionContextError('Record not found'))
77
+ }
78
+
79
+ // Resolve pagination values (default: all results)
80
+ const resolvedLimit = limit ?? total
81
+ const resolvedOffset = offset ?? 0
82
+
83
+ // Format response
84
+ return {
85
+ history: entries.map(formatActivityEntry),
86
+ pagination: {
87
+ limit: resolvedLimit,
88
+ offset: resolvedOffset,
89
+ total,
90
+ },
91
+ }
92
+ })
93
+ }
@@ -0,0 +1,156 @@
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 {
10
+ BatchRepository,
11
+ type BatchValidationError,
12
+ } from '@/application/ports/repositories/batch-repository'
13
+ import { transformRecords, type TransformedRecord } from './utils/record-transformer'
14
+ import type { UserSession } from '@/application/ports/models/user-session'
15
+ import type { SessionContextError, ValidationError } from '@/domain/errors'
16
+ import type { BatchRestoreRecordsResponse } from '@/domain/models/api/tables'
17
+ import type { App } from '@/domain/models/app'
18
+
19
+ export function batchCreateProgram(config: {
20
+ readonly session: Readonly<UserSession>
21
+ readonly tableName: string
22
+ readonly recordsData: readonly Record<string, unknown>[]
23
+ readonly returnRecords?: boolean
24
+ readonly app?: App
25
+ }): Effect.Effect<
26
+ { readonly created: number; readonly records?: readonly TransformedRecord[] },
27
+ SessionContextError | ValidationError,
28
+ BatchRepository
29
+ > {
30
+ const { session, tableName, recordsData, returnRecords = false, app } = config
31
+ return Effect.gen(function* () {
32
+ const batch = yield* BatchRepository
33
+
34
+ // Create records in the database
35
+ const createdRecords = yield* batch.batchCreate(session, tableName, recordsData)
36
+
37
+ // Transform records to API format with app schema for numeric coercion
38
+ const transformed = transformRecords(createdRecords, { app, tableName })
39
+
40
+ // Use functional pattern to build response object
41
+ const response: { readonly created: number; readonly records?: readonly TransformedRecord[] } =
42
+ returnRecords
43
+ ? {
44
+ created: transformed.length,
45
+ records: transformed as TransformedRecord[],
46
+ }
47
+ : {
48
+ created: transformed.length,
49
+ }
50
+
51
+ return response
52
+ })
53
+ }
54
+
55
+ export function batchUpdateProgram(config: {
56
+ readonly session: Readonly<UserSession>
57
+ readonly tableName: string
58
+ readonly recordsData: readonly {
59
+ readonly id: string
60
+ readonly fields?: Record<string, unknown>
61
+ }[]
62
+ readonly returnRecords?: boolean
63
+ readonly app?: App
64
+ }): Effect.Effect<
65
+ { readonly updated: number; readonly records?: readonly TransformedRecord[] },
66
+ SessionContextError | ValidationError,
67
+ BatchRepository
68
+ > {
69
+ const { session, tableName, recordsData, returnRecords = false, app } = config
70
+ return Effect.gen(function* () {
71
+ const batch = yield* BatchRepository
72
+ const updatedRecords = yield* batch.batchUpdate(session, tableName, recordsData)
73
+
74
+ // Transform records to API format with app schema for numeric coercion
75
+ const transformed = transformRecords(updatedRecords, { app, tableName })
76
+
77
+ // Use functional pattern to build response object
78
+ const response: { readonly updated: number; readonly records?: readonly TransformedRecord[] } =
79
+ returnRecords
80
+ ? {
81
+ updated: transformed.length,
82
+ records: transformed as TransformedRecord[],
83
+ }
84
+ : {
85
+ updated: transformed.length,
86
+ }
87
+
88
+ return response
89
+ })
90
+ }
91
+
92
+ export function batchDeleteProgram(
93
+ session: Readonly<UserSession>,
94
+ tableName: string,
95
+ ids: readonly string[],
96
+ permanent = false
97
+ ): Effect.Effect<{ deleted: number }, SessionContextError, BatchRepository> {
98
+ return Effect.gen(function* () {
99
+ const batch = yield* BatchRepository
100
+ const deletedCount = yield* batch.batchDelete(session, tableName, ids, permanent)
101
+ return {
102
+ deleted: deletedCount,
103
+ }
104
+ })
105
+ }
106
+
107
+ export function batchRestoreProgram(
108
+ session: Readonly<UserSession>,
109
+ tableName: string,
110
+ ids: readonly string[]
111
+ ): Effect.Effect<BatchRestoreRecordsResponse, SessionContextError, BatchRepository> {
112
+ return Effect.gen(function* () {
113
+ const batch = yield* BatchRepository
114
+ const restored = yield* batch.batchRestore(session, tableName, ids)
115
+ return {
116
+ success: true as const,
117
+ restored,
118
+ }
119
+ })
120
+ }
121
+
122
+ export function upsertProgram(
123
+ session: Readonly<UserSession>,
124
+ tableName: string,
125
+ params: {
126
+ readonly recordsData: readonly Record<string, unknown>[]
127
+ readonly fieldsToMergeOn: readonly string[]
128
+ readonly returnRecords: boolean
129
+ readonly app?: App
130
+ }
131
+ ): Effect.Effect<
132
+ {
133
+ readonly records: readonly TransformedRecord[]
134
+ readonly created: number
135
+ readonly updated: number
136
+ },
137
+ SessionContextError | ValidationError | BatchValidationError,
138
+ BatchRepository
139
+ > {
140
+ return Effect.gen(function* () {
141
+ const batch = yield* BatchRepository
142
+ const result = yield* batch.upsert(
143
+ session,
144
+ tableName,
145
+ params.recordsData,
146
+ params.fieldsToMergeOn
147
+ )
148
+
149
+ const transformed = transformRecords(result.records, { app: params.app, tableName })
150
+ return {
151
+ records: transformed,
152
+ created: result.created,
153
+ updated: result.updated,
154
+ }
155
+ })
156
+ }