@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
@@ -91,6 +91,37 @@ var createMongoKv = ({ db, collection = "action_kv" }) => {
91
91
  _id: key,
92
92
  $expr: LIVE
93
93
  }, [{ $set: { expiresAt: expiryIn(ttlSeconds) } }]);
94
+ },
95
+ /**
96
+ * Each branch is one update Mongo applies to the document atomically. Expecting a value, the filter names it.
97
+ * Expecting nothing is two cases: a lapsed document, taken over by a filter that matches only a lapsed one; and no
98
+ * document, inserted by an upsert whose filter no stored document satisfies — so a live one makes it collide on
99
+ * `_id`, which is the refusal, not an error. (An upsert may not filter with `$expr`, or this would be one step.)
100
+ */
101
+ swap: async (key, expected, next, ttlSeconds) => {
102
+ const keysNow = await store();
103
+ const write = [{ $set: {
104
+ value: next,
105
+ expiresAt: ttlSeconds === void 0 ? null : expiryIn(ttlSeconds)
106
+ } }];
107
+ if (expected !== void 0) return (await keysNow.updateOne({
108
+ _id: key,
109
+ value: expected,
110
+ $expr: LIVE
111
+ }, write)).matchedCount === 1;
112
+ if ((await keysNow.updateOne({
113
+ _id: key,
114
+ $expr: { $not: [LIVE] }
115
+ }, write)).matchedCount === 1) return true;
116
+ try {
117
+ return (await keysNow.updateOne({
118
+ _id: key,
119
+ value: { $exists: false }
120
+ }, write, { upsert: true })).upsertedCount === 1;
121
+ } catch (error) {
122
+ if (error.code === DUPLICATE_KEY) return false;
123
+ throw error;
124
+ }
94
125
  }
95
126
  };
96
127
  };
@@ -1,3 +1,4 @@
1
+ import { activityDue } from "../../core/auth/sessionActivity.js";
1
2
  import { execute, selectOne, selectRows } from "./query.js";
2
3
  import { createSessionStore } from "./sessions.js";
3
4
  //#region src/modules/mysql/accounts.ts
@@ -50,12 +51,15 @@ var createAccountStore = (db, t) => {
50
51
  * check — deactivating somebody has to end the session they are already holding, not just the next login.
51
52
  */
52
53
  findAccountByToken: async (token) => {
53
- const row = await selectOne(db, `SELECT ${ACCOUNT_COLUMNS}, s.expires_at AS session_expires_at
54
+ const row = await selectOne(db, `SELECT ${ACCOUNT_COLUMNS}, s.id AS session_id, s.expires_at AS session_expires_at,
55
+ s.last_active_at AS session_last_active_at
54
56
  FROM ${t.session} s
55
57
  JOIN ${t.account} a ON a.id = s.account_id
56
58
  WHERE s.token = ? AND a.status = 'active'
57
59
  LIMIT 1`, [token]);
58
60
  if (!row) return;
61
+ const now = Math.floor(Date.now() / 1e3);
62
+ if (row.session_id !== void 0 && activityDue(row.session_last_active_at, now)) execute(db, `UPDATE ${t.session} SET last_active_at = ? WHERE id = ?`, [now, row.session_id]).catch(() => void 0);
59
63
  const { roles, permissions } = await loadAccess(row.id);
60
64
  return {
61
65
  id: row.id,
@@ -120,15 +124,18 @@ var createAccountStore = (db, t) => {
120
124
  ]);
121
125
  return;
122
126
  }
123
- await execute(db, `INSERT INTO ${t.session} (account_id, token, expires_at, refresh_token, refresh_expires_at, user_agent, ip)
124
- VALUES (?, ?, ?, ?, ?, ?, ?)`, [
127
+ await execute(db, `INSERT INTO ${t.session}
128
+ (account_id, token, expires_at, refresh_token, refresh_expires_at, user_agent, ip, app_name, app_id)
129
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
125
130
  userId,
126
131
  session.token,
127
132
  session.expiresAt,
128
133
  session.refreshToken ?? null,
129
134
  session.refreshExpiresAt ?? null,
130
135
  context?.client?.userAgent?.slice(0, 255) ?? null,
131
- context?.client?.ip ?? null
136
+ context?.client?.ip ?? null,
137
+ context?.client?.app?.name.slice(0, 120) ?? null,
138
+ context?.client?.app?.softwareId?.slice(0, 64) ?? null
132
139
  ]);
133
140
  },
