serverless-ircd 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,410 @@
1
+ # AWS TCP+TLS Deployment Guide (`irc+tls` :6697)
2
+
3
+ End-to-end guide for deploying the **raw TCP+TLS** transport of
4
+ ServerlessIRCd to AWS (Phase 7 of `PLAN.md`, TICKET-056). This is the
5
+ RFC 1459/2812-compliant path that older clients, bots, and bridges use.
6
+ The default **wss** transport is documented separately in
7
+ [`AWS-Deployment.md`](./AWS-Deployment.md); the two coexist in a single
8
+ CDK stack and a deployment can enable either or both.
9
+
10
+ > **Read first:** [`ADR-009`](./ADR-009-dual-transport-wss-and-irc-tls.md)
11
+ > for *why* a second transport exists and the per-platform mapping.
12
+ > This doc covers the *how*.
13
+
14
+ **Acceptance criterion (TICKET-058):** a new contributor can deploy
15
+ their own TCP+TLS staging instance following only this doc.
16
+
17
+ Cross-reference: `apps/aws-stack/src/aws-stack.ts` (CDK), the wss path
18
+ (`AWS-Deployment.md`), TICKET-054 (mTLS), ADR-006 (SASL scope).
19
+
20
+ ---
21
+
22
+ ## 1. What gets deployed
23
+
24
+ The wss path is API Gateway WebSocket → Lambda → DynamoDB. The TCP path
25
+ **cannot** reuse API Gateway (it is WebSocket-only), so it adds a second
26
+ entry point: a **Network Load Balancer** with a `TLS` listener that
27
+ terminates TLS and invokes a **Lambda streaming function** as the
28
+ target. The Lambda reuses the exact same DynamoDB tables and the same
29
+ IRC core as the wss path — only the I/O shell differs.
30
+
31
+ ```
32
+ IRC client (irc+tls :6697)
33
+
34
+ │ TLS handshake (client ⟷ NLB / ACM cert)
35
+
36
+ ┌──────────────────────────────────────────────────────────────┐
37
+ │ Network Load Balancer (internet-facing, 2-AZ VPC) │
38
+ │ • TLS listener on :6697 (ACM certificate) │
39
+ │ • terminates TLS, forwards plaintext chunks │
40
+ │ • flow metadata: x-forwarded-for / x-forwarded-port │
41
+ └──────────────────────────┬───────────────────────────────────┘
42
+ │ invoke (per chunk, base64 body)
43
+
44
+ ┌──────────────────────────────────────────────────────────────┐
45
+ │ Lambda: IrcNlbHandler (Node 24, nlbStreamHandler) │
46
+ │ • TcpByteStreamTransport reassembles \r\n lines │
47
+ │ (partial-line buffer persisted to Connections row) │
48
+ │ • ConnectionActor + AwsRuntime (same core as $default) │
49
+ │ • connId = nlb-<ip>-<port> (distinct from APIGW ids) │
50
+ └──────────────────────────┬───────────────────────────────────┘
51
+ │ read/write
52
+
53
+ ┌──────────────────────────────────────────────────────────────┐
54
+ │ DynamoDB (same 5 tables as the wss path) │
55
+ │ Connections (+ transportBuffer attr) ChannelMeta │
56
+ │ ChannelMembers Nicks Accounts │
57
+ └──────────────────────────────────────────────────────────────┘
58
+ ```
59
+
60
+ Both transports live in **one CDK stack** (`IrcAwsStack`). The TCP path
61
+ is opt-in: it is only provisioned when `tcpTlsDomainName` is supplied.
62
+ When enabled, the stack adds (verified by `apps/aws-stack/tests/stack.test.ts`):
63
+
64
+ | Resource | Purpose |
65
+ |---------------------------------------|----------------------------------------------------|
66
+ | `AWS::CertificateManager::Certificate`| TLS cert for the domain (DNS-validated). |
67
+ | `AWS::EC2::Vpc` (`NlbVpc`) | Dedicated 2-AZ VPC, **no NAT gateways**. |
68
+ | `AWS::ElasticLoadBalancingV2::LoadBalancer` | Internet-facing NLB. |
69
+ | `AWS::ElasticLoadBalancingV2::Listener` (`IrcNlbTlsListener`) | `TLS` on :6697. |
70
+ | `AWS::ElasticLoadBalancingV2::TargetGroup` | `TargetType: lambda`, target = `IrcNlbHandler`. |
71
+ | `AWS::Lambda::Function` (`IrcNlbHandler`) | `nlbStreamHandler` export, Node 24. |
72
+ | `CfnOutput: TcpConnectUrl` | `<nlb-dns>:6697`. |
73
+
74
+ The **parser → reducer → dispatch pipeline is shared verbatim** with the
75
+ wss path (`$default`). Only the framing differs: each NLB chunk is a
76
+ fresh Lambda invocation, so the `TcpByteStreamTransport`'s partial-line
77
+ buffer is snapshotted to a `transportBuffer` attribute on the
78
+ `Connections` row and rehydrated on the next chunk — no bytes are lost
79
+ across the compute boundary.
80
+
81
+ ---
82
+
83
+ ## 2. Prerequisites
84
+
85
+ | Requirement | Detail |
86
+ |-------------------|---------------------------------------------------------------------|
87
+ | Everything in `AWS-Deployment.md` §2 | Same CDK v2, pnpm 11, Node 24, AWS account, `cdk bootstrap`. |
88
+ | ACM certificate | Provisioned automatically by the stack for the domain you pass; the hosted zone must exist in Route 53 (or be validated out-of-band) for DNS validation. |
89
+ | A domain | The public hostname (e.g. `irc.example.com`) you will pass as `tcpTlsDomainName`. |
90
+ | NLB Lambda invoke | The target group uses `TargetType.LAMBDA`; no ALB/Lambda-HTTP needed. |
91
+
92
+ No extra IAM beyond what the stack already grants — the CDK adds the
93
+ `elasticloadbalancing.amazonaws.com` invoke permission and the NLB
94
+ handler's DynamoDB grants automatically.
95
+
96
+ ---
97
+
98
+ ## 3. How the TCP path is enabled
99
+
100
+ The `IrcAwsStack` construct accepts an optional prop
101
+ (`apps/aws-stack/src/aws-stack.ts`):
102
+
103
+ ```ts
104
+ export interface IrcStackProps extends StackProps {
105
+ readonly environmentName?: string;
106
+ readonly serverName?: string;
107
+ readonly networkName?: string;
108
+ readonly motdLines?: string[];
109
+ /** When provided, provisions NLB + ACM cert + Lambda streaming target. */
110
+ readonly tcpTlsDomainName?: string;
111
+ }
112
+ ```
113
+
114
+ Omit `tcpTlsDomainName` → only the wss (API Gateway WebSocket) path is
115
+ deployed (the default; what `AWS-Deployment.md` describes). Provide it →
116
+ the TCP+TLS path is **added alongside** the wss path; both share the
117
+ same DynamoDB tables, server identity, and `MANAGEMENT_URL`.
118
+
119
+ `bin/aws.ts` reads `tcpTlsDomainName` from CDK context so it can be
120
+ passed on the CLI without editing source:
121
+
122
+ ```ts
123
+ const tcpTlsDomainName =
124
+ app.node.tryGetContext('tcpTlsDomainName') as string | undefined;
125
+ ```
126
+
127
+ …then forwarded into `IrcStackProps.tcpTlsDomainName`. When the context
128
+ var is absent (the default), the prop is omitted and only the wss path
129
+ deploys — the existing pipeline stays green.
130
+
131
+ ---
132
+
133
+ ## 4. Deploy staging (TCP+TLS)
134
+
135
+ ```bash
136
+ # From the repo root:
137
+ pnpm deploy:aws:staging -- \
138
+ -c environmentName=staging \
139
+ -c tcpTlsDomainName=irc-staging.example.com
140
+ ```
141
+
142
+ This is the same `cdk deploy --all` the wss path uses
143
+ (`apps/aws-stack/package.json` `deploy:staging`), just with the extra
144
+ context var. The deploy will:
145
+
146
+ 1. Create the ACM certificate for `irc-staging.example.com` (DNS
147
+ validation via Route 53; if the hosted zone is not in this account,
148
+ validate out-of-band and import the cert ARN — see §9.2).
149
+ 2. Provision `NlbVpc`, the internet-facing NLB, the `TLS` listener on
150
+ `:6697`, and the `IrcNlbHandler` Lambda target group.
151
+ 3. Grant the NLB handler the same DynamoDB access as the wss handler
152
+ and inject the same table names, `SERVER_NAME`, `NETWORK_NAME`,
153
+ `MOTD`, and `MANAGEMENT_URL`.
154
+ 4. Emit the new stack output:
155
+
156
+ ```
157
+ TcpConnectUrl = <nlb-dns>.elb.<region>.amazonaws.com:6697
158
+ ```
159
+
160
+ The wss path (`ConnectUrl`, `ManagementUrl`) is **still deployed and
161
+ unchanged** — enabling TCP does not disable wss.
162
+
163
+ ### 4.1 Wait for DNS / cert validation
164
+
165
+ The first deploy can take several minutes while ACM validates the
166
+ certificate. `cdk deploy` waits on the certificate resource; do not
167
+ interrupt it. If validation times out, confirm the hosted zone for the
168
+ domain is in this account and Route 53 can write the `_acme-challenge`
169
+ CNAME.
170
+
171
+ ### 4.2 Point a DNS record at the NLB (recommended)
172
+
173
+ The NLB prints a long `.elb.amazonaws.com` DNS name. For a stable,
174
+ owned hostname, create an **alias A record** (Route 53) or a CNAME
175
+ pointing `irc-staging.example.com` → the NLB DNS name. Clients then
176
+ connect to `irc-staging.example.com:6697`.
177
+
178
+ (You can connect directly to the raw NLB DNS, but the cert is issued for
179
+ your domain, so TLS SNI/hostname verification wants the owned name.)
180
+
181
+ ### 4.3 Smoke test over TLS
182
+
183
+ ```bash
184
+ printf 'NICK smoke\r\nUSER smoke 0 * :Smoke\r\nQUIT :smoke\r\n' | \
185
+ timeout 10 openssl s_client -connect irc-staging.example.com:6697 -quiet | \
186
+ grep -q '001' && echo "SMOKE PASS" || echo "SMOKE FAIL"
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 5. Deploy production
192
+
193
+ ```bash
194
+ pnpm deploy:aws:prod -- \
195
+ -c environmentName=production \
196
+ -c tcpTlsDomainName=irc.example.com
197
+ ```
198
+
199
+ Production deploys are manual. The staging CI workflow
200
+ (`deploy-aws.yml`) deploys only the wss path (`-c environmentName=staging`
201
+ without `tcpTlsDomainName`); there is **no auto-deploy for the TCP path
202
+ yet** — add a `deploy-aws-tcp.yml` workflow (mirroring `deploy-cf-tcp.yml`)
203
+ as a follow-up when the TCP path is ready for continuous staging.
204
+
205
+ ---
206
+
207
+ ## 6. Operational model & limits
208
+
209
+ The NLB + Lambda streaming model has hard platform limits the wss path
210
+ does not. Understanding these is **required** before production:
211
+
212
+ | Limit | Impact on IRC |
213
+ |--------------------------------|------------------------------------------------------------|
214
+ | **NLB idle timeout** (10–350s) | A connection with no client data for this long is closed. IRC clients that only receive (lurkers) hit this. Mitigation: client-side PING, or accept reconnects. |
215
+ | **No server-push** | The Lambda can only respond to a client chunk; it cannot proactively push bytes. Real-time fanout (PRIVMSG broadcast, QUIT) **piggybacks on the recipient's next chunk** (response) or flows via the `MANAGEMENT_URL` (APIGW `postToConnection`) when both transports are deployed. |
216
+ | **No force-close from Lambda** | A `Disconnect("Excess Flood")` effect cannot close the NLB-held socket from inside the invocation. Canonical teardown is the sweeper + `idleSince` TTL (same pattern as the APIGW `$default` path). |
217
+ | **Flow rebalancing** | The NLB may rebalance the underlying flow at any time; the 4-tuple `(ip, port)` stays stable for the connection's life, so `connId` (`nlb-<ip>-<port>`) is stable. |
218
+ | **Lambda invoke per chunk** | Each client byte-chunk is one invocation. The `transportBuffer` reassembly across invocations is correct but adds a DynamoDB write per chunk. Coalescing client writes reduces cost. |
219
+
220
+ Because of the no-server-push limit, **deploying both transports is the
221
+ recommended shape for full-featured IRC**: the wss path (or just the
222
+ `MANAGEMENT_URL` from it) provides the push channel for real-time
223
+ fanout, while the TCP path serves RFC-compliant clients. An NLB-only
224
+ deploy (`managementApi = null`) silently no-ops cross-connection sends —
225
+ documented platform limit, see `packages/aws-adapter/src/handlers/nlb-stream.ts`.
226
+
227
+ State lifecycle is identical to the wss path: the **sweeper** Lambda
228
+ (`rate(5 minutes)`) prunes `Connections` rows past the hard TTL, and the
229
+ **ping-checker** Lambda (`rate(1 minute)`) sends PING / disconnects
230
+ no-PONG. Both cover TCP connections because the `connId` lives in the
231
+ same `Connections` table.
232
+
233
+ ---
234
+
235
+ ## 7. Configuration reference
236
+
237
+ All knobs are CDK context vars (`-c key=value`) or construct props. The
238
+ TCP-specific ones:
239
+
240
+ | Knob (context var / prop) | Purpose | Default when omitted |
241
+ |-------------------------------|--------------------------------------------------|----------------------------|
242
+ | `tcpTlsDomainName` | Domain for the ACM cert + NLB TLS listener. | *(TCP path not provisioned)* |
243
+ | `environmentName` | Env prefix for table names + stack id. | `staging` |
244
+
245
+ The NLB handler Lambda inherits the **same** identity/table knobs as the
246
+ wss handler (`serverName`, `networkName`, `motd`, `environmentName`),
247
+ injected in `aws-stack.ts`:
248
+
249
+ - `SERVER_NAME`, `NETWORK_NAME`, `MOTD` (read by the shared
250
+ `config-loader.ts`).
251
+ - `MANAGEMENT_URL` — the wss stage's callback URL, so cross-connection
252
+ fanout reaches recipients on either transport.
253
+ - `<TABLE>_TABLE` for each of the five tables (prefixed by environment).
254
+
255
+ The Lambda handler entry is the same file
256
+ (`packages/aws-adapter/src/handlers/index.ts`); esbuild tree-shakes to
257
+ the `nlbStreamHandler` export. Runtime `nodejs24.x`; `@aws-sdk/*`
258
+ excluded from the bundle (ships with the runtime).
259
+
260
+ There are **no TCP-specific DynamoDB tables** — the existing `Connections`
261
+ table gains a `transportBuffer` string attribute (SET when non-empty,
262
+ REMOVE when empty) on rows whose `connectionId` starts with `nlb-`.
263
+
264
+ ---
265
+
266
+ ## 8. mTLS (SASL EXTERNAL)
267
+
268
+ SASL `EXTERNAL` needs the client certificate to reach the application
269
+ (ADR-006, TICKET-054). On the TCP path:
270
+
271
+ - The NLB `TLS` listener terminates TLS with the ACM cert.
272
+ - For client-cert (mTLS) verification, configure the NLB listener's
273
+ **mutual TLS / trust store** (AWS: a trust-store bundle of CA certs on
274
+ the listener). The verified client-cert subject is then surfaced and
275
+ the origin's `MtlsIdentityProvider` port resolves it to an account,
276
+ enabling `AUTHENTICATE EXTERNAL` (`903` on success, `904` otherwise).
277
+ - Without a trust store on the listener, EXTERNAL is not available and
278
+ the `sasl` cap advertises `PLAIN` only (driven by whether
279
+ `ctx.mtlsIdentity` is defined — TICKET-079).
280
+
281
+ Cross-reference: TICKET-054 (mTLS support), TICKET-079 (EXTERNAL
282
+ advertisement). The wss path achieves the same via API Gateway
283
+ custom-domain mTLS (`AWS-Deployment.md`).
284
+
285
+ ---
286
+
287
+ ## 9. Troubleshooting
288
+
289
+ ### 9.1 `cdk deploy` hangs on the ACM certificate
290
+
291
+ DNS validation is waiting on a CNAME it cannot create. Confirm the
292
+ hosted zone for `tcpTlsDomainName` is in this AWS account/region. If the
293
+ zone is elsewhere, validate out-of-band and import the certificate:
294
+
295
+ ```ts
296
+ // In aws-stack.ts, replace `new Certificate(...)` with:
297
+ import { Certificate, Certificate.fromCertificateArn } from 'aws-cdk-lib/aws-certificatemanager';
298
+ const cert = Certificate.fromCertificateArn(this, 'TlsCert', 'arn:aws:acm:...:certificate/...');
299
+ ```
300
+
301
+ (This is a code edit; the default path assumes in-account Route 53.)
302
+
303
+ ### 9.2 Client connects to :6697 but gets no `001`
304
+
305
+ TLS completed (NLB + cert are fine) but the Lambda did not respond with
306
+ the welcome block. Causes:
307
+
308
+ - The first chunk did not contain a complete `NICK`/`USER` line (split
309
+ across chunks). The `transportBuffer` will complete it on the next
310
+ chunk — send the rest. A well-behaved client sends the full handshake
311
+ in one write.
312
+ - CloudWatch Logs for `IrcNlbHandler` show an actor throw. The handler
313
+ catches and logs (`[nlb-stream] actor.receiveTextFrame failed`); it
314
+ does **not** crash — the connection continues, but a malformed line is
315
+ dropped. Tail with `aws logs tail /aws/lambda/<IrcNlbHandler>`.
316
+ - `MANAGEMENT_URL` is wrong → cross-connection fanout no-ops, but
317
+ self-reply (welcome, PONG) still works, so `001` should still appear.
318
+
319
+ ### 9.3 Broadcasts (PRIVMSG to a channel) don't reach TCP recipients
320
+
321
+ The no-server-push limit (§6). Outbound bytes only flow when the
322
+ recipient sends a chunk (the Lambda response). For real-time delivery,
323
+ ensure the deploy includes the wss path so `MANAGEMENT_URL` is set —
324
+ then fanout uses `ApiGatewayManagementApi.postToConnection`, which works
325
+ for both transports. If `MANAGEMENT_URL` is unset, `managementApi` is
326
+ `null` and cross-connection sends are silent no-ops.
327
+
328
+ ### 9.4 Lurkers get disconnected after a few minutes
329
+
330
+ The NLB idle timeout. A receiving-only client sends no chunks, so the
331
+ Lambda is never invoked and the NLB closes the idle flow. Mitigations:
332
+
333
+ - Configure the client to send a periodic `PING` (most clients do).
334
+ - Raise the NLB idle timeout (max 350s) — but this only delays, it does
335
+ not eliminate, the issue. The wss path does not have this limit and is
336
+ preferred for lurkers.
337
+
338
+ ### 9.5 Messages split across chunks get lost
339
+
340
+ This is a transport-layer regression, not config — the
341
+ `TcpByteStreamTransport` + `transportBuffer` persistence is contract-
342
+ tested (`packages/aws-adapter/tests/nlb-stream.test.ts`). If you see it,
343
+ run `pnpm --filter @serverless-ircd/aws-adapter test`; a failure there
344
+ will pinpoint it.
345
+
346
+ ### 9.6 `TcpConnectUrl` output is missing
347
+
348
+ `tcpTlsDomainName` was not passed. Confirm `-c tcpTlsDomainName=...` is
349
+ on the `cdk deploy` command and that `bin/aws.ts` forwards it into
350
+ `IrcStackProps` (§3). Without it, only the wss resources deploy.
351
+
352
+ ---
353
+
354
+ ## 10. Cost notes (versus the wss path)
355
+
356
+ | Dimension | wss path (APIGW WebSocket) | TCP path (NLB + Lambda streaming) |
357
+ |------------------------|----------------------------------------|----------------------------------------------|
358
+ | **Per-message** | APIGW WebSocket + 1 Lambda invoke. | **1 Lambda invoke per byte-chunk** (more invokes for chatty clients). |
359
+ | **Idle connections** | APIGW connection minutes. | NLB LCUs (Load Balancer Capacity Units) per hour; no Lambda cost when idle. |
360
+ | **TLS termination** | APIGW (managed). | NLB (per-LCU) + ACM (free). |
361
+ | **State** | DynamoDB (shared). | DynamoDB (shared) + extra `transportBuffer` write per chunk. |
362
+ | **Cross-connection fanout** | `postToConnection` per recipient. | Same — but only works if `MANAGEMENT_URL` (wss path) is deployed. |
363
+
364
+ The dominant cost driver on the TCP path is **invocations per chunk**.
365
+ Clients that coalesce writes (send the full `NICK\r\nUSER\r\n` in one
366
+ chunk, not byte-by-byte) are dramatically cheaper. Recommend the wss
367
+ path for high-fanout / lurker-heavy workloads and the TCP path for
368
+ RFC-compliant clients that need it.
369
+
370
+ ---
371
+
372
+ ## 11. Quick reference
373
+
374
+ ```bash
375
+ # Staging (TCP+TLS added alongside wss)
376
+ pnpm deploy:aws:staging -- \
377
+ -c environmentName=staging \
378
+ -c tcpTlsDomainName=irc-staging.example.com
379
+
380
+ # Read the TCP endpoint from stack outputs
381
+ aws cloudformation describe-stacks \
382
+ --stack-name IrcAwsStack-staging \
383
+ --query 'Stacks[0].Outputs[?OutputKey==`TcpConnectUrl`].OutputValue' \
384
+ --output text
385
+
386
+ # Smoke over TLS
387
+ printf 'NICK smoke\r\nUSER smoke 0 * :Smoke\r\nQUIT :smoke\r\n' | \
388
+ openssl s_client -connect irc-staging.example.com:6697 -quiet | grep '001'
389
+
390
+ # Production (manual)
391
+ pnpm deploy:aws:prod -- \
392
+ -c environmentName=production \
393
+ -c tcpTlsDomainName=irc.example.com
394
+
395
+ # Tests (synth-time assertions for the NLB path)
396
+ pnpm --filter @serverless-ircd/aws-stack test
397
+ pnpm --filter @serverless-ircd/aws-adapter test
398
+ ```
399
+
400
+ Key files:
401
+
402
+ | Path | What |
403
+ |---------------------------------------------------|--------------------------------------------|
404
+ | `apps/aws-stack/src/aws-stack.ts` | CDK stack — NLB/VPC/cert conditional block.|
405
+ | `apps/aws-stack/bin/aws.ts` | Reads `tcpTlsDomainName` from context. |
406
+ | `packages/aws-adapter/src/handlers/nlb-stream.ts` | `handleNlbStream` + `deriveNlbConnectionId`.|
407
+ | `packages/aws-adapter/src/handlers/index.ts` | `nlbStreamHandler` Lambda export. |
408
+ | `apps/aws-stack/tests/stack.test.ts` | Synth-time NLB assertions (lines 466–559). |
409
+ | `packages/aws-adapter/tests/nlb-stream.test.ts` | NLB framing/transport-buffer unit tests. |
410
+ | `docs/AWS-Deployment.md` | The wss path this sits alongside. |