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,695 @@
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 { Schema } from 'effect'
9
+ import { TableSchema } from '@/domain/models/app/table'
10
+ import { detectCycles } from '@/domain/models/app/table/cycle-detection'
11
+
12
+ /**
13
+ * Auto-generate table IDs for tables that don't have one
14
+ *
15
+ * Tables without explicit IDs get auto-generated numeric IDs.
16
+ * IDs are assigned sequentially starting from the highest existing ID + 1.
17
+ */
18
+ const autoGenerateTableIds = (
19
+ tables: ReadonlyArray<Record<string, unknown>>
20
+ ): ReadonlyArray<Record<string, unknown>> => {
21
+ // Find the highest existing numeric ID
22
+ const maxId = tables.reduce((max, table) => {
23
+ if (table.id !== undefined && typeof table.id === 'number') {
24
+ return Math.max(max, table.id)
25
+ }
26
+ return max
27
+ }, 0)
28
+
29
+ // Assign IDs to tables without one using reduce (functional pattern)
30
+ const { tablesWithIds } = tables.reduce<{
31
+ tablesWithIds: ReadonlyArray<Record<string, unknown>>
32
+ nextId: number
33
+ }>(
34
+ (acc, table) => {
35
+ if (table.id === undefined) {
36
+ return {
37
+ tablesWithIds: [...acc.tablesWithIds, { ...table, id: acc.nextId }],
38
+ nextId: acc.nextId + 1,
39
+ }
40
+ }
41
+ return {
42
+ ...acc,
43
+ tablesWithIds: [...acc.tablesWithIds, table],
44
+ }
45
+ },
46
+ { tablesWithIds: [], nextId: maxId + 1 }
47
+ )
48
+
49
+ return tablesWithIds
50
+ }
51
+
52
+ /**
53
+ * Detect circular relationship dependencies between tables.
54
+ * A circular dependency exists when Table A references Table B, and Table B references Table A
55
+ * (directly or through a chain of other tables).
56
+ *
57
+ * Self-referencing relationships (e.g., employees.manager → employees) are NOT considered circular
58
+ * dependencies because they don't prevent table creation order determination.
59
+ *
60
+ * Bidirectional relationships (one-to-many in one direction, many-to-one in the reverse) are also
61
+ * NOT circular dependencies because the foreign key is always on the "many" side, allowing proper
62
+ * table creation order (create "one" side first, then "many" side with FK).
63
+ *
64
+ * Circular dependencies are ALLOWED when at least one side allows NULL (required: false).
65
+ * This enables the INSERT-UPDATE pattern:
66
+ * 1. INSERT with NULL foreign key
67
+ * 2. INSERT second table with reference to first
68
+ * 3. UPDATE first table to complete the circular reference
69
+ * PostgreSQL validates constraints at statement end, making this pattern valid.
70
+ *
71
+ * @param tables - Array of tables to validate
72
+ * @returns Array of table names involved in circular dependencies, or empty array if none found
73
+ */
74
+ const detectCircularRelationships = (
75
+ tables: ReadonlyArray<{
76
+ readonly name: string
77
+ readonly fields: ReadonlyArray<{
78
+ readonly name: string
79
+ readonly type: string
80
+ readonly relatedTable?: string
81
+ readonly relationType?: string
82
+ readonly required?: boolean
83
+ }>
84
+ }>
85
+ ): ReadonlyArray<string> => {
86
+ // Build dependency graph: table name -> tables it references via REQUIRED many-to-one relationships
87
+ // We only track REQUIRED (NOT NULL) relationships because those prevent INSERT-UPDATE pattern
88
+ // Optional (NULL-able) relationships can be created later via UPDATE after both tables exist
89
+ const dependencyGraph: ReadonlyMap<string, ReadonlyArray<string>> = new Map(
90
+ tables.map((table) => {
91
+ const relatedTables = table.fields
92
+ .filter(
93
+ (field) =>
94
+ field.type === 'relationship' &&
95
+ field.relatedTable !== undefined &&
96
+ field.relatedTable !== table.name && // Exclude self-references
97
+ (field.relationType === 'many-to-one' || field.relationType === undefined) && // Only track dependencies from FK side
98
+ field.required !== false // Track required relationships (undefined = required by default, only false = optional)
99
+ )
100
+ .map((field) => field.relatedTable as string)
101
+ return [table.name, relatedTables] as const
102
+ })
103
+ )
104
+
105
+ // Use shared cycle detection utility
106
+ return detectCycles(dependencyGraph)
107
+ }
108
+
109
+ /**
110
+ * Detect circular permission inheritance between tables.
111
+ * A circular permission inheritance exists when Table A inherits from Table B,
112
+ * and Table B inherits from Table A (directly or through a chain of other tables).
113
+ *
114
+ * @param tables - Array of tables to validate
115
+ * @returns Array of table names involved in circular permission inheritance, or empty array if none found
116
+ */
117
+ const detectCircularPermissionInheritance = (
118
+ tables: ReadonlyArray<{
119
+ readonly name: string
120
+ readonly permissions?: {
121
+ readonly inherit?: string
122
+ }
123
+ }>
124
+ ): ReadonlyArray<string> => {
125
+ // Build dependency graph: table name -> parent table it inherits permissions from
126
+ const dependencyGraph: ReadonlyMap<string, ReadonlyArray<string>> = new Map(
127
+ tables.map((table) => {
128
+ const inheritFrom = table.permissions?.inherit
129
+ return [table.name, inheritFrom ? [inheritFrom] : []] as const
130
+ })
131
+ )
132
+
133
+ // Use shared cycle detection utility
134
+ return detectCycles(dependencyGraph)
135
+ }
136
+
137
+ /**
138
+ * Validate that a relationship field reference is valid.
139
+ *
140
+ * This helper validates that:
141
+ * 1. The relationshipField exists in the current table
142
+ * 2. The relationshipField is a relationship type
143
+ * 3. The relatedField exists in the related table (if applicable)
144
+ *
145
+ * Used by both lookup and rollup field validation.
146
+ *
147
+ * @param params - Validation parameters
148
+ * @returns Error object if validation fails, undefined if valid
149
+ */
150
+ const validateRelationshipFieldReference = (params: {
151
+ readonly table: {
152
+ readonly name: string
153
+ readonly fields: ReadonlyArray<{
154
+ readonly name: string
155
+ readonly type: string
156
+ }>
157
+ }
158
+ readonly fieldName: string
159
+ readonly relationshipField: string
160
+ readonly relatedField: string | undefined
161
+ readonly tablesByName: ReadonlyMap<
162
+ string,
163
+ {
164
+ readonly name: string
165
+ readonly fields: ReadonlyArray<{
166
+ readonly name: string
167
+ readonly type: string
168
+ }>
169
+ }
170
+ >
171
+ }):
172
+ | {
173
+ readonly table: string
174
+ readonly field: string
175
+ readonly error: string
176
+ }
177
+ | undefined => {
178
+ const { table, fieldName, relationshipField, relatedField, tablesByName } = params
179
+
180
+ // Check if relationshipField exists in the same table
181
+ const fieldInSameTable = table.fields.find((f) => f.name === relationshipField)
182
+ if (!fieldInSameTable) {
183
+ return {
184
+ table: table.name,
185
+ field: fieldName,
186
+ error: `relationshipField "${relationshipField}" not found`,
187
+ }
188
+ }
189
+
190
+ // Relationship field must be a relationship type
191
+ if (fieldInSameTable.type !== 'relationship') {
192
+ return {
193
+ table: table.name,
194
+ field: fieldName,
195
+ error: `relationshipField "${relationshipField}" must reference a relationship field`,
196
+ }
197
+ }
198
+
199
+ // Validate relatedField if provided
200
+ if (relatedField !== undefined) {
201
+ const relatedTableName = (fieldInSameTable as { relatedTable?: string }).relatedTable
202
+ if (relatedTableName) {
203
+ const relatedTable = tablesByName.get(relatedTableName)
204
+ if (relatedTable) {
205
+ // Check if relatedField exists in the related table
206
+ // Note: 'id' is always allowed as it's auto-generated (SERIAL primary key)
207
+ const relatedFieldExists =
208
+ relatedField === 'id' || relatedTable.fields.some((f) => f.name === relatedField)
209
+ if (!relatedFieldExists) {
210
+ return {
211
+ table: table.name,
212
+ field: fieldName,
213
+ error: `relatedField "${relatedField}" not found in related table "${relatedTableName}"`,
214
+ }
215
+ }
216
+ }
217
+ }
218
+ }
219
+
220
+ return undefined
221
+ }
222
+
223
+ /**
224
+ * Find the related field type for a rollup aggregation.
225
+ *
226
+ * @param params - Lookup parameters
227
+ * @returns The related field type, or undefined if not found
228
+ */
229
+ const findRelatedFieldType = (params: {
230
+ readonly table: {
231
+ readonly name: string
232
+ readonly fields: ReadonlyArray<{
233
+ readonly name: string
234
+ readonly type: string
235
+ }>
236
+ }
237
+ readonly relationshipField: string
238
+ readonly relatedField: string
239
+ readonly tablesByName: ReadonlyMap<
240
+ string,
241
+ {
242
+ readonly name: string
243
+ readonly fields: ReadonlyArray<{
244
+ readonly name: string
245
+ readonly type: string
246
+ }>
247
+ }
248
+ >
249
+ }): string | undefined => {
250
+ const { table, relationshipField, relatedField, tablesByName } = params
251
+
252
+ // Find the relationship field to get the related table name
253
+ const relationshipFieldObj = table.fields.find((f) => f.name === relationshipField)
254
+ if (!relationshipFieldObj || relationshipFieldObj.type !== 'relationship') {
255
+ return undefined
256
+ }
257
+
258
+ const relatedTableName = (relationshipFieldObj as { relatedTable?: string }).relatedTable
259
+ if (!relatedTableName) {
260
+ return undefined
261
+ }
262
+
263
+ const relatedTable = tablesByName.get(relatedTableName)
264
+ if (!relatedTable) {
265
+ return undefined
266
+ }
267
+
268
+ // Find the related field to check its type
269
+ // Note: 'id' is always allowed as it's auto-generated (SERIAL primary key - integer type)
270
+ const relatedFieldObj =
271
+ relatedField === 'id'
272
+ ? { name: 'id', type: 'integer' }
273
+ : relatedTable.fields.find((f) => f.name === relatedField)
274
+
275
+ return relatedFieldObj?.type
276
+ }
277
+
278
+ /**
279
+ * Check if field type is numeric.
280
+ */
281
+ const isNumericFieldType = (fieldType: string): boolean => {
282
+ const numericTypes = ['integer', 'decimal', 'currency', 'percentage', 'duration']
283
+ return numericTypes.includes(fieldType)
284
+ }
285
+
286
+ /**
287
+ * Check if field type is date.
288
+ */
289
+ const isDateFieldType = (fieldType: string): boolean => {
290
+ return fieldType === 'date'
291
+ }
292
+
293
+ /**
294
+ * Validate sum/avg aggregation requires numeric field.
295
+ */
296
+ const validateNumericAggregation = (aggregation: string, fieldType: string): string | undefined => {
297
+ if (!isNumericFieldType(fieldType)) {
298
+ return `aggregation function "${aggregation}" is incompatible with field type "${fieldType}" - numeric field required`
299
+ }
300
+ return undefined
301
+ }
302
+
303
+ /**
304
+ * Validate min/max aggregation requires numeric or date field.
305
+ */
306
+ const validateMinMaxAggregation = (aggregation: string, fieldType: string): string | undefined => {
307
+ if (!isNumericFieldType(fieldType) && !isDateFieldType(fieldType)) {
308
+ return `aggregation function "${aggregation}" is incompatible with field type "${fieldType}" - numeric or date field required`
309
+ }
310
+ return undefined
311
+ }
312
+
313
+ /**
314
+ * Check if aggregation function is compatible with field type.
315
+ *
316
+ * @param aggregation - The aggregation function (sum, avg, min, max, count, counta, countall)
317
+ * @param fieldType - The field type to check compatibility against
318
+ * @returns Error message if incompatible, undefined if valid
319
+ */
320
+ const checkAggregationCompatibility = (
321
+ aggregation: string,
322
+ fieldType: string
323
+ ): string | undefined => {
324
+ const aggregationLower = aggregation.toLowerCase()
325
+
326
+ switch (aggregationLower) {
327
+ case 'sum':
328
+ case 'avg':
329
+ return validateNumericAggregation(aggregation, fieldType)
330
+ case 'min':
331
+ case 'max':
332
+ return validateMinMaxAggregation(aggregation, fieldType)
333
+ case 'count':
334
+ case 'counta':
335
+ case 'countall':
336
+ // Count functions work with any field type
337
+ return undefined
338
+ }
339
+
340
+ return undefined
341
+ }
342
+
343
+ /**
344
+ * Validate that a rollup aggregation function is compatible with the related field type.
345
+ *
346
+ * This helper validates that:
347
+ * 1. The aggregation function is supported
348
+ * 2. The aggregation function is compatible with the related field's type
349
+ *
350
+ * Aggregation compatibility:
351
+ * - sum, avg: Only numeric fields (integer, decimal, currency, percentage, duration)
352
+ * - min, max: Numeric and date fields
353
+ * - count, counta, countall: Any field type
354
+ *
355
+ * @param params - Validation parameters
356
+ * @returns Error object if validation fails, undefined if valid
357
+ */
358
+ const validateRollupAggregation = (params: {
359
+ readonly table: {
360
+ readonly name: string
361
+ readonly fields: ReadonlyArray<{
362
+ readonly name: string
363
+ readonly type: string
364
+ }>
365
+ }
366
+ readonly fieldName: string
367
+ readonly relationshipField: string
368
+ readonly relatedField: string
369
+ readonly aggregation: string
370
+ readonly tablesByName: ReadonlyMap<
371
+ string,
372
+ {
373
+ readonly name: string
374
+ readonly fields: ReadonlyArray<{
375
+ readonly name: string
376
+ readonly type: string
377
+ }>
378
+ }
379
+ >
380
+ }):
381
+ | {
382
+ readonly table: string
383
+ readonly field: string
384
+ readonly error: string
385
+ }
386
+ | undefined => {
387
+ const { table, fieldName, relationshipField, relatedField, aggregation, tablesByName } = params
388
+
389
+ const relatedFieldType = findRelatedFieldType({
390
+ table,
391
+ relationshipField,
392
+ relatedField,
393
+ tablesByName,
394
+ })
395
+
396
+ if (!relatedFieldType) {
397
+ // This should already be caught by validateRelationshipFieldReference
398
+ return undefined
399
+ }
400
+
401
+ const error = checkAggregationCompatibility(aggregation, relatedFieldType)
402
+ if (error) {
403
+ return {
404
+ table: table.name,
405
+ field: fieldName,
406
+ error,
407
+ }
408
+ }
409
+
410
+ return undefined
411
+ }
412
+
413
+ /**
414
+ * Validate all rollup fields across all tables.
415
+ *
416
+ * @param tables - Array of tables to validate
417
+ * @param tablesByName - Map of table names to table objects
418
+ * @returns Error object if validation fails, undefined if valid
419
+ */
420
+ const validateAllRollupFields = (
421
+ tables: ReadonlyArray<{
422
+ readonly name: string
423
+ readonly fields: ReadonlyArray<{
424
+ readonly name: string
425
+ readonly type: string
426
+ }>
427
+ }>,
428
+ tablesByName: ReadonlyMap<
429
+ string,
430
+ {
431
+ readonly name: string
432
+ readonly fields: ReadonlyArray<{
433
+ readonly name: string
434
+ readonly type: string
435
+ }>
436
+ }
437
+ >
438
+ ):
439
+ | {
440
+ readonly table: string
441
+ readonly field: string
442
+ readonly error: string
443
+ }
444
+ | undefined => {
445
+ return tables
446
+ .flatMap((table) =>
447
+ table.fields
448
+ .filter((field) => field.type === 'rollup')
449
+ .map((rollupField) => {
450
+ const { relationshipField, relatedField, aggregation } = rollupField as unknown as {
451
+ relationshipField: string
452
+ relatedField: string
453
+ aggregation: string
454
+ }
455
+
456
+ // First validate the relationship reference
457
+ const relationshipError = validateRelationshipFieldReference({
458
+ table,
459
+ fieldName: rollupField.name,
460
+ relationshipField,
461
+ relatedField,
462
+ tablesByName,
463
+ })
464
+
465
+ if (relationshipError) {
466
+ return relationshipError
467
+ }
468
+
469
+ // Then validate aggregation function compatibility with field type
470
+ return validateRollupAggregation({
471
+ table,
472
+ fieldName: rollupField.name,
473
+ relationshipField,
474
+ relatedField,
475
+ aggregation,
476
+ tablesByName,
477
+ })
478
+ })
479
+ .filter((error) => error !== undefined)
480
+ )
481
+ .at(0)
482
+ }
483
+
484
+ /**
485
+ * Data Tables
486
+ *
487
+ * Collection of database tables that define the data structure of your application.
488
+ * Each table represents an entity (e.g., users, products, orders) with fields that
489
+ * define the schema. Tables support relationships, indexes, constraints, and various
490
+ * field types. Tables are the foundation of your application's data model and
491
+ * determine what information can be stored and how it relates.
492
+ *
493
+ * Table IDs can be:
494
+ * - Explicit numeric IDs (e.g., 1, 2, 3)
495
+ * - UUID strings (e.g., '550e8400-e29b-41d4-a716-446655440000')
496
+ * - Simple string identifiers (e.g., 'products', 'users')
497
+ * - Auto-generated (omit the id field and it will be assigned automatically)
498
+ *
499
+ * @example
500
+ * ```typescript
501
+ * const tables = [
502
+ * {
503
+ * id: 1,
504
+ * name: 'users',
505
+ * fields: [
506
+ * { id: 1, name: 'email', type: 'email', required: true },
507
+ * { id: 2, name: 'name', type: 'text', required: true }
508
+ * ]
509
+ * }
510
+ * ]
511
+ * ```
512
+ *
513
+ * @see docs/specifications/roadmap/tables.md for full specification
514
+ */
515
+ export const TablesSchema = Schema.Array(TableSchema).pipe(
516
+ Schema.transform(
517
+ Schema.Array(TableSchema.pipe(Schema.annotations({ identifier: 'TableWithRequiredId' }))),
518
+ {
519
+ strict: true,
520
+ decode: (tables) =>
521
+ autoGenerateTableIds(tables as ReadonlyArray<Record<string, unknown>>) as ReadonlyArray<
522
+ Schema.Schema.Type<typeof TableSchema>
523
+ >,
524
+ encode: (tables) => tables,
525
+ }
526
+ ),
527
+ Schema.filter((tables) => {
528
+ const ids = tables.map((table) => table.id)
529
+ const uniqueIds = new Set(ids)
530
+ return ids.length === uniqueIds.size || 'Table IDs must be unique within the schema'
531
+ }),
532
+ Schema.filter((tables) => {
533
+ const names = tables.map((table) => table.name)
534
+ const uniqueNames = new Set(names)
535
+ return names.length === uniqueNames.size || 'Table names must be unique within the schema'
536
+ }),
537
+ Schema.filter((tables) => {
538
+ const circularTables = detectCircularRelationships(tables)
539
+ if (circularTables.length > 0) {
540
+ return `Circular relationship dependency detected: ${circularTables.join(' -> ')} - cannot resolve table creation order`
541
+ }
542
+ return true
543
+ }),
544
+ Schema.filter((tables) => {
545
+ const circularPermissions = detectCircularPermissionInheritance(tables)
546
+ if (circularPermissions.length > 0) {
547
+ return `Circular permission inheritance detected: ${circularPermissions.join(' -> ')} - inheritance cycle not allowed`
548
+ }
549
+ return true
550
+ }),
551
+ Schema.filter((tables) => {
552
+ // Validate that inherited tables exist
553
+ const tableNames = new Set(tables.map((table) => table.name))
554
+ const invalidInheritance = tables
555
+ .filter((table) => table.permissions?.inherit !== undefined)
556
+ .find((table) => !tableNames.has(table.permissions!.inherit!))
557
+
558
+ if (invalidInheritance) {
559
+ return `Table "${invalidInheritance.name}" inherits from table "${invalidInheritance.permissions!.inherit}" which does not exist`
560
+ }
561
+ return true
562
+ }),
563
+ Schema.filter((tables) => {
564
+ // Create tablesByName map once for all field validations
565
+ const tablesByName = new Map(tables.map((table) => [table.name, table]))
566
+
567
+ // Validate relationship fields reference existing tables
568
+ const invalidRelationship = tables
569
+ .flatMap((table) =>
570
+ table.fields
571
+ .filter((field) => field.type === 'relationship')
572
+ .map((relationshipField) => {
573
+ const { relatedTable } = relationshipField as { relatedTable?: string }
574
+
575
+ if (relatedTable && !tablesByName.has(relatedTable)) {
576
+ return {
577
+ table: table.name,
578
+ field: relationshipField.name,
579
+ relatedTable,
580
+ }
581
+ }
582
+
583
+ return undefined
584
+ })
585
+ .filter((error) => error !== undefined)
586
+ )
587
+ .at(0)
588
+
589
+ if (invalidRelationship) {
590
+ return `Relationship field "${invalidRelationship.table}.${invalidRelationship.field}": relatedTable "${invalidRelationship.relatedTable}" does not exist`
591
+ }
592
+
593
+ // Validate lookup fields reference existing relationship fields (either in same table or reverse relationship)
594
+ const invalidLookup = tables
595
+ .flatMap((table) =>
596
+ table.fields
597
+ .filter((field) => field.type === 'lookup')
598
+ .map((lookupField) => {
599
+ const { relationshipField, relatedField } = lookupField as {
600
+ relationshipField: string
601
+ relatedField: string
602
+ }
603
+
604
+ // Check if relationshipField exists in the same table (forward lookup)
605
+ const fieldInSameTable = table.fields.find((f) => f.name === relationshipField)
606
+ if (fieldInSameTable) {
607
+ // Use shared validation helper for forward lookup
608
+ return validateRelationshipFieldReference({
609
+ table,
610
+ fieldName: lookupField.name,
611
+ relationshipField,
612
+ relatedField,
613
+ tablesByName,
614
+ })
615
+ }
616
+
617
+ // Check if relationshipField exists in other tables (reverse lookup)
618
+ const reverseRelationship = [...tablesByName.values()]
619
+ .flatMap((otherTable) =>
620
+ otherTable.fields
621
+ .filter(
622
+ (field) =>
623
+ field.type === 'relationship' &&
624
+ field.name === relationshipField &&
625
+ (field as { relatedTable?: string }).relatedTable === table.name
626
+ )
627
+ .map(() => ({ found: true, relatedTable: otherTable }))
628
+ )
629
+ .at(0)
630
+
631
+ if (!reverseRelationship) {
632
+ return {
633
+ table: table.name,
634
+ field: lookupField.name,
635
+ error: `relationshipField "${relationshipField}" not found`,
636
+ }
637
+ }
638
+
639
+ // For reverse lookup, check if relatedField exists in the table that has the relationship
640
+ if (reverseRelationship.relatedTable) {
641
+ const relatedFieldExists = reverseRelationship.relatedTable.fields.some(
642
+ (f) => f.name === relatedField
643
+ )
644
+ if (!relatedFieldExists) {
645
+ return {
646
+ table: table.name,
647
+ field: lookupField.name,
648
+ error: `relatedField "${relatedField}" not found in related table "${reverseRelationship.relatedTable.name}"`,
649
+ }
650
+ }
651
+ }
652
+
653
+ return undefined
654
+ })
655
+ .filter((error) => error !== undefined)
656
+ )
657
+ .at(0)
658
+
659
+ if (invalidLookup) {
660
+ return `Lookup field "${invalidLookup.table}.${invalidLookup.field}" ${invalidLookup.error}`
661
+ }
662
+
663
+ // Validate rollup fields reference existing relationship fields and related fields
664
+ const invalidRollup = validateAllRollupFields(tables, tablesByName)
665
+
666
+ if (invalidRollup) {
667
+ return `Rollup field "${invalidRollup.table}.${invalidRollup.field}" ${invalidRollup.error}`
668
+ }
669
+
670
+ return true
671
+ }),
672
+ Schema.annotations({
673
+ title: 'Data Tables',
674
+ description:
675
+ 'Collection of database tables that define the data structure of your application. Each table represents an entity (e.g., users, products, orders) with fields that define the schema. Tables support relationships, indexes, constraints, and various field types. Tables are the foundation of your application data model and determine what information can be stored and how it relates.',
676
+ examples: [
677
+ [
678
+ {
679
+ id: 1,
680
+ name: 'users',
681
+ fields: [
682
+ { id: 1, name: 'email', type: 'email' as const, required: true },
683
+ { id: 2, name: 'name', type: 'single-line-text' as const, required: true },
684
+ ],
685
+ },
686
+ ],
687
+ ],
688
+ })
689
+ )
690
+
691
+ export type Tables = Schema.Schema.Type<typeof TablesSchema>
692
+
693
+ // Re-export Table and TableSchema for convenience
694
+ export { TableSchema } from '@/domain/models/app/table'
695
+ export type { Table } from '@/domain/models/app/table'