134
141
  /**
@@ -335,22 +342,25 @@ var createAccountStore = (db, t) => {
335
342
  * read whole and written whole, and a text column that a human can read in a crisis beats one they cannot.
336
343
  */
337
344
  loadMfa: async (userId) => {
338
- const row = await selectOne(db, `SELECT secret, confirmed_at, recovery_codes FROM ${t.mfa} WHERE account_id = ? LIMIT 1`, [userId]);
345
+ const row = await selectOne(db, `SELECT secret, confirmed_at, recovery_codes, last_used_step FROM ${t.mfa} WHERE account_id = ? LIMIT 1`, [userId]);
339
346
  if (!row) return;
340
347
  return {
341
348
  secret: row.secret,
342
349
  ...row.confirmed_at !== null ? { confirmedAt: row.confirmed_at } : {},
343
- recoveryCodes: (row.recovery_codes ?? "").split("\n").filter(Boolean)
350
+ recoveryCodes: (row.recovery_codes ?? "").split("\n").filter(Boolean),
351
+ ...row.last_used_step !== null ? { lastUsedStep: row.last_used_step } : {}
344
352
  };
345
353
  },
346
354
  saveMfa: async (userId, mfaRecord) => {
347
- await execute(db, `INSERT INTO ${t.mfa} (account_id, secret, confirmed_at, recovery_codes) VALUES (?, ?, ?, ?)
355
+ await execute(db, `INSERT INTO ${t.mfa} (account_id, secret, confirmed_at, recovery_codes, last_used_step) VALUES (?, ?, ?, ?, ?)
348
356
  ON DUPLICATE KEY UPDATE
349
- secret = VALUES(secret), confirmed_at = VALUES(confirmed_at), recovery_codes = VALUES(recovery_codes)`, [
357
+ secret = VALUES(secret), confirmed_at = VALUES(confirmed_at), recovery_codes = VALUES(recovery_codes),
358
+ last_used_step = VALUES(last_used_step)`, [
350
359
  userId,
351
360
  mfaRecord.secret,
352
361
  mfaRecord.confirmedAt ?? null,
353
- (mfaRecord.recoveryCodes ?? []).join("\n")
362
+ (mfaRecord.recoveryCodes ?? []).join("\n"),
363
+ mfaRecord.lastUsedStep ?? null
354
364
  ]);
355
365
  },
