@plitzi/sdk-server 0.37.2 → 0.37.4

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 (359) hide show
  1. package/CHANGELOG.md +1170 -0
  2. package/README.md +100 -7
  3. package/dist/actions.d.ts +6 -2
  4. package/dist/actions.js +5 -2
  5. package/dist/adapters/cloudAdapters.js +4 -3
  6. package/dist/adapters/jsonAdapters.js +28 -6
  7. package/dist/adapters/jsonAdapters.test.d.ts +1 -0
  8. package/dist/auth.d.ts +7 -4
  9. package/dist/auth.js +8 -6
  10. package/dist/core/auth/api.d.ts +29 -1
  11. package/dist/core/auth/api.js +47 -13
  12. package/dist/core/auth/createAuth.d.ts +4 -0
  13. package/dist/core/auth/createAuth.js +3 -3
  14. package/dist/core/auth/credentials.d.ts +3 -0
  15. package/dist/core/auth/credentials.js +1 -1
  16. package/dist/core/auth/csrf.d.ts +25 -1
  17. package/dist/core/auth/csrf.js +29 -4
  18. package/dist/core/auth/renewal.d.ts +26 -0
  19. package/dist/core/auth/renewal.js +24 -0
  20. package/dist/core/auth/routes.js +8 -6
  21. package/dist/core/auth/session.d.ts +45 -0
  22. package/dist/core/auth/session.js +47 -1
  23. package/dist/core/auth/sessionActivity.d.ts +11 -0
  24. package/dist/core/auth/sessionActivity.js +14 -0
  25. package/dist/core/auth/throttle.d.ts +15 -0
  26. package/dist/core/auth/throttle.js +29 -1
  27. package/dist/core/auth/totp.d.ts +9 -1
  28. package/dist/core/auth/totp.js +11 -6
  29. package/dist/core/createServer.actions.test.d.ts +1 -0
  30. package/dist/core/createServer.d.ts +14 -1
  31. package/dist/core/createServer.functions.test.d.ts +1 -0
  32. package/dist/core/createServer.js +22 -12
  33. package/dist/core/fontStore.js +1 -1
  34. package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
  35. package/dist/core/handlers/authRouteHandlers.js +57 -21
  36. package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
  37. package/dist/core/handlers/csrfMiddleware.js +31 -7
  38. package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
  39. package/dist/core/handlers/oauthRouteHandlers.js +5 -3
  40. package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
  41. package/dist/core/handlers/socialRouteHandlers.js +2 -1
  42. package/dist/core/http/dispatcher.d.ts +9 -1
  43. package/dist/core/http/dispatcher.js +38 -11
  44. package/dist/core/http/navigation.d.ts +5 -2
  45. package/dist/core/http/navigation.js +7 -4
  46. package/dist/core/http/socketResponse.d.ts +18 -0
  47. package/dist/core/http/socketResponse.js +59 -0
  48. package/dist/core/http/sse.d.ts +26 -0
  49. package/dist/core/http/sse.js +78 -0
  50. package/dist/core/http/stages/authRoutes.js +3 -1
  51. package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
  52. package/dist/core/http/stages/middlewares.d.ts +1 -1
  53. package/dist/core/http/stages/middlewares.js +16 -10
  54. package/dist/core/http/stages/middlewares.test.d.ts +1 -0
  55. package/dist/core/http/stages/oauth.js +2 -2
  56. package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
  57. package/dist/core/http/stages/sessionRenewal.js +81 -0
  58. package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
  59. package/dist/core/http/stages/signInRoute.d.ts +2 -0
  60. package/dist/core/http/stages/signInRoute.js +139 -0
  61. package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
  62. package/dist/core/http/types.d.ts +9 -0
  63. package/dist/core/http/webExchange.d.ts +18 -0
  64. package/dist/core/http/webExchange.js +53 -0
  65. package/dist/core/oauth/authorize.js +37 -13
  66. package/dist/core/oauth/consentPage.js +11 -0
  67. package/dist/core/oauth/issueContext.d.ts +14 -0
  68. package/dist/core/oauth/issueContext.js +32 -0
  69. package/dist/core/oauth/records.d.ts +7 -0
  70. package/dist/core/oauth/register.d.ts +2 -0
  71. package/dist/core/oauth/register.js +32 -11
  72. package/dist/core/oauth/register.test.d.ts +1 -0
  73. package/dist/core/oauth/token.js +21 -6
  74. package/dist/core/oauth/token.test.d.ts +1 -0
  75. package/dist/core/requestParser.d.ts +4 -0
  76. package/dist/core/requestParser.js +14 -7
  77. package/dist/core/sdkAssets.js +1 -1
  78. package/dist/core/server/baseServer.d.ts +10 -0
  79. package/dist/core/server/baseServer.js +42 -11
  80. package/dist/core/server/closeOnSignals.js +4 -3
  81. package/dist/core/server/drain.d.ts +18 -0
  82. package/dist/core/server/drain.js +62 -0
  83. package/dist/core/server/drain.test.d.ts +1 -0
  84. package/dist/core/server/fleet/channel.d.ts +103 -0
  85. package/dist/core/server/fleet/channel.js +191 -0
  86. package/dist/core/server/fleet/channel.test.d.ts +1 -0
  87. package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
  88. package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
  89. package/dist/core/server/fleet/link.d.ts +23 -0
  90. package/dist/core/server/fleet/link.js +70 -0
  91. package/dist/core/server/fleet/relay.d.ts +11 -0
  92. package/dist/core/server/fleet/relay.js +104 -0
  93. package/dist/core/server/fleet/relay.test.d.ts +1 -0
  94. package/dist/core/server/fleet/role.d.ts +46 -0
  95. package/dist/core/server/fleet/role.js +59 -0
  96. package/dist/core/server/fleet/role.test.d.ts +1 -0
  97. package/dist/core/server/fleet/stores.d.ts +7 -0
  98. package/dist/core/server/fleet/stores.js +19 -0
  99. package/dist/core/server/fleet/supervisor.d.ts +26 -0
  100. package/dist/core/server/fleet/supervisor.js +108 -0
  101. package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
  102. package/dist/core/server/pageServer.js +34 -5
  103. package/dist/core/services/action.js +10 -2
  104. package/dist/core/services/functionRoutes.d.ts +9 -0
  105. package/dist/core/services/functionRoutes.js +47 -0
  106. package/dist/core/services/realtime.d.ts +8 -0
  107. package/dist/core/services/realtime.js +57 -0
  108. package/dist/core/services/registry.js +6 -2
  109. package/dist/core/staticFiles.js +12 -4
  110. package/dist/core/staticFiles.test.d.ts +1 -0
  111. package/dist/core/transports.d.ts +13 -3
  112. package/dist/core/transports.js +14 -5
  113. package/dist/functions-api.d.ts +281 -0
  114. package/dist/functions.d.ts +19 -0
  115. package/dist/functions.js +5 -0
  116. package/dist/functionsRunner.d.ts +15 -0
  117. package/dist/functionsRunner.js +6 -0
  118. package/dist/handlers.d.ts +2 -2
  119. package/dist/handlers.js +2 -2
  120. package/dist/helpers/atomicFile.d.ts +10 -0
  121. package/dist/helpers/atomicFile.js +33 -0
  122. package/dist/helpers/atomicFile.test.d.ts +1 -0
  123. package/dist/helpers/buildResponseHelpers.js +35 -11
  124. package/dist/helpers/buildServerInfo.js +4 -2
  125. package/dist/helpers/buildServerInfo.test.d.ts +1 -0
  126. package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
  127. package/dist/helpers/cache/CompressedFileCache.js +44 -0
  128. package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
  129. package/dist/helpers/cache/cacheManager.d.ts +1 -1
  130. package/dist/helpers/cache/cacheManager.js +2 -1
  131. package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
  132. package/dist/helpers/cache/index.d.ts +1 -1
  133. package/dist/helpers/cache/keys.d.ts +15 -1
  134. package/dist/helpers/cache/keys.js +47 -5
  135. package/dist/helpers/cache/serverCaches.d.ts +7 -1
  136. package/dist/helpers/compress.d.ts +3 -1
  137. package/dist/helpers/compress.js +11 -3
  138. package/dist/helpers/hydrationPayload.d.ts +11 -0
  139. package/dist/helpers/hydrationPayload.js +51 -0
  140. package/dist/helpers/hydrationPayload.test.d.ts +1 -0
  141. package/dist/helpers/metrics.d.ts +1 -1
  142. package/dist/helpers/metrics.js +3 -2
  143. package/dist/helpers/outboundGuard.d.ts +14 -6
  144. package/dist/helpers/outboundGuard.js +132 -8
  145. package/dist/helpers/paintedCookie.d.ts +8 -0
  146. package/dist/helpers/paintedCookie.js +13 -0
  147. package/dist/helpers/reportReactBuild.d.ts +9 -0
  148. package/dist/helpers/reportReactBuild.js +18 -0
  149. package/dist/helpers/reportReactBuild.test.d.ts +1 -0
  150. package/dist/helpers/serverLog.d.ts +25 -1
  151. package/dist/helpers/serverLog.js +68 -3
  152. package/dist/helpers/serverLog.test.d.ts +1 -0
  153. package/dist/helpers/wsFrame.d.ts +3 -0
  154. package/dist/helpers/wsFrame.js +5 -0
  155. package/dist/index.d.ts +6 -1
  156. package/dist/index.js +5 -2
  157. package/dist/kernel.d.ts +2 -1
  158. package/dist/kernel.js +3 -2
  159. package/dist/modules/actions/connectorWrite.js +2 -1
  160. package/dist/modules/actions/index.d.ts +29 -10
  161. package/dist/modules/actions/index.js +43 -6
  162. package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
  163. package/dist/modules/actions/jobs/failureStreak.js +47 -0
  164. package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
  165. package/dist/modules/actions/jobs/index.d.ts +2 -1
  166. package/dist/modules/actions/jobs/index.js +5 -3
  167. package/dist/modules/actions/jobs/memoryQueue.d.ts +3 -0
  168. package/dist/modules/actions/jobs/memoryQueue.js +17 -1
  169. package/dist/modules/actions/jobs/scheduler.d.ts +2 -0
  170. package/dist/modules/actions/jobs/scheduler.js +12 -7
  171. package/dist/modules/actions/jobs/worker.d.ts +3 -1
  172. package/dist/modules/actions/jobs/worker.js +9 -4
  173. package/dist/modules/actions/moduleFor.js +5 -2
  174. package/dist/modules/actions/runtime/errors.d.ts +12 -0
  175. package/dist/modules/actions/runtime/errors.js +16 -1
  176. package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
  177. package/dist/modules/actions/runtime/kvChange.js +23 -0
  178. package/dist/modules/actions/runtime/kvList.d.ts +51 -0
  179. package/dist/modules/actions/runtime/kvList.js +52 -0
  180. package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
  181. package/dist/modules/actions/runtime/kvStore.js +46 -20
  182. package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
  183. package/dist/modules/actions/runtime/memoryKv.js +19 -1
  184. package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
  185. package/dist/modules/actions/runtime/namespaceKv.js +15 -3
  186. package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
  187. package/dist/modules/actions/runtime/precheck.js +4 -8
  188. package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
  189. package/dist/modules/actions/runtime/rateLimit.js +19 -0
  190. package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
  191. package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
  192. package/dist/modules/actions/runtime/redisKv.js +46 -0
  193. package/dist/modules/actions/runtime/report.js +2 -1
  194. package/dist/modules/actions/runtime/runAction.d.ts +10 -2
  195. package/dist/modules/actions/runtime/runAction.js +52 -116
  196. package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
  197. package/dist/modules/actions/runtime/runLogger.js +8 -2
  198. package/dist/modules/actions/runtime/signing.d.ts +20 -0
  199. package/dist/modules/actions/runtime/signing.js +25 -0
  200. package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
  201. package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
  202. package/dist/modules/actions/runtime/taskContext.js +124 -0
  203. package/dist/modules/actions/tasks/builtins.js +5 -1
  204. package/dist/modules/actions/tasks/flow.js +81 -10
  205. package/dist/modules/actions/tasks/helpers.js +2 -2
  206. package/dist/modules/actions/tasks/http.js +2 -3
  207. package/dist/modules/actions/tasks/kv.js +39 -0
  208. package/dist/modules/actions/tasks/list.d.ts +2 -0
  209. package/dist/modules/actions/tasks/list.js +145 -0
  210. package/dist/modules/actions/tasks/realtime.d.ts +2 -0
  211. package/dist/modules/actions/tasks/realtime.js +104 -0
  212. package/dist/modules/actions/tasks/registry.d.ts +10 -1
  213. package/dist/modules/actions/tasks/registry.js +17 -6
  214. package/dist/modules/actions/transport/callHandler.js +5 -2
  215. package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
  216. package/dist/modules/actions/transport/catalogHandler.js +3 -2
  217. package/dist/modules/actions/transport/stream.d.ts +4 -5
  218. package/dist/modules/actions/transport/stream.js +12 -76
  219. package/dist/modules/actions/transport/webhookHandler.js +4 -3
  220. package/dist/modules/actions/types.d.ts +86 -3
  221. package/dist/modules/connectors/engine.js +3 -5
  222. package/dist/modules/functions/build.d.ts +22 -0
  223. package/dist/modules/functions/build.js +122 -0
  224. package/dist/modules/functions/build.test.d.ts +1 -0
  225. package/dist/modules/functions/capabilities.d.ts +22 -0
  226. package/dist/modules/functions/capabilities.js +228 -0
  227. package/dist/modules/functions/config.d.ts +34 -0
  228. package/dist/modules/functions/config.js +24 -0
  229. package/dist/modules/functions/context.d.ts +17 -0
  230. package/dist/modules/functions/context.js +88 -0
  231. package/dist/modules/functions/contract.d.ts +121 -0
  232. package/dist/modules/functions/contract.js +9 -0
  233. package/dist/modules/functions/driver.d.ts +36 -0
  234. package/dist/modules/functions/driver.js +150 -0
  235. package/dist/modules/functions/load.d.ts +15 -0
  236. package/dist/modules/functions/load.js +45 -0
  237. package/dist/modules/functions/load.test.d.ts +1 -0
  238. package/dist/modules/functions/local.d.ts +29 -0
  239. package/dist/modules/functions/local.js +61 -0
  240. package/dist/modules/functions/local.test.d.ts +1 -0
  241. package/dist/modules/functions/manifest.d.ts +24 -0
  242. package/dist/modules/functions/manifest.js +163 -0
  243. package/dist/modules/functions/manifest.test.d.ts +1 -0
  244. package/dist/modules/functions/native.d.ts +8 -0
  245. package/dist/modules/functions/native.js +21 -0
  246. package/dist/modules/functions/native.test.d.ts +1 -0
  247. package/dist/modules/functions/protocol.d.ts +216 -0
  248. package/dist/modules/functions/protocol.js +18 -0
  249. package/dist/modules/functions/routes.d.ts +36 -0
  250. package/dist/modules/functions/routes.js +181 -0
  251. package/dist/modules/functions/routes.test.d.ts +1 -0
  252. package/dist/modules/functions/runner/messages.d.ts +5 -0
  253. package/dist/modules/functions/runner/messages.js +188 -0
  254. package/dist/modules/functions/runner/remote.d.ts +19 -0
  255. package/dist/modules/functions/runner/remote.js +116 -0
  256. package/dist/modules/functions/runner/remote.test.d.ts +1 -0
  257. package/dist/modules/functions/runner/service.d.ts +32 -0
  258. package/dist/modules/functions/runner/service.js +209 -0
  259. package/dist/modules/functions/sandbox/guest.d.ts +45 -0
  260. package/dist/modules/functions/sandbox/guest.js +660 -0
  261. package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
  262. package/dist/modules/functions/sandbox/isolate.js +347 -0
  263. package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
  264. package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
  265. package/dist/modules/functions/sandbox/prelude.js +49 -0
  266. package/dist/modules/functions/space.d.ts +40 -0
  267. package/dist/modules/functions/space.js +162 -0
  268. package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
  269. package/dist/modules/functions/tryEntry.d.ts +9 -0
  270. package/dist/modules/functions/tryEntry.js +49 -0
  271. package/dist/modules/mongo/kv.js +31 -0
  272. package/dist/modules/mysql/accounts.js +19 -9
  273. package/dist/modules/mysql/index.d.ts +2 -1
  274. package/dist/modules/mysql/jobs/index.d.ts +1 -1
  275. package/dist/modules/mysql/jobs/kv.js +30 -0
  276. package/dist/modules/mysql/jobs/schema.d.ts +9 -0
  277. package/dist/modules/mysql/jobs/schema.js +21 -4
  278. package/dist/modules/mysql/schema.js +25 -0
  279. package/dist/modules/mysql/sessions.d.ts +1 -11
  280. package/dist/modules/mysql/sessions.js +6 -1
  281. package/dist/modules/realtime/declarations.d.ts +11 -0
  282. package/dist/modules/realtime/declarations.js +26 -0
  283. package/dist/modules/realtime/grants.d.ts +23 -0
  284. package/dist/modules/realtime/grants.js +61 -0
  285. package/dist/modules/realtime/handlers.d.ts +74 -0
  286. package/dist/modules/realtime/handlers.js +224 -0
  287. package/dist/modules/realtime/hub.d.ts +62 -0
  288. package/dist/modules/realtime/hub.js +141 -0
  289. package/dist/modules/realtime/index.d.ts +51 -0
  290. package/dist/modules/realtime/index.js +62 -0
  291. package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
  292. package/dist/modules/realtime/memoryPubSub.js +43 -0
  293. package/dist/modules/realtime/realtime.test.d.ts +1 -0
  294. package/dist/modules/realtime/redisPubSub.d.ts +24 -0
  295. package/dist/modules/realtime/redisPubSub.js +36 -0
  296. package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
  297. package/dist/modules/realtime/socket.d.ts +25 -0
  298. package/dist/modules/realtime/socket.js +122 -0
  299. package/dist/modules/realtime/socket.test.d.ts +1 -0
  300. package/dist/modules/rsc/handler.d.ts +16 -0
  301. package/dist/modules/rsc/handler.js +17 -10
  302. package/dist/modules/rsc/handler.test.d.ts +1 -0
  303. package/dist/modules/rsc/resolveRscData.js +2 -1
  304. package/dist/modules/runtime/bundle.d.ts +24 -0
  305. package/dist/modules/runtime/bundle.js +102 -0
  306. package/dist/modules/runtime/contract.d.ts +37 -0
  307. package/dist/modules/runtime/contract.js +21 -0
  308. package/dist/modules/runtime/entry.test.d.ts +1 -0
  309. package/dist/modules/runtime/host.d.ts +38 -0
  310. package/dist/modules/runtime/host.js +68 -0
  311. package/dist/modules/runtime/inside.d.ts +15 -0
  312. package/dist/modules/runtime/inside.js +47 -0
  313. package/dist/modules/runtime/inside.test.d.ts +1 -0
  314. package/dist/modules/runtime/runner.d.ts +11 -0
  315. package/dist/modules/runtime/runner.js +38 -0
  316. package/dist/modules/runtime/runtime.test.d.ts +1 -0
  317. package/dist/modules/runtime/stages.d.ts +50 -0
  318. package/dist/modules/runtime/stages.js +117 -0
  319. package/dist/modules/ssr/Component.d.ts +3 -1
  320. package/dist/modules/ssr/Component.js +2 -1
  321. package/dist/modules/ssr/handler.js +10 -2
  322. package/dist/modules/ssr/loadPluginComponents.js +2 -1
  323. package/dist/modules/ssr/prepareRender.js +39 -10
  324. package/dist/modules/ssr/preview.d.ts +3 -0
  325. package/dist/modules/ssr/preview.js +17 -7
  326. package/dist/modules/ssr/redirect.test.d.ts +1 -0
  327. package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
  328. package/dist/modules/ssr/registerExternalPlugins.js +19 -16
  329. package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
  330. package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
  331. package/dist/modules/ssr/reportMissingPlugins.js +24 -0
  332. package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
  333. package/dist/modules/ssr/streamBody.d.ts +2 -2
  334. package/dist/modules/ssr/streamBody.js +4 -1
  335. package/dist/modules/ssr/template.js +1 -1
  336. package/dist/modules/ssr/views/template.ejs +8 -5
  337. package/dist/mysql.d.ts +1 -1
  338. package/dist/mysql.js +2 -2
  339. package/dist/oauth.js +2 -2
  340. package/dist/plugins/compile.js +9 -4
  341. package/dist/plugins/copy.js +3 -3
  342. package/dist/plugins/manager.d.ts +35 -3
  343. package/dist/plugins/manager.js +109 -38
  344. package/dist/runtime.d.ts +15 -0
  345. package/dist/runtime.js +7 -0
  346. package/package.json +39 -8
  347. package/skills/plitzi-authoring/SKILL.md +98 -14
  348. package/skills/plitzi-authoring/reference/accessibility.md +111 -0
  349. package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
  350. package/skills/plitzi-authoring/reference/data-and-visibility.md +84 -18
  351. package/skills/plitzi-authoring/reference/elements-and-styles.md +61 -7
  352. package/skills/plitzi-authoring/reference/flows.md +108 -17
  353. package/skills/plitzi-authoring/reference/lists.md +74 -0
  354. package/skills/plitzi-authoring/reference/performance.md +67 -0
  355. package/skills/plitzi-authoring/reference/plugins.md +167 -0
  356. package/skills/plitzi-authoring/reference/review-checklist.md +19 -2
  357. package/skills/plitzi-authoring/reference/templates.md +67 -27
  358. package/skills/plitzi-authoring/reference/testing.md +72 -5
  359. package/skills/plitzi-authoring/reference/validation.md +73 -0
