@enbox/dwn-server 0.0.2 → 0.0.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 (309) hide show
  1. package/LICENSE +3 -2
  2. package/README.md +115 -215
  3. package/dist/esm/src/admin/activity-log.d.ts +44 -0
  4. package/dist/esm/src/admin/activity-log.d.ts.map +1 -0
  5. package/dist/esm/src/admin/activity-log.js +85 -0
  6. package/dist/esm/src/admin/activity-log.js.map +1 -0
  7. package/dist/esm/src/admin/admin-api.d.ts +61 -0
  8. package/dist/esm/src/admin/admin-api.d.ts.map +1 -0
  9. package/dist/esm/src/admin/admin-api.js +1047 -0
  10. package/dist/esm/src/admin/admin-api.js.map +1 -0
  11. package/dist/esm/src/admin/admin-auth.d.ts +9 -0
  12. package/dist/esm/src/admin/admin-auth.d.ts.map +1 -0
  13. package/dist/esm/src/admin/admin-auth.js +45 -0
  14. package/dist/esm/src/admin/admin-auth.js.map +1 -0
  15. package/dist/esm/src/admin/admin-store.d.ts +111 -0
  16. package/dist/esm/src/admin/admin-store.d.ts.map +1 -0
  17. package/dist/esm/src/admin/admin-store.js +376 -0
  18. package/dist/esm/src/admin/admin-store.js.map +1 -0
  19. package/dist/esm/src/admin/audit-log.d.ts +94 -0
  20. package/dist/esm/src/admin/audit-log.d.ts.map +1 -0
  21. package/dist/esm/src/admin/audit-log.js +220 -0
  22. package/dist/esm/src/admin/audit-log.js.map +1 -0
  23. package/dist/esm/src/admin/index.d.ts +10 -0
  24. package/dist/esm/src/admin/index.d.ts.map +1 -0
  25. package/dist/esm/src/admin/index.js +7 -0
  26. package/dist/esm/src/admin/index.js.map +1 -0
  27. package/dist/esm/src/admin/types.d.ts +306 -0
  28. package/dist/esm/src/admin/types.d.ts.map +1 -0
  29. package/dist/esm/src/admin/types.js +2 -0
  30. package/dist/esm/src/admin/types.js.map +1 -0
  31. package/dist/esm/src/admin/webhook-manager.d.ts +55 -0
  32. package/dist/esm/src/admin/webhook-manager.d.ts.map +1 -0
  33. package/dist/esm/src/admin/webhook-manager.js +184 -0
  34. package/dist/esm/src/admin/webhook-manager.js.map +1 -0
  35. package/dist/esm/src/config.d.ts +124 -9
  36. package/dist/esm/src/config.d.ts.map +1 -1
  37. package/dist/esm/src/config.js +155 -13
  38. package/dist/esm/src/config.js.map +1 -1
  39. package/dist/esm/src/connection/connection-manager.d.ts +32 -9
  40. package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
  41. package/dist/esm/src/connection/connection-manager.js +38 -5
  42. package/dist/esm/src/connection/connection-manager.js.map +1 -1
  43. package/dist/esm/src/connection/flow-controller.d.ts +53 -0
  44. package/dist/esm/src/connection/flow-controller.d.ts.map +1 -0
  45. package/dist/esm/src/connection/flow-controller.js +101 -0
  46. package/dist/esm/src/connection/flow-controller.js.map +1 -0
  47. package/dist/esm/src/connection/socket-connection.d.ts +54 -18
  48. package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
  49. package/dist/esm/src/connection/socket-connection.js +102 -40
  50. package/dist/esm/src/connection/socket-connection.js.map +1 -1
  51. package/dist/esm/src/delivery-service.d.ts +43 -0
  52. package/dist/esm/src/delivery-service.d.ts.map +1 -0
  53. package/dist/esm/src/delivery-service.js +574 -0
  54. package/dist/esm/src/delivery-service.js.map +1 -0
  55. package/dist/esm/src/dwn-error.d.ts +10 -1
  56. package/dist/esm/src/dwn-error.d.ts.map +1 -1
  57. package/dist/esm/src/dwn-error.js +9 -0
  58. package/dist/esm/src/dwn-error.js.map +1 -1
  59. package/dist/esm/src/dwn-server.d.ts +13 -6
  60. package/dist/esm/src/dwn-server.d.ts.map +1 -1
  61. package/dist/esm/src/dwn-server.js +199 -24
  62. package/dist/esm/src/dwn-server.js.map +1 -1
  63. package/dist/esm/src/http-api.d.ts +28 -13
  64. package/dist/esm/src/http-api.d.ts.map +1 -1
  65. package/dist/esm/src/http-api.js +649 -374
  66. package/dist/esm/src/http-api.js.map +1 -1
  67. package/dist/esm/src/index.d.ts +6 -2
  68. package/dist/esm/src/index.d.ts.map +1 -1
  69. package/dist/esm/src/index.js +4 -1
  70. package/dist/esm/src/index.js.map +1 -1
  71. package/dist/esm/src/json-rpc-api.js +2 -1
  72. package/dist/esm/src/json-rpc-api.js.map +1 -1
  73. package/dist/esm/src/json-rpc-handlers/dwn/process-message.d.ts.map +1 -1
  74. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js +109 -7
  75. package/dist/esm/src/json-rpc-handlers/dwn/process-message.js.map +1 -1
  76. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts +20 -0
  77. package/dist/esm/src/json-rpc-handlers/subscription/ack.d.ts.map +1 -0
  78. package/dist/esm/src/json-rpc-handlers/subscription/ack.js +41 -0
  79. package/dist/esm/src/json-rpc-handlers/subscription/ack.js.map +1 -0
  80. package/dist/esm/src/json-rpc-handlers/subscription/close.d.ts.map +1 -1
  81. package/dist/esm/src/json-rpc-handlers/subscription/close.js +1 -1
  82. package/dist/esm/src/json-rpc-handlers/subscription/close.js.map +1 -1
  83. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts +1 -0
  84. package/dist/esm/src/json-rpc-handlers/subscription/index.d.ts.map +1 -1
  85. package/dist/esm/src/json-rpc-handlers/subscription/index.js +1 -0
  86. package/dist/esm/src/json-rpc-handlers/subscription/index.js.map +1 -1
  87. package/dist/esm/src/lib/json-rpc-router.d.ts +25 -8
  88. package/dist/esm/src/lib/json-rpc-router.d.ts.map +1 -1
  89. package/dist/esm/src/lib/json-rpc-router.js.map +1 -1
  90. package/dist/esm/src/lib/sql-utils.d.ts +6 -0
  91. package/dist/esm/src/lib/sql-utils.d.ts.map +1 -0
  92. package/dist/esm/src/lib/sql-utils.js +8 -0
  93. package/dist/esm/src/lib/sql-utils.js.map +1 -0
  94. package/dist/esm/src/main.js +0 -6
  95. package/dist/esm/src/main.js.map +1 -1
  96. package/dist/esm/src/message-processed-hook.d.ts +35 -0
  97. package/dist/esm/src/message-processed-hook.d.ts.map +1 -0
  98. package/dist/esm/src/message-processed-hook.js +2 -0
  99. package/dist/esm/src/message-processed-hook.js.map +1 -0
  100. package/dist/esm/src/metrics.d.ts +14 -2
  101. package/dist/esm/src/metrics.d.ts.map +1 -1
  102. package/dist/esm/src/metrics.js +41 -1
  103. package/dist/esm/src/metrics.js.map +1 -1
  104. package/dist/esm/src/plugins/event-log-nats.d.ts +25 -0
  105. package/dist/esm/src/plugins/event-log-nats.d.ts.map +1 -0
  106. package/dist/esm/src/plugins/event-log-nats.js +379 -0
  107. package/dist/esm/src/plugins/event-log-nats.js.map +1 -0
  108. package/dist/esm/src/rate-limiter.d.ts +60 -0
  109. package/dist/esm/src/rate-limiter.d.ts.map +1 -0
  110. package/dist/esm/src/rate-limiter.js +116 -0
  111. package/dist/esm/src/rate-limiter.js.map +1 -0
  112. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts +53 -0
  113. package/dist/esm/src/registration/jwt-provider-auth-plugin.d.ts.map +1 -0
  114. package/dist/esm/src/registration/jwt-provider-auth-plugin.js +90 -0
  115. package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -0
  116. package/dist/esm/src/registration/open-auth-handler.d.ts +37 -0
  117. package/dist/esm/src/registration/open-auth-handler.d.ts.map +1 -0
  118. package/dist/esm/src/registration/open-auth-handler.js +214 -0
  119. package/dist/esm/src/registration/open-auth-handler.js.map +1 -0
  120. package/dist/esm/src/registration/proof-of-work-manager.d.ts +1 -1
  121. package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
  122. package/dist/esm/src/registration/proof-of-work-manager.js +3 -3
  123. package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
  124. package/dist/esm/src/registration/provider-auth-plugin.d.ts +46 -0
  125. package/dist/esm/src/registration/provider-auth-plugin.d.ts.map +1 -0
  126. package/dist/esm/src/registration/provider-auth-plugin.js +29 -0
  127. package/dist/esm/src/registration/provider-auth-plugin.js.map +1 -0
  128. package/dist/esm/src/registration/registration-manager.d.ts +28 -5
  129. package/dist/esm/src/registration/registration-manager.d.ts.map +1 -1
  130. package/dist/esm/src/registration/registration-manager.js +83 -12
  131. package/dist/esm/src/registration/registration-manager.js.map +1 -1
  132. package/dist/esm/src/registration/registration-store.d.ts +83 -3
  133. package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
  134. package/dist/esm/src/registration/registration-store.js +248 -11
  135. package/dist/esm/src/registration/registration-store.js.map +1 -1
  136. package/dist/esm/src/storage.d.ts +5 -5
  137. package/dist/esm/src/storage.d.ts.map +1 -1
  138. package/dist/esm/src/storage.js +105 -24
  139. package/dist/esm/src/storage.js.map +1 -1
  140. package/dist/esm/src/web5-connect/sql-ttl-cache.d.ts.map +1 -1
  141. package/dist/esm/src/web5-connect/sql-ttl-cache.js +11 -3
  142. package/dist/esm/src/web5-connect/sql-ttl-cache.js.map +1 -1
  143. package/dist/esm/src/web5-connect/web5-connect-server.d.ts.map +1 -1
  144. package/dist/esm/src/web5-connect/web5-connect-server.js +2 -2
  145. package/dist/esm/src/web5-connect/web5-connect-server.js.map +1 -1
  146. package/dist/esm/src/ws-api.d.ts +18 -4
  147. package/dist/esm/src/ws-api.d.ts.map +1 -1
  148. package/dist/esm/src/ws-api.js +12 -16
  149. package/dist/esm/src/ws-api.js.map +1 -1
  150. package/package.json +34 -53
  151. package/src/admin/activity-log.ts +100 -0
  152. package/src/admin/admin-api.ts +1308 -0
  153. package/src/admin/admin-auth.ts +56 -0
  154. package/src/admin/admin-store.ts +515 -0
  155. package/src/admin/audit-log.ts +327 -0
  156. package/src/admin/index.ts +34 -0
  157. package/src/admin/types.ts +352 -0
  158. package/src/admin/webhook-manager.ts +245 -0
  159. package/src/config.ts +190 -22
  160. package/src/connection/connection-manager.ts +67 -17
  161. package/src/connection/flow-controller.ts +117 -0
  162. package/src/connection/socket-connection.ts +144 -67
  163. package/src/delivery-service.ts +740 -0
  164. package/src/dwn-error.ts +11 -2
  165. package/src/dwn-server.ts +254 -39
  166. package/src/http-api.ts +736 -392
  167. package/src/index.ts +13 -2
  168. package/src/json-rpc-api.ts +2 -1
  169. package/src/json-rpc-handlers/dwn/process-message.ts +149 -15
  170. package/src/json-rpc-handlers/subscription/ack.ts +63 -0
  171. package/src/json-rpc-handlers/subscription/close.ts +5 -9
  172. package/src/json-rpc-handlers/subscription/index.ts +1 -0
  173. package/src/lib/json-rpc-router.ts +26 -11
  174. package/src/lib/sql-utils.ts +7 -0
  175. package/src/main.ts +0 -8
  176. package/src/message-processed-hook.ts +33 -0
  177. package/src/metrics.ts +57 -8
  178. package/src/plugins/event-log-nats.ts +466 -0
  179. package/src/process-handlers.ts +5 -5
  180. package/src/rate-limiter.ts +143 -0
  181. package/src/registration/jwt-provider-auth-plugin.ts +119 -0
  182. package/src/registration/open-auth-handler.ts +263 -0
  183. package/src/registration/proof-of-work-manager.ts +11 -10
  184. package/src/registration/provider-auth-plugin.ts +84 -0
  185. package/src/registration/registration-manager.ts +129 -31
  186. package/src/registration/registration-store.ts +332 -22
  187. package/src/storage.ts +136 -40
  188. package/src/web5-connect/sql-ttl-cache.ts +12 -5
  189. package/src/web5-connect/web5-connect-server.ts +9 -8
  190. package/src/ws-api.ts +39 -26
  191. package/dist/cjs/index.js +0 -6811
  192. package/dist/cjs/package.json +0 -1
  193. package/dist/esm/src/json-rpc-socket.d.ts +0 -39
  194. package/dist/esm/src/json-rpc-socket.d.ts.map +0 -1
  195. package/dist/esm/src/json-rpc-socket.js +0 -125
  196. package/dist/esm/src/json-rpc-socket.js.map +0 -1
  197. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts +0 -10
  198. package/dist/esm/src/lib/http-server-shutdown-handler.d.ts.map +0 -1
  199. package/dist/esm/src/lib/http-server-shutdown-handler.js +0 -65
  200. package/dist/esm/src/lib/http-server-shutdown-handler.js.map +0 -1
  201. package/dist/esm/src/lib/json-rpc.d.ts +0 -54
  202. package/dist/esm/src/lib/json-rpc.d.ts.map +0 -1
  203. package/dist/esm/src/lib/json-rpc.js +0 -60
  204. package/dist/esm/src/lib/json-rpc.js.map +0 -1
  205. package/dist/esm/src/registration/proof-of-work-types.d.ts +0 -8
  206. package/dist/esm/src/registration/proof-of-work-types.d.ts.map +0 -1
  207. package/dist/esm/src/registration/proof-of-work-types.js +0 -2
  208. package/dist/esm/src/registration/proof-of-work-types.js.map +0 -1
  209. package/dist/esm/src/registration/registration-types.d.ts +0 -18
  210. package/dist/esm/src/registration/registration-types.d.ts.map +0 -1
  211. package/dist/esm/src/registration/registration-types.js +0 -2
  212. package/dist/esm/src/registration/registration-types.js.map +0 -1
  213. package/dist/esm/tests/common-scenario-validator.d.ts +0 -11
  214. package/dist/esm/tests/common-scenario-validator.d.ts.map +0 -1
  215. package/dist/esm/tests/common-scenario-validator.js +0 -114
  216. package/dist/esm/tests/common-scenario-validator.js.map +0 -1
  217. package/dist/esm/tests/connection/connection-manager.spec.d.ts +0 -2
  218. package/dist/esm/tests/connection/connection-manager.spec.d.ts.map +0 -1
  219. package/dist/esm/tests/connection/connection-manager.spec.js +0 -47
  220. package/dist/esm/tests/connection/connection-manager.spec.js.map +0 -1
  221. package/dist/esm/tests/connection/socket-connection.spec.d.ts +0 -2
  222. package/dist/esm/tests/connection/socket-connection.spec.d.ts.map +0 -1
  223. package/dist/esm/tests/connection/socket-connection.spec.js +0 -125
  224. package/dist/esm/tests/connection/socket-connection.spec.js.map +0 -1
  225. package/dist/esm/tests/cors/http-api.browser.d.ts +0 -2
  226. package/dist/esm/tests/cors/http-api.browser.d.ts.map +0 -1
  227. package/dist/esm/tests/cors/http-api.browser.js +0 -60
  228. package/dist/esm/tests/cors/http-api.browser.js.map +0 -1
  229. package/dist/esm/tests/cors/ping.browser.d.ts +0 -2
  230. package/dist/esm/tests/cors/ping.browser.d.ts.map +0 -1
  231. package/dist/esm/tests/cors/ping.browser.js +0 -7
  232. package/dist/esm/tests/cors/ping.browser.js.map +0 -1
  233. package/dist/esm/tests/dwn-process-message.spec.d.ts +0 -2
  234. package/dist/esm/tests/dwn-process-message.spec.d.ts.map +0 -1
  235. package/dist/esm/tests/dwn-process-message.spec.js +0 -172
  236. package/dist/esm/tests/dwn-process-message.spec.js.map +0 -1
  237. package/dist/esm/tests/dwn-server.spec.d.ts +0 -2
  238. package/dist/esm/tests/dwn-server.spec.d.ts.map +0 -1
  239. package/dist/esm/tests/dwn-server.spec.js +0 -49
  240. package/dist/esm/tests/dwn-server.spec.js.map +0 -1
  241. package/dist/esm/tests/http-api.spec.d.ts +0 -2
  242. package/dist/esm/tests/http-api.spec.d.ts.map +0 -1
  243. package/dist/esm/tests/http-api.spec.js +0 -775
  244. package/dist/esm/tests/http-api.spec.js.map +0 -1
  245. package/dist/esm/tests/json-rpc-socket.spec.d.ts +0 -2
  246. package/dist/esm/tests/json-rpc-socket.spec.d.ts.map +0 -1
  247. package/dist/esm/tests/json-rpc-socket.spec.js +0 -225
  248. package/dist/esm/tests/json-rpc-socket.spec.js.map +0 -1
  249. package/dist/esm/tests/plugins/data-store-sqlite.d.ts +0 -17
  250. package/dist/esm/tests/plugins/data-store-sqlite.d.ts.map +0 -1
  251. package/dist/esm/tests/plugins/data-store-sqlite.js +0 -23
  252. package/dist/esm/tests/plugins/data-store-sqlite.js.map +0 -1
  253. package/dist/esm/tests/plugins/event-log-sqlite.d.ts +0 -17
  254. package/dist/esm/tests/plugins/event-log-sqlite.d.ts.map +0 -1
  255. package/dist/esm/tests/plugins/event-log-sqlite.js +0 -23
  256. package/dist/esm/tests/plugins/event-log-sqlite.js.map +0 -1
  257. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts +0 -17
  258. package/dist/esm/tests/plugins/event-stream-in-memory.d.ts.map +0 -1
  259. package/dist/esm/tests/plugins/event-stream-in-memory.js +0 -21
  260. package/dist/esm/tests/plugins/event-stream-in-memory.js.map +0 -1
  261. package/dist/esm/tests/plugins/message-store-sqlite.d.ts +0 -17
  262. package/dist/esm/tests/plugins/message-store-sqlite.d.ts.map +0 -1
  263. package/dist/esm/tests/plugins/message-store-sqlite.js +0 -23
  264. package/dist/esm/tests/plugins/message-store-sqlite.js.map +0 -1
  265. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts +0 -17
  266. package/dist/esm/tests/plugins/resumable-task-store-sqlite.d.ts.map +0 -1
  267. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js +0 -23
  268. package/dist/esm/tests/plugins/resumable-task-store-sqlite.js.map +0 -1
  269. package/dist/esm/tests/process-handler.spec.d.ts +0 -2
  270. package/dist/esm/tests/process-handler.spec.d.ts.map +0 -1
  271. package/dist/esm/tests/process-handler.spec.js +0 -60
  272. package/dist/esm/tests/process-handler.spec.js.map +0 -1
  273. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts +0 -2
  274. package/dist/esm/tests/registration/proof-of-work-manager.spec.d.ts.map +0 -1
  275. package/dist/esm/tests/registration/proof-of-work-manager.spec.js +0 -157
  276. package/dist/esm/tests/registration/proof-of-work-manager.spec.js.map +0 -1
  277. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts +0 -2
  278. package/dist/esm/tests/rpc-subscribe-close.spec.d.ts.map +0 -1
  279. package/dist/esm/tests/rpc-subscribe-close.spec.js +0 -81
  280. package/dist/esm/tests/rpc-subscribe-close.spec.js.map +0 -1
  281. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts +0 -2
  282. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.d.ts.map +0 -1
  283. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js +0 -73
  284. package/dist/esm/tests/scenarios/dynamic-plugin-loading.spec.js.map +0 -1
  285. package/dist/esm/tests/scenarios/registration.spec.d.ts +0 -2
  286. package/dist/esm/tests/scenarios/registration.spec.d.ts.map +0 -1
  287. package/dist/esm/tests/scenarios/registration.spec.js +0 -507
  288. package/dist/esm/tests/scenarios/registration.spec.js.map +0 -1
  289. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts +0 -2
  290. package/dist/esm/tests/scenarios/web5-connect.spec.d.ts.map +0 -1
  291. package/dist/esm/tests/scenarios/web5-connect.spec.js +0 -137
  292. package/dist/esm/tests/scenarios/web5-connect.spec.js.map +0 -1
  293. package/dist/esm/tests/test-dwn.d.ts +0 -7
  294. package/dist/esm/tests/test-dwn.d.ts.map +0 -1
  295. package/dist/esm/tests/test-dwn.js +0 -34
  296. package/dist/esm/tests/test-dwn.js.map +0 -1
  297. package/dist/esm/tests/utils.d.ts +0 -46
  298. package/dist/esm/tests/utils.d.ts.map +0 -1
  299. package/dist/esm/tests/utils.js +0 -116
  300. package/dist/esm/tests/utils.js.map +0 -1
  301. package/dist/esm/tests/ws-api.spec.d.ts +0 -2
  302. package/dist/esm/tests/ws-api.spec.d.ts.map +0 -1
  303. package/dist/esm/tests/ws-api.spec.js +0 -327
  304. package/dist/esm/tests/ws-api.spec.js.map +0 -1
  305. package/src/json-rpc-socket.ts +0 -155
  306. package/src/lib/http-server-shutdown-handler.ts +0 -79
  307. package/src/lib/json-rpc.ts +0 -126
  308. package/src/registration/proof-of-work-types.ts +0 -7
  309. package/src/registration/registration-types.ts +0 -18