356
366
  deleteMfa: async (userId) => {
@@ -63,5 +63,6 @@ export { TABLE_NAMES, tableNames } from './config';
63
63
  export { SCHEMA_VERSION } from './schema';
64
64
  export type { MysqlConfig, TableKey, Tables } from './config';
65
65
  export type { AccountSeed, MysqlAdmin, SpaceTokenSeed } from './admin';
66
- export type { SessionStore, SessionSummary } from './sessions';
66
+ export type { SessionStore } from './sessions';
67
+ export type { SessionSummary } from '../../core/auth/api';
67
68
  export type { IdentityStore, LinkedIdentity } from './identities';
@@ -1,5 +1,5 @@
1
1
  export { createMysqlJobQueue } from './jobQueue';
2
2
  export { createMysqlKv } from './kv';
3
- export { mysqlJobSchemaStatements } from './schema';
3
+ export { mysqlJobSchemaStatements, mysqlJobSchemaUpgrades } from './schema';
4
4
  export type { MysqlJobQueue, MysqlJobQueueOptions } from './jobQueue';
5
5
  export type { MysqlKvOptions } from './kv';
@@ -71,6 +71,36 @@ var createMysqlKv = ({ pool, tablePrefix = "", createTables = true }) => {
71
71
  },
72
72
  expire: async (key, ttlSeconds) => {
73
73
  await execute(await db(), `UPDATE ${kv} SET expires_at = ${NOW_MS} + ? WHERE k = ? AND ${LIVE}`, [ttlSeconds * 1e3, key]);
74
+ },
75
+ /**
76
+ * Each branch is ONE statement MySQL applies under the row's lock, so two writers racing for the same key cannot
77
+ * both win. `affectedRows` counts the rows MATCHED because mysql2 connects with `FOUND_ROWS`: without it, writing
78
+ * back the value a key already holds would read as a refusal. The comparison is on bytes — the table's collation
79
+ * ignores case and trailing spaces, and "unchanged" has to mean the same bytes.
80
+ */
81
+ swap: async (key, expected, next, ttlSeconds) => {
82
+ const expiry = ttlSeconds === void 0 ? "NULL" : `${NOW_MS} + ?`;
83
+ const lifetime = ttlSeconds === void 0 ? [] : [ttlSeconds * 1e3];
84
+ const pool = await db();
85
+ if (expected !== void 0) return (await execute(pool, `UPDATE ${kv} SET v = ?, expires_at = ${expiry}
86
+ WHERE k = ? AND ${LIVE} AND CAST(v AS BINARY) = CAST(? AS BINARY)`, [
87
+ next,
88
+ ...lifetime,
89
+ key,
90
+ expected
91
+ ])).affectedRows === 1;
92
+ if ((await execute(pool, `UPDATE ${kv} SET v = ?, expires_at = ${expiry} WHERE k = ? AND NOT ${LIVE}`, [
93
+ next,
94
+ ...lifetime,
95
+ key
96
+ ])).affectedRows === 1) return true;
97
+ const inserted = await execute(pool, `INSERT IGNORE INTO ${kv} (k, v, expires_at) VALUES (?, ?, ${expiry})`, [
98
+ key,
99
+ next,
100
+ ...lifetime
101
+ ]);
102
+ await sweep();
103
+ return inserted.affectedRows === 1;
74
104
  }
75
105
  };
76
106
  };
@@ -16,8 +16,17 @@ export declare const jobTables: (prefix?: string) => JobTables;
16
16
  * Instants are epoch milliseconds in `BIGINT`, read from the SERVER's clock; `history` and `input` are JSON kept as
17
17
  * text, because they are only ever read whole. Separate from the account store's schema and its version: a deployment
18
18
  * can use either without the other.
19
+ *
20
+ * A `kv` key is BYTES, as it is in Redis and in memory: under a collation `Board` and `board` — and `a` and `a ` — were
21
+ * one key here and two everywhere else. A value is a `MEDIUMTEXT`: a `TEXT` stops at 64 KB, and past it MySQL
22
+ * refuses the write, or cuts it short where it is not strict.
19
23
  */
20
24
  export declare const mysqlJobSchemaStatements: (prefix?: string) => string[];
25
+ /**
26
+ * What brings a `kv` table made before the columns above up to them — for a deployment that runs its own migrations,
27
+ * once. `ensureJobTables` does it by itself, and only when the table needs it: the `ALTER` copies the table.
28
+ */
29
+ export declare const mysqlJobSchemaUpgrades: (prefix?: string) => string[];
21
30
  /** The server's now, in epoch ms, as an SQL expression — what every comparison reads instead of a caller's clock. */
22
31
  export declare const NOW_MS = "CAST(ROUND(UNIX_TIMESTAMP(NOW(3)) * 1000) AS SIGNED)";
23
32
  /** Creates the tables once per process, on first use; a failure is retried by the next call rather than remembered. */
@@ -1,4 +1,4 @@
1
- import { execute } from "../query.js";
1
+ import { execute, selectRows } from "../query.js";
2
2
  //#region src/modules/mysql/jobs/schema.ts
