@baasix/baasix 0.1.0

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 (666) hide show
  1. package/LICENSE.MD +85 -0
  2. package/README.md +526 -0
  3. package/assets/banner.jpg +0 -0
  4. package/assets/banner_small.jpg +0 -0
  5. package/assets/logo_icon.svg +20 -0
  6. package/assets/logo_icon_rounded.svg +20 -0
  7. package/dist/LICENSE.MD +85 -0
  8. package/dist/README.md +526 -0
  9. package/dist/app/404/index.html +1 -0
  10. package/dist/app/404.html +1 -0
  11. package/dist/app/_next/static/chunks/041e1f03-56ae8a902a7f2fe6.js +24 -0
  12. package/dist/app/_next/static/chunks/1117-05479929a8da73e3.js +1 -0
  13. package/dist/app/_next/static/chunks/1299.77cc7b7b76b75cba.js +1 -0
  14. package/dist/app/_next/static/chunks/1303-35a96e9c9cdeab9d.js +1 -0
  15. package/dist/app/_next/static/chunks/1509-56ac00cdaaecdf53.js +1 -0
  16. package/dist/app/_next/static/chunks/1668-e3eabd0f6753c780.js +1 -0
  17. package/dist/app/_next/static/chunks/1783-d9fb550fd324300c.js +1 -0
  18. package/dist/app/_next/static/chunks/2117-29b5fa47421595ad.js +2 -0
  19. package/dist/app/_next/static/chunks/2344.35b46d2179a765b5.js +1 -0
  20. package/dist/app/_next/static/chunks/257.990da16794a31292.js +1 -0
  21. package/dist/app/_next/static/chunks/2676-73b0ee7c80073a84.js +1 -0
  22. package/dist/app/_next/static/chunks/3563-b8842744384391fe.js +1 -0
  23. package/dist/app/_next/static/chunks/363642f4-933b579ed3c85f60.js +1 -0
  24. package/dist/app/_next/static/chunks/3817-e20c8f0a0810fc95.js +1 -0
  25. package/dist/app/_next/static/chunks/3834.84944e390d902509.js +2 -0
  26. package/dist/app/_next/static/chunks/4043-3a30c8a75896f241.js +1 -0
  27. package/dist/app/_next/static/chunks/4225-14090c7c0cd9dec6.js +1 -0
  28. package/dist/app/_next/static/chunks/4438-c9a12ca15b6e9160.js +1 -0
  29. package/dist/app/_next/static/chunks/4458-679fd0c6884f456a.js +1 -0
  30. package/dist/app/_next/static/chunks/4475-8bdfbd536fba8c48.js +1 -0
  31. package/dist/app/_next/static/chunks/4883-8a924721bb21b3b0.js +1 -0
  32. package/dist/app/_next/static/chunks/489-683ab07188f9df2b.js +1 -0
  33. package/dist/app/_next/static/chunks/4952-1b97320cf61f3f21.js +1 -0
  34. package/dist/app/_next/static/chunks/5094-8d53e403235d4ca6.js +1 -0
  35. package/dist/app/_next/static/chunks/5101-3a146e0625747ad1.js +1 -0
  36. package/dist/app/_next/static/chunks/54a60aa6-d9747982e0a81f58.js +79 -0
  37. package/dist/app/_next/static/chunks/5650-f096291df402bfc2.js +1 -0
  38. package/dist/app/_next/static/chunks/600-539045311240f579.js +1 -0
  39. package/dist/app/_next/static/chunks/6170-803b82e19d3ade6d.js +89 -0
  40. package/dist/app/_next/static/chunks/6241-30d7169d1010e5a4.js +1 -0
  41. package/dist/app/_next/static/chunks/6530-a91e10cffa4200c4.js +1 -0
  42. package/dist/app/_next/static/chunks/6547-4bbbdb5c399aef1e.js +1 -0
  43. package/dist/app/_next/static/chunks/6712-781937c53a2c49da.js +1 -0
  44. package/dist/app/_next/static/chunks/6fcbdc68-90be1a5480b8d353.js +1 -0
  45. package/dist/app/_next/static/chunks/70e0d97a-aeaf0cdc26ba1a58.js +1 -0
  46. package/dist/app/_next/static/chunks/7214-5154a89d08d24dde.js +1 -0
  47. package/dist/app/_next/static/chunks/7324-b53229c59a640880.js +10 -0
  48. package/dist/app/_next/static/chunks/7636-66424f0b51d350e9.js +1 -0
  49. package/dist/app/_next/static/chunks/7874-39a3f2541165a675.js +1 -0
  50. package/dist/app/_next/static/chunks/7982-9da12b83f11e3f5f.js +1 -0
  51. package/dist/app/_next/static/chunks/8213a2eb-da25a3b3c5521b2b.js +1 -0
  52. package/dist/app/_next/static/chunks/8473-6598318371eca31b.js +1 -0
  53. package/dist/app/_next/static/chunks/8640fa6b-72e43370f68e5587.js +1 -0
  54. package/dist/app/_next/static/chunks/9090-3ef676f29c95f1c7.js +1 -0
  55. package/dist/app/_next/static/chunks/9124-a02f9e209e6e3cce.js +1 -0
  56. package/dist/app/_next/static/chunks/926-156f32067d111d6b.js +1 -0
  57. package/dist/app/_next/static/chunks/9487-b17481605e513b83.js +1 -0
  58. package/dist/app/_next/static/chunks/9599-a7e572bb88c3392b.js +1 -0
  59. package/dist/app/_next/static/chunks/9881-419697138376e755.js +1 -0
  60. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/all-activity/page-8917930b4d663405.js +1 -0
  61. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/email-log/page-b27a6ee32782d7df.js +1 -0
  62. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/notifications/page-b7eda523ede2702c.js +1 -0
  63. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/page-1cfa62d1caedaed0.js +1 -0
  64. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/sessions/page-3e21e20db90aeff7.js +1 -0
  65. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-executions/page-27bcc26b747fb29b.js +1 -0
  66. package/dist/app/_next/static/chunks/app/(authenticated)/activity-log/workflow-logs/page-9f9e9e952aef436e.js +1 -0
  67. package/dist/app/_next/static/chunks/app/(authenticated)/change-password/page-8d61aa499eabb127.js +1 -0
  68. package/dist/app/_next/static/chunks/app/(authenticated)/dashboard/page-1ceeac9e72997a8a.js +1 -0
  69. package/dist/app/_next/static/chunks/app/(authenticated)/data-browser/page-8cda2b57759dd670.js +1 -0
  70. package/dist/app/_next/static/chunks/app/(authenticated)/file-manager/page-8c6f1b1da66ad7e4.js +1 -0
  71. package/dist/app/_next/static/chunks/app/(authenticated)/layout-f70d225b2759c998.js +1 -0
  72. package/dist/app/_next/static/chunks/app/(authenticated)/settings/migrations/page-aacec8f7cfb40ab2.js +1 -0
  73. package/dist/app/_next/static/chunks/app/(authenticated)/settings/permissions/page-828110cfcde429c6.js +1 -0
  74. package/dist/app/_next/static/chunks/app/(authenticated)/settings/project/page-420e794bb76bd204.js +1 -0
  75. package/dist/app/_next/static/chunks/app/(authenticated)/settings/roles/page-9001d02b28f70708.js +1 -0
  76. package/dist/app/_next/static/chunks/app/(authenticated)/settings/schema/page-899574f35091dd58.js +1 -0
  77. package/dist/app/_next/static/chunks/app/(authenticated)/settings/tasks/page-ad7ab3e27c83f44f.js +1 -0
  78. package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/edit/page-bd83414cb8c4cb04.js +1 -0
  79. package/dist/app/_next/static/chunks/app/(authenticated)/settings/templates/page-3181447f8772b1d3.js +1 -0
  80. package/dist/app/_next/static/chunks/app/(authenticated)/settings/tenants/page-ef9bfbacef5a1d73.js +1 -0
  81. package/dist/app/_next/static/chunks/app/(authenticated)/users/invites/page-480306b7b2bbac7e.js +1 -0
  82. package/dist/app/_next/static/chunks/app/(authenticated)/users/list/page-74da51254c2606b3.js +1 -0
  83. package/dist/app/_next/static/chunks/app/(authenticated)/users/page-e99c6f0b915001b2.js +1 -0
  84. package/dist/app/_next/static/chunks/app/(authenticated)/users/preferences/page-1a935630ce8f2b12.js +1 -0
  85. package/dist/app/_next/static/chunks/app/(authenticated)/users/user-roles/page-901dfb8ea1f39ca8.js +1 -0
  86. package/dist/app/_next/static/chunks/app/(authenticated)/workflows/detail/page-9a6b839aea688ca4.js +1 -0
  87. package/dist/app/_next/static/chunks/app/(authenticated)/workflows/edit/page-11774efbc2fecae2.js +1 -0
  88. package/dist/app/_next/static/chunks/app/(authenticated)/workflows/execution/page-8ec1aea90412c03d.js +1 -0
  89. package/dist/app/_next/static/chunks/app/(authenticated)/workflows/page-88bc5b36ccb0a1f7.js +1 -0
  90. package/dist/app/_next/static/chunks/app/(public)/forgot-password/page-ed263fd46ef81c20.js +1 -0
  91. package/dist/app/_next/static/chunks/app/(public)/layout-f538977545844af8.js +1 -0
  92. package/dist/app/_next/static/chunks/app/(public)/login/page-c0a10b137f346096.js +1 -0
  93. package/dist/app/_next/static/chunks/app/(public)/register/page-4cb7644893efd9b3.js +1 -0
  94. package/dist/app/_next/static/chunks/app/_not-found/page-653f8815b78256cc.js +1 -0
  95. package/dist/app/_next/static/chunks/app/layout-591ca7a3e16528a1.js +1 -0
  96. package/dist/app/_next/static/chunks/app/page-dd19d124b5fa2577.js +1 -0
  97. package/dist/app/_next/static/chunks/c37d3baf.c2ff165f5b02c692.js +1 -0
  98. package/dist/app/_next/static/chunks/d0deef33.0379166a4ec23470.js +1 -0
  99. package/dist/app/_next/static/chunks/fd9d1056-54169f07cd680d6c.js +1 -0
  100. package/dist/app/_next/static/chunks/framework-8e0e0f4a6b83a956.js +1 -0
  101. package/dist/app/_next/static/chunks/main-324e91f5a430cddf.js +1 -0
  102. package/dist/app/_next/static/chunks/main-app-55bcae20c77aaf0e.js +1 -0
  103. package/dist/app/_next/static/chunks/pages/_app-3c9ca398d360b709.js +1 -0
  104. package/dist/app/_next/static/chunks/pages/_error-cf5ca766ac8f493f.js +1 -0
  105. package/dist/app/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  106. package/dist/app/_next/static/chunks/webpack-2c306566f7ee1b63.js +1 -0
  107. package/dist/app/_next/static/css/6c4002bae4e236b2.css +3 -0
  108. package/dist/app/_next/static/css/a275cc2b185e04f8.css +1 -0
  109. package/dist/app/_next/static/eCWhKA8XHqmB1zgFcEtN2/_buildManifest.js +1 -0
  110. package/dist/app/_next/static/eCWhKA8XHqmB1zgFcEtN2/_ssgManifest.js +1 -0
  111. package/dist/app/activity-log/all-activity/index.html +1 -0
  112. package/dist/app/activity-log/all-activity/index.txt +14 -0
  113. package/dist/app/activity-log/email-log/index.html +1 -0
  114. package/dist/app/activity-log/email-log/index.txt +14 -0
  115. package/dist/app/activity-log/index.html +1 -0
  116. package/dist/app/activity-log/index.txt +14 -0
  117. package/dist/app/activity-log/notifications/index.html +1 -0
  118. package/dist/app/activity-log/notifications/index.txt +14 -0
  119. package/dist/app/activity-log/sessions/index.html +1 -0
  120. package/dist/app/activity-log/sessions/index.txt +14 -0
  121. package/dist/app/activity-log/workflow-executions/index.html +1 -0
  122. package/dist/app/activity-log/workflow-executions/index.txt +14 -0
  123. package/dist/app/activity-log/workflow-logs/index.html +1 -0
  124. package/dist/app/activity-log/workflow-logs/index.txt +14 -0
  125. package/dist/app/change-password/index.html +1 -0
  126. package/dist/app/change-password/index.txt +14 -0
  127. package/dist/app/dashboard/index.html +1 -0
  128. package/dist/app/dashboard/index.txt +14 -0
  129. package/dist/app/data-browser/index.html +1 -0
  130. package/dist/app/data-browser/index.txt +14 -0
  131. package/dist/app/file-manager/index.html +1 -0
  132. package/dist/app/file-manager/index.txt +14 -0
  133. package/dist/app/forgot-password/index.html +1 -0
  134. package/dist/app/forgot-password/index.txt +13 -0
  135. package/dist/app/index.html +1 -0
  136. package/dist/app/index.txt +9 -0
  137. package/dist/app/login/index.html +1 -0
  138. package/dist/app/login/index.txt +13 -0
  139. package/dist/app/logo-dark.png +0 -0
  140. package/dist/app/logo-icon.svg +81 -0
  141. package/dist/app/logo-light.png +0 -0
  142. package/dist/app/register/index.html +1 -0
  143. package/dist/app/register/index.txt +13 -0
  144. package/dist/app/settings/migrations/index.html +1 -0
  145. package/dist/app/settings/migrations/index.txt +14 -0
  146. package/dist/app/settings/permissions/index.html +1 -0
  147. package/dist/app/settings/permissions/index.txt +14 -0
  148. package/dist/app/settings/project/index.html +1 -0
  149. package/dist/app/settings/project/index.txt +14 -0
  150. package/dist/app/settings/roles/index.html +1 -0
  151. package/dist/app/settings/roles/index.txt +14 -0
  152. package/dist/app/settings/schema/index.html +1 -0
  153. package/dist/app/settings/schema/index.txt +14 -0
  154. package/dist/app/settings/tasks/index.html +1 -0
  155. package/dist/app/settings/tasks/index.txt +14 -0
  156. package/dist/app/settings/templates/edit/index.html +1 -0
  157. package/dist/app/settings/templates/edit/index.txt +14 -0
  158. package/dist/app/settings/templates/index.html +1 -0
  159. package/dist/app/settings/templates/index.txt +14 -0
  160. package/dist/app/settings/tenants/index.html +1 -0
  161. package/dist/app/settings/tenants/index.txt +14 -0
  162. package/dist/app/users/index.html +1 -0
  163. package/dist/app/users/index.txt +14 -0
  164. package/dist/app/users/invites/index.html +1 -0
  165. package/dist/app/users/invites/index.txt +14 -0
  166. package/dist/app/users/list/index.html +1 -0
  167. package/dist/app/users/list/index.txt +14 -0
  168. package/dist/app/users/preferences/index.html +1 -0
  169. package/dist/app/users/preferences/index.txt +14 -0
  170. package/dist/app/users/user-roles/index.html +1 -0
  171. package/dist/app/users/user-roles/index.txt +14 -0
  172. package/dist/app/workflows/detail/index.html +1 -0
  173. package/dist/app/workflows/detail/index.txt +14 -0
  174. package/dist/app/workflows/edit/index.html +1 -0
  175. package/dist/app/workflows/edit/index.txt +14 -0
  176. package/dist/app/workflows/execution/index.html +1 -0
  177. package/dist/app/workflows/execution/index.txt +14 -0
  178. package/dist/app/workflows/index.html +1 -0
  179. package/dist/app/workflows/index.txt +14 -0
  180. package/dist/app.d.ts +36 -0
  181. package/dist/app.d.ts.map +1 -0
  182. package/dist/app.js +546 -0
  183. package/dist/app.js.map +1 -0
  184. package/dist/auth/adapters/baasix-adapter.d.ts +12 -0
  185. package/dist/auth/adapters/baasix-adapter.d.ts.map +1 -0
  186. package/dist/auth/adapters/baasix-adapter.js +318 -0
  187. package/dist/auth/adapters/baasix-adapter.js.map +1 -0
  188. package/dist/auth/adapters/index.d.ts +6 -0
  189. package/dist/auth/adapters/index.d.ts.map +1 -0
  190. package/dist/auth/adapters/index.js +5 -0
  191. package/dist/auth/adapters/index.js.map +1 -0
  192. package/dist/auth/core.d.ts +73 -0
  193. package/dist/auth/core.d.ts.map +1 -0
  194. package/dist/auth/core.js +528 -0
  195. package/dist/auth/core.js.map +1 -0
  196. package/dist/auth/index.d.ts +56 -0
  197. package/dist/auth/index.d.ts.map +1 -0
  198. package/dist/auth/index.js +58 -0
  199. package/dist/auth/index.js.map +1 -0
  200. package/dist/auth/oauth2/index.d.ts +5 -0
  201. package/dist/auth/oauth2/index.d.ts.map +1 -0
  202. package/dist/auth/oauth2/index.js +5 -0
  203. package/dist/auth/oauth2/index.js.map +1 -0
  204. package/dist/auth/oauth2/utils.d.ts +90 -0
  205. package/dist/auth/oauth2/utils.d.ts.map +1 -0
  206. package/dist/auth/oauth2/utils.js +167 -0
  207. package/dist/auth/oauth2/utils.js.map +1 -0
  208. package/dist/auth/providers/apple.d.ts +28 -0
  209. package/dist/auth/providers/apple.d.ts.map +1 -0
  210. package/dist/auth/providers/apple.js +192 -0
  211. package/dist/auth/providers/apple.js.map +1 -0
  212. package/dist/auth/providers/credential.d.ts +87 -0
  213. package/dist/auth/providers/credential.d.ts.map +1 -0
  214. package/dist/auth/providers/credential.js +162 -0
  215. package/dist/auth/providers/credential.js.map +1 -0
  216. package/dist/auth/providers/facebook.d.ts +26 -0
  217. package/dist/auth/providers/facebook.d.ts.map +1 -0
  218. package/dist/auth/providers/facebook.js +112 -0
  219. package/dist/auth/providers/facebook.js.map +1 -0
  220. package/dist/auth/providers/github.d.ts +29 -0
  221. package/dist/auth/providers/github.d.ts.map +1 -0
  222. package/dist/auth/providers/github.js +144 -0
  223. package/dist/auth/providers/github.js.map +1 -0
  224. package/dist/auth/providers/google.d.ts +32 -0
  225. package/dist/auth/providers/google.d.ts.map +1 -0
  226. package/dist/auth/providers/google.js +145 -0
  227. package/dist/auth/providers/google.js.map +1 -0
  228. package/dist/auth/providers/index.d.ts +22 -0
  229. package/dist/auth/providers/index.d.ts.map +1 -0
  230. package/dist/auth/providers/index.js +17 -0
  231. package/dist/auth/providers/index.js.map +1 -0
  232. package/dist/auth/routes.d.ts +63 -0
  233. package/dist/auth/routes.d.ts.map +1 -0
  234. package/dist/auth/routes.js +827 -0
  235. package/dist/auth/routes.js.map +1 -0
  236. package/dist/auth/services/index.d.ts +10 -0
  237. package/dist/auth/services/index.d.ts.map +1 -0
  238. package/dist/auth/services/index.js +7 -0
  239. package/dist/auth/services/index.js.map +1 -0
  240. package/dist/auth/services/session.d.ts +81 -0
  241. package/dist/auth/services/session.d.ts.map +1 -0
  242. package/dist/auth/services/session.js +186 -0
  243. package/dist/auth/services/session.js.map +1 -0
  244. package/dist/auth/services/token.d.ts +41 -0
  245. package/dist/auth/services/token.d.ts.map +1 -0
  246. package/dist/auth/services/token.js +44 -0
  247. package/dist/auth/services/token.js.map +1 -0
  248. package/dist/auth/services/verification.d.ts +77 -0
  249. package/dist/auth/services/verification.d.ts.map +1 -0
  250. package/dist/auth/services/verification.js +143 -0
  251. package/dist/auth/services/verification.js.map +1 -0
  252. package/dist/auth/types.d.ts +318 -0
  253. package/dist/auth/types.d.ts.map +1 -0
  254. package/dist/auth/types.js +6 -0
  255. package/dist/auth/types.js.map +1 -0
  256. package/dist/customTypes/arrays.d.ts +200 -0
  257. package/dist/customTypes/arrays.d.ts.map +1 -0
  258. package/dist/customTypes/arrays.js +309 -0
  259. package/dist/customTypes/arrays.js.map +1 -0
  260. package/dist/customTypes/index.d.ts +8 -0
  261. package/dist/customTypes/index.d.ts.map +1 -0
  262. package/dist/customTypes/index.js +11 -0
  263. package/dist/customTypes/index.js.map +1 -0
  264. package/dist/customTypes/postgis.d.ts +146 -0
  265. package/dist/customTypes/postgis.d.ts.map +1 -0
  266. package/dist/customTypes/postgis.js +315 -0
  267. package/dist/customTypes/postgis.js.map +1 -0
  268. package/dist/customTypes/ranges.d.ts +128 -0
  269. package/dist/customTypes/ranges.d.ts.map +1 -0
  270. package/dist/customTypes/ranges.js +257 -0
  271. package/dist/customTypes/ranges.js.map +1 -0
  272. package/dist/index.d.ts +37 -0
  273. package/dist/index.d.ts.map +1 -0
  274. package/dist/index.js +42 -0
  275. package/dist/index.js.map +1 -0
  276. package/dist/migrations/0.1.0-alpha.0_initial_setup.d.ts +29 -0
  277. package/dist/migrations/0.1.0-alpha.0_initial_setup.d.ts.map +1 -0
  278. package/dist/migrations/0.1.0-alpha.0_initial_setup.js +72 -0
  279. package/dist/migrations/0.1.0-alpha.0_initial_setup.js.map +1 -0
  280. package/dist/migrations/_example_migration.d.ts +31 -0
  281. package/dist/migrations/_example_migration.d.ts.map +1 -0
  282. package/dist/migrations/_example_migration.js +75 -0
  283. package/dist/migrations/_example_migration.js.map +1 -0
  284. package/dist/plugins/definePlugin.d.ts +49 -0
  285. package/dist/plugins/definePlugin.d.ts.map +1 -0
  286. package/dist/plugins/definePlugin.js +131 -0
  287. package/dist/plugins/definePlugin.js.map +1 -0
  288. package/dist/plugins/softDelete.d.ts +179 -0
  289. package/dist/plugins/softDelete.d.ts.map +1 -0
  290. package/dist/plugins/softDelete.js +235 -0
  291. package/dist/plugins/softDelete.js.map +1 -0
  292. package/dist/routes/auth.route.d.ts +14 -0
  293. package/dist/routes/auth.route.d.ts.map +1 -0
  294. package/dist/routes/auth.route.js +421 -0
  295. package/dist/routes/auth.route.js.map +1 -0
  296. package/dist/routes/file.route.d.ts +7 -0
  297. package/dist/routes/file.route.d.ts.map +1 -0
  298. package/dist/routes/file.route.js +274 -0
  299. package/dist/routes/file.route.js.map +1 -0
  300. package/dist/routes/items.route.d.ts +7 -0
  301. package/dist/routes/items.route.d.ts.map +1 -0
  302. package/dist/routes/items.route.js +369 -0
  303. package/dist/routes/items.route.js.map +1 -0
  304. package/dist/routes/migration.route.d.ts +7 -0
  305. package/dist/routes/migration.route.d.ts.map +1 -0
  306. package/dist/routes/migration.route.js +225 -0
  307. package/dist/routes/migration.route.js.map +1 -0
  308. package/dist/routes/notification.route.d.ts +7 -0
  309. package/dist/routes/notification.route.d.ts.map +1 -0
  310. package/dist/routes/notification.route.js +124 -0
  311. package/dist/routes/notification.route.js.map +1 -0
  312. package/dist/routes/openapi.route.d.ts +7 -0
  313. package/dist/routes/openapi.route.d.ts.map +1 -0
  314. package/dist/routes/openapi.route.js +2169 -0
  315. package/dist/routes/openapi.route.js.map +1 -0
  316. package/dist/routes/permission.route.d.ts +7 -0
  317. package/dist/routes/permission.route.d.ts.map +1 -0
  318. package/dist/routes/permission.route.js +158 -0
  319. package/dist/routes/permission.route.js.map +1 -0
  320. package/dist/routes/realtime.route.d.ts +21 -0
  321. package/dist/routes/realtime.route.d.ts.map +1 -0
  322. package/dist/routes/realtime.route.js +243 -0
  323. package/dist/routes/realtime.route.js.map +1 -0
  324. package/dist/routes/reports.route.d.ts +7 -0
  325. package/dist/routes/reports.route.d.ts.map +1 -0
  326. package/dist/routes/reports.route.js +95 -0
  327. package/dist/routes/reports.route.js.map +1 -0
  328. package/dist/routes/schema.route.d.ts +7 -0
  329. package/dist/routes/schema.route.d.ts.map +1 -0
  330. package/dist/routes/schema.route.js +1780 -0
  331. package/dist/routes/schema.route.js.map +1 -0
  332. package/dist/routes/settings.route.d.ts +7 -0
  333. package/dist/routes/settings.route.d.ts.map +1 -0
  334. package/dist/routes/settings.route.js +154 -0
  335. package/dist/routes/settings.route.js.map +1 -0
  336. package/dist/routes/templates.route.d.ts +7 -0
  337. package/dist/routes/templates.route.d.ts.map +1 -0
  338. package/dist/routes/templates.route.js +91 -0
  339. package/dist/routes/templates.route.js.map +1 -0
  340. package/dist/routes/utils.route.d.ts +7 -0
  341. package/dist/routes/utils.route.d.ts.map +1 -0
  342. package/dist/routes/utils.route.js +33 -0
  343. package/dist/routes/utils.route.js.map +1 -0
  344. package/dist/routes/workflow.route.d.ts +7 -0
  345. package/dist/routes/workflow.route.d.ts.map +1 -0
  346. package/dist/routes/workflow.route.js +787 -0
  347. package/dist/routes/workflow.route.js.map +1 -0
  348. package/dist/services/AssetsService.d.ts +39 -0
  349. package/dist/services/AssetsService.d.ts.map +1 -0
  350. package/dist/services/AssetsService.js +255 -0
  351. package/dist/services/AssetsService.js.map +1 -0
  352. package/dist/services/CacheService.d.ts +169 -0
  353. package/dist/services/CacheService.d.ts.map +1 -0
  354. package/dist/services/CacheService.js +722 -0
  355. package/dist/services/CacheService.js.map +1 -0
  356. package/dist/services/FilesService.d.ts +30 -0
  357. package/dist/services/FilesService.d.ts.map +1 -0
  358. package/dist/services/FilesService.js +268 -0
  359. package/dist/services/FilesService.js.map +1 -0
  360. package/dist/services/HooksManager.d.ts +38 -0
  361. package/dist/services/HooksManager.d.ts.map +1 -0
  362. package/dist/services/HooksManager.js +165 -0
  363. package/dist/services/HooksManager.js.map +1 -0
  364. package/dist/services/ItemsService.d.ts +273 -0
  365. package/dist/services/ItemsService.d.ts.map +1 -0
  366. package/dist/services/ItemsService.js +2458 -0
  367. package/dist/services/ItemsService.js.map +1 -0
  368. package/dist/services/MailService.d.ts +76 -0
  369. package/dist/services/MailService.d.ts.map +1 -0
  370. package/dist/services/MailService.js +585 -0
  371. package/dist/services/MailService.js.map +1 -0
  372. package/dist/services/MigrationService.d.ts +243 -0
  373. package/dist/services/MigrationService.d.ts.map +1 -0
  374. package/dist/services/MigrationService.js +914 -0
  375. package/dist/services/MigrationService.js.map +1 -0
  376. package/dist/services/NotificationService.d.ts +35 -0
  377. package/dist/services/NotificationService.d.ts.map +1 -0
  378. package/dist/services/NotificationService.js +159 -0
  379. package/dist/services/NotificationService.js.map +1 -0
  380. package/dist/services/PermissionService.d.ts +128 -0
  381. package/dist/services/PermissionService.d.ts.map +1 -0
  382. package/dist/services/PermissionService.js +373 -0
  383. package/dist/services/PermissionService.js.map +1 -0
  384. package/dist/services/PluginManager.d.ts +138 -0
  385. package/dist/services/PluginManager.d.ts.map +1 -0
  386. package/dist/services/PluginManager.js +463 -0
  387. package/dist/services/PluginManager.js.map +1 -0
  388. package/dist/services/RealtimeService.d.ts +209 -0
  389. package/dist/services/RealtimeService.d.ts.map +1 -0
  390. package/dist/services/RealtimeService.js +978 -0
  391. package/dist/services/RealtimeService.js.map +1 -0
  392. package/dist/services/ReportService.d.ts +13 -0
  393. package/dist/services/ReportService.d.ts.map +1 -0
  394. package/dist/services/ReportService.js +91 -0
  395. package/dist/services/ReportService.js.map +1 -0
  396. package/dist/services/SettingsService.d.ts +60 -0
  397. package/dist/services/SettingsService.d.ts.map +1 -0
  398. package/dist/services/SettingsService.js +474 -0
  399. package/dist/services/SettingsService.js.map +1 -0
  400. package/dist/services/SocketService.d.ts +129 -0
  401. package/dist/services/SocketService.d.ts.map +1 -0
  402. package/dist/services/SocketService.js +600 -0
  403. package/dist/services/SocketService.js.map +1 -0
  404. package/dist/services/StatsService.d.ts +10 -0
  405. package/dist/services/StatsService.d.ts.map +1 -0
  406. package/dist/services/StatsService.js +40 -0
  407. package/dist/services/StatsService.js.map +1 -0
  408. package/dist/services/StorageService.d.ts +20 -0
  409. package/dist/services/StorageService.d.ts.map +1 -0
  410. package/dist/services/StorageService.js +164 -0
  411. package/dist/services/StorageService.js.map +1 -0
  412. package/dist/services/TasksService.d.ts +74 -0
  413. package/dist/services/TasksService.d.ts.map +1 -0
  414. package/dist/services/TasksService.js +404 -0
  415. package/dist/services/TasksService.js.map +1 -0
  416. package/dist/services/WorkflowService.d.ts +305 -0
  417. package/dist/services/WorkflowService.d.ts.map +1 -0
  418. package/dist/services/WorkflowService.js +1811 -0
  419. package/dist/services/WorkflowService.js.map +1 -0
  420. package/dist/templates/logo/logo.png +0 -0
  421. package/dist/templates/mails/default.liquid +23 -0
  422. package/dist/types/aggregation.d.ts +40 -0
  423. package/dist/types/aggregation.d.ts.map +1 -0
  424. package/dist/types/aggregation.js +6 -0
  425. package/dist/types/aggregation.js.map +1 -0
  426. package/dist/types/assets.d.ts +32 -0
  427. package/dist/types/assets.d.ts.map +1 -0
  428. package/dist/types/assets.js +6 -0
  429. package/dist/types/assets.js.map +1 -0
  430. package/dist/types/auth.d.ts +50 -0
  431. package/dist/types/auth.d.ts.map +1 -0
  432. package/dist/types/auth.js +6 -0
  433. package/dist/types/auth.js.map +1 -0
  434. package/dist/types/cache.d.ts +47 -0
  435. package/dist/types/cache.d.ts.map +1 -0
  436. package/dist/types/cache.js +6 -0
  437. package/dist/types/cache.js.map +1 -0
  438. package/dist/types/database.d.ts +16 -0
  439. package/dist/types/database.d.ts.map +1 -0
  440. package/dist/types/database.js +6 -0
  441. package/dist/types/database.js.map +1 -0
  442. package/dist/types/fields.d.ts +71 -0
  443. package/dist/types/fields.d.ts.map +1 -0
  444. package/dist/types/fields.js +6 -0
  445. package/dist/types/fields.js.map +1 -0
  446. package/dist/types/files.d.ts +33 -0
  447. package/dist/types/files.d.ts.map +1 -0
  448. package/dist/types/files.js +6 -0
  449. package/dist/types/files.js.map +1 -0
  450. package/dist/types/hooks.d.ts +29 -0
  451. package/dist/types/hooks.d.ts.map +1 -0
  452. package/dist/types/hooks.js +6 -0
  453. package/dist/types/hooks.js.map +1 -0
  454. package/dist/types/import-export.d.ts +62 -0
  455. package/dist/types/import-export.d.ts.map +1 -0
  456. package/dist/types/import-export.js +6 -0
  457. package/dist/types/import-export.js.map +1 -0
  458. package/dist/types/index.d.ts +31 -0
  459. package/dist/types/index.d.ts.map +1 -0
  460. package/dist/types/index.js +58 -0
  461. package/dist/types/index.js.map +1 -0
  462. package/dist/types/mail.d.ts +34 -0
  463. package/dist/types/mail.d.ts.map +1 -0
  464. package/dist/types/mail.js +6 -0
  465. package/dist/types/mail.js.map +1 -0
  466. package/dist/types/notifications.d.ts +16 -0
  467. package/dist/types/notifications.d.ts.map +1 -0
  468. package/dist/types/notifications.js +6 -0
  469. package/dist/types/notifications.js.map +1 -0
  470. package/dist/types/plugin.d.ts +351 -0
  471. package/dist/types/plugin.d.ts.map +1 -0
  472. package/dist/types/plugin.js +8 -0
  473. package/dist/types/plugin.js.map +1 -0
  474. package/dist/types/query.d.ts +71 -0
  475. package/dist/types/query.d.ts.map +1 -0
  476. package/dist/types/query.js +6 -0
  477. package/dist/types/query.js.map +1 -0
  478. package/dist/types/relations.d.ts +111 -0
  479. package/dist/types/relations.d.ts.map +1 -0
  480. package/dist/types/relations.js +6 -0
  481. package/dist/types/relations.js.map +1 -0
  482. package/dist/types/reports.d.ts +17 -0
  483. package/dist/types/reports.d.ts.map +1 -0
  484. package/dist/types/reports.js +6 -0
  485. package/dist/types/reports.js.map +1 -0
  486. package/dist/types/schema.d.ts +26 -0
  487. package/dist/types/schema.d.ts.map +1 -0
  488. package/dist/types/schema.js +6 -0
  489. package/dist/types/schema.js.map +1 -0
  490. package/dist/types/seed.d.ts +27 -0
  491. package/dist/types/seed.d.ts.map +1 -0
  492. package/dist/types/seed.js +6 -0
  493. package/dist/types/seed.js.map +1 -0
  494. package/dist/types/services.d.ts +68 -0
  495. package/dist/types/services.d.ts.map +1 -0
  496. package/dist/types/services.js +6 -0
  497. package/dist/types/services.js.map +1 -0
  498. package/dist/types/settings.d.ts +36 -0
  499. package/dist/types/settings.d.ts.map +1 -0
  500. package/dist/types/settings.js +6 -0
  501. package/dist/types/settings.js.map +1 -0
  502. package/dist/types/sockets.d.ts +26 -0
  503. package/dist/types/sockets.d.ts.map +1 -0
  504. package/dist/types/sockets.js +6 -0
  505. package/dist/types/sockets.js.map +1 -0
  506. package/dist/types/sort.d.ts +25 -0
  507. package/dist/types/sort.d.ts.map +1 -0
  508. package/dist/types/sort.js +6 -0
  509. package/dist/types/sort.js.map +1 -0
  510. package/dist/types/spatial.d.ts +19 -0
  511. package/dist/types/spatial.d.ts.map +1 -0
  512. package/dist/types/spatial.js +6 -0
  513. package/dist/types/spatial.js.map +1 -0
  514. package/dist/types/stats.d.ts +21 -0
  515. package/dist/types/stats.d.ts.map +1 -0
  516. package/dist/types/stats.js +6 -0
  517. package/dist/types/stats.js.map +1 -0
  518. package/dist/types/storage.d.ts +19 -0
  519. package/dist/types/storage.d.ts.map +1 -0
  520. package/dist/types/storage.js +6 -0
  521. package/dist/types/storage.js.map +1 -0
  522. package/dist/types/tasks.d.ts +14 -0
  523. package/dist/types/tasks.d.ts.map +1 -0
  524. package/dist/types/tasks.js +6 -0
  525. package/dist/types/tasks.js.map +1 -0
  526. package/dist/types/utils.d.ts +54 -0
  527. package/dist/types/utils.d.ts.map +1 -0
  528. package/dist/types/utils.js +6 -0
  529. package/dist/types/utils.js.map +1 -0
  530. package/dist/types/workflow.d.ts +17 -0
  531. package/dist/types/workflow.d.ts.map +1 -0
  532. package/dist/types/workflow.js +6 -0
  533. package/dist/types/workflow.js.map +1 -0
  534. package/dist/utils/aggregationUtils.d.ts +192 -0
  535. package/dist/utils/aggregationUtils.d.ts.map +1 -0
  536. package/dist/utils/aggregationUtils.js +450 -0
  537. package/dist/utils/aggregationUtils.js.map +1 -0
  538. package/dist/utils/auth.d.ts +93 -0
  539. package/dist/utils/auth.d.ts.map +1 -0
  540. package/dist/utils/auth.js +557 -0
  541. package/dist/utils/auth.js.map +1 -0
  542. package/dist/utils/cache.d.ts +64 -0
  543. package/dist/utils/cache.d.ts.map +1 -0
  544. package/dist/utils/cache.js +464 -0
  545. package/dist/utils/cache.js.map +1 -0
  546. package/dist/utils/common.d.ts +53 -0
  547. package/dist/utils/common.d.ts.map +1 -0
  548. package/dist/utils/common.js +162 -0
  549. package/dist/utils/common.js.map +1 -0
  550. package/dist/utils/db.d.ts +101 -0
  551. package/dist/utils/db.d.ts.map +1 -0
  552. package/dist/utils/db.js +413 -0
  553. package/dist/utils/db.js.map +1 -0
  554. package/dist/utils/dirname.d.ts +30 -0
  555. package/dist/utils/dirname.d.ts.map +1 -0
  556. package/dist/utils/dirname.js +95 -0
  557. package/dist/utils/dirname.js.map +1 -0
  558. package/dist/utils/dynamicVariableResolver.d.ts +17 -0
  559. package/dist/utils/dynamicVariableResolver.d.ts.map +1 -0
  560. package/dist/utils/dynamicVariableResolver.js +262 -0
  561. package/dist/utils/dynamicVariableResolver.js.map +1 -0
  562. package/dist/utils/env.d.ts +38 -0
  563. package/dist/utils/env.d.ts.map +1 -0
  564. package/dist/utils/env.js +80 -0
  565. package/dist/utils/env.js.map +1 -0
  566. package/dist/utils/errorHandler.d.ts +14 -0
  567. package/dist/utils/errorHandler.d.ts.map +1 -0
  568. package/dist/utils/errorHandler.js +79 -0
  569. package/dist/utils/errorHandler.js.map +1 -0
  570. package/dist/utils/fieldExpansion.d.ts +30 -0
  571. package/dist/utils/fieldExpansion.d.ts.map +1 -0
  572. package/dist/utils/fieldExpansion.js +145 -0
  573. package/dist/utils/fieldExpansion.js.map +1 -0
  574. package/dist/utils/fieldUtils.d.ts +179 -0
  575. package/dist/utils/fieldUtils.d.ts.map +1 -0
  576. package/dist/utils/fieldUtils.js +424 -0
  577. package/dist/utils/fieldUtils.js.map +1 -0
  578. package/dist/utils/filterOperators.d.ts +472 -0
  579. package/dist/utils/filterOperators.d.ts.map +1 -0
  580. package/dist/utils/filterOperators.js +1229 -0
  581. package/dist/utils/filterOperators.js.map +1 -0
  582. package/dist/utils/importUtils.d.ts +127 -0
  583. package/dist/utils/importUtils.d.ts.map +1 -0
  584. package/dist/utils/importUtils.js +437 -0
  585. package/dist/utils/importUtils.js.map +1 -0
  586. package/dist/utils/index.d.ts +75 -0
  587. package/dist/utils/index.d.ts.map +1 -0
  588. package/dist/utils/index.js +101 -0
  589. package/dist/utils/index.js.map +1 -0
  590. package/dist/utils/logger.d.ts +41 -0
  591. package/dist/utils/logger.d.ts.map +1 -0
  592. package/dist/utils/logger.js +217 -0
  593. package/dist/utils/logger.js.map +1 -0
  594. package/dist/utils/orderUtils.d.ts +117 -0
  595. package/dist/utils/orderUtils.d.ts.map +1 -0
  596. package/dist/utils/orderUtils.js +249 -0
  597. package/dist/utils/orderUtils.js.map +1 -0
  598. package/dist/utils/queryBuilder.d.ts +118 -0
  599. package/dist/utils/queryBuilder.d.ts.map +1 -0
  600. package/dist/utils/queryBuilder.js +489 -0
  601. package/dist/utils/queryBuilder.js.map +1 -0
  602. package/dist/utils/relationLoader.d.ts +65 -0
  603. package/dist/utils/relationLoader.d.ts.map +1 -0
  604. package/dist/utils/relationLoader.js +1081 -0
  605. package/dist/utils/relationLoader.js.map +1 -0
  606. package/dist/utils/relationPathResolver.d.ts +30 -0
  607. package/dist/utils/relationPathResolver.d.ts.map +1 -0
  608. package/dist/utils/relationPathResolver.js +173 -0
  609. package/dist/utils/relationPathResolver.js.map +1 -0
  610. package/dist/utils/relationUtils.d.ts +139 -0
  611. package/dist/utils/relationUtils.d.ts.map +1 -0
  612. package/dist/utils/relationUtils.js +711 -0
  613. package/dist/utils/relationUtils.js.map +1 -0
  614. package/dist/utils/router.d.ts +6 -0
  615. package/dist/utils/router.d.ts.map +1 -0
  616. package/dist/utils/router.js +95 -0
  617. package/dist/utils/router.js.map +1 -0
  618. package/dist/utils/schema.d.ts +88 -0
  619. package/dist/utils/schema.d.ts.map +1 -0
  620. package/dist/utils/schema.js +24 -0
  621. package/dist/utils/schema.js.map +1 -0
  622. package/dist/utils/schemaManager.d.ts +238 -0
  623. package/dist/utils/schemaManager.d.ts.map +1 -0
  624. package/dist/utils/schemaManager.js +1992 -0
  625. package/dist/utils/schemaManager.js.map +1 -0
  626. package/dist/utils/schemaValidator.d.ts +83 -0
  627. package/dist/utils/schemaValidator.d.ts.map +1 -0
  628. package/dist/utils/schemaValidator.js +491 -0
  629. package/dist/utils/schemaValidator.js.map +1 -0
  630. package/dist/utils/seed.d.ts +45 -0
  631. package/dist/utils/seed.d.ts.map +1 -0
  632. package/dist/utils/seed.js +248 -0
  633. package/dist/utils/seed.js.map +1 -0
  634. package/dist/utils/sessionCleanup.d.ts +10 -0
  635. package/dist/utils/sessionCleanup.d.ts.map +1 -0
  636. package/dist/utils/sessionCleanup.js +49 -0
  637. package/dist/utils/sessionCleanup.js.map +1 -0
  638. package/dist/utils/sortUtils.d.ts +117 -0
  639. package/dist/utils/sortUtils.d.ts.map +1 -0
  640. package/dist/utils/sortUtils.js +232 -0
  641. package/dist/utils/sortUtils.js.map +1 -0
  642. package/dist/utils/spatialUtils.d.ts +244 -0
  643. package/dist/utils/spatialUtils.d.ts.map +1 -0
  644. package/dist/utils/spatialUtils.js +359 -0
  645. package/dist/utils/spatialUtils.js.map +1 -0
  646. package/dist/utils/systemschema.d.ts +11040 -0
  647. package/dist/utils/systemschema.d.ts.map +1 -0
  648. package/dist/utils/systemschema.js +1777 -0
  649. package/dist/utils/systemschema.js.map +1 -0
  650. package/dist/utils/tenantUtils.d.ts +34 -0
  651. package/dist/utils/tenantUtils.d.ts.map +1 -0
  652. package/dist/utils/tenantUtils.js +124 -0
  653. package/dist/utils/tenantUtils.js.map +1 -0
  654. package/dist/utils/typeMapper.d.ts +25 -0
  655. package/dist/utils/typeMapper.d.ts.map +1 -0
  656. package/dist/utils/typeMapper.js +282 -0
  657. package/dist/utils/typeMapper.js.map +1 -0
  658. package/dist/utils/valueValidator.d.ts +60 -0
  659. package/dist/utils/valueValidator.d.ts.map +1 -0
  660. package/dist/utils/valueValidator.js +303 -0
  661. package/dist/utils/valueValidator.js.map +1 -0
  662. package/dist/utils/workflow.d.ts +87 -0
  663. package/dist/utils/workflow.d.ts.map +1 -0
  664. package/dist/utils/workflow.js +205 -0
  665. package/dist/utils/workflow.js.map +1 -0
  666. package/package.json +115 -0