@@ -0,0 +1,117 @@
1
+ import type { SubscriptionMessage } from '@enbox/dwn-sdk-js';
2
+ import type { JsonRpcId, JsonRpcSuccessResponse } from '@enbox/dwn-clients';
3
+
4
+ import log from 'loglevel';
5
+
6
+ import { createJsonRpcSuccessResponse } from '@enbox/dwn-clients';
7
+
8
+ /** Default maximum number of unacknowledged events before pausing delivery. */
9
+ export const DEFAULT_MAX_IN_FLIGHT = 32;
10
+
11
+ /** Maximum buffer size before the subscription is force-closed to prevent OOM. */
12
+ export const MAX_BUFFER_SIZE = 1000;
13
+
14
+ /**
15
+ * Per-subscription flow controller that enforces a sliding window of
16
+ * unacknowledged events. When the window is full, incoming events are
17
+ * buffered. When the client sends `rpc.ack` with a cursor, events up
18
+ * to that cursor are acknowledged and buffered events are flushed.
19
+ *
20
+ * If the buffer exceeds {@link MAX_BUFFER_SIZE}, the subscription is
21
+ * closed via the provided `onOverflow` callback to prevent unbounded
22
+ * memory growth.
23
+ */
24
+ export class FlowController {
25
+ /** Ordered list of cursors for events that have been sent but not yet acknowledged. */
26
+ private unacked: string[] = [];
27
+
28
+ /** Buffer of events waiting to be sent once the window opens. */
29
+ private buffer: SubscriptionMessage[] = [];
30
+
31
+ /** Whether the controller has been closed due to overflow. */
32
+ private closed = false;
33
+
34
+ constructor(
35
+ private readonly subscriptionId: JsonRpcId,
36
+ private readonly maxInFlight: number,
37
+ private readonly send: (response: JsonRpcSuccessResponse) => void,
38
+ private readonly onOverflow: () => void,
39
+ ) {}
40
+
41
+ /**
42
+ * Accept an incoming {@link SubscriptionMessage} from the EventLog listener.
43
+ * If the window has room, send immediately. Otherwise buffer.
44
+ */
45
+ public push(message: SubscriptionMessage): void {
46
+ if (this.closed) {
47
+ return;
48
+ }
49
+
50
+ if (this.unacked.length < this.maxInFlight) {
51
+ this.sendMessage(message);
52
+ } else {
53
+ this.buffer.push(message);
54
+
55
+ if (this.buffer.length > MAX_BUFFER_SIZE) {
56
+ log.warn(
57
+ `FlowController: buffer overflow for subscription ${String(this.subscriptionId)}, ` +
58
+ `closing subscription (buffer=${this.buffer.length}, unacked=${this.unacked.length})`
59
+ );
60
+ this.closed = true;
61
+ this.buffer = [];
62
+ this.unacked = [];
63
+ this.onOverflow();
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Process an `rpc.ack` for this subscription. Acknowledges all events up
70
+ * to and including the given cursor, then flushes buffered events into the
71
+ * newly opened window slots.
72
+ */
73
+ public ack(cursor: string): void {
74
+ if (this.closed) {
75
+ return;
76
+ }
77
+
78
+ const idx = this.unacked.lastIndexOf(cursor);
79
+ if (idx === -1) {
80
+ // Unknown cursor — could be a stale or duplicate ack. Ignore silently.
81
+ log.debug(`FlowController: unknown cursor in ack for subscription ${String(this.subscriptionId)}: ${cursor}`);
82
+ return;
83
+ }
84
+
85
+ // Remove all entries up to and including the acked cursor.
86
+ this.unacked.splice(0, idx + 1);
87
+
88
+ // Flush buffered messages into the freed window slots.
89
+ while (this.buffer.length > 0 && this.unacked.length < this.maxInFlight) {
90
+ const buffered = this.buffer.shift()!;
91
+ this.sendMessage(buffered);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Returns the number of events currently in flight (sent but unacknowledged).
97
+ */
98
+ public get inFlightCount(): number {
99
+ return this.unacked.length;
100
+ }
101
+
102
+ /**
103
+ * Returns the number of events currently buffered (waiting to be sent).
104
+ */
105
+ public get bufferCount(): number {
106
+ return this.buffer.length;
107
+ }
108
+
109
+ /**
110
+ * Sends a single message over the wire and tracks its cursor.
111
+ */
112
+ private sendMessage(message: SubscriptionMessage): void {
113
+ const response = createJsonRpcSuccessResponse(this.subscriptionId, { subscription: message });
114
+ this.send(response);
115
+ this.unacked.push(message.cursor);
116
+ }
117
+ }
@@ -1,54 +1,80 @@
1
- import type { Dwn, GenericMessage, MessageEvent } from "@enbox/dwn-sdk-js";
2
- import { DwnMethodName } from "@enbox/dwn-sdk-js";
1
+ import type { ActivityLog } from '../admin/activity-log.js';
2
+ import type { AdminConnectionSnapshot } from '../admin/types.js';
3
+ import type { AdminStore } from '../admin/admin-store.js';
4
+ import type { DwnServerConfig } from '../config.js';
5
+ import type { MessageProcessedHook } from '../message-processed-hook.js';
6
+ import type { RateLimiter } from '../rate-limiter.js';
7
+ import type { RegistrationStore } from '../registration/registration-store.js';
8
+ import type { RequestContext } from '../lib/json-rpc-router.js';
9
+ import type { ServerWebSocket } from 'bun';
10
+ import type { WsData } from '../http-api.js';
11
+ import type { Dwn, GenericMessage, SubscriptionMessage } from '@enbox/dwn-sdk-js';
12
+ import type { JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest, JsonRpcResponse, JsonRpcSubscription } from '@enbox/dwn-clients';
3
13
 
4
- import type { WebSocket } from "ws";
5
14
  import log from 'loglevel';
6
- import { v4 as uuidv4 } from 'uuid';
7
-
8
- import type { RequestContext } from "../lib/json-rpc-router.js";
9
- import type { JsonRpcErrorResponse, JsonRpcId, JsonRpcRequest, JsonRpcResponse, JsonRpcSubscription } from "../lib/json-rpc.js";
10
15
 
11
- import { requestCounter } from "../metrics.js";
12
- import { jsonRpcRouter } from "../json-rpc-api.js";
13
- import { JsonRpcErrorCodes, createJsonRpcErrorResponse, createJsonRpcSuccessResponse } from "../lib/json-rpc.js";
14
- import { DwnServerError, DwnServerErrorCode } from "../dwn-error.js";
16
+ import { DwnMethodName } from '@enbox/dwn-sdk-js';
17
+ import { jsonRpcRouter } from '../json-rpc-api.js';
18
+ import { requestCounter } from '../metrics.js';
19
+ import { v4 as uuidv4 } from 'uuid';
20
+ import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from '@enbox/dwn-clients';
21
+ import { DEFAULT_MAX_IN_FLIGHT, FlowController } from './flow-controller.js';
22
+ import { DwnServerError, DwnServerErrorCode } from '../dwn-error.js';
15
23
 
16
24
  const HEARTBEAT_INTERVAL = 30_000;
17
25
 
18
26
  /**
19
27
  * SocketConnection handles a WebSocket connection to a DWN using JSON RPC.
20
28
  * It also manages references to the long running RPC subscriptions for the connection.
29
+ *
30
+ * With Bun's native WebSocket, the message/close/error events are dispatched by the
31
+ * Bun.serve() websocket handlers in http-api.ts, which delegate to the public `message()`
32
+ * and `close()` methods on this class.
21
33
  */
22
34
  export class SocketConnection {
23
- private heartbeatInterval: NodeJS.Timer;
35
+ /** Unique identifier for this connection (for admin introspection). */
36
+ public readonly id: string = uuidv4();
37
+
38
+ /** Timestamp when the connection was established (for admin introspection). */
39
+ public readonly connectedAt: number = Date.now();
40
+
41
+ private heartbeatInterval: ReturnType<typeof setInterval>;
24
42
  private subscriptions: Map<JsonRpcId, JsonRpcSubscription> = new Map();
43
+ private flowControllers: Map<JsonRpcId, FlowController> = new Map();
25
44
  private isAlive: boolean;
26
45
 
27
46
  constructor(
28
- private socket: WebSocket,
47
+ private socket: ServerWebSocket<WsData>,
29
48
  private dwn: Dwn,
30
- private onClose?: () => void
49
+ private onCloseCallback?: () => void,
50
+ private maxInFlight: number = DEFAULT_MAX_IN_FLIGHT,
51
+ private activityLog?: ActivityLog,
52
+ private adminStore?: AdminStore,
53
+ private registrationStore?: RegistrationStore,
54
+ private serverConfig?: DwnServerConfig,
55
+ private tenantRateLimiter?: RateLimiter,
56
+ private messageProcessedHooks?: MessageProcessedHook[],
31
57
  ){
32
- socket.on('message', this.message.bind(this));
33
- socket.on('close', this.close.bind(this));
34
- socket.on('error', this.error.bind(this));
35
- socket.on('pong', this.pong.bind(this));
36
-
37
- // Sometimes connections between client <-> server can get borked in such a way that
38
- // leaves both unaware of the borkage. ping messages can be used as a means to verify
39
- // that the remote endpoint is still responsive. Server will ping each socket every 30s
40
- // if a pong hasn't received from a socket by the next ping, the server will terminate
41
- // the socket connection
58
+ // Bun handles ping/pong automatically at the protocol level, but we still
59
+ // want an application-level heartbeat to detect dead connections.
42
60
  this.isAlive = true;
43
61
  this.heartbeatInterval = setInterval(() => {
44
62
  if (this.isAlive === false) {
45
63
  this.close();
64
+ return;
46
65
  }
47
66
  this.isAlive = false;
48
67
  this.socket.ping();
49
68
  }, HEARTBEAT_INTERVAL);
50
69
  }
51
70
 
71
+ /**
72
+ * Called when a pong is received (triggered by Bun's built-in ping/pong handling).
73
+ */
74
+ pong(): void {
75
+ this.isAlive = true;
76
+ }
77
+
52
78
  /**
53
79
  * Checks to see if the incoming `JsonRpcId` is already in use for a subscription.
54
80
  */
@@ -65,7 +91,7 @@ export class SocketConnection {
65
91
  throw new DwnServerError(
66
92
  DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdExists,
67
93
  `the subscription with id ${subscription.id} already exists`
68
- )
94
+ );
69
95
  }
70
96
 
71
97
  this.subscriptions.set(subscription.id, subscription);
@@ -81,12 +107,24 @@ export class SocketConnection {
81
107
  throw new DwnServerError(
82
108
  DwnServerErrorCode.ConnectionSubscriptionJsonRpcIdNotFound,
83
109
  `the subscription with id ${id} was not found`
84
- )
110
+ );
85
111
  }
86
112
 
87
113
  const connection = this.subscriptions.get(id);
88
114
  await connection.close();
89
115
  this.subscriptions.delete(id);
116
+ this.flowControllers.delete(id);
117
+ }
118
+
119
+ /**
120
+ * Acknowledges subscription events up to the given cursor, advancing the
121
+ * flow-control window for the subscription.
122
+ */
123
+ ackSubscription(id: JsonRpcId, cursor: string): void {
124
+ const fc = this.flowControllers.get(id);
125
+ if (fc) {
126
+ fc.ack(cursor);
127
+ }
90
128
  }
91
129
 
92
130
  /**
@@ -95,10 +133,7 @@ export class SocketConnection {
95
133
  async close(): Promise<void> {
96
134
  clearInterval(this.heartbeatInterval);
97
135
 
98
- // clean up all socket event listeners
99
- this.socket.removeAllListeners();
100
-
101
- const closePromises = [];
136
+ const closePromises: Promise<void>[] = [];
102
137
  for (const [id, subscription] of this.subscriptions) {
103
138
  closePromises.push(subscription.close());
104
139
  this.subscriptions.delete(id);
@@ -107,56 +142,52 @@ export class SocketConnection {
107
142
  // close all of the associated subscriptions
108
143
  await Promise.all(closePromises);
109
144
 
145
+ // clear all flow controllers
146
+ this.flowControllers.clear();
147
+
110
148
  // close the socket.
111
149
  this.socket.close();
112
150
 
113
151
  // if there was a close handler passed call it after the connection has been closed
114
- if (this.onClose !== undefined) {
115
- this.onClose();
152
+ if (this.onCloseCallback !== undefined) {
153
+ this.onCloseCallback();
116
154
  }
117
155
  }
118
156
 
119
- /**
120
- * Pong messages are automatically sent in response to ping messages as required by
121
- * the websocket spec. So, no need to send explicit pongs.
122
- */
123
- private pong(): void {
124
- this.isAlive = true;
125
- }
126
-
127
157
  /**
128
158
  * Log the error and close the connection.
129
159
  */
130
- private async error(error:Error): Promise<void>{
160
+ async error(error: Error): Promise<void> {
131
161
  log.error(`SocketConnection error, terminating connection`, error);
132
- this.socket.terminate();
162
+ this.socket.close();
133
163
  await this.close();
134
164
  }
135
165
 
136
166
  /**
137
167
  * Handles a `JSON RPC 2.0` encoded message.
168
+ * This is called by Bun's websocket message handler via http-api.ts.
138
169
  */
139
- private async message(dataBuffer: Buffer): Promise<void> {
170
+ async message(dataBuffer: Buffer): Promise<void> {
140
171
  const requestData = dataBuffer.toString();
141
172
  if (!requestData) {
142
173
  return this.send(createJsonRpcErrorResponse(
143
174
  uuidv4(),
144
175
  JsonRpcErrorCodes.BadRequest,
145
176
  'request payload required.'
146
- ))
177
+ ));
147
178
  }
148
179
 
149
180
  let jsonRequest: JsonRpcRequest;
150
181
  try {
151
182
  jsonRequest = JSON.parse(requestData);
152
- } catch(error) {
183
+ } catch (error) {
153
184
  const errorResponse = createJsonRpcErrorResponse(
154
185
  uuidv4(),
155
186
  JsonRpcErrorCodes.BadRequest,
156
187
  (error as Error).message
157
188
  );
158
189
  return this.send(errorResponse);
159
- };
190
+ }
160
191
 
161
192
  const requestContext = await this.buildRequestContext(jsonRequest);
162
193
  const { jsonRpcResponse } = await jsonRpcRouter.handle(jsonRequest, requestContext);
@@ -164,55 +195,101 @@ export class SocketConnection {
164
195
  requestCounter.inc({ method: jsonRequest.method, error: 1 });
165
196
  } else {
166
197
  requestCounter.inc({
167
- method: jsonRequest.method,
168
- status: jsonRpcResponse?.result?.reply?.status?.code || 0,
198
+ method : jsonRequest.method,
199
+ status : jsonRpcResponse?.result?.reply?.status?.code || 0,
169
200
  });
170
201
  }
171
202
  this.send(jsonRpcResponse);
172
203
  }
173
204
 
174
205
  /**
175
- * Sends a JSON encoded Buffer through the Websocket.
206
+ * Returns the number of active subscriptions on this connection.
207
+ */
208
+ get subscriptionCount(): number {
209
+ return this.subscriptions.size;
210
+ }
211
+
212
+ /**
213
+ * Returns a serializable snapshot of this connection for the admin inspector.
214
+ */
215
+ toSnapshot(): AdminConnectionSnapshot {
216
+ const subscriptions = Array.from(this.flowControllers.entries()).map(
217
+ ([id, fc]): AdminConnectionSnapshot['subscriptions'][number] => ({
218
+ id : id as string | number,
219
+ inflight : fc.inFlightCount,
220
+ buffered : fc.bufferCount,
221
+ }),
222
+ );
223
+
224
+ return {
225
+ id : this.id,
226
+ connectedAt : new Date(this.connectedAt).toISOString(),
227
+ subscriptionCount : this.subscriptions.size,
228
+ subscriptions,
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Sends a JSON encoded string through the WebSocket.
176
234
  */
177
235
  private send(response: JsonRpcResponse | JsonRpcErrorResponse): void {
178
236
  this.socket.send(JSON.stringify(response));
179
237
  }
180
238
 
181
239
  /**
182
- * Creates a subscription handler to send messages matching the subscription requested.
183
- *
184
- * Wraps the incoming `message` in a `JSON RPC Success Response` using the original subscription`JSON RPC Id` to send through the WebSocket.
240
+ * Creates a flow-controlled subscription handler that enforces the
241
+ * `maxInFlight` window. Returns a `SubscriptionListener` to be passed
242
+ * to the EventLog, and stores the `FlowController` for later `rpc.ack`
243
+ * processing.
185
244
  */
186
- private createSubscriptionHandler(id: JsonRpcId): (message: MessageEvent) => void {
187
- return (event) => {
188
- const response = createJsonRpcSuccessResponse(id, { event });
189
- this.send(response);
190
- }
245
+ private createSubscriptionHandler(id: JsonRpcId): (message: SubscriptionMessage) => void {
246
+ const fc = new FlowController(
247
+ id,
248
+ this.maxInFlight,
249
+ (response) => {
250
+ this.send(response);
251
+ },
252
+ () => {
253
+ // overflow: close the subscription to prevent OOM
254
+ this.closeSubscription(id).catch((err) => {
255
+ log.error(`FlowController: error closing subscription ${String(id)} on overflow`, err);
256
+ });
257
+ },
258
+ );
259
+
260
+ this.flowControllers.set(id, fc);
261
+
262
+ return (message) => {
263
+ fc.push(message);
264
+ };
191
265
  }
192
266
 
193
267
  /**
194
268
  * Builds a `RequestContext` object to use with the `JSON RPC API`.
195
- *
196
- * Adds a `subscriptionHandler` for `Subscribe` messages.
197
269
  */
198
270
  private async buildRequestContext(request: JsonRpcRequest): Promise<RequestContext> {
199
271
  const { params, method, subscription } = request;
200
272
 
201
273
  const requestContext: RequestContext = {
202
- transport : 'ws',
203
- dwn : this.dwn,
204
- socketConnection : this,
205
- }
274
+ transport : 'ws',
275
+ dwn : this.dwn,
276
+ socketConnection : this,
277
+ activityLog : this.activityLog,
278
+ adminStore : this.adminStore,
279
+ registrationStore : this.registrationStore,
280
+ config : this.serverConfig,
281
+ tenantRateLimiter : this.tenantRateLimiter,
282
+ messageProcessedHooks : this.messageProcessedHooks,
283
+ };
206
284
 
207
285
  // methods that expect a long-running subscription begin with `rpc.subscribe.`
208
286
  if (method.startsWith('rpc.subscribe.') && subscription) {
209
287
  const { message } = params as { message?: GenericMessage };
210
288
  if (message?.descriptor.method === DwnMethodName.Subscribe) {
211
- const handlerFunc = this.createSubscriptionHandler(subscription.id);
212
289
  requestContext.subscriptionRequest = {
213
- id: subscription.id,
214
- subscriptionHandler: (message): void => handlerFunc(message),
215
- }
290
+ id : subscription.id,
291
+ subscriptionHandler : this.createSubscriptionHandler(subscription.id),
292
+ };
216
293
  }
217
294
  }
218
295