3
3
  var PREFIX = /^[A-Za-z0-9_]*$/;
4
4
  /**
@@ -20,6 +20,10 @@ var CHARSET = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci"
20
20
  * Instants are epoch milliseconds in `BIGINT`, read from the SERVER's clock; `history` and `input` are JSON kept as
21
21
  * text, because they are only ever read whole. Separate from the account store's schema and its version: a deployment
22
22
  * can use either without the other.
23
+ *
24
+ * A `kv` key is BYTES, as it is in Redis and in memory: under a collation `Board` and `board` — and `a` and `a ` — were
25
+ * one key here and two everywhere else. A value is a `MEDIUMTEXT`: a `TEXT` stops at 64 KB, and past it MySQL
26
+ * refuses the write, or cuts it short where it is not strict.
23
27
  */
24
28
  var mysqlJobSchemaStatements = (prefix = "") => {
25
29
  const t = jobTables(prefix);
@@ -68,14 +72,26 @@ var mysqlJobSchemaStatements = (prefix = "") => {
68
72
  KEY action_schedules_due (enabled, next_run_at)
69
73
  ) ${CHARSET}`,
70
74
  `CREATE TABLE IF NOT EXISTS ${t.kv} (
71
- k VARCHAR(191) NOT NULL,
72
- v TEXT NOT NULL,
75
+ k VARBINARY(764) NOT NULL,
76
+ v MEDIUMTEXT NOT NULL,
73
77
  expires_at BIGINT NULL,
74
78
  PRIMARY KEY (k),
75
79
  KEY action_kv_expires (expires_at)
76
80
  ) ${CHARSET}`
77
81
  ];
78
82
  };
83
+ /**
84
+ * What brings a `kv` table made before the columns above up to them — for a deployment that runs its own migrations,
85
+ * once. `ensureJobTables` does it by itself, and only when the table needs it: the `ALTER` copies the table.
86
+ */
87
+ var mysqlJobSchemaUpgrades = (prefix = "") => [`ALTER TABLE ${jobTables(prefix).kv} MODIFY k VARBINARY(764) NOT NULL, MODIFY v MEDIUMTEXT NOT NULL`];
88
+ /** Whether the `kv` table still has the columns it was first made with. */
89
+ var kvNeedsUpgrade = async (db, prefix) => {
90
+ const columns = await selectRows(db, `SELECT COLUMN_NAME AS name, DATA_TYPE AS type FROM INFORMATION_SCHEMA.COLUMNS
91
+ WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME IN ('k', 'v')`, [`${prefix}action_kv`]);
92
+ const typeOf = (name) => columns.find((column) => column.name === name)?.type.toLowerCase();
93
+ return typeOf("k") !== "varbinary" || typeOf("v") !== "mediumtext";
94
+ };
79
95
  /** The server's now, in epoch ms, as an SQL expression — what every comparison reads instead of a caller's clock. */
80
96
  var NOW_MS = "CAST(ROUND(UNIX_TIMESTAMP(NOW(3)) * 1000) AS SIGNED)";
81
97
  /** Creates the tables once per process, on first use; a failure is retried by the next call rather than remembered. */
@@ -85,6 +101,7 @@ var ensureJobTables = (db, prefix, create) => {
85
101
  if (!create) return;
86
102
  ready ??= (async () => {
87
103
  for (const statement of mysqlJobSchemaStatements(prefix)) await execute(db, statement);
104
+ if (await kvNeedsUpgrade(db, prefix)) for (const statement of mysqlJobSchemaUpgrades(prefix)) await execute(db, statement);
88
105
  })().catch((error) => {
89
106
  ready = void 0;
90
107
  throw error;
@@ -93,4 +110,4 @@ var ensureJobTables = (db, prefix, create) => {
93
110
  };
94
111
  };
95
112
  //#endregion
96
- export { NOW_MS, ensureJobTables, jobTables, mysqlJobSchemaStatements };
113
+ export { NOW_MS, ensureJobTables, jobTables, mysqlJobSchemaStatements, mysqlJobSchemaUpgrades };
@@ -196,6 +196,23 @@ var step3 = (t) => {
196
196
  ...addIndex(account, "account_pending_email_token", "(pending_email_token)")
197
197
  ];
198
198
  };
199
+ /**
200
+ * What tells one device from another in the account's session list: the application holding it, when it is not a
201
+ * browser (`Plitzi CLI on carlos-mbp`, software `plitzi-cli`), and when it was last used — see `sessionActivity`.
202
+ */
203
+ var step4 = (t) => {
204
+ const session = unquote(t.session);
205
+ return [
206
+ ...addColumn(session, "app_name", "VARCHAR(120) NULL AFTER ip"),
207
+ ...addColumn(session, "app_id", "VARCHAR(64) NULL AFTER app_name"),
208
+ ...addColumn(session, "last_active_at", "BIGINT NULL AFTER created_at")
209
+ ];
210
+ };
211
+ /**
212
+ * The time step of the last second-factor code accepted, so the same code cannot sign in twice within the window it
213
+ * stays valid for (RFC 6238 §5.2) — see `MfaRecord.lastUsedStep`.
214
+ */
215
+ var step5 = (t) => [...addColumn(unquote(t.mfa), "last_used_step", "BIGINT NULL AFTER recovery_codes")];
199
216
  /** Append only. A released version is never edited — the deployments that already ran it would never see the change. */
200
217
  var STEPS = [
201
218
  {
@@ -209,6 +226,14 @@ var STEPS = [
209
226
  {
210
227
  version: 3,
211
228
  statements: step3
229
+ },
230
+ {
231
+ version: 4,
232
+ statements: step4
233
+ },
234
+ {
235
+ version: 5,
236
+ statements: step5
212
237
  }
213
238
  ];
214
239
  var SCHEMA_VERSION = STEPS[STEPS.length - 1].version;
@@ -1,16 +1,6 @@
1
1
  import { Tables } from './config';
2
2
  import { Queryable } from './query';
3
- /** One signed-in device, as somebody looking at their own account should see it. Never the credential itself. */
4
- export interface SessionSummary {
5
- id: number;
6
- userAgent?: string;
7
- ip?: string;
8
- /** Unix seconds. */
9
- createdAt: number;
10
- expiresAt: number;
11
- /** This is the session asking. A device list without it invites someone to revoke the one they are using. */
12
- current: boolean;
13
- }
3
+ import { SessionSummary } from '../../core/auth/api';
14
4
  /**
15
5
  * The sessions of one account, as a thing that can be looked at and taken away one at a time.
16
6
  *
@@ -9,14 +9,19 @@ import { execute, selectRows } from "./query.js";
9
9
  */
10
10
  var createSessionStore = (db, t) => {
11
11
  const list = async (accountId, currentToken) => {
12
- return (await selectRows(db, `SELECT id, token, user_agent, ip, created_at, expires_at
12
+ return (await selectRows(db, `SELECT id, token, user_agent, ip, app_name, app_id, created_at, last_active_at, expires_at
13
13
  FROM ${t.session}
14
14
  WHERE account_id = ?
15
15
  ORDER BY created_at DESC`, [accountId])).map((row) => ({
16
16
  id: row.id,
17
17
  ...row.user_agent ? { userAgent: row.user_agent } : {},
18
18
  ...row.ip ? { ip: row.ip } : {},
19
+ ...row.app_name ? { app: {
20
+ name: row.app_name,
21
+ ...row.app_id ? { softwareId: row.app_id } : {}
22
+ } } : {},
19
23
  createdAt: Math.floor(row.created_at.getTime() / 1e3),
24
+ ...row.last_active_at ? { lastActiveAt: row.last_active_at } : {},
20
25
  expiresAt: row.expires_at,
21
26
  current: currentToken !== void 0 && row.token === currentToken
22
27
  }));
@@ -0,0 +1,11 @@
1
+ import { ChannelDeclarations, OfflineDataRaw } from '@plitzi/sdk-shared';
2
+ type GetOfflineData = (spaceId: number, environment: string, revision?: number) => Promise<OfflineDataRaw | undefined>;
3
+ /**
4
+ * The channels a space declares, read from its schema and remembered for a few seconds.
5
+ *
6
+ * A publish is checked against them on every message; reading the space's documents for each one would cost more
7
+ * than the message. A space that changes its channels is heard within the TTL.
8
+ */
9
+ export declare const createChannelResolver: (getOfflineData: GetOfflineData) => (spaceId: number, environment: string, revision?: number) => Promise<ChannelDeclarations | undefined>;
10
+ export type ChannelResolver = ReturnType<typeof createChannelResolver>;
11
+ export {};
@@ -0,0 +1,26 @@
1
+ //#region src/modules/realtime/declarations.ts
2
+ /** How long a space's channels are remembered: a publish is frequent, and a space's settings change rarely. */
3
+ var TTL_MS = 5e3;
4
+ /**
5
+ * The channels a space declares, read from its schema and remembered for a few seconds.
6
+ *
7
+ * A publish is checked against them on every message; reading the space's documents for each one would cost more
8
+ * than the message. A space that changes its channels is heard within the TTL.
9
+ */
10
+ var createChannelResolver = (getOfflineData) => {
11
+ const cache = /* @__PURE__ */ new Map();
12
+ return (spaceId, environment, revision) => {
13
+ const key = `${spaceId}:${environment}:${revision ?? ""}`;
14
+ const hit = cache.get(key);
15
+ if (hit && Date.now() - hit.at < TTL_MS) return hit.channels;
16
+ const channels = getOfflineData(spaceId, environment, revision).then((data) => data?.schema.settings.channels);
17
+ cache.set(key, {
18
+ at: Date.now(),
19
+ channels
20
+ });
21
+ channels.catch(() => cache.delete(key));
22
+ return channels;
23
+ };
24
+ };
25
+ //#endregion
26
+ export { createChannelResolver };
@@ -0,0 +1,23 @@
1
+ import { RealtimeSpace } from './hub';
2
+ import { ActionKvAdapter } from '../actions/types';
3
+ /** How long a grant opens its topic when the flow does not say: a day, which outlives any one visit. */
4
+ export declare const DEFAULT_GRANT_SECONDS = 86400;
5
+ /** The longest a grant may last: thirty days. A page that stays open longer asks its action for a new one. */
6
+ export declare const MAX_GRANT_SECONDS: number;
7
+ export type RealtimeGrants = {
8
+ /** A grant for `topic`, good for `ttlSeconds` — what `realtime.grant` hands the page. */
9
+ issue: (space: RealtimeSpace, topic: string, ttlSeconds?: number) => Promise<string>;
10
+ /** Whether `grant` was issued for exactly `topic` of this space, and has neither run out nor been revoked. */
11
+ opens: (space: RealtimeSpace, topic: string, grant: string) => Promise<boolean>;
12
+ /** `grant` no longer opens `topic` — or, with none named, no grant issued for it until now does. */
13
+ revoke: (space: RealtimeSpace, topic: string, grant?: string) => Promise<void>;
14
+ };
15
+ export declare const grantSeconds: (ttlSeconds: number | undefined) => number;
16
+ /**
17
+ * The grants a server has issued, kept in the store its actions' `kv` uses — so a grant one replica issued opens the
18
+ * topic on whichever replica the page connects to, and nothing new has to be configured for it. A random token rather
19
+ * than a signature: there is no secret to share between replicas, and a grant is only ever as old as its lifetime.
20
+ *
21
+ * Keyed under `realtime-grant:`, a prefix no key a flow writes can reach (theirs are `kv:action:<space>:…`).
22
+ */
23
+ export declare const createRealtimeGrants: (store: ActionKvAdapter) => RealtimeGrants;
@@ -0,0 +1,61 @@
1
+ import { randomBytes } from "node:crypto";
2
+ //#region src/modules/realtime/grants.ts
3
+ /** How long a grant opens its topic when the flow does not say: a day, which outlives any one visit. */
4
+ var DEFAULT_GRANT_SECONDS = 86400;
5
+ /** The longest a grant may last: thirty days. A page that stays open longer asks its action for a new one. */
6
+ var MAX_GRANT_SECONDS = 2592e3;
7
+ /** What a grant looks like on the wire: long enough to be unguessable, and nothing a query string has to escape. */
8
+ var TOKEN = /^[A-Za-z0-9_-]{24,64}$/;
9
+ var parseGrant = (raw) => {
10
+ try {
11
+ const value = JSON.parse(raw ?? "");
12
+ return typeof value === "object" && value !== null && "topic" in value && typeof value.topic === "string" && "generation" in value && typeof value.generation === "number" ? {
13
+ topic: value.topic,
14
+ generation: value.generation
15
+ } : void 0;
16
+ } catch {
17
+ return;
18
+ }
19
+ };
20
+ var grantSeconds = (ttlSeconds) => ttlSeconds === void 0 || !Number.isFinite(ttlSeconds) ? DEFAULT_GRANT_SECONDS : Math.min(MAX_GRANT_SECONDS, Math.max(60, Math.floor(ttlSeconds)));
21
+ /**
22
+ * The grants a server has issued, kept in the store its actions' `kv` uses — so a grant one replica issued opens the
23
+ * topic on whichever replica the page connects to, and nothing new has to be configured for it. A random token rather
24
+ * than a signature: there is no secret to share between replicas, and a grant is only ever as old as its lifetime.
25
+ *
26
+ * Keyed under `realtime-grant:`, a prefix no key a flow writes can reach (theirs are `kv:action:<space>:…`).
27
+ */
28
+ var createRealtimeGrants = (store) => {
29
+ const key = ({ spaceId, environment }, grant) => `realtime-grant:${String(spaceId)}:${environment}:${grant}`;
30
+ /**
31
+ * How many times every grant of a topic was revoked at once. A grant carries the number it was issued under, so
32
+ * raising it voids them all — the ones in use and the ones not used yet — without a list of them to walk.
33
+ */
34
+ const generationKey = ({ spaceId, environment }, topic) => `realtime-grant-generation:${String(spaceId)}:${environment}:${topic}`;
35
+ const generationOf = async (space, topic) => Number(await store.get(generationKey(space, topic))) || 0;
36
+ return {
37
+ issue: async (space, topic, ttlSeconds) => {
38
+ const grant = randomBytes(18).toString("base64url");
39
+ const stored = {
40
+ topic,
41
+ generation: await generationOf(space, topic)
42
+ };
43
+ await store.set(key(space, grant), JSON.stringify(stored), grantSeconds(ttlSeconds));
44
+ return grant;
45
+ },
46
+ opens: async (space, topic, grant) => {
47
+ if (!TOKEN.test(grant)) return false;
48
+ const stored = parseGrant(await store.get(key(space, grant)));
49
+ return stored?.topic === topic && stored.generation === await generationOf(space, topic);
50
+ },
51
+ revoke: async (space, topic, grant) => {
52
+ if (grant === void 0) {
53
+ await store.increment(generationKey(space, topic), 1);
54
+ return;
55
+ }
56
+ if (TOKEN.test(grant) && parseGrant(await store.get(key(space, grant)))?.topic === topic) await store.delete(key(space, grant));
57
+ }
58
+ };
59
+ };
60
+ //#endregion
61
+ export { DEFAULT_GRANT_SECONDS, MAX_GRANT_SECONDS, createRealtimeGrants, grantSeconds };
@@ -0,0 +1,74 @@
1
+ import { ChannelResolver } from './declarations';
2
+ import { RealtimeGrants } from './grants';
3
+ import { RealtimeConnection, RealtimeHub, RealtimeSpace } from './hub';
4
+ import { RawResponse } from '../../helpers/buildResponseHelpers';
5
+ import { ChannelDeclaration, RealtimeSender, SSRRequest, SSRResponseHelpers } from '@plitzi/sdk-shared';
6
+ export type RealtimeRefusal = {
7
+ topic: string;
8
+ /** `ungranted`: the channel is declared `grant: true`, and no grant the page sent opens this topic. */
9
+ reason: 'undeclared' | 'unauthenticated' | 'forbidden' | 'ungranted';
10
+ };
11
+ /** An answer in HTTP terms, whichever transport carries it: a status, and what went wrong when something did. */
12
+ export type RealtimeAnswer = {
13
+ status: number;
14
+ error?: string;
15
+ reason?: string;
16
+ refused?: RealtimeRefusal[];
17
+ };
18
+ export declare const answer: (res: SSRResponseHelpers, { status, ...payload }: RealtimeAnswer) => void;
19
+ /** What a page may open, decided once for either transport. */
20
+ export type Admission = {
21
+ ok: false;
22
+ answer: RealtimeAnswer;
23
+ } | {
24
+ ok: true;
25
+ space: RealtimeSpace;
26
+ user?: RealtimeSender['user'];
27
+ accepted: Map<string, ChannelDeclaration>;
28
+ /** The grant each accepted topic of a `grant: true` channel was opened with. */
29
+ granted: Map<string, string>;
30
+ refused: RealtimeRefusal[];
31
+ };
32
+ /**
33
+ * Which of the topics a page asked for (`?topics=a,b`) it may open: each checked against the channels the space
34
+ * declares and against the visitor's access. One that fails is reported, not a reason to refuse the rest; all failing
35
+ * is a `403`.
36
+ */
37
+ export declare const admit: (req: SSRRequest, resolveChannels: ChannelResolver, grants: RealtimeGrants) => Promise<Admission>;
38
+ /** A new connection for what was admitted, speaking through `send`. */
39
+ export declare const connectionFor: (admission: Extract<Admission, {
40
+ ok: true;
41
+ }>, send: RealtimeConnection["send"], end: RealtimeConnection["end"]) => RealtimeConnection;
42
+ /**
43
+ * A page publishing, whichever way it arrived: only on a topic its connection subscribed to, only on a channel pages
44
+ * may send on, within its size and its rate — and stamped by the server with who sent it and when, whatever the
45
+ * message said.
46
+ */
47
+ export declare const publishFrom: (hub: RealtimeHub, connection: RealtimeConnection, { topic, type, data }: {
48
+ topic?: unknown;
49
+ type?: unknown;
50
+ data?: unknown;
51
+ }) => Promise<RealtimeAnswer>;
52
+ export type SubscribeDeps = {
53
+ req: SSRRequest;
54
+ res: SSRResponseHelpers;
55
+ raw: RawResponse;
56
+ signal: AbortSignal;
57
+ hub: RealtimeHub;
58
+ resolveChannels: ChannelResolver;
59
+ grants: RealtimeGrants;
60
+ };
61
+ /**
62
+ * `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.
63
+ *
64
+ * The first event, `ready`, tells the page its public name (`from`) and the secret its publishes carry. Resolves when
65
+ * the page goes.
66
+ */
67
+ export declare const handleRealtimeSubscribe: ({ req, res, raw, signal, hub, resolveChannels, grants }: SubscribeDeps) => Promise<void>;
68
+ export type PublishDeps = {
69
+ req: SSRRequest;
70
+ res: SSRResponseHelpers;
71
+ hub: RealtimeHub;
72
+ };
73
+ /** `POST /_realtime` `{ token, topic, type, data }` — a page on a Server-Sent Events connection, publishing. */
74
+ export declare const handleRealtimePublish: ({ req, res, hub }: PublishDeps) => Promise<void>;