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,372 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import type { TablePermissions, TableFieldPermissions } from '.'
9
+
10
+ /**
11
+ * Check if user has permission based on permission configuration.
12
+ *
13
+ * Permission format (3-format system):
14
+ * - `'all'` — Everyone (including unauthenticated)
15
+ * - `'authenticated'` — Any logged-in user
16
+ * - `string[]` — Specific role names
17
+ */
18
+ export function hasPermission(permission: unknown, userRole: string): boolean {
19
+ if (!permission) return false
20
+ if (permission === 'all') return true
21
+ if (permission === 'authenticated') return true
22
+ if (Array.isArray(permission)) return permission.includes(userRole)
23
+ return false
24
+ }
25
+
26
+ /**
27
+ * Check if user role has admin privileges
28
+ */
29
+ export function isAdminRole(userRole: string): boolean {
30
+ return userRole === 'admin'
31
+ }
32
+
33
+ /**
34
+ * Check permission with admin override
35
+ */
36
+ export function checkPermissionWithAdminOverride(
37
+ isAdmin: boolean,
38
+ permission: unknown,
39
+ userRole: string
40
+ ): boolean {
41
+ return isAdmin || hasPermission(permission, userRole)
42
+ }
43
+
44
+ /**
45
+ * Evaluate table-level permissions for a user
46
+ */
47
+ export function evaluateTablePermissions(
48
+ tablePermissions: TablePermissions | undefined,
49
+ userRole: string,
50
+ isAdmin: boolean
51
+ ): Readonly<{ read: boolean; create: boolean; update: boolean; delete: boolean }> {
52
+ return {
53
+ read: checkPermissionWithAdminOverride(isAdmin, tablePermissions?.read, userRole),
54
+ create: checkPermissionWithAdminOverride(isAdmin, tablePermissions?.create, userRole),
55
+ update: checkPermissionWithAdminOverride(isAdmin, tablePermissions?.update, userRole),
56
+ // eslint-disable-next-line drizzle/enforce-delete-with-where -- This is accessing a property, not a Drizzle delete operation
57
+ delete: checkPermissionWithAdminOverride(isAdmin, tablePermissions?.delete, userRole),
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Evaluate field-level permissions for a user
63
+ */
64
+ export function evaluateFieldPermissions(
65
+ fieldPerms: TableFieldPermissions | undefined,
66
+ userRole: string,
67
+ isAdmin: boolean
68
+ ): Record<string, { read: boolean; write: boolean }> {
69
+ const fields = fieldPerms ?? []
70
+ return Object.fromEntries(
71
+ fields.map((fieldPerm) => [
72
+ fieldPerm.field,
73
+ {
74
+ read: checkPermissionWithAdminOverride(isAdmin, fieldPerm.read, userRole),
75
+ write: checkPermissionWithAdminOverride(isAdmin, fieldPerm.write, userRole),
76
+ },
77
+ ])
78
+ )
79
+ }
80
+
81
+ /**
82
+ * Check if user has role-based create permission for a table
83
+ * Returns true if permission granted, false if denied
84
+ *
85
+ * Permission logic:
86
+ * - Viewers: denied by default (tables must explicitly grant viewer create access)
87
+ * - Other roles: allowed by default (unless table restricts with role-based permissions)
88
+ *
89
+ * Supports permission inheritance via the `inherit` field.
90
+ */
91
+ export function hasCreatePermission(
92
+ table:
93
+ | Readonly<{
94
+ name: string
95
+ permissions?: Readonly<{
96
+ create?: unknown
97
+ inherit?: string
98
+ override?: { create?: unknown }
99
+ }>
100
+ }>
101
+ | undefined,
102
+ userRole: string,
103
+ allTables?: readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
104
+ ): boolean {
105
+ const effectivePerms = getEffectivePermissions(table, allTables) as
106
+ | Readonly<{ create?: unknown }>
107
+ | undefined
108
+
109
+ if (inheritanceFailed(table, allTables, effectivePerms)) return false
110
+ if (userRole === 'viewer') return false
111
+
112
+ const createPermission = effectivePerms?.create
113
+ if (!createPermission || !Array.isArray(createPermission)) return true
114
+ return createPermission.includes(userRole)
115
+ }
116
+
117
+ /**
118
+ * Check if user has delete permission for the table
119
+ *
120
+ * Supports permission inheritance via the `inherit` field.
121
+ */
122
+ export function hasDeletePermission(
123
+ table:
124
+ | Readonly<{
125
+ name: string
126
+ permissions?: Readonly<{
127
+ delete?: unknown
128
+ inherit?: string
129
+ override?: { delete?: unknown }
130
+ }>
131
+ }>
132
+ | undefined,
133
+ userRole: string,
134
+ allTables?: readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
135
+ ): boolean {
136
+ const effectivePerms = getEffectivePermissions(table, allTables) as
137
+ | Readonly<{ delete?: unknown }>
138
+ | undefined
139
+
140
+ if (inheritanceFailed(table, allTables, effectivePerms)) return false
141
+
142
+ // eslint-disable-next-line drizzle/enforce-delete-with-where -- This is not a Drizzle delete operation, it's accessing a property
143
+ const deletePermission = effectivePerms?.delete
144
+
145
+ if (userRole === 'viewer') {
146
+ return Array.isArray(deletePermission) && deletePermission.includes(userRole)
147
+ }
148
+
149
+ if (!deletePermission || !Array.isArray(deletePermission)) return true
150
+ return deletePermission.includes(userRole)
151
+ }
152
+
153
+ /**
154
+ * Check if user has update permission for a table
155
+ * Returns true if permission granted, false if denied
156
+ *
157
+ * Note: When no explicit permissions are defined:
158
+ * - Admins and members: allowed by default
159
+ * - Viewers: denied by default (tables must explicitly grant viewer update access)
160
+ *
161
+ * Supports permission inheritance via the `inherit` field.
162
+ */
163
+ export function hasUpdatePermission(
164
+ table:
165
+ | Readonly<{
166
+ name: string
167
+ permissions?: Readonly<{
168
+ update?: unknown
169
+ inherit?: string
170
+ override?: { update?: unknown }
171
+ }>
172
+ }>
173
+ | undefined,
174
+ userRole: string,
175
+ allTables?: readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
176
+ ): boolean {
177
+ const effectivePerms = getEffectivePermissions(table, allTables) as
178
+ | Readonly<{ update?: unknown }>
179
+ | undefined
180
+
181
+ if (inheritanceFailed(table, allTables, effectivePerms)) return false
182
+
183
+ const updatePermission = effectivePerms?.update
184
+
185
+ if (Array.isArray(updatePermission)) {
186
+ return updatePermission.includes(userRole)
187
+ }
188
+
189
+ if (userRole === 'viewer') return false
190
+
191
+ return true
192
+ }
193
+
194
+ /**
195
+ * Resolve effective permissions considering inheritance
196
+ */
197
+ function getEffectivePermissions(
198
+ table: Readonly<{ name: string; permissions?: unknown }> | undefined,
199
+ allTables: readonly Readonly<{ name: string; permissions?: unknown }>[] | undefined
200
+ ): unknown {
201
+ if (!allTables || !table) return table?.permissions
202
+
203
+ const tableWithInheritance = table as Readonly<{
204
+ name: string
205
+ permissions?: Readonly<{ inherit?: string }>
206
+ }>
207
+
208
+ if (!tableWithInheritance.permissions?.inherit) {
209
+ return table.permissions
210
+ }
211
+
212
+ try {
213
+ return resolveInheritedPermissions(
214
+ table as Readonly<{ name: string; permissions?: TablePermissions }>,
215
+ allTables as readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
216
+ )
217
+ } catch {
218
+ return undefined
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Check if inheritance resolution failed
224
+ */
225
+ function inheritanceFailed(
226
+ table: Readonly<{ permissions?: Readonly<{ inherit?: string }> }> | undefined,
227
+ allTables: readonly unknown[] | undefined,
228
+ effectivePermissions: unknown
229
+ ): boolean {
230
+ return Boolean(allTables && table?.permissions?.inherit && !effectivePermissions)
231
+ }
232
+
233
+ /**
234
+ * Check if circular inheritance exists
235
+ */
236
+ function hasCircularInheritance(tableName: string, visited: ReadonlySet<string>): boolean {
237
+ return visited.has(tableName)
238
+ }
239
+
240
+ /**
241
+ * Find parent table by name
242
+ */
243
+ function findParentTable(
244
+ parentName: string | undefined,
245
+ allTables: readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
246
+ ): Readonly<{ name: string; permissions?: TablePermissions }> | undefined {
247
+ if (!parentName) return undefined
248
+ return allTables.find((t) => t.name === parentName)
249
+ }
250
+
251
+ /**
252
+ * Merge a single permission property with override support
253
+ */
254
+ function mergePermission<T>(
255
+ overrideValue: T | undefined,
256
+ currentValue: T | undefined,
257
+ parentValue: T | undefined
258
+ ): T | undefined {
259
+ return overrideValue ?? currentValue ?? parentValue
260
+ }
261
+
262
+ /**
263
+ * Merge parent and current permissions with override support
264
+ */
265
+ function mergePermissions(
266
+ permissions: TablePermissions,
267
+ parentPermissions: TablePermissions
268
+ ): TablePermissions {
269
+ const { override, read, comment, create, update, delete: deletePerms, fields } = permissions
270
+
271
+ return {
272
+ read: mergePermission(override?.read, read, parentPermissions.read),
273
+ comment: mergePermission(override?.comment, comment, parentPermissions.comment),
274
+ create: mergePermission(override?.create, create, parentPermissions.create),
275
+ update: mergePermission(override?.update, update, parentPermissions.update),
276
+ // eslint-disable-next-line drizzle/enforce-delete-with-where -- This is accessing a property, not a Drizzle delete operation
277
+ delete: mergePermission(override?.delete, deletePerms, parentPermissions.delete),
278
+ fields: fields ?? parentPermissions.fields,
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Resolve inherited permissions for a table
284
+ *
285
+ * Recursively resolves permissions by following the inheritance chain.
286
+ * Handles circular inheritance detection and merges override permissions.
287
+ *
288
+ * @param table - The table to resolve permissions for
289
+ * @param allTables - All tables in the app (for parent lookup)
290
+ * @param visited - Set of visited table names (for circular detection)
291
+ * @returns Resolved permissions or undefined if inheritance chain is invalid
292
+ * @throws Error if circular inheritance detected or parent table not found
293
+ */
294
+ export function resolveInheritedPermissions(
295
+ table: Readonly<{ name: string; permissions?: TablePermissions }> | undefined,
296
+ allTables: readonly Readonly<{ name: string; permissions?: TablePermissions }>[],
297
+ visited: ReadonlySet<string> = new Set()
298
+ ): TablePermissions | undefined {
299
+ if (!table?.permissions) return undefined
300
+
301
+ const { permissions } = table
302
+
303
+ // If no inheritance, return current permissions
304
+ if (!permissions.inherit) {
305
+ return permissions
306
+ }
307
+
308
+ // Circular inheritance detection
309
+ if (hasCircularInheritance(table.name, visited)) {
310
+ // Return undefined to indicate error (caught by callers)
311
+ return undefined
312
+ }
313
+
314
+ // Find parent table
315
+ const parentTable = findParentTable(permissions.inherit, allTables)
316
+ if (!parentTable) {
317
+ // Return undefined to indicate error (caught by callers)
318
+ return undefined
319
+ }
320
+
321
+ // Recursively resolve parent permissions
322
+ const parentPermissions = resolveInheritedPermissions(
323
+ parentTable,
324
+ allTables,
325
+ new Set([...visited, table.name])
326
+ )
327
+
328
+ if (!parentPermissions) return permissions
329
+
330
+ // Merge parent permissions with current permissions (current takes precedence)
331
+ return mergePermissions(permissions, parentPermissions)
332
+ }
333
+
334
+ /**
335
+ * Check if user has read permission for a table
336
+ * Returns true if permission granted, false if denied
337
+ *
338
+ * Note: When no explicit permissions are defined:
339
+ * - Admins and members: allowed by default
340
+ * - Viewers: denied by default (tables must explicitly grant viewer access)
341
+ *
342
+ * Supports permission inheritance via the `inherit` field.
343
+ */
344
+ export function hasReadPermission(
345
+ table:
346
+ | Readonly<{
347
+ name: string
348
+ permissions?: Readonly<{ read?: unknown; inherit?: string; override?: { read?: unknown } }>
349
+ }>
350
+ | undefined,
351
+ userRole: string,
352
+ allTables?: readonly Readonly<{ name: string; permissions?: TablePermissions }>[]
353
+ ): boolean {
354
+ const effectivePerms = getEffectivePermissions(table, allTables) as
355
+ | Readonly<{ read?: unknown }>
356
+ | undefined
357
+
358
+ if (inheritanceFailed(table, allTables, effectivePerms)) return false
359
+
360
+ const readPermission = effectivePerms?.read
361
+
362
+ if (Array.isArray(readPermission)) {
363
+ return readPermission.includes(userRole)
364
+ }
365
+
366
+ if (readPermission === 'all') return true
367
+ if (readPermission === 'authenticated') return true
368
+
369
+ if (userRole === 'viewer') return false
370
+
371
+ return true
372
+ }
@@ -0,0 +1,49 @@
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
+
10
+ /**
11
+ * Table Permission Schema
12
+ *
13
+ * Simplified permission value for a single CRUD operation.
14
+ * Accepts one of 3 formats:
15
+ *
16
+ * - `'all'` — Everyone (including unauthenticated users)
17
+ * - `'authenticated'` — Any logged-in user
18
+ * - `['admin', 'editor']` — Specific role names (array)
19
+ *
20
+ * @example
21
+ * ```yaml
22
+ * permissions:
23
+ * read: all
24
+ * comment: authenticated
25
+ * create: ['admin', 'editor']
26
+ * update: ['admin', 'editor']
27
+ * delete: ['admin']
28
+ * ```
29
+ */
30
+ export const TablePermissionSchema = Schema.Union(
31
+ Schema.Literal('all'),
32
+ Schema.Literal('authenticated'),
33
+ Schema.Array(Schema.String).pipe(
34
+ Schema.minItems(1),
35
+ Schema.annotations({
36
+ description: 'Array of role names that have access (e.g., admin, editor). At least one role.',
37
+ examples: [['admin'], ['admin', 'editor'], ['admin', 'member', 'viewer']],
38
+ })
39
+ )
40
+ ).pipe(
41
+ Schema.annotations({
42
+ title: 'Table Permission',
43
+ description:
44
+ "Permission value for a single operation. 'all' (everyone), 'authenticated' (logged-in users), or role array ['admin', 'editor'].",
45
+ examples: ['all', 'authenticated', ['admin'], ['admin', 'editor']],
46
+ })
47
+ )
48
+
49
+ export type TablePermission = Schema.Schema.Type<typeof TablePermissionSchema>
@@ -0,0 +1,62 @@
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
+
10
+ /**
11
+ * Primary Key
12
+ *
13
+ * Primary key configuration for the table. The primary key uniquely identifies each row and is automatically indexed.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * {
18
+ * "type": "auto-increment",
19
+ * "field": "id"
20
+ * }
21
+ * ```
22
+ */
23
+ export const PrimaryKeySchema = Schema.Struct({
24
+ type: Schema.String.pipe(
25
+ Schema.minLength(1),
26
+ Schema.annotations({
27
+ description:
28
+ "Primary key generation strategy. 'auto-increment' uses sequential integers (1, 2, 3...), 'uuid' generates random unique identifiers, 'composite' uses multiple fields together.",
29
+ })
30
+ ),
31
+ field: Schema.optional(
32
+ Schema.String.pipe(
33
+ Schema.pattern(/^[a-z][a-z0-9_]*$/, {
34
+ message: () =>
35
+ "Field name for single-column primary key. Only used with 'auto-increment' or 'uuid' type.",
36
+ }),
37
+ Schema.annotations({
38
+ description:
39
+ "Field name for single-column primary key. Only used with 'auto-increment' or 'uuid' type.",
40
+ examples: ['id', 'user_id', 'product_id'],
41
+ })
42
+ )
43
+ ),
44
+ fields: Schema.optional(
45
+ Schema.Array(
46
+ Schema.String.pipe(Schema.minLength(1, { message: () => 'This field is required' }))
47
+ )
48
+ ),
49
+ }).pipe(
50
+ Schema.annotations({
51
+ title: 'Primary Key',
52
+ description:
53
+ 'Primary key configuration for the table. The primary key uniquely identifies each row and is automatically indexed.',
54
+ examples: [
55
+ { type: 'auto-increment', field: 'id' },
56
+ { type: 'uuid', field: 'id' },
57
+ { type: 'composite', fields: ['tenant_id', 'user_id'] },
58
+ ],
59
+ })
60
+ )
61
+
62
+ export type PrimaryKey = Schema.Schema.Type<typeof PrimaryKeySchema>
@@ -0,0 +1,168 @@
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 { detectCycles } from './cycle-detection'
9
+ import { FORMULA_KEYWORDS } from './formula-keywords'
10
+
11
+ /**
12
+ * Special field references that are always available in formulas.
13
+ * These are system-managed fields that exist on all tables.
14
+ */
15
+ export const SPECIAL_FIELDS = new Set([
16
+ 'id',
17
+ 'created_at',
18
+ 'updated_at',
19
+ 'deleted_at',
20
+ ]) as ReadonlySet<string>
21
+
22
+ /**
23
+ * Validate formula syntax to detect common syntax errors.
24
+ * Checks for patterns that would cause SQL syntax errors.
25
+ *
26
+ * @param formula - The formula expression to validate
27
+ * @returns Error message if invalid, undefined if valid
28
+ */
29
+ const validateFormulaSyntax = (formula: string): string | undefined => {
30
+ // Remove string literals and regex patterns to avoid false positives
31
+ // String literals: 'text' or "text"
32
+ // Regex patterns: text ~ 'pattern' or text ~ '^pattern$'
33
+ const withoutLiterals = formula
34
+ .replace(/'[^']*'/g, '') // Remove single-quoted strings
35
+ .replace(/"[^"]*"/g, '') // Remove double-quoted strings
36
+
37
+ // Check for consecutive operators (e.g., "* *", "+ +", "- -")
38
+ // Only check outside of string literals and regex patterns
39
+ const consecutiveOperatorPattern = /[+\-*/%]\s*[+\-*/%]/
40
+ if (consecutiveOperatorPattern.test(withoutLiterals)) {
41
+ return 'Invalid formula syntax: consecutive operators detected'
42
+ }
43
+
44
+ // Check for unmatched parentheses
45
+ const openParens = (formula.match(/\(/g) || []).length
46
+ const closeParens = (formula.match(/\)/g) || []).length
47
+ if (openParens !== closeParens) {
48
+ return 'Invalid formula syntax: unmatched parentheses'
49
+ }
50
+
51
+ // Check for empty parentheses
52
+ if (/\(\s*\)/.test(formula)) {
53
+ return 'Invalid formula syntax: empty parentheses'
54
+ }
55
+
56
+ return undefined
57
+ }
58
+
59
+ /**
60
+ * Extract potential field references from a formula expression.
61
+ * This is a simplified parser that extracts identifiers (words) from the formula,
62
+ * excluding string literals (content within quotes).
63
+ * It doesn't handle complex syntax but catches common field reference patterns.
64
+ *
65
+ * @param formula - The formula expression to parse
66
+ * @returns Array of field names referenced in the formula
67
+ */
68
+ export const extractFieldReferences = (formula: string): ReadonlyArray<string> => {
69
+ // Remove single-quoted and double-quoted string literals to avoid treating
70
+ // literal values as field names (e.g., 'World' in STRPOS(text, 'World'))
71
+ const withoutStringLiterals = formula
72
+ .replace(/'[^']*'/g, '') // Remove single-quoted strings
73
+ .replace(/"[^"]*"/g, '') // Remove double-quoted strings
74
+
75
+ // Match word characters (field names) - exclude function names and operators
76
+ // This regex matches identifiers that could be field names
77
+ const identifierPattern = /\b([a-z_][a-z0-9_]*)\b/gi
78
+ const matches = withoutStringLiterals.match(identifierPattern) || []
79
+
80
+ return matches
81
+ .map((match) => match.toLowerCase())
82
+ .filter((identifier) => !FORMULA_KEYWORDS.has(identifier))
83
+ }
84
+
85
+ /**
86
+ * Detect circular dependencies in formula fields using depth-first search.
87
+ * A circular dependency exists when a formula field references itself directly or indirectly
88
+ * through a chain of other formula fields.
89
+ *
90
+ * @param fields - Array of fields to validate
91
+ * @returns Array of field names involved in circular dependencies, or empty array if none found
92
+ */
93
+ const detectCircularDependencies = (
94
+ fields: ReadonlyArray<{ readonly name: string; readonly type: string; readonly formula?: string }>
95
+ ): ReadonlyArray<string> => {
96
+ // Build dependency graph: field name -> fields it references
97
+ const dependencyGraph: ReadonlyMap<string, ReadonlyArray<string>> = new Map(
98
+ fields
99
+ .filter(
100
+ (field): field is typeof field & { formula: string } =>
101
+ 'formula' in field && typeof field.formula === 'string'
102
+ )
103
+ .map((field) => [field.name, extractFieldReferences(field.formula)] as const)
104
+ )
105
+
106
+ // Use shared cycle detection utility
107
+ return detectCycles(dependencyGraph)
108
+ }
109
+
110
+ /**
111
+ * Validate formula fields in a table (syntax, field references, circular dependencies).
112
+ *
113
+ * @param fields - Array of fields to validate
114
+ * @returns Error object if invalid, undefined if valid
115
+ */
116
+ export const validateFormulaFields = (
117
+ fields: ReadonlyArray<{ readonly name: string; readonly type: string; readonly formula?: string }>
118
+ ): { readonly message: string; readonly path: ReadonlyArray<string> } | undefined => {
119
+ const fieldNames = new Set(fields.map((field) => field.name))
120
+ const formulaFields = fields.filter(
121
+ (field): field is typeof field & { readonly formula: string } =>
122
+ field.type === 'formula' && typeof field.formula === 'string'
123
+ )
124
+
125
+ // Validate formula syntax first (before checking field references)
126
+ const syntaxError = formulaFields
127
+ .map((formulaField) => ({
128
+ field: formulaField,
129
+ error: validateFormulaSyntax(formulaField.formula),
130
+ }))
131
+ .find((result) => result.error !== undefined)
132
+
133
+ if (syntaxError?.error) {
134
+ return {
135
+ message: syntaxError.error,
136
+ path: ['fields'],
137
+ }
138
+ }
139
+
140
+ // Find the first invalid field reference across all formula fields
141
+ const invalidReference = formulaFields
142
+ .flatMap((formulaField) => {
143
+ const referencedFields = extractFieldReferences(formulaField.formula)
144
+ const invalidField = referencedFields.find(
145
+ (refField) => !fieldNames.has(refField) && !SPECIAL_FIELDS.has(refField)
146
+ )
147
+ return invalidField ? [{ formulaField, invalidField }] : []
148
+ })
149
+ .at(0)
150
+
151
+ if (invalidReference) {
152
+ return {
153
+ message: `Invalid field reference: field '${invalidReference.invalidField}' not found in formula '${invalidReference.formulaField.formula}'`,
154
+ path: ['fields'],
155
+ }
156
+ }
157
+
158
+ // Detect circular dependencies in formula fields
159
+ const circularFields = detectCircularDependencies(fields)
160
+ if (circularFields.length > 0) {
161
+ return {
162
+ message: `Circular dependency detected in formula fields: ${circularFields.join(' -> ')}`,
163
+ path: ['fields'],
164
+ }
165
+ }
166
+
167
+ return undefined
168
+ }
@@ -0,0 +1,38 @@
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
+ /**
9
+ * Validate that all index fields exist in the table.
10
+ *
11
+ * @param indexes - Array of index definitions
12
+ * @param fieldNames - Set of valid field names
13
+ * @returns Validation error object if invalid, undefined if valid
14
+ */
15
+ export const validateIndexes = (
16
+ indexes: ReadonlyArray<{
17
+ readonly name: string
18
+ readonly fields: ReadonlyArray<string>
19
+ }>,
20
+ fieldNames: ReadonlySet<string>
21
+ ): { readonly message: string; readonly path: ReadonlyArray<string> } | undefined => {
22
+ const invalidIndex = indexes
23
+ .flatMap((index) =>
24
+ index.fields
25
+ .filter((fieldName) => !fieldNames.has(fieldName))
26
+ .map((fieldName) => ({ indexName: index.name, fieldName }))
27
+ )
28
+ .at(0)
29
+
30
+ if (invalidIndex) {
31
+ return {
32
+ message: `Index "${invalidIndex.indexName}" references non-existent column "${invalidIndex.fieldName}"`,
33
+ path: ['indexes'],
34
+ }
35
+ }
36
+
37
+ return undefined
38
+ }