@@ -0,0 +1,224 @@
1
+ import onAbort from "../../helpers/onAbort.js";
2
+ import { openEventStream } from "../../core/http/sse.js";
3
+ import { accessRefusal } from "@plitzi/sdk-shared/actions";
4
+ import { randomBytes, randomUUID } from "node:crypto";
5
+ import { PRESENCE_TYPE, channelLimits, matchChannel } from "@plitzi/sdk-shared/realtime";
6
+ //#region src/modules/realtime/handlers.ts
7
+ /** Topics one connection may hold. A page shows a handful; past this it is somebody enumerating. */
8
+ var MAX_TOPICS = 8;
9
+ /** A type a page may send: its own vocabulary, never one of the channel's `$` types but `$presence`. */
10
+ var TYPE = /^[A-Za-z0-9_.:-]{1,64}$/;
11
+ /** How long a disconnected client waits before trying again — ours reconnects itself; this is for anyone else's. */
12
+ var RETRY_MS = 3e3;
13
+ var answer = (res, { status, ...payload }) => {
14
+ res.setStatus(status);
15
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
16
+ res.setHeader("Cache-Control", "no-store");
17
+ res.send(JSON.stringify(payload));
18
+ };
19
+ var listOf = (query, name) => [...new Set((Object.hasOwn(query, name) ? query[name] : "").split(",").map((item) => item.trim()).filter(Boolean))];
20
+ /** The one of the grants the page sent (`?grants=g1,g2`) that opens `topic` — or none. */
21
+ var grantFor = async (grants, space, topic, offered) => {
22
+ for (const grant of offered) if (await grants.opens(space, topic, grant)) return grant;
23
+ };
24
+ /**
25
+ * Which of the topics a page asked for (`?topics=a,b`) it may open: each checked against the channels the space
26
+ * declares and against the visitor's access. One that fails is reported, not a reason to refuse the rest; all failing
27
+ * is a `403`.
28
+ */
29
+ var admit = async (req, resolveChannels, grants) => {
30
+ const { environment = "main", spaceId, revision } = req.ctx.spaceDeployment ?? {};
31
+ if (typeof spaceId !== "number") return {
32
+ ok: false,
33
+ answer: {
34
+ status: 404,
35
+ error: "No space is served here",
36
+ reason: "not_found"
37
+ }
38
+ };
39
+ const requested = listOf(req.query, "topics");
40
+ const offered = listOf(req.query, "grants").slice(0, MAX_TOPICS);
41
+ if (requested.length === 0 || requested.length > MAX_TOPICS) return {
42
+ ok: false,
43
+ answer: {
44
+ status: 422,
45
+ error: `Name between 1 and ${MAX_TOPICS} topics: ?topics=a,b`,
46
+ reason: "invalid_input"
47
+ }
48
+ };
49
+ const channels = await resolveChannels(spaceId, environment, revision);
50
+ const accepted = /* @__PURE__ */ new Map();
51
+ const granted = /* @__PURE__ */ new Map();
52
+ const refused = [];
53
+ const space = {
54
+ spaceId,
55
+ environment
56
+ };
57
+ for (const topic of requested) {
58
+ const match = matchChannel(topic, channels);
59
+ const refusal = match ? accessRefusal(match.declaration.access, req.ctx.user) : "undeclared";
60
+ const grant = match && !refusal && match.declaration.grant === true ? await grantFor(grants, space, topic, offered) : void 0;
61
+ if (!match || refusal) refused.push({
62
+ topic,
63
+ reason: refusal ?? "undeclared"
64
+ });
65
+ else if (match.declaration.grant === true && grant === void 0) refused.push({
66
+ topic,
67
+ reason: "ungranted"
68
+ });
69
+ else {
70
+ accepted.set(topic, match.declaration);
71
+ if (grant !== void 0) granted.set(topic, grant);
72
+ }
73
+ }
74
+ if (accepted.size === 0) return {
75
+ ok: false,
76
+ answer: {
77
+ status: 403,
78
+ error: "None of these topics may be opened here",
79
+ reason: "forbidden",
80
+ refused
81
+ }
82
+ };
83
+ const { user } = req.ctx;
84
+ return {
85
+ ok: true,
86
+ space,
87
+ ...user ? { user: {
88
+ id: user.id,
89
+ name: user.username
90
+ } } : {},
91
+ accepted,
92
+ granted,
93
+ refused
94
+ };
95
+ };
96
+ /** A new connection for what was admitted, speaking through `send`. */
97
+ var connectionFor = (admission, send, end) => ({
98
+ id: randomUUID(),
99
+ token: randomBytes(24).toString("base64url"),
100
+ space: admission.space,
101
+ ...admission.user ? { user: admission.user } : {},
102
+ topics: admission.accepted,
103
+ grants: admission.granted,
104
+ announced: /* @__PURE__ */ new Set(),
105
+ sent: /* @__PURE__ */ new Map(),
106
+ send,
107
+ end
108
+ });
109
+ /** Whether this connection may send one more message on `topic` this second. */
110
+ var withinRate = (connection, topic, perSecond) => {
111
+ const second = Math.floor(Date.now() / 1e3);
112
+ const sent = connection.sent.get(topic);
113
+ const count = sent?.second === second ? sent.count + 1 : 1;
114
+ connection.sent.set(topic, {
115
+ second,
116
+ count
117
+ });
118
+ return count <= perSecond;
119
+ };
120
+ /**
121
+ * A page publishing, whichever way it arrived: only on a topic its connection subscribed to, only on a channel pages
122
+ * may send on, within its size and its rate — and stamped by the server with who sent it and when, whatever the
123
+ * message said.
124
+ */
125
+ var publishFrom = async (hub, connection, { topic, type, data }) => {
126
+ const name = typeof topic === "string" ? topic : "";
127
+ const declaration = connection.topics.get(name);
128
+ if (!declaration) return {
129
+ status: 403,
130
+ error: `This connection did not subscribe to "${name}"`,
131
+ reason: "not_subscribed"
132
+ };
133
+ if (declaration.publish === "server") return {
134
+ status: 403,
135
+ error: `Only the server publishes on "${name}"`,
136
+ reason: "server_only"
137
+ };
138
+ const kind = typeof type === "string" ? type : "";
139
+ const presence = kind === PRESENCE_TYPE && declaration.presence === true;
140
+ if (!presence && !TYPE.test(kind)) return {
141
+ status: 422,
142
+ error: "A message type is 1-64 of A-Z a-z 0-9 _ . : -",
143
+ reason: "invalid_input"
144
+ };
145
+ const limits = channelLimits(declaration);
146
+ const value = data ?? null;
147
+ if (Buffer.byteLength(JSON.stringify(value)) > limits.maxMessageBytes) return {
148
+ status: 413,
149
+ error: `A message on "${name}" is at most ${limits.maxMessageBytes} bytes`,
150
+ reason: "too_large"
151
+ };
152
+ if (!withinRate(connection, name, limits.messagesPerSecond)) return {
153
+ status: 429,
154
+ error: `At most ${limits.messagesPerSecond} messages a second`,
155
+ reason: "rate_limited"
156
+ };
157
+ if (presence) connection.announced.add(name);
158
+ await hub.publish(connection.space, hub.from(connection, name, kind, value));
159
+ return { status: 204 };
160
+ };
161
+ /**
162
+ * `GET /_realtime?topics=a,b` (and `&grants=g1,g2` for the topics of a `grant: true` channel) — one Server-Sent Events connection for every topic a page listens to.
163
+ *
164
+ * The first event, `ready`, tells the page its public name (`from`) and the secret its publishes carry. Resolves when
165
+ * the page goes.
166
+ */
167
+ var handleRealtimeSubscribe = async ({ req, res, raw, signal, hub, resolveChannels, grants }) => {
168
+ const admission = await admit(req, resolveChannels, grants);
169
+ if (!admission.ok) {
170
+ answer(res, admission.answer);
171
+ return;
172
+ }
173
+ let finish = () => void 0;
174
+ const gone = new Promise((resolve) => {
175
+ finish = resolve;
176
+ });
177
+ const stream = openEventStream(raw, {
178
+ onAbort: () => finish(),
179
+ retryMs: RETRY_MS
180
+ });
181
+ const connection = connectionFor(admission, stream.send, () => finish());
182
+ const release = onAbort(signal, () => finish());
183
+ stream.send("ready", {
184
+ connection: connection.id,
185
+ token: connection.token,
186
+ topics: [...admission.accepted.keys()],
187
+ refused: admission.refused
188
+ });
189
+ await hub.connect(connection);
190
+ await gone;
191
+ release();
192
+ stream.close();
193
+ await hub.disconnect(connection);
194
+ };
195
+ var parseBody = (body) => {
196
+ try {
197
+ const value = body ? JSON.parse(body) : void 0;
198
+ return typeof value === "object" && value !== null ? value : void 0;
199
+ } catch {
200
+ return;
201
+ }
202
+ };
203
+ /** `POST /_realtime` `{ token, topic, type, data }` — a page on a Server-Sent Events connection, publishing. */
204
+ var handleRealtimePublish = async ({ req, res, hub }) => {
205
+ const body = parseBody(req.body);
206
+ const connection = typeof body?.token === "string" ? hub.find(body.token) : void 0;
207
+ if (!body || !connection) {
208
+ answer(res, {
209
+ status: 401,
210
+ error: "Not connected: open /_realtime first and publish with its token",
211
+ reason: "not_connected"
212
+ });
213
+ return;
214
+ }
215
+ const result = await publishFrom(hub, connection, body);
216
+ if (result.status === 204) {
217
+ res.setStatus(204);
218
+ res.send("");
219
+ return;
220
+ }
221
+ answer(res, result);
222
+ };
223
+ //#endregion
224
+ export { admit, answer, connectionFor, handleRealtimePublish, handleRealtimeSubscribe, publishFrom };
@@ -0,0 +1,62 @@
1
+ import { ChannelDeclaration, PubSubAdapter, RealtimeMessage, RealtimeSender } from '@plitzi/sdk-shared';
2
+ /** The space a connection belongs to: its topics are that space's and no other's. */
3
+ export type RealtimeSpace = {
4
+ spaceId: number;
5
+ environment: string;
6
+ };
7
+ /** One page, connected. */
8
+ export type RealtimeConnection = {
9
+ /** Its public name — the `from` of everything it sends. */
10
+ id: string;
11
+ /** Its secret: what a publish proves it comes from this connection with. Never sent to anyone else. */
12
+ token: string;
13
+ space: RealtimeSpace;
14
+ user?: RealtimeSender['user'];
15
+ /** The topics it subscribed to, with the channel each falls under. */
16
+ topics: Map<string, ChannelDeclaration>;
17
+ /** The grant each topic of a `grant: true` channel was opened with — what a revoke names. */
18
+ grants: Map<string, string>;
19
+ /** The topics it announced itself on, which hear `$leave` when it goes. */
20
+ announced: Set<string>;
21
+ /** Messages sent this second, by topic — the rate a channel allows is per connection, and a connection is here. */
22
+ sent: Map<string, {
23
+ second: number;
24
+ count: number;
25
+ }>;
26
+ send: (event: string, data: unknown) => void;
27
+ /** Closes it from the server's side — the server is going away. Its own `close` then lets it go as any other. */
28
+ end: () => void;
29
+ };
30
+ /** A space's topic, as the adapter knows it: namespaced, so two spaces never hear each other. */
31
+ export declare const adapterTopic: ({ spaceId, environment }: RealtimeSpace, topic: string) => string;
32
+ export type RealtimeHub = ReturnType<typeof createRealtimeHub>;
33
+ /**
34
+ * This process's connections, and the ONE adapter subscription per topic they share.
35
+ *
36
+ * A hundred pages on one board are a hundred connections here and a single subscription to the adapter: a message
37
+ * arrives once and is written to each of them. The last one to leave a topic unsubscribes it.
38
+ */
39
+ export declare const createRealtimeHub: (pubsub: PubSubAdapter) => {
40
+ publish: (space: RealtimeSpace, message: RealtimeMessage) => Promise<void>;
41
+ from: (connection: RealtimeConnection, topic: string, type: string, data: unknown) => RealtimeMessage;
42
+ find: (token: string) => RealtimeConnection | undefined;
43
+ /**
44
+ * Lets go of `topic` every connection, on every replica, that opened it with `grant` — or with any grant, naming
45
+ * none. What stops a grant that is already in use; `grants.revoke` is what stops it being used again.
46
+ */
47
+ revoke: (space: RealtimeSpace, topic: string, grant?: string) => Promise<void>;
48
+ /** Subscribes a new connection to its topics, and tells the members of each presence channel it arrived. */
49
+ connect: (connection: RealtimeConnection) => Promise<void>;
50
+ /** Unsubscribes a connection that went away, and tells the channels it announced itself on. */
51
+ disconnect: (connection: RealtimeConnection) => Promise<void>;
52
+ /**
53
+ * Every connection closed from here: the server is shutting down. An open socket or event stream is a request that
54
+ * never ends, so a server waiting for them to finish waited for ever — each is told to go, and goes the way it
55
+ * goes when its page leaves.
56
+ */
57
+ closeAll: () => void;
58
+ /** How many connections this process holds — for tests and for a health endpoint. */
59
+ readonly connectionCount: number;
60
+ /** How many topics this process holds a subscription for — for tests and for a health endpoint. */
61
+ readonly topicCount: number;
62
+ };
@@ -0,0 +1,141 @@
1
+ import { JOIN_TYPE, LEAVE_TYPE, REVOKED_TYPE } from "@plitzi/sdk-shared/realtime";
2
+ //#region src/modules/realtime/hub.ts
3
+ /** A space's topic, as the adapter knows it: namespaced, so two spaces never hear each other. */
4
+ var adapterTopic = ({ spaceId, environment }, topic) => `${spaceId}/${environment}/${topic}`;
5
+ /**
6
+ * Said between the servers, never to a page: every connection opened with the grant it names — or, naming none, with
7
+ * any grant for the topic — is let go of it. On the topic's own adapter channel, so it reaches every replica holding
8
+ * the topic, and only them. A page cannot send it: the types a page or a flow may use have no `$` but `$presence`.
9
+ */
10
+ var REVOKE_TYPE = "$revoke";
11
+ var revokedGrant = (data) => typeof data === "object" && data !== null && "grant" in data && typeof data.grant === "string" ? data.grant : void 0;
12
+ /** A message as the adapter carried it — written by this module, but read from a transport that is not. */
13
+ var isMessage = (value) => typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "type" in value && typeof value.type === "string" && "from" in value && typeof value.from === "string" && "at" in value && typeof value.at === "number";
14
+ var parse = (raw) => {
15
+ try {
16
+ const value = JSON.parse(raw);
17
+ return isMessage(value) ? value : void 0;
18
+ } catch {
19
+ return;
20
+ }
21
+ };
22
+ /**
23
+ * This process's connections, and the ONE adapter subscription per topic they share.
24
+ *
25
+ * A hundred pages on one board are a hundred connections here and a single subscription to the adapter: a message
26
+ * arrives once and is written to each of them. The last one to leave a topic unsubscribes it.
27
+ */
28
+ var createRealtimeHub = (pubsub) => {
29
+ const byToken = /* @__PURE__ */ new Map();
30
+ const local = /* @__PURE__ */ new Map();
31
+ const publish = (space, message) => pubsub.publish(adapterTopic(space, message.topic), JSON.stringify(message));
32
+ const join = async (connection, topic) => {
33
+ const key = adapterTopic(connection.space, topic);
34
+ let entry = local.get(key);
35
+ if (!entry) {
36
+ const connections = /* @__PURE__ */ new Set();
37
+ entry = {
38
+ connections,
39
+ release: pubsub.subscribe(key, (raw) => {
40
+ const message = parse(raw);
41
+ if (message?.type === REVOKE_TYPE) {
42
+ const grant = revokedGrant(message.data);
43
+ [...connections].filter((member) => {
44
+ const opened = member.grants.get(message.topic);
45
+ return opened !== void 0 && (grant === void 0 || opened === grant);
46
+ }).forEach((member) => void releaseTopic(member, message.topic));
47
+ } else if (message) connections.forEach((member) => member.send("message", message));
48
+ })
49
+ };
50
+ local.set(key, entry);
51
+ }
52
+ entry.connections.add(connection);
53
+ await entry.release;
54
+ };
55
+ const leave = async (connection, topic) => {
56
+ const key = adapterTopic(connection.space, topic);
57
+ const entry = local.get(key);
58
+ if (!entry) return;
59
+ entry.connections.delete(connection);
60
+ if (entry.connections.size === 0) {
61
+ local.delete(key);
62
+ await (await entry.release)();
63
+ }
64
+ };
65
+ /** A stamped message from this connection: `from` and `user` are the server's, whatever was sent. */
66
+ const from = (connection, topic, type, data) => ({
67
+ topic,
68
+ type,
69
+ data,
70
+ from: connection.id,
71
+ ...connection.user ? { user: connection.user } : {},
72
+ at: Date.now()
73
+ });
74
+ /**
75
+ * Lets `connection` go of `topic`: it is told, the others on a presence channel hear it leave, and it no longer
76
+ * hears the topic or may send on it.
77
+ */
78
+ const releaseTopic = async (connection, topic) => {
79
+ connection.send("message", {
80
+ topic,
81
+ type: REVOKED_TYPE,
82
+ data: null,
83
+ from: "server",
84
+ at: Date.now()
85
+ });
86
+ connection.topics.delete(topic);
87
+ connection.grants.delete(topic);
88
+ await leave(connection, topic);
89
+ if (connection.announced.delete(topic)) await publish(connection.space, from(connection, topic, LEAVE_TYPE, null));
90
+ };
91
+ return {
92
+ publish,
93
+ from,
94
+ find: (token) => byToken.get(token),
95
+ /**
96
+ * Lets go of `topic` every connection, on every replica, that opened it with `grant` — or with any grant, naming
97
+ * none. What stops a grant that is already in use; `grants.revoke` is what stops it being used again.
98
+ */
99
+ revoke: (space, topic, grant) => publish(space, {
100
+ topic,
101
+ type: REVOKE_TYPE,
102
+ data: grant === void 0 ? null : { grant },
103
+ from: "server",
104
+ at: Date.now()
105
+ }),
106
+ /** Subscribes a new connection to its topics, and tells the members of each presence channel it arrived. */
107
+ connect: async (connection) => {
108
+ byToken.set(connection.token, connection);
109
+ for (const [topic, declaration] of connection.topics) {
110
+ await join(connection, topic);
111
+ if (declaration.presence) await publish(connection.space, from(connection, topic, JOIN_TYPE, null));
112
+ }
113
+ },
114
+ /** Unsubscribes a connection that went away, and tells the channels it announced itself on. */
115
+ disconnect: async (connection) => {
116
+ byToken.delete(connection.token);
117
+ for (const topic of connection.topics.keys()) {
118
+ if (connection.announced.has(topic)) await publish(connection.space, from(connection, topic, LEAVE_TYPE, null));
119
+ await leave(connection, topic);
120
+ }
121
+ },
122
+ /**
123
+ * Every connection closed from here: the server is shutting down. An open socket or event stream is a request that
124
+ * never ends, so a server waiting for them to finish waited for ever — each is told to go, and goes the way it
125
+ * goes when its page leaves.
126
+ */
127
+ closeAll: () => {
128
+ [...byToken.values()].forEach((connection) => connection.end());
129
+ },
130
+ /** How many connections this process holds — for tests and for a health endpoint. */
131
+ get connectionCount() {
132
+ return byToken.size;
133
+ },
134
+ /** How many topics this process holds a subscription for — for tests and for a health endpoint. */
135
+ get topicCount() {
136
+ return local.size;
137
+ }
138
+ };
139
+ };
140
+ //#endregion
141
+ export { adapterTopic, createRealtimeHub };
@@ -0,0 +1,51 @@
1
+ import { ChannelResolver } from './declarations';
2
+ import { RealtimeGrants } from './grants';
3
+ import { RealtimeHub } from './hub';
4
+ import { RealtimeTransport, SSRServerConfig } from '@plitzi/sdk-shared';
5
+ export { createMemoryPubSub } from './memoryPubSub';
6
+ export { handleRealtimePublish, handleRealtimeSubscribe } from './handlers';
7
+ export { handleRealtimeSocket } from './socket';
8
+ export type RealtimeModule = {
9
+ hub: RealtimeHub;
10
+ path: string;
11
+ /** What pages are told to connect with. Both are served whatever this says. */
12
+ transport: RealtimeTransport;
13
+ /** Origins besides the server's own whose pages may open a socket. */
14
+ allowedOrigins: readonly string[];
15
+ resolveChannels: ChannelResolver;
16
+ /** The grants issued for topics of channels declared `grant: true`, and the check a subscription makes. */
17
+ grants: RealtimeGrants;
18
+ /** Closes every connection this process holds: the server is shutting down (`HttpServerParts.onClosing`). */
19
+ close: () => void;
20
+ /**
21
+ * Publishes from the server — what a flow's `realtime.publish` does. Checked against the space's channels like a
22
+ * page's publish, and sent `from: 'server'`: the one sender a page cannot be.
23
+ */
24
+ publish: (space: {
25
+ spaceId: number;
26
+ environment: string;
27
+ }, topic: string, type: string, data: unknown) => Promise<void>;
28
+ /**
29
+ * A grant for one topic of a channel declared `grant: true` — what a flow's `realtime.grant` hands a page, once it
30
+ * has decided the visitor may be there. Refused for a topic of any other channel: a grant it would never ask for
31
+ * is a mistake in the flow, not a key to hand out.
32
+ */
33
+ grant: (space: {
34
+ spaceId: number;
35
+ environment: string;
36
+ }, topic: string, ttlSeconds?: number) => Promise<string>;
37
+ /**
38
+ * `grant` no longer opens `topic` — or, naming none, no grant issued for it until now does: nobody who holds one gets
39
+ * in again, and whoever is on the topic with one, on any replica, is let go of it (`$revoked`). A page kept out asks
40
+ * the action that lets it in for a new grant, if it still may.
41
+ */
42
+ revoke: (space: {
43
+ spaceId: number;
44
+ environment: string;
45
+ }, topic: string, grant?: string) => Promise<void>;
46
+ };
47
+ /**
48
+ * The server's realtime channels, built once per config — or `undefined` when they are off or cannot be: without the
49
+ * space's documents (`getOfflineData`) there is nothing to say which channels a space declares, so none is opened.
50
+ */
51
+ export declare const realtimeModuleFor: (config: SSRServerConfig) => RealtimeModule | undefined;
@@ -0,0 +1,62 @@
1
+ import { fleetStore } from "../../core/server/fleet/link.js";
2
+ import { KV_METHODS, createMemoryKv } from "../actions/runtime/memoryKv.js";
3
+ import { createRealtimeGrants } from "./grants.js";
4
+ import { createChannelResolver } from "./declarations.js";
5
+ import { createRealtimeHub } from "./hub.js";
6
+ import { createMemoryPubSub } from "./memoryPubSub.js";
7
+ import { handleRealtimePublish, handleRealtimeSubscribe } from "./handlers.js";
8
+ import { handleRealtimeSocket } from "./socket.js";
9
+ import { matchChannel } from "@plitzi/sdk-shared/realtime";
10
+ //#region src/modules/realtime/index.ts
11
+ /** A type a flow publishes: its own vocabulary — the `$` types are the channel's. */
12
+ var SERVER_TYPE = /^[A-Za-z0-9_.:-]{1,64}$/;
13
+ var modules = /* @__PURE__ */ new WeakMap();
14
+ /**
15
+ * The server's realtime channels, built once per config — or `undefined` when they are off or cannot be: without the
16
+ * space's documents (`getOfflineData`) there is nothing to say which channels a space declares, so none is opened.
17
+ */
18
+ var realtimeModuleFor = (config) => {
19
+ const getOfflineData = config.adapters.getOfflineData;
20
+ if (config.realtime === false || !getOfflineData) return;
21
+ const existing = modules.get(config);
22
+ if (existing) return existing;
23
+ const hub = createRealtimeHub(config.realtime?.pubsub ?? createMemoryPubSub());
24
+ const resolveChannels = createChannelResolver(getOfflineData);
25
+ const grants = createRealtimeGrants(config.action?.kv ?? fleetStore("realtime.grants", KV_METHODS) ?? createMemoryKv());
26
+ const module = {
27
+ hub,
28
+ path: config.realtime?.path ?? "/_realtime",
29
+ transport: config.realtime?.transport ?? "sse",
30
+ allowedOrigins: config.realtime?.allowedOrigins ?? [],
31
+ resolveChannels,
32
+ grants,
33
+ close: () => hub.closeAll(),
34
+ publish: async (space, topic, type, data) => {
35
+ const channels = await resolveChannels(space.spaceId, space.environment);
36
+ if (!matchChannel(topic, channels)) throw new Error(`No channel of this space matches "${topic}": declare it in the space's \`channels\``);
37
+ if (!SERVER_TYPE.test(type)) throw new Error("A message type is 1-64 of A-Z a-z 0-9 _ . : -");
38
+ await hub.publish(space, {
39
+ topic,
40
+ type,
41
+ data,
42
+ from: "server",
43
+ at: Date.now()
44
+ });
45
+ },
46
+ grant: async (space, topic, ttlSeconds) => {
47
+ const match = matchChannel(topic, await resolveChannels(space.spaceId, space.environment));
48
+ if (!match) throw new Error(`No channel of this space matches "${topic}": declare it in the space's \`channels\``);
49
+ if (match.declaration.grant !== true) throw new Error(`"${match.pattern}" is open to anyone its access lets in, so it takes no grant: declare it \`grant: true\` to make its topics private`);
50
+ return grants.issue(space, topic, ttlSeconds);
51
+ },
52
+ revoke: async (space, topic, grant) => {
53
+ if (matchChannel(topic, await resolveChannels(space.spaceId, space.environment))?.declaration.grant !== true) throw new Error(`"${topic}" is not a topic of a private (\`grant: true\`) channel of this space: it has no grants to revoke`);
54
+ await grants.revoke(space, topic, grant);
55
+ await hub.revoke(space, topic, grant);
56
+ }
57
+ };
58
+ modules.set(config, module);
59
+ return module;
60
+ };
61
+ //#endregion
62
+ export { handleRealtimePublish, handleRealtimeSocket, handleRealtimeSubscribe, realtimeModuleFor };
@@ -0,0 +1,10 @@
1
+ import { PubSubAdapter } from '@plitzi/sdk-shared';
2
+ /**
3
+ * The default pub/sub: in this process, and across its workers.
4
+ *
5
+ * A page connected to one worker must hear what another worker published, so a publish is delivered here AND sent to
6
+ * the rest of the fleet as a broadcast, which each process delivers to its own listeners. Without workers the
7
+ * broadcast goes nowhere and this is a Map of listeners. A second REPLICA shares nothing with this one: a deployment
8
+ * that runs several hands the server an adapter over something shared (Redis, NATS…).
9
+ */
10
+ export declare const createMemoryPubSub: () => PubSubAdapter;
@@ -0,0 +1,43 @@
1
+ import { broadcast, channelName, onBroadcast } from "../../core/server/fleet/link.js";
2
+ //#region src/modules/realtime/memoryPubSub.ts
3
+ var isRelayed = (value) => typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "message" in value && typeof value.message === "string";
4
+ /**
5
+ * The default pub/sub: in this process, and across its workers.
6
+ *
7
+ * A page connected to one worker must hear what another worker published, so a publish is delivered here AND sent to
8
+ * the rest of the fleet as a broadcast, which each process delivers to its own listeners. Without workers the
9
+ * broadcast goes nowhere and this is a Map of listeners. A second REPLICA shares nothing with this one: a deployment
10
+ * that runs several hands the server an adapter over something shared (Redis, NATS…).
11
+ */
12
+ var createMemoryPubSub = () => {
13
+ const topics = /* @__PURE__ */ new Map();
14
+ const fleet = channelName("realtime.pubsub");
15
+ const deliver = (topic, message) => {
16
+ for (const listener of topics.get(topic) ?? []) listener(message);
17
+ };
18
+ onBroadcast(fleet, (payload) => {
19
+ if (isRelayed(payload)) deliver(payload.topic, payload.message);
20
+ });
21
+ return {
22
+ publish: (topic, message) => {
23
+ deliver(topic, message);
24
+ broadcast(fleet, {
25
+ topic,
26
+ message
27
+ });
28
+ return Promise.resolve();
29
+ },
30
+ subscribe: (topic, listener) => {
31
+ const listeners = topics.get(topic) ?? /* @__PURE__ */ new Set();
32
+ listeners.add(listener);
33
+ topics.set(topic, listeners);
34
+ return Promise.resolve(() => {
35
+ listeners.delete(listener);
36
+ if (listeners.size === 0) topics.delete(topic);
37
+ return Promise.resolve();
38
+ });
39
+ }
40
+ };
41
+ };
42
+ //#endregion
43
+ export { createMemoryPubSub };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { PubSubAdapter } from '@plitzi/sdk-shared';
2
+ /**
3
+ * The two Redis connections pub/sub needs, by shape — `ioredis` fits as it is, and so does anything answering the same
4
+ * calls. A connection in subscriber mode can do nothing else, which is why it is a second one.
5
+ */
6
+ export type RedisPubSubClients = {
7
+ publisher: {
8
+ publish: (channel: string, message: string) => Promise<unknown>;
9
+ };
10
+ subscriber: {
11
+ subscribe: (channel: string) => Promise<unknown>;
12
+ unsubscribe: (channel: string) => Promise<unknown>;
13
+ on: (event: 'message', listener: (channel: string, message: string) => void) => unknown;
14
+ };
15
+ /** Prefixed onto every channel, so the realtime topics cannot collide with anything else on the server. */
16
+ prefix?: string;
17
+ };
18
+ /**
19
+ * Pub/sub over Redis: what a deployment of several replicas hands `createServer({ realtime: { pubsub } })`.
20
+ *
21
+ * One Redis subscription per topic per process, however many pages of that process listen to it: the listeners are
22
+ * kept here, and the channel is subscribed on the first and unsubscribed with the last.
23
+ */
24
+ export declare const createRedisPubSub: ({ publisher, subscriber, prefix }: RedisPubSubClients) => PubSubAdapter;
@@ -0,0 +1,36 @@
1
+ //#region src/modules/realtime/redisPubSub.ts
2
+ /**
3
+ * Pub/sub over Redis: what a deployment of several replicas hands `createServer({ realtime: { pubsub } })`.
4
+ *
5
+ * One Redis subscription per topic per process, however many pages of that process listen to it: the listeners are
6
+ * kept here, and the channel is subscribed on the first and unsubscribed with the last.
7
+ */
8
+ var createRedisPubSub = ({ publisher, subscriber, prefix = "plitzi:rt:" }) => {
9
+ const listeners = /* @__PURE__ */ new Map();
10
+ subscriber.on("message", (channel, message) => {
11
+ if (!channel.startsWith(prefix)) return;
12
+ listeners.get(channel.slice(prefix.length))?.forEach((listener) => listener(message));
13
+ });
14
+ return {
15
+ publish: async (topic, message) => {
16
+ await publisher.publish(`${prefix}${topic}`, message);
17
+ },
18
+ subscribe: async (topic, listener) => {
19
+ const set = listeners.get(topic) ?? /* @__PURE__ */ new Set();
20
+ if (!set.size) {
21
+ listeners.set(topic, set);
22
+ await subscriber.subscribe(`${prefix}${topic}`);
23
+ }
24
+ set.add(listener);
25
+ return async () => {
26
+ set.delete(listener);
27
+ if (!set.size) {
28
+ listeners.delete(topic);
29
+ await subscriber.unsubscribe(`${prefix}${topic}`);
30
+ }
31
+ };
32
+ }
33
+ };
34
+ };
35
+ //#endregion
36
+ export { createRedisPubSub };
@@ -0,0 +1 @@
1
+ export {};