@@ -0,0 +1,978 @@
1
+ /**
2
+ * RealtimeService - PostgreSQL WAL-based realtime change data capture
3
+ *
4
+ * This service uses PostgreSQL logical replication to capture database changes
5
+ * and broadcast them via WebSockets using the existing SocketService.
6
+ *
7
+ * Similar to Supabase Realtime, it allows enabling realtime on specific collections
8
+ * with granular control over which actions (insert, update, delete) trigger events.
9
+ *
10
+ * Realtime configuration is stored in the schema definition:
11
+ * {
12
+ * "realtime": {
13
+ * "enabled": true,
14
+ * "actions": ["insert", "update", "delete"]
15
+ * }
16
+ * }
17
+ *
18
+ * NOTE: This service ONLY works when PostgreSQL has wal_level=logical configured.
19
+ * If WAL is not available, no realtime broadcasting will occur.
20
+ */
21
+ import env from "../utils/env.js";
22
+ import { db } from "../utils/db.js";
23
+ import { sql } from "drizzle-orm";
24
+ // Constants
25
+ const PUBLICATION_NAME = 'baasix_realtime';
26
+ const SLOT_NAME = 'baasix_realtime_slot';
27
+ // Redis key for WAL consumer leader election
28
+ const WAL_LEADER_KEY = 'baasix:wal:leader';
29
+ const WAL_LEADER_TTL = 30; // seconds
30
+ /**
31
+ * Custom PgoutputPlugin that doesn't send the 'messages' option
32
+ * This is compatible with PostgreSQL 10+ (the default library sends 'messages' which requires PG 14+)
33
+ */
34
+ class CompatiblePgoutputPlugin {
35
+ options;
36
+ parser;
37
+ constructor(options) {
38
+ this.options = options;
39
+ this.parser = null;
40
+ }
41
+ get name() {
42
+ return 'pgoutput';
43
+ }
44
+ async initParser() {
45
+ if (!this.parser) {
46
+ const { PgoutputParser } = await import('pg-logical-replication/dist/output-plugins/pgoutput/pgoutput-parser.js');
47
+ this.parser = new PgoutputParser();
48
+ }
49
+ }
50
+ parse(buffer) {
51
+ return this.parser.parse(buffer);
52
+ }
53
+ start(client, slotName, lastLsn) {
54
+ // Only send proto_version and publication_names - no 'messages' option for PG < 14 compatibility
55
+ const options = [
56
+ `proto_version '${this.options.protoVersion}'`,
57
+ `publication_names '${this.options.publicationNames.join(',')}'`,
58
+ ];
59
+ const sql = `START_REPLICATION SLOT "${slotName}" LOGICAL ${lastLsn} (${options.join(', ')})`;
60
+ return client.query(sql);
61
+ }
62
+ }
63
+ class RealtimeService {
64
+ initialized = false;
65
+ connected = false;
66
+ replicationService = null;
67
+ socketService = null;
68
+ // Map of collection name to realtime config
69
+ collectionConfigs = new Map();
70
+ reconnectTimeout = null;
71
+ shuttingDown = false;
72
+ walAvailable = false;
73
+ // Whether this instance is the WAL consumer leader
74
+ isWalLeader = false;
75
+ // Unique instance ID for Redis leader election
76
+ instanceId = `${process.pid}-${Date.now()}`;
77
+ // Redis client for leader election
78
+ redisClient = null;
79
+ // Leader lock renewal interval
80
+ leaderRenewalInterval = null;
81
+ constructor() {
82
+ console.info("Realtime Service instance created");
83
+ }
84
+ /**
85
+ * Set the socket service instance for broadcasting
86
+ */
87
+ setSocketService(socketService) {
88
+ this.socketService = socketService;
89
+ }
90
+ /**
91
+ * Check if PostgreSQL logical replication is enabled
92
+ */
93
+ async checkLogicalReplicationEnabled() {
94
+ try {
95
+ const result = await db.execute(sql `SHOW wal_level`);
96
+ const walLevel = result[0]?.wal_level || 'unknown';
97
+ return {
98
+ enabled: walLevel === 'logical',
99
+ walLevel,
100
+ error: walLevel !== 'logical'
101
+ ? `PostgreSQL wal_level is '${walLevel}'. Logical replication requires wal_level = 'logical'. Please update postgresql.conf and restart PostgreSQL.`
102
+ : undefined
103
+ };
104
+ }
105
+ catch (error) {
106
+ return {
107
+ enabled: false,
108
+ walLevel: 'unknown',
109
+ error: `Failed to check wal_level: ${error.message}`
110
+ };
111
+ }
112
+ }
113
+ /**
114
+ * Check replication configuration
115
+ */
116
+ async checkReplicationConfig() {
117
+ const issues = [];
118
+ try {
119
+ const [walLevelResult, slotsResult, sendersResult] = await Promise.all([
120
+ db.execute(sql `SHOW wal_level`),
121
+ db.execute(sql `SHOW max_replication_slots`),
122
+ db.execute(sql `SHOW max_wal_senders`)
123
+ ]);
124
+ const walLevel = walLevelResult[0]?.wal_level || 'unknown';
125
+ const maxReplicationSlots = parseInt(slotsResult[0]?.max_replication_slots || '0');
126
+ const maxWalSenders = parseInt(sendersResult[0]?.max_wal_senders || '0');
127
+ if (walLevel !== 'logical') {
128
+ issues.push(`wal_level is '${walLevel}', should be 'logical'`);
129
+ }
130
+ if (maxReplicationSlots < 1) {
131
+ issues.push(`max_replication_slots is ${maxReplicationSlots}, should be at least 1`);
132
+ }
133
+ if (maxWalSenders < 1) {
134
+ issues.push(`max_wal_senders is ${maxWalSenders}, should be at least 1`);
135
+ }
136
+ return {
137
+ walLevel,
138
+ maxReplicationSlots,
139
+ maxWalSenders,
140
+ isConfigured: issues.length === 0,
141
+ issues
142
+ };
143
+ }
144
+ catch (error) {
145
+ issues.push(`Failed to check config: ${error.message}`);
146
+ return {
147
+ walLevel: 'unknown',
148
+ maxReplicationSlots: 0,
149
+ maxWalSenders: 0,
150
+ isConfigured: false,
151
+ issues
152
+ };
153
+ }
154
+ }
155
+ /**
156
+ * Initialize the realtime service
157
+ * Creates publication and replication slot if they don't exist
158
+ * Only works when PostgreSQL has wal_level=logical configured
159
+ */
160
+ async initialize() {
161
+ if (this.initialized) {
162
+ console.warn("Realtime service already initialized");
163
+ return;
164
+ }
165
+ console.info("Initializing Realtime Service...");
166
+ // Check if logical replication is enabled
167
+ const replicationCheck = await this.checkLogicalReplicationEnabled();
168
+ if (!replicationCheck.enabled) {
169
+ console.warn(`⚠️ PostgreSQL logical replication not available (wal_level=${replicationCheck.walLevel})`);
170
+ console.warn(" WAL-based realtime is disabled. No automatic broadcasting will occur.");
171
+ console.warn(" To enable, set wal_level=logical in postgresql.conf and restart PostgreSQL.");
172
+ this.walAvailable = false;
173
+ this.initialized = true;
174
+ return;
175
+ }
176
+ this.walAvailable = true;
177
+ // Create publication if it doesn't exist
178
+ await this.ensurePublicationExists();
179
+ // Create replication slot if it doesn't exist
180
+ await this.ensureReplicationSlotExists();
181
+ // Load enabled collections from database
182
+ await this.loadEnabledCollections();
183
+ // Sync publication with enabled collections
184
+ await this.syncPublicationWithCollections();
185
+ this.initialized = true;
186
+ console.info("✅ Realtime Service initialized with WAL support");
187
+ }
188
+ /**
189
+ * Check if WAL-based realtime is available
190
+ */
191
+ isWalAvailable() {
192
+ return this.walAvailable;
193
+ }
194
+ /**
195
+ * Ensure the publication exists
196
+ */
197
+ async ensurePublicationExists() {
198
+ try {
199
+ // Check if publication exists
200
+ const result = await db.execute(sql `
201
+ SELECT pubname FROM pg_publication WHERE pubname = ${PUBLICATION_NAME}
202
+ `);
203
+ if (result.length === 0) {
204
+ // Create empty publication (tables will be added as they're enabled)
205
+ await db.execute(sql.raw(`CREATE PUBLICATION ${PUBLICATION_NAME}`));
206
+ console.info(`Created publication: ${PUBLICATION_NAME}`);
207
+ }
208
+ else {
209
+ console.info(`Publication ${PUBLICATION_NAME} already exists`);
210
+ }
211
+ }
212
+ catch (error) {
213
+ // Handle case where publication already exists (race condition)
214
+ if (!error.message.includes('already exists')) {
215
+ throw error;
216
+ }
217
+ }
218
+ }
219
+ /**
220
+ * Ensure the replication slot exists
221
+ */
222
+ async ensureReplicationSlotExists() {
223
+ try {
224
+ // Check if slot exists
225
+ const result = await db.execute(sql `
226
+ SELECT slot_name FROM pg_replication_slots WHERE slot_name = ${SLOT_NAME}
227
+ `);
228
+ if (result.length === 0) {
229
+ // Create replication slot
230
+ await db.execute(sql.raw(`
231
+ SELECT pg_create_logical_replication_slot('${SLOT_NAME}', 'pgoutput')
232
+ `));
233
+ console.info(`Created replication slot: ${SLOT_NAME}`);
234
+ }
235
+ else {
236
+ console.info(`Replication slot ${SLOT_NAME} already exists`);
237
+ }
238
+ }
239
+ catch (error) {
240
+ // Handle case where slot already exists (race condition)
241
+ if (!error.message.includes('already exists')) {
242
+ throw error;
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Load collections with realtime enabled from schema definitions
248
+ * Supports both old format (realtime: true) and new format (realtime: { enabled: true, actions: [...] })
249
+ */
250
+ async loadEnabledCollections() {
251
+ try {
252
+ // Query for collections with realtime enabled (supports both old and new format)
253
+ const result = await db.execute(sql `
254
+ SELECT "collectionName", schema
255
+ FROM "baasix_SchemaDefinition"
256
+ WHERE schema->>'realtime' = 'true'
257
+ OR schema->'realtime'->>'enabled' = 'true'
258
+ `);
259
+ this.collectionConfigs.clear();
260
+ for (const row of result) {
261
+ const schema = typeof row.schema === 'string' ? JSON.parse(row.schema) : row.schema;
262
+ const realtimeConfig = schema.realtime;
263
+ // Handle old format (realtime: true)
264
+ if (realtimeConfig === true) {
265
+ this.collectionConfigs.set(row.collectionName, {
266
+ enabled: true,
267
+ actions: ['insert', 'update', 'delete']
268
+ });
269
+ }
270
+ // Handle new format (realtime: { enabled: true, actions: [...] })
271
+ else if (typeof realtimeConfig === 'object') {
272
+ this.collectionConfigs.set(row.collectionName, {
273
+ enabled: realtimeConfig.enabled !== false,
274
+ actions: realtimeConfig.actions || ['insert', 'update', 'delete']
275
+ });
276
+ }
277
+ }
278
+ console.info(`Loaded ${this.collectionConfigs.size} realtime-enabled collections`);
279
+ }
280
+ catch (error) {
281
+ console.warn(`Failed to load realtime collections: ${error.message}`);
282
+ }
283
+ }
284
+ /**
285
+ * Sync the PostgreSQL publication with enabled collections
286
+ */
287
+ async syncPublicationWithCollections() {
288
+ if (!this.walAvailable)
289
+ return;
290
+ try {
291
+ // Get current tables in publication
292
+ const currentTables = await db.execute(sql `
293
+ SELECT schemaname, tablename
294
+ FROM pg_publication_tables
295
+ WHERE pubname = ${PUBLICATION_NAME}
296
+ `);
297
+ const currentTableNames = new Set(currentTables.map(t => t.tablename));
298
+ // Add missing tables
299
+ for (const [collectionName, config] of this.collectionConfigs) {
300
+ if (config.enabled && !currentTableNames.has(collectionName)) {
301
+ try {
302
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} ADD TABLE "${collectionName}"`));
303
+ console.info(`Added ${collectionName} to publication`);
304
+ }
305
+ catch (error) {
306
+ if (!error.message.includes('already member')) {
307
+ console.warn(`Failed to add ${collectionName} to publication: ${error.message}`);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ // Remove tables that are no longer enabled
313
+ for (const tableName of currentTableNames) {
314
+ const config = this.collectionConfigs.get(tableName);
315
+ if (!config || !config.enabled) {
316
+ try {
317
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} DROP TABLE "${tableName}"`));
318
+ console.info(`Removed ${tableName} from publication`);
319
+ }
320
+ catch (error) {
321
+ console.warn(`Failed to remove ${tableName} from publication: ${error.message}`);
322
+ }
323
+ }
324
+ }
325
+ }
326
+ catch (error) {
327
+ console.warn(`Failed to sync publication: ${error.message}`);
328
+ }
329
+ }
330
+ /**
331
+ * Start consuming WAL changes
332
+ */
333
+ async startConsuming() {
334
+ if (!this.initialized) {
335
+ throw new Error("Realtime service not initialized. Call initialize() first.");
336
+ }
337
+ if (!this.walAvailable) {
338
+ console.info("WAL not available, skipping WAL consumer startup");
339
+ return;
340
+ }
341
+ // Check if WAL consumer is explicitly disabled via environment variable
342
+ if (env.get("REALTIME_WAL_CONSUMER_ENABLED") === "false") {
343
+ console.info("WAL consumer explicitly disabled via REALTIME_WAL_CONSUMER_ENABLED=false");
344
+ return;
345
+ }
346
+ if (this.connected) {
347
+ console.warn("Already consuming WAL changes");
348
+ return;
349
+ }
350
+ // Multi-instance mode: Use Redis for leader election
351
+ // Only one instance should consume WAL to avoid duplicate processing
352
+ if (env.get("SOCKET_REDIS_ENABLED") === "true") {
353
+ const isLeader = await this.tryBecomeWalLeader();
354
+ if (!isLeader) {
355
+ console.info("Another instance is the WAL consumer leader. This instance will not consume WAL.");
356
+ console.info("Realtime broadcasts will still work via Redis adapter.");
357
+ this.isWalLeader = false;
358
+ return;
359
+ }
360
+ this.isWalLeader = true;
361
+ this.startLeaderRenewal();
362
+ console.info("This instance became the WAL consumer leader");
363
+ }
364
+ else {
365
+ // Single instance mode - no Redis, this instance is the leader
366
+ this.isWalLeader = true;
367
+ console.info("Single instance mode - this instance will consume WAL");
368
+ }
369
+ try {
370
+ // Dynamic import to avoid issues if package is not installed
371
+ const { LogicalReplicationService } = await import('pg-logical-replication');
372
+ const connectionString = env.get("DATABASE_URL");
373
+ if (!connectionString) {
374
+ throw new Error("DATABASE_URL is required for realtime service");
375
+ }
376
+ this.replicationService = new LogicalReplicationService({ connectionString }, { acknowledge: { auto: true, timeoutSeconds: 10 } });
377
+ // Use our compatible plugin that doesn't send the 'messages' option (PG < 14 compatible)
378
+ const plugin = new CompatiblePgoutputPlugin({
379
+ protoVersion: 1,
380
+ publicationNames: [PUBLICATION_NAME]
381
+ });
382
+ await plugin.initParser();
383
+ // Handle incoming WAL messages
384
+ this.replicationService.on('data', (lsn, log) => {
385
+ this.handleWalMessage(log);
386
+ });
387
+ // Handle errors
388
+ this.replicationService.on('error', (error) => {
389
+ console.error("Realtime replication error:", error);
390
+ this.handleDisconnect();
391
+ });
392
+ // Start subscription
393
+ await this.replicationService.subscribe(plugin, SLOT_NAME);
394
+ this.connected = true;
395
+ console.info("✅ Started consuming WAL changes");
396
+ }
397
+ catch (error) {
398
+ // Check if this is a "slot already active" error
399
+ const errorMsg = error.message?.toLowerCase() || '';
400
+ if (errorMsg.includes('is active for pid') || error.code === '55006') {
401
+ console.warn("WAL replication slot is already active on another connection");
402
+ this.isWalLeader = false;
403
+ this.stopLeaderRenewal();
404
+ await this.releaseWalLeaderLock();
405
+ return;
406
+ }
407
+ console.error("Failed to start WAL consumer:", error);
408
+ throw error;
409
+ }
410
+ }
411
+ /**
412
+ * Try to become the WAL consumer leader using Redis
413
+ * Uses SETNX with TTL - only one instance can hold the key
414
+ */
415
+ async tryBecomeWalLeader() {
416
+ try {
417
+ const redis = await this.getRedisClient();
418
+ if (!redis)
419
+ return true; // No Redis = single instance mode
420
+ // Try to set the key only if it doesn't exist (NX), with TTL
421
+ const result = await redis.set(WAL_LEADER_KEY, this.instanceId, 'EX', WAL_LEADER_TTL, 'NX');
422
+ return result === 'OK';
423
+ }
424
+ catch (error) {
425
+ console.warn(`Failed to acquire WAL leader via Redis: ${error.message}`);
426
+ // On Redis error, allow this instance to consume WAL (fallback to single-instance behavior)
427
+ return true;
428
+ }
429
+ }
430
+ /**
431
+ * Start periodic renewal of leader lock
432
+ */
433
+ startLeaderRenewal() {
434
+ if (this.leaderRenewalInterval)
435
+ return;
436
+ // Renew every 10 seconds (TTL is 30s, so we have buffer)
437
+ this.leaderRenewalInterval = setInterval(async () => {
438
+ try {
439
+ const redis = await this.getRedisClient();
440
+ if (!redis)
441
+ return;
442
+ // Only renew if we're still the leader
443
+ const currentLeader = await redis.get(WAL_LEADER_KEY);
444
+ if (currentLeader === this.instanceId) {
445
+ await redis.expire(WAL_LEADER_KEY, WAL_LEADER_TTL);
446
+ }
447
+ else if (currentLeader) {
448
+ // Lost leadership to another instance
449
+ console.warn("Lost WAL consumer leadership to another instance");
450
+ this.isWalLeader = false;
451
+ this.stopLeaderRenewal();
452
+ }
453
+ }
454
+ catch (error) {
455
+ console.warn(`Failed to renew WAL leader lock: ${error.message}`);
456
+ }
457
+ }, 10000);
458
+ }
459
+ /**
460
+ * Stop leader renewal interval
461
+ */
462
+ stopLeaderRenewal() {
463
+ if (this.leaderRenewalInterval) {
464
+ clearInterval(this.leaderRenewalInterval);
465
+ this.leaderRenewalInterval = null;
466
+ }
467
+ }
468
+ /**
469
+ * Release WAL leader lock in Redis
470
+ */
471
+ async releaseWalLeaderLock() {
472
+ try {
473
+ const redis = await this.getRedisClient();
474
+ if (!redis)
475
+ return;
476
+ // Only delete if we're the current leader (atomic check-and-delete)
477
+ const currentLeader = await redis.get(WAL_LEADER_KEY);
478
+ if (currentLeader === this.instanceId) {
479
+ await redis.del(WAL_LEADER_KEY);
480
+ console.info("Released WAL consumer leadership");
481
+ }
482
+ }
483
+ catch (error) {
484
+ console.warn(`Failed to release WAL leader lock: ${error.message}`);
485
+ }
486
+ }
487
+ /**
488
+ * Get or create Redis client for leader election
489
+ * Reuses the same Redis URL as Socket.IO
490
+ */
491
+ async getRedisClient() {
492
+ if (env.get("SOCKET_REDIS_ENABLED") !== "true") {
493
+ return null;
494
+ }
495
+ if (this.redisClient) {
496
+ return this.redisClient;
497
+ }
498
+ try {
499
+ const Redis = (await import('ioredis')).default;
500
+ const redisUrl = env.get("SOCKET_REDIS_URL");
501
+ if (!redisUrl) {
502
+ console.warn("SOCKET_REDIS_URL not set, cannot use Redis for WAL leader election");
503
+ return null;
504
+ }
505
+ this.redisClient = new Redis(redisUrl, {
506
+ maxRetriesPerRequest: 3,
507
+ lazyConnect: true,
508
+ retryStrategy: (times) => {
509
+ if (times > 3)
510
+ return null; // Stop retrying
511
+ return Math.min(times * 100, 1000);
512
+ },
513
+ });
514
+ this.redisClient.on('error', (err) => {
515
+ console.warn(`Redis client error (WAL leader): ${err.message}`);
516
+ });
517
+ await this.redisClient.connect();
518
+ return this.redisClient;
519
+ }
520
+ catch (error) {
521
+ console.warn(`Failed to create Redis client: ${error.message}`);
522
+ return null;
523
+ }
524
+ }
525
+ /**
526
+ * Handle WAL messages and broadcast changes
527
+ */
528
+ handleWalMessage(message) {
529
+ // Only process insert, update, delete
530
+ if (!['insert', 'update', 'delete'].includes(message.tag)) {
531
+ return;
532
+ }
533
+ const relationName = message.relation?.name;
534
+ const schemaName = message.relation?.schema;
535
+ // Skip system schemas
536
+ if (schemaName && schemaName !== 'public') {
537
+ return;
538
+ }
539
+ if (!relationName) {
540
+ return;
541
+ }
542
+ // Get collection config
543
+ const config = this.collectionConfigs.get(relationName);
544
+ if (!config || !config.enabled) {
545
+ return;
546
+ }
547
+ // Check if this action is enabled for this collection
548
+ if (!config.actions.includes(message.tag)) {
549
+ return;
550
+ }
551
+ // Map WAL tag to action
552
+ const actionMap = {
553
+ 'insert': 'create',
554
+ 'update': 'update',
555
+ 'delete': 'delete'
556
+ };
557
+ const action = actionMap[message.tag];
558
+ if (!action)
559
+ return;
560
+ // Prepare data payload
561
+ let data;
562
+ if (message.tag === 'delete') {
563
+ // For deletes, use old values or key values
564
+ data = message.old || message.key || {};
565
+ }
566
+ else {
567
+ // For inserts and updates, use new values
568
+ data = message.new || {};
569
+ }
570
+ // Include old values for updates if available (requires REPLICA IDENTITY FULL)
571
+ const payload = { ...data };
572
+ if (message.tag === 'update' && message.old) {
573
+ payload._old = message.old;
574
+ }
575
+ // Broadcast via SocketService
576
+ if (!this.socketService) {
577
+ console.warn("Socket service not set, cannot broadcast change");
578
+ return;
579
+ }
580
+ this.socketService.broadcastChange(relationName, action, payload);
581
+ }
582
+ /**
583
+ * Handle disconnect and attempt reconnection
584
+ */
585
+ handleDisconnect() {
586
+ this.connected = false;
587
+ if (this.shuttingDown) {
588
+ return;
589
+ }
590
+ // Only the leader should attempt reconnection
591
+ if (!this.isWalLeader) {
592
+ return;
593
+ }
594
+ // Single reconnect attempt after 5 seconds
595
+ if (!this.reconnectTimeout) {
596
+ console.info("Realtime connection lost. Attempting reconnect in 5 seconds...");
597
+ this.reconnectTimeout = setTimeout(async () => {
598
+ this.reconnectTimeout = null;
599
+ try {
600
+ // Reset connected state and try again
601
+ await this.startConsuming();
602
+ }
603
+ catch (error) {
604
+ console.error("Reconnection failed:", error);
605
+ // Release leadership so another instance can try
606
+ this.stopLeaderRenewal();
607
+ await this.releaseWalLeaderLock();
608
+ this.isWalLeader = false;
609
+ console.info("Released WAL leadership after reconnection failure");
610
+ }
611
+ }, 5000);
612
+ }
613
+ }
614
+ /**
615
+ * Enable realtime for a collection with specific actions
616
+ */
617
+ async enableCollection(collectionName, actions = ['insert', 'update', 'delete']) {
618
+ if (!this.initialized) {
619
+ throw new Error("Realtime service not initialized");
620
+ }
621
+ const config = { enabled: true, actions };
622
+ this.collectionConfigs.set(collectionName, config);
623
+ // Add table to publication if WAL is available
624
+ if (this.walAvailable) {
625
+ try {
626
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} ADD TABLE "${collectionName}"`));
627
+ console.info(`Added ${collectionName} to publication`);
628
+ }
629
+ catch (error) {
630
+ if (!error.message.includes('already member')) {
631
+ throw error;
632
+ }
633
+ }
634
+ }
635
+ // Update schema definition with new config format
636
+ await this.updateSchemaRealtimeConfig(collectionName, config);
637
+ console.info(`Enabled realtime for ${collectionName} with actions: ${actions.join(', ')}`);
638
+ }
639
+ /**
640
+ * Disable realtime for a collection
641
+ */
642
+ async disableCollection(collectionName) {
643
+ if (!this.initialized) {
644
+ throw new Error("Realtime service not initialized");
645
+ }
646
+ this.collectionConfigs.delete(collectionName);
647
+ // Remove table from publication if WAL is available
648
+ if (this.walAvailable) {
649
+ try {
650
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} DROP TABLE "${collectionName}"`));
651
+ console.info(`Removed ${collectionName} from publication`);
652
+ }
653
+ catch (error) {
654
+ if (!error.message.includes('not member') && !error.message.includes('does not exist')) {
655
+ throw error;
656
+ }
657
+ }
658
+ }
659
+ // Update schema definition
660
+ await this.updateSchemaRealtimeConfig(collectionName, { enabled: false, actions: [] });
661
+ console.info(`Disabled realtime for ${collectionName}`);
662
+ }
663
+ /**
664
+ * Update realtime actions for a collection
665
+ */
666
+ async updateCollectionActions(collectionName, actions) {
667
+ const config = this.collectionConfigs.get(collectionName);
668
+ if (!config || !config.enabled) {
669
+ throw new Error(`Realtime is not enabled for collection: ${collectionName}`);
670
+ }
671
+ config.actions = actions;
672
+ await this.updateSchemaRealtimeConfig(collectionName, config);
673
+ console.info(`Updated realtime actions for ${collectionName}: ${actions.join(', ')}`);
674
+ }
675
+ /**
676
+ * Update the realtime config in schema definition
677
+ */
678
+ async updateSchemaRealtimeConfig(collectionName, config) {
679
+ try {
680
+ await db.execute(sql `
681
+ UPDATE "baasix_SchemaDefinition"
682
+ SET schema = jsonb_set(
683
+ COALESCE(schema::jsonb, '{}'::jsonb),
684
+ '{realtime}',
685
+ ${JSON.stringify(config)}::jsonb
686
+ )
687
+ WHERE "collectionName" = ${collectionName}
688
+ `);
689
+ }
690
+ catch (error) {
691
+ console.warn(`Failed to update schema realtime flag: ${error.message}`);
692
+ }
693
+ }
694
+ /**
695
+ * Set replica identity to FULL for a table (enables old values on UPDATE/DELETE)
696
+ */
697
+ async setReplicaIdentityFull(collectionName) {
698
+ try {
699
+ await db.execute(sql.raw(`
700
+ ALTER TABLE "${collectionName}" REPLICA IDENTITY FULL
701
+ `));
702
+ console.info(`Set REPLICA IDENTITY FULL for: ${collectionName}`);
703
+ }
704
+ catch (error) {
705
+ console.error(`Failed to set replica identity: ${error.message}`);
706
+ throw error;
707
+ }
708
+ }
709
+ /**
710
+ * Get realtime config for a collection
711
+ */
712
+ getCollectionConfig(collectionName) {
713
+ return this.collectionConfigs.get(collectionName);
714
+ }
715
+ /**
716
+ * Check if a collection has realtime enabled
717
+ */
718
+ isCollectionEnabled(collectionName) {
719
+ const config = this.collectionConfigs.get(collectionName);
720
+ return config?.enabled || false;
721
+ }
722
+ /**
723
+ * Get list of enabled collections with their configs
724
+ */
725
+ getEnabledCollections() {
726
+ const result = [];
727
+ for (const [collection, config] of this.collectionConfigs) {
728
+ if (config.enabled) {
729
+ result.push({ collection, config });
730
+ }
731
+ }
732
+ return result;
733
+ }
734
+ /**
735
+ * Reload collection configs from database and sync publication
736
+ * @param collectionNames - Optional list of collection names to reload. If not provided, reloads all.
737
+ */
738
+ async reloadCollections(collectionNames) {
739
+ if (collectionNames && collectionNames.length > 0) {
740
+ // Efficient path: reload only specific collections
741
+ await this.loadSpecificCollections(collectionNames);
742
+ if (this.walAvailable) {
743
+ await this.syncSpecificCollections(collectionNames);
744
+ // Restart the replication stream to pick up publication changes
745
+ await this.restartReplicationStream();
746
+ }
747
+ }
748
+ else {
749
+ // Full reload
750
+ await this.loadEnabledCollections();
751
+ if (this.walAvailable) {
752
+ await this.syncPublicationWithCollections();
753
+ // Restart the replication stream to pick up publication changes
754
+ await this.restartReplicationStream();
755
+ }
756
+ }
757
+ }
758
+ /**
759
+ * Restart the replication stream to pick up publication changes
760
+ * PostgreSQL logical replication doesn't dynamically detect publication changes,
761
+ * so we need to disconnect and reconnect the stream.
762
+ */
763
+ async restartReplicationStream() {
764
+ if (!this.connected || !this.replicationService) {
765
+ console.info("Replication stream not running, no restart needed");
766
+ return;
767
+ }
768
+ console.info("Restarting replication stream to apply publication changes...");
769
+ try {
770
+ // Stop the current replication service
771
+ if (this.replicationService) {
772
+ try {
773
+ await this.replicationService.stop();
774
+ }
775
+ catch (error) {
776
+ // Ignore stop errors
777
+ }
778
+ this.replicationService = null;
779
+ }
780
+ this.connected = false;
781
+ // Small delay to ensure clean disconnect
782
+ await new Promise(resolve => setTimeout(resolve, 500));
783
+ // Restart consuming (non-blocking)
784
+ this.startConsuming().catch((error) => {
785
+ console.error("Failed to restart replication stream:", error);
786
+ });
787
+ console.info("Replication stream restart initiated");
788
+ }
789
+ catch (error) {
790
+ console.warn(`Failed to restart replication stream: ${error.message}`);
791
+ }
792
+ }
793
+ /**
794
+ * Load realtime config for specific collections only
795
+ */
796
+ async loadSpecificCollections(collectionNames) {
797
+ if (collectionNames.length === 0)
798
+ return;
799
+ try {
800
+ // Use sql.join to properly construct the IN clause with parameters
801
+ const result = await db.execute(sql `
802
+ SELECT "collectionName", schema
803
+ FROM "baasix_SchemaDefinition"
804
+ WHERE "collectionName" IN (${sql.join(collectionNames.map(n => sql `${n}`), sql `, `)})
805
+ `);
806
+ // Update configs for the specified collections
807
+ const foundCollections = new Set();
808
+ for (const row of result) {
809
+ foundCollections.add(row.collectionName);
810
+ const schema = typeof row.schema === 'string' ? JSON.parse(row.schema) : row.schema;
811
+ const realtimeConfig = schema?.realtime;
812
+ if (realtimeConfig === true) {
813
+ this.collectionConfigs.set(row.collectionName, {
814
+ enabled: true,
815
+ actions: ['insert', 'update', 'delete']
816
+ });
817
+ }
818
+ else if (typeof realtimeConfig === 'object' && realtimeConfig?.enabled) {
819
+ this.collectionConfigs.set(row.collectionName, {
820
+ enabled: true,
821
+ actions: realtimeConfig.actions || ['insert', 'update', 'delete']
822
+ });
823
+ }
824
+ else {
825
+ // Realtime not enabled or disabled - remove from configs
826
+ this.collectionConfigs.delete(row.collectionName);
827
+ }
828
+ }
829
+ // Handle collections that weren't found (might be deleted)
830
+ for (const name of collectionNames) {
831
+ if (!foundCollections.has(name)) {
832
+ this.collectionConfigs.delete(name);
833
+ }
834
+ }
835
+ console.info(`Loaded realtime config for ${collectionNames.length} specific collections`);
836
+ }
837
+ catch (error) {
838
+ console.warn(`Failed to load specific collections: ${error.message}`);
839
+ }
840
+ }
841
+ /**
842
+ * Sync publication for specific collections only
843
+ */
844
+ async syncSpecificCollections(collectionNames) {
845
+ if (!this.walAvailable || collectionNames.length === 0)
846
+ return;
847
+ try {
848
+ // Get current tables in publication
849
+ const currentTables = await db.execute(sql `
850
+ SELECT tablename
851
+ FROM pg_publication_tables
852
+ WHERE pubname = ${PUBLICATION_NAME}
853
+ `);
854
+ const currentTableNames = new Set(currentTables.map(t => t.tablename));
855
+ for (const collectionName of collectionNames) {
856
+ const config = this.collectionConfigs.get(collectionName);
857
+ const isInPublication = currentTableNames.has(collectionName);
858
+ const shouldBeInPublication = config?.enabled || false;
859
+ if (shouldBeInPublication && !isInPublication) {
860
+ // Add to publication
861
+ try {
862
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} ADD TABLE "${collectionName}"`));
863
+ console.info(`Added ${collectionName} to publication`);
864
+ }
865
+ catch (error) {
866
+ if (!error.message.includes('already member')) {
867
+ console.warn(`Failed to add ${collectionName} to publication: ${error.message}`);
868
+ }
869
+ }
870
+ }
871
+ else if (!shouldBeInPublication && isInPublication) {
872
+ // Remove from publication
873
+ try {
874
+ await db.execute(sql.raw(`ALTER PUBLICATION ${PUBLICATION_NAME} DROP TABLE "${collectionName}"`));
875
+ console.info(`Removed ${collectionName} from publication`);
876
+ }
877
+ catch (error) {
878
+ if (!error.message.includes('not member') && !error.message.includes('does not exist')) {
879
+ console.warn(`Failed to remove ${collectionName} from publication: ${error.message}`);
880
+ }
881
+ }
882
+ }
883
+ // If both match (both in or both out), no action needed
884
+ }
885
+ }
886
+ catch (error) {
887
+ console.warn(`Failed to sync specific collections: ${error.message}`);
888
+ }
889
+ }
890
+ /**
891
+ * Get service status
892
+ */
893
+ getStatus() {
894
+ return {
895
+ initialized: this.initialized,
896
+ connected: this.connected,
897
+ walAvailable: this.walAvailable,
898
+ isWalLeader: this.isWalLeader,
899
+ enabledCollections: this.getEnabledCollections(),
900
+ publicationName: PUBLICATION_NAME,
901
+ slotName: SLOT_NAME
902
+ };
903
+ }
904
+ /**
905
+ * Cleanup resources on shutdown
906
+ */
907
+ async shutdown() {
908
+ console.info("Shutting down Realtime Service...");
909
+ this.shuttingDown = true;
910
+ if (this.reconnectTimeout) {
911
+ clearTimeout(this.reconnectTimeout);
912
+ this.reconnectTimeout = null;
913
+ }
914
+ // Stop leader renewal interval
915
+ this.stopLeaderRenewal();
916
+ // Release WAL leader lock if we held it
917
+ if (this.isWalLeader) {
918
+ await this.releaseWalLeaderLock();
919
+ this.isWalLeader = false;
920
+ }
921
+ if (this.replicationService) {
922
+ try {
923
+ await this.replicationService.stop();
924
+ }
925
+ catch (error) {
926
+ console.warn("Error stopping replication service:", error);
927
+ }
928
+ this.replicationService = null;
929
+ }
930
+ // Cleanup Redis client
931
+ if (this.redisClient) {
932
+ try {
933
+ await this.redisClient.quit();
934
+ }
935
+ catch (error) {
936
+ // Ignore
937
+ }
938
+ this.redisClient = null;
939
+ }
940
+ this.connected = false;
941
+ console.info("Realtime Service shut down");
942
+ }
943
+ /**
944
+ * Drop replication slot (use with caution - only for cleanup)
945
+ */
946
+ async dropReplicationSlot() {
947
+ try {
948
+ await db.execute(sql.raw(`
949
+ SELECT pg_drop_replication_slot('${SLOT_NAME}')
950
+ `));
951
+ console.info(`Dropped replication slot: ${SLOT_NAME}`);
952
+ }
953
+ catch (error) {
954
+ if (!error.message.includes('does not exist')) {
955
+ throw error;
956
+ }
957
+ }
958
+ }
959
+ /**
960
+ * Drop publication (use with caution - only for cleanup)
961
+ */
962
+ async dropPublication() {
963
+ try {
964
+ await db.execute(sql.raw(`DROP PUBLICATION IF EXISTS ${PUBLICATION_NAME}`));
965
+ console.info(`Dropped publication: ${PUBLICATION_NAME}`);
966
+ }
967
+ catch (error) {
968
+ console.warn(`Failed to drop publication: ${error.message}`);
969
+ }
970
+ }
971
+ }
972
+ // Create singleton instance only if it doesn't exist
973
+ if (!globalThis.__baasix_realtimeService) {
974
+ globalThis.__baasix_realtimeService = new RealtimeService();
975
+ }
976
+ const realtimeService = globalThis.__baasix_realtimeService;
977
+ export default realtimeService;
978
+ //# sourceMappingURL=RealtimeService.js.map