sonamu 0.11.16 → 0.13.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 (322) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +1 -2
  2. package/dist/ai/agents/agent.js +2 -3
  3. package/dist/ai/agents/index.js +1 -2
  4. package/dist/ai/index.js +1 -2
  5. package/dist/ai/providers/rtzr/api.js +1 -2
  6. package/dist/ai/providers/rtzr/error.js +2 -3
  7. package/dist/ai/providers/rtzr/model.js +1 -2
  8. package/dist/ai/providers/rtzr/options.js +1 -2
  9. package/dist/ai/providers/rtzr/provider.js +1 -2
  10. package/dist/ai/providers/rtzr/utils.js +1 -2
  11. package/dist/api/base-frame.js +4 -4
  12. package/dist/api/caster.d.ts +1 -1
  13. package/dist/api/caster.d.ts.map +1 -1
  14. package/dist/api/caster.js +9 -9
  15. package/dist/api/code-converters.js +6 -5
  16. package/dist/api/config.d.ts +1 -1
  17. package/dist/api/config.d.ts.map +1 -1
  18. package/dist/api/config.js +2 -3
  19. package/dist/api/context.d.ts +2 -3
  20. package/dist/api/context.d.ts.map +1 -1
  21. package/dist/api/context.js +2 -3
  22. package/dist/api/decorators.js +2 -3
  23. package/dist/api/http-validator.js +2 -3
  24. package/dist/api/index.js +8 -3
  25. package/dist/api/secret.js +1 -2
  26. package/dist/api/sonamu.d.ts +3 -5
  27. package/dist/api/sonamu.d.ts.map +1 -1
  28. package/dist/api/sonamu.js +30 -24
  29. package/dist/api/validator.js +1 -2
  30. package/dist/api/websocket-helpers.js +2 -3
  31. package/dist/auth/audit-log/builders.js +1 -2
  32. package/dist/auth/audit-log/client-ip.js +2 -3
  33. package/dist/auth/audit-log/events.js +1 -2
  34. package/dist/auth/audit-log/plugin.d.ts.map +1 -1
  35. package/dist/auth/audit-log/plugin.js +2 -3
  36. package/dist/auth/audit-log-ingestor.js +2 -3
  37. package/dist/auth/auth-generator.js +2 -3
  38. package/dist/auth/better-auth-entities.js +1 -2
  39. package/dist/auth/better-auth-request.js +2 -3
  40. package/dist/auth/index.js +2 -3
  41. package/dist/auth/knex-adapter.js +2 -3
  42. package/dist/auth/plugins/entity-definitions/admin.js +1 -2
  43. package/dist/auth/plugins/entity-definitions/anonymous.js +1 -2
  44. package/dist/auth/plugins/entity-definitions/api-key.js +1 -2
  45. package/dist/auth/plugins/entity-definitions/audit-log.js +1 -2
  46. package/dist/auth/plugins/entity-definitions/index.js +1 -2
  47. package/dist/auth/plugins/entity-definitions/jwt.js +1 -2
  48. package/dist/auth/plugins/entity-definitions/organization.js +1 -2
  49. package/dist/auth/plugins/entity-definitions/passkey.js +1 -2
  50. package/dist/auth/plugins/entity-definitions/phone-number.js +1 -2
  51. package/dist/auth/plugins/entity-definitions/sso.js +1 -2
  52. package/dist/auth/plugins/entity-definitions/two-factor.js +1 -2
  53. package/dist/auth/plugins/entity-definitions/username.js +1 -2
  54. package/dist/auth/plugins/wrappers/admin.d.ts +33 -32
  55. package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
  56. package/dist/auth/plugins/wrappers/admin.js +2 -3
  57. package/dist/auth/plugins/wrappers/anonymous.js +2 -3
  58. package/dist/auth/plugins/wrappers/api-key.js +2 -3
  59. package/dist/auth/plugins/wrappers/jwt.js +2 -3
  60. package/dist/auth/plugins/wrappers/organization.js +2 -3
  61. package/dist/auth/plugins/wrappers/passkey.js +2 -3
  62. package/dist/auth/plugins/wrappers/phone-number.d.ts +43 -1
  63. package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
  64. package/dist/auth/plugins/wrappers/phone-number.js +2 -3
  65. package/dist/auth/plugins/wrappers/sso.d.ts +138 -187
  66. package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -1
  67. package/dist/auth/plugins/wrappers/sso.js +1 -2
  68. package/dist/auth/plugins/wrappers/two-factor.d.ts +51 -10
  69. package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
  70. package/dist/auth/plugins/wrappers/two-factor.js +2 -3
  71. package/dist/auth/plugins/wrappers/username.d.ts +39 -5
  72. package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
  73. package/dist/auth/plugins/wrappers/username.js +2 -3
  74. package/dist/bin/build-config.js +5 -5
  75. package/dist/bin/compiler-policy.js +2 -3
  76. package/dist/bin/fixture.js +7 -5
  77. package/dist/bin/hmr-hook-register.js +1 -2
  78. package/dist/bin/migrate-targets.js +2 -3
  79. package/dist/bin/test-command.js +2 -3
  80. package/dist/bin/ts-loader-register.js +2 -3
  81. package/dist/bin/ts-loader-registration.js +2 -3
  82. package/dist/bin/typecheck-web.js +2 -3
  83. package/dist/cache/cache-manager.js +1 -2
  84. package/dist/cache/decorator.js +2 -3
  85. package/dist/cache/drivers.js +1 -2
  86. package/dist/cache/index.js +2 -3
  87. package/dist/cache-control/cache-control.js +1 -2
  88. package/dist/code-formatting/index.js +2 -3
  89. package/dist/code-formatting/oxfmt.js +2 -3
  90. package/dist/code-formatting/oxlint.js +2 -3
  91. package/dist/compress/compress.js +1 -2
  92. package/dist/compress/index.js +2 -2
  93. package/dist/compress/types.d.ts +1 -1
  94. package/dist/compress/types.d.ts.map +1 -1
  95. package/dist/cone/cone-generator.js +2 -3
  96. package/dist/database/_batch_update.js +1 -2
  97. package/dist/database/base-model.js +4 -4
  98. package/dist/database/db.js +4 -4
  99. package/dist/database/knex.js +2 -3
  100. package/dist/database/puri-wrapper.js +2 -3
  101. package/dist/database/puri.js +23 -22
  102. package/dist/database/puri.types.js +1 -2
  103. package/dist/database/transaction-context.js +1 -2
  104. package/dist/database/upsert-builder.js +2 -3
  105. package/dist/dict/en.js +2 -3
  106. package/dist/dict/index.js +3 -3
  107. package/dist/dict/ko.js +2 -3
  108. package/dist/dict/rc-keys.js +1 -2
  109. package/dist/dict/sd.js +2 -3
  110. package/dist/dict/sonamu-dictionary.js +7 -6
  111. package/dist/dict/utils.js +2 -3
  112. package/dist/entity/entity-manager.d.ts +66 -66
  113. package/dist/entity/entity-manager.js +4 -4
  114. package/dist/entity/entity-template-cone.js +2 -3
  115. package/dist/entity/entity.js +21 -18
  116. package/dist/env.js +1 -2
  117. package/dist/exceptions/error-handler.d.ts.map +1 -1
  118. package/dist/exceptions/error-handler.js +2 -3
  119. package/dist/exceptions/so-exceptions.js +2 -3
  120. package/dist/filter/index.js +3 -2
  121. package/dist/filter/types.js +1 -2
  122. package/dist/filter/utils.js +7 -6
  123. package/dist/index.d.ts +1 -0
  124. package/dist/index.d.ts.map +1 -1
  125. package/dist/index.js +42 -6
  126. package/dist/logger/category.js +2 -3
  127. package/dist/logger/configure.js +5 -5
  128. package/dist/migration/code-generation.js +23 -16
  129. package/dist/migration/index-where-predicate.js +2 -3
  130. package/dist/migration/migration-set.js +2 -3
  131. package/dist/migration/migrator.js +2 -3
  132. package/dist/migration/postgresql-schema-reader.js +6 -9
  133. package/dist/migration/slack-confirm.js +2 -3
  134. package/dist/naite/naite-reporter.js +2 -3
  135. package/dist/naite/naite.js +10 -8
  136. package/dist/runtime-type.js +2 -3
  137. package/dist/ssr/hmr-opt-out.js +1 -2
  138. package/dist/ssr/index.js +7 -4
  139. package/dist/ssr/registry.js +1 -2
  140. package/dist/ssr/renderer.js +2 -3
  141. package/dist/storage/base-file.js +1 -2
  142. package/dist/storage/buffered-file.js +2 -3
  143. package/dist/storage/drivers.js +1 -2
  144. package/dist/storage/index.js +2 -3
  145. package/dist/storage/s3-driver.js +1 -2
  146. package/dist/storage/storage-manager.js +2 -3
  147. package/dist/storage/uploaded-file.js +2 -3
  148. package/dist/stream/__tests__/ws-test-helpers.d.ts +108 -0
  149. package/dist/stream/__tests__/ws-test-helpers.d.ts.map +1 -0
  150. package/dist/stream/__tests__/ws-test-helpers.js +259 -0
  151. package/dist/stream/index.d.ts +1 -0
  152. package/dist/stream/index.d.ts.map +1 -1
  153. package/dist/stream/index.js +15 -4
  154. package/dist/stream/sse.js +1 -2
  155. package/dist/stream/ws-audience-resolver.d.ts +8 -5
  156. package/dist/stream/ws-audience-resolver.d.ts.map +1 -1
  157. package/dist/stream/ws-audience-resolver.js +17 -15
  158. package/dist/stream/ws-audience.js +1 -2
  159. package/dist/stream/ws-cluster-bus.d.ts +9 -4
  160. package/dist/stream/ws-cluster-bus.d.ts.map +1 -1
  161. package/dist/stream/ws-cluster-bus.js +2 -3
  162. package/dist/stream/ws-core.d.ts +10 -0
  163. package/dist/stream/ws-core.d.ts.map +1 -1
  164. package/dist/stream/ws-delivery.d.ts +30 -4
  165. package/dist/stream/ws-delivery.d.ts.map +1 -1
  166. package/dist/stream/ws-delivery.js +200 -54
  167. package/dist/stream/ws-frame.d.ts +15 -0
  168. package/dist/stream/ws-frame.d.ts.map +1 -0
  169. package/dist/stream/ws-frame.js +28 -0
  170. package/dist/stream/ws-local-connection-store.d.ts +4 -4
  171. package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
  172. package/dist/stream/ws-local-connection-store.js +3 -4
  173. package/dist/stream/ws-presence-index.d.ts +36 -0
  174. package/dist/stream/ws-presence-index.d.ts.map +1 -0
  175. package/dist/stream/ws-presence-index.js +176 -0
  176. package/dist/stream/ws-presence-store.d.ts +39 -39
  177. package/dist/stream/ws-presence-store.d.ts.map +1 -1
  178. package/dist/stream/ws-presence-store.js +23 -154
  179. package/dist/stream/ws-registry.d.ts +45 -17
  180. package/dist/stream/ws-registry.d.ts.map +1 -1
  181. package/dist/stream/ws-registry.js +170 -62
  182. package/dist/stream/ws-session-id.d.ts +14 -0
  183. package/dist/stream/ws-session-id.d.ts.map +1 -0
  184. package/dist/stream/ws-session-id.js +36 -0
  185. package/dist/stream/ws-telemetry-memory.js +3 -4
  186. package/dist/stream/ws-telemetry-trace.js +1 -2
  187. package/dist/stream/ws-telemetry.js +2 -3
  188. package/dist/stream/ws.d.ts +20 -10
  189. package/dist/stream/ws.d.ts.map +1 -1
  190. package/dist/stream/ws.js +60 -38
  191. package/dist/syncer/api-parser.js +2 -3
  192. package/dist/syncer/checksum.js +4 -4
  193. package/dist/syncer/code-generator.js +4 -4
  194. package/dist/syncer/entity-operations.js +2 -3
  195. package/dist/syncer/event-batcher.js +1 -2
  196. package/dist/syncer/file-copy.js +2 -3
  197. package/dist/syncer/file-patterns.js +2 -3
  198. package/dist/syncer/file-tracking.js +2 -3
  199. package/dist/syncer/filesystem-dependencies.js +1 -2
  200. package/dist/syncer/index.js +8 -2
  201. package/dist/syncer/module-loader.js +8 -6
  202. package/dist/syncer/syncer-actions.js +8 -7
  203. package/dist/syncer/syncer.js +13 -8
  204. package/dist/syncer/watcher.js +2 -3
  205. package/dist/tasks/decorator.js +1 -2
  206. package/dist/tasks/step-wrapper.js +2 -3
  207. package/dist/tasks/workflow-manager.js +2 -3
  208. package/dist/template/entity-converter.js +17 -16
  209. package/dist/template/helpers.js +2 -3
  210. package/dist/template/implementations/entity.template.js +2 -3
  211. package/dist/template/implementations/entry-server.template.js +2 -3
  212. package/dist/template/implementations/generated.template.js +2 -3
  213. package/dist/template/implementations/generated_http.template.js +6 -6
  214. package/dist/template/implementations/generated_sso.template.js +2 -3
  215. package/dist/template/implementations/http_validators.template.js +2 -3
  216. package/dist/template/implementations/init_types.template.js +2 -3
  217. package/dist/template/implementations/model.template.js +4 -4
  218. package/dist/template/implementations/model_test.template.js +2 -3
  219. package/dist/template/implementations/queries.template.js +2 -3
  220. package/dist/template/implementations/sd.template.js +2 -3
  221. package/dist/template/implementations/services.template.js +2 -3
  222. package/dist/template/implementations/view_form.template.js +12 -9
  223. package/dist/template/implementations/view_id_all_select.template.js +2 -3
  224. package/dist/template/implementations/view_list.template.d.ts.map +1 -1
  225. package/dist/template/implementations/view_list.template.js +19 -17
  226. package/dist/template/implementations/view_search_input.template.js +2 -3
  227. package/dist/template/index.js +9 -3
  228. package/dist/template/template-manager.js +2 -3
  229. package/dist/template/template-types.js +1 -2
  230. package/dist/template/template.js +2 -3
  231. package/dist/template/zod-converter.js +54 -47
  232. package/dist/testing/_relation-graph.js +2 -3
  233. package/dist/testing/bootstrap.js +2 -3
  234. package/dist/testing/data-explorer.js +2 -3
  235. package/dist/testing/dev-test-routes.js +2 -3
  236. package/dist/testing/dev-vitest-manager.js +15 -12
  237. package/dist/testing/faker-mappings.js +1 -2
  238. package/dist/testing/filesystem.js +2 -3
  239. package/dist/testing/fixture-generator.js +2 -3
  240. package/dist/testing/fixture-loader.js +1 -2
  241. package/dist/testing/fixture-manager.js +12 -9
  242. package/dist/testing/global-setup.js +5 -5
  243. package/dist/testing/index.js +6 -3
  244. package/dist/testing/naite-vitest-reporter.js +1 -2
  245. package/dist/testing/parallel-db-manager.js +2 -3
  246. package/dist/testing/vitest-helpers.js +2 -3
  247. package/dist/tooling/__tests__/helpers/sonamu-test-root.js +2 -3
  248. package/dist/tooling/cdd-service.js +1 -2
  249. package/dist/tooling/cli-tooling.js +1 -2
  250. package/dist/tooling/fixture-service.js +1 -2
  251. package/dist/tooling/index.js +1 -2
  252. package/dist/tooling/test-run-client.js +2 -3
  253. package/dist/tsdown.api.config.js +1 -2
  254. package/dist/types/types.js +2 -3
  255. package/dist/ui/ai-api.d.ts.map +1 -1
  256. package/dist/ui/ai-api.js +4 -5
  257. package/dist/ui/ai-client.d.ts +2 -2
  258. package/dist/ui/ai-client.d.ts.map +1 -1
  259. package/dist/ui/ai-client.js +9 -8
  260. package/dist/ui/api.d.ts.map +1 -1
  261. package/dist/ui/api.js +11 -8
  262. package/dist/ui/async-route.js +1 -2
  263. package/dist/ui/cdd-service.js +22 -21
  264. package/dist/ui/migrations-api.js +6 -5
  265. package/dist/ui-web/assets/index-QDMyy31K.js +180 -0
  266. package/dist/ui-web/assets/index-dV7akMFB.css +1 -0
  267. package/dist/ui-web/index.html +2 -2
  268. package/dist/utils/async-utils.js +1 -2
  269. package/dist/utils/class-name.js +1 -2
  270. package/dist/utils/console-util.js +1 -2
  271. package/dist/utils/controller.js +2 -3
  272. package/dist/utils/esm-utils.js +1 -2
  273. package/dist/utils/fs-utils.js +2 -3
  274. package/dist/utils/lodash-able.js +1 -2
  275. package/dist/utils/model.js +1 -2
  276. package/dist/utils/object-utils.js +2 -3
  277. package/dist/utils/path-utils.js +2 -3
  278. package/dist/utils/process-utils.js +2 -3
  279. package/dist/utils/runtime-value.js +1 -2
  280. package/dist/utils/sql-parser.js +2 -3
  281. package/dist/utils/type-utils.js +8 -8
  282. package/dist/utils/utils.js +2 -3
  283. package/dist/utils/zod-error.js +2 -3
  284. package/dist/vector/chunking.js +1 -2
  285. package/dist/vector/config.js +1 -2
  286. package/dist/vector/embedding.js +4 -4
  287. package/package.json +63 -63
  288. package/src/ai/agents/agent.ts +1 -1
  289. package/src/api/__tests__/sonamu.plugins.test.ts +11 -1
  290. package/src/api/__tests__/sonamu.websocket-routes.test.ts +234 -0
  291. package/src/api/__tests__/sonamu.websocket.test.ts +241 -24
  292. package/src/api/config.ts +1 -1
  293. package/src/api/context.ts +2 -8
  294. package/src/api/sonamu.ts +46 -28
  295. package/src/auth/audit-log/plugin.ts +2 -0
  296. package/src/compress/types.ts +1 -1
  297. package/src/exceptions/error-handler.ts +3 -2
  298. package/src/index.ts +1 -0
  299. package/src/stream/__tests__/ws-contracts.test.ts +483 -265
  300. package/src/stream/__tests__/ws-multinode.test.ts +326 -0
  301. package/src/stream/__tests__/ws-registry-async.test.ts +855 -0
  302. package/src/stream/__tests__/ws-test-helpers.ts +360 -0
  303. package/src/stream/__tests__/ws.test.ts +342 -145
  304. package/src/stream/index.ts +1 -0
  305. package/src/stream/ws-audience-resolver.ts +21 -19
  306. package/src/stream/ws-cluster-bus.ts +12 -4
  307. package/src/stream/ws-core.ts +14 -0
  308. package/src/stream/ws-delivery.ts +321 -97
  309. package/src/stream/ws-frame.ts +39 -0
  310. package/src/stream/ws-local-connection-store.ts +10 -9
  311. package/src/stream/ws-presence-index.ts +273 -0
  312. package/src/stream/ws-presence-store.ts +64 -284
  313. package/src/stream/ws-registry.ts +272 -85
  314. package/src/stream/ws-session-id.ts +39 -0
  315. package/src/stream/ws.ts +91 -50
  316. package/src/template/implementations/model.template.ts +3 -3
  317. package/src/template/implementations/view_list.template.ts +12 -13
  318. package/src/ui/ai-api.ts +4 -2
  319. package/src/ui/ai-client.ts +2 -2
  320. package/src/ui/api.ts +58 -62
  321. package/dist/ui-web/assets/index-BMdIyKXS.css +0 -1
  322. package/dist/ui-web/assets/index-LpE8u19O.js +0 -178
@@ -1,6 +1,10 @@
1
1
  import { type WebSocketAudience } from "./ws-audience";
2
- import { type WebSocketRegistryStats, type WebSocketRoomId, type WebSocketUserId } from "./ws-core";
2
+ import { type MaybePromise, type WebSocketRoomId, type WebSocketUserId } from "./ws-core";
3
+ import { WebSocketPresenceIndex } from "./ws-presence-index";
3
4
 
5
+ /**
6
+ * 세션 presence snapshot. live 객체가 아니므로 값을 바꿔도 registry나 store 상태에 영향을 주지 않는다.
7
+ */
4
8
  export type WebSocketSessionPresence = {
5
9
  sessionId: string;
6
10
  nodeId: string;
@@ -12,320 +16,96 @@ export type WebSocketSessionPresence = {
12
16
  userId?: WebSocketUserId;
13
17
  };
14
18
 
19
+ export type WebSocketPresenceRegistration = {
20
+ sessionId: string;
21
+ nodeId: string;
22
+ namespace: string;
23
+ active: boolean;
24
+ connectedAt: Date;
25
+ };
26
+
27
+ export type WebSocketRoutingPlan = {
28
+ localSessionIds: string[];
29
+ remoteNodeIds: string[];
30
+ };
31
+
32
+ /**
33
+ * 클러스터 presence replica 계약. 각 노드의 registry가 자기 세션의 mutation을 세션별 호출 순서대로 전달한다.
34
+ * 모든 메서드는 값이나 promise를 반환할 수 있고, registry는 동기 throw도 rejection으로 바꿔 처리한다.
35
+ * userId는 타입까지 일치해야 매칭된다(1과 "1"은 다르다).
36
+ */
15
37
  export interface WebSocketPresenceStore {
16
- register(input: {
17
- sessionId: string;
18
- nodeId: string;
19
- namespace: string;
20
- active?: boolean;
21
- }): WebSocketSessionPresence;
22
- activate(sessionId: string): void;
23
- unregister(sessionId: string): WebSocketSessionPresence | undefined;
24
- touch(sessionId: string): void;
25
- setUserId(sessionId: string, userId: WebSocketUserId): void;
26
- clearUserId(sessionId: string): void;
27
- join(sessionId: string, roomId: WebSocketRoomId): void;
28
- leave(sessionId: string, roomId: WebSocketRoomId): void;
29
- getConnection(sessionId: string): WebSocketSessionPresence | undefined;
30
- getConnectionCount(namespace?: string): number;
31
- getRoomMembers(roomId: WebSocketRoomId, namespace?: string): WebSocketSessionPresence[];
32
- getStats(): WebSocketRegistryStats;
33
- queryAudience(audience: WebSocketAudience): WebSocketSessionPresence[];
38
+ // 설정하면 runtime nodeId와 같아야 한다. 다르면 runtime 생성이 실패한다
39
+ readonly nodeId?: string;
40
+ register(input: WebSocketPresenceRegistration): MaybePromise<void>;
41
+ activate(sessionId: string): MaybePromise<void>;
42
+ unregister(sessionId: string): MaybePromise<void>;
43
+ setUserId(sessionId: string, userId: WebSocketUserId): MaybePromise<void>;
44
+ clearUserId(sessionId: string): MaybePromise<void>;
45
+ join(sessionId: string, roomId: WebSocketRoomId): MaybePromise<void>;
46
+ leave(sessionId: string, roomId: WebSocketRoomId): MaybePromise<void>;
47
+ // nodeId 관점에서 audience를 로컬 세션 ID와 중복 없는 원격 노드 ID로 나눈다. 비활성 세션은 제외한다
48
+ resolveRoute(audience: WebSocketAudience, nodeId: string): MaybePromise<WebSocketRoutingPlan>;
49
+ // 클러스터 전체의 활성 방 인원수
50
+ getRoomMemberCount(roomId: WebSocketRoomId, namespace?: string): MaybePromise<number>;
51
+ shutdown?(): MaybePromise<void>;
34
52
  }
35
53
 
54
+ /**
55
+ * 프로세스 메모리에 presence를 두는 store. 단일 노드의 기본값이며, 같은 프로세스의 여러 runtime이 공유하면 클러스터 replica로도 동작한다.
56
+ */
36
57
  export class InMemoryWebSocketPresenceStore implements WebSocketPresenceStore {
37
- private readonly connections = new Map<string, WebSocketSessionPresence>();
38
- private readonly rooms = new Map<string, Set<string>>();
39
- private readonly users = new Map<string, Set<string>>();
58
+ private readonly index = new WebSocketPresenceIndex();
40
59
 
41
- register(input: {
42
- sessionId: string;
43
- nodeId: string;
44
- namespace: string;
45
- active?: boolean;
46
- }): WebSocketSessionPresence {
47
- const meta: WebSocketSessionPresence = {
48
- sessionId: input.sessionId,
49
- nodeId: input.nodeId,
50
- namespace: input.namespace,
51
- active: input.active ?? true,
52
- rooms: new Set(),
53
- connectedAt: new Date(),
54
- lastSeenAt: new Date(),
55
- };
56
- this.connections.set(input.sessionId, meta);
57
- return meta;
60
+ register(input: WebSocketPresenceRegistration): void {
61
+ this.index.register(input);
58
62
  }
59
63
 
60
64
  activate(sessionId: string): void {
61
- const meta = this.connections.get(sessionId);
62
- if (!meta) {
63
- return;
64
- }
65
-
66
- meta.active = true;
65
+ this.index.activate(sessionId);
67
66
  }
68
67
 
69
- unregister(sessionId: string): WebSocketSessionPresence | undefined {
70
- const meta = this.connections.get(sessionId);
71
- if (!meta) {
72
- return undefined;
73
- }
74
-
75
- if (meta.userId !== undefined) {
76
- this.removeUserBinding(meta.namespace, meta.userId, sessionId);
77
- }
78
-
79
- for (const roomId of meta.rooms) {
80
- this.removeRoomBinding(meta.namespace, roomId, sessionId);
81
- }
82
-
83
- this.connections.delete(sessionId);
84
- return meta;
85
- }
86
-
87
- touch(sessionId: string): void {
88
- const meta = this.connections.get(sessionId);
89
- if (!meta) {
90
- return;
91
- }
92
-
93
- meta.lastSeenAt = new Date();
68
+ unregister(sessionId: string): void {
69
+ this.index.unregister(sessionId);
94
70
  }
95
71
 
96
72
  setUserId(sessionId: string, userId: WebSocketUserId): void {
97
- const meta = this.connections.get(sessionId);
98
- if (!meta) {
99
- return;
100
- }
101
-
102
- if (meta.userId !== undefined) {
103
- this.removeUserBinding(meta.namespace, meta.userId, sessionId);
104
- }
105
-
106
- meta.userId = userId;
107
- const key = getBindingKey(meta.namespace, String(userId));
108
- const bound = this.users.get(key) ?? new Set<string>();
109
- bound.add(sessionId);
110
- this.users.set(key, bound);
73
+ this.index.setUserId(sessionId, userId);
111
74
  }
112
75
 
113
76
  clearUserId(sessionId: string): void {
114
- const meta = this.connections.get(sessionId);
115
- if (!meta || meta.userId === undefined) {
116
- return;
117
- }
118
-
119
- this.removeUserBinding(meta.namespace, meta.userId, sessionId);
120
- delete meta.userId;
77
+ this.index.clearUserId(sessionId);
121
78
  }
122
79
 
123
80
  join(sessionId: string, roomId: WebSocketRoomId): void {
124
- const meta = this.connections.get(sessionId);
125
- if (!meta) {
126
- return;
127
- }
128
-
129
- meta.rooms.add(roomId);
130
- const key = getBindingKey(meta.namespace, roomId);
131
- const members = this.rooms.get(key) ?? new Set<string>();
132
- members.add(sessionId);
133
- this.rooms.set(key, members);
81
+ this.index.join(sessionId, roomId);
134
82
  }
135
83
 
136
84
  leave(sessionId: string, roomId: WebSocketRoomId): void {
137
- const meta = this.connections.get(sessionId);
138
- if (!meta) {
139
- return;
140
- }
141
-
142
- meta.rooms.delete(roomId);
143
- this.removeRoomBinding(meta.namespace, roomId, sessionId);
144
- }
145
-
146
- getConnection(sessionId: string): WebSocketSessionPresence | undefined {
147
- return this.connections.get(sessionId);
148
- }
149
-
150
- getConnectionCount(namespace?: string): number {
151
- let count = 0;
152
-
153
- for (const meta of this.connections.values()) {
154
- if (!meta.active || (namespace && meta.namespace !== namespace)) {
155
- continue;
156
- }
157
-
158
- count += 1;
159
- }
160
-
161
- return count;
162
- }
163
-
164
- getRoomMembers(roomId: WebSocketRoomId, namespace?: string): WebSocketSessionPresence[] {
165
- return this.getBoundConnections(this.rooms, roomId, namespace);
166
- }
167
-
168
- getStats(): WebSocketRegistryStats {
169
- const byNamespace: Record<string, number> = {};
170
-
171
- for (const meta of this.connections.values()) {
172
- if (!meta.active) {
173
- continue;
174
- }
175
- byNamespace[meta.namespace] = (byNamespace[meta.namespace] ?? 0) + 1;
176
- }
177
-
178
- return {
179
- totalConnections: Object.values(byNamespace).reduce((sum, count) => sum + count, 0),
180
- totalRooms: this.rooms.size,
181
- byNamespace,
182
- };
183
- }
184
-
185
- queryAudience(audience: WebSocketAudience): WebSocketSessionPresence[] {
186
- switch (audience.type) {
187
- case "all":
188
- return this.getActiveConnections(audience.namespace);
189
- case "room":
190
- return this.getBoundConnections(this.rooms, audience.roomId, audience.namespace);
191
- case "user":
192
- return this.getBoundConnections(this.users, String(audience.userId), audience.namespace);
193
- case "connections": {
194
- const metas: WebSocketSessionPresence[] = [];
195
-
196
- for (const connectionId of audience.connectionIds) {
197
- const meta = this.connections.get(connectionId);
198
- if (!meta?.active) {
199
- continue;
200
- }
201
- if (audience.namespace && meta.namespace !== audience.namespace) {
202
- continue;
203
- }
204
- metas.push(meta);
205
- }
206
-
207
- return metas;
208
- }
209
- case "union": {
210
- const seen = new Set<string>();
211
- const metas: WebSocketSessionPresence[] = [];
212
-
213
- for (const item of audience.audiences) {
214
- for (const meta of this.queryAudience(item)) {
215
- if (seen.has(meta.sessionId)) {
216
- continue;
217
- }
218
-
219
- seen.add(meta.sessionId);
220
- metas.push(meta);
221
- }
222
- }
223
-
224
- return metas;
225
- }
226
- }
227
- }
228
-
229
- private getActiveConnections(namespace?: string): WebSocketSessionPresence[] {
230
- const metas: WebSocketSessionPresence[] = [];
231
-
232
- for (const meta of this.connections.values()) {
233
- if (!meta.active || (namespace && meta.namespace !== namespace)) {
234
- continue;
235
- }
236
-
237
- metas.push(meta);
238
- }
239
-
240
- return metas;
85
+ this.index.leave(sessionId, roomId);
241
86
  }
242
87
 
243
- private getBoundConnections(
244
- bindings: Map<string, Set<string>>,
245
- id: string,
246
- namespace?: string,
247
- ): WebSocketSessionPresence[] {
248
- const metas: WebSocketSessionPresence[] = [];
249
- const seen = new Set<string>();
88
+ resolveRoute(audience: WebSocketAudience, nodeId: string): WebSocketRoutingPlan {
89
+ const localSessionIds: string[] = [];
90
+ const remoteNodeIds = new Set<string>();
250
91
 
251
- for (const members of this.getScopedBindings(bindings, id, namespace)) {
252
- for (const connectionId of members) {
253
- if (seen.has(connectionId)) {
254
- continue;
255
- }
256
- seen.add(connectionId);
257
-
258
- const meta = this.connections.get(connectionId);
259
- if (meta?.active) {
260
- metas.push(meta);
261
- }
92
+ for (const session of this.index.queryAudience(audience)) {
93
+ if (session.nodeId === nodeId) {
94
+ localSessionIds.push(session.sessionId);
95
+ } else {
96
+ remoteNodeIds.add(session.nodeId);
262
97
  }
263
98
  }
264
99
 
265
- return metas;
100
+ return { localSessionIds, remoteNodeIds: [...remoteNodeIds] };
266
101
  }
267
102
 
268
- private getScopedBindings(
269
- bindings: Map<string, Set<string>>,
270
- id: string,
271
- namespace?: string,
272
- ): Set<string>[] {
273
- if (namespace) {
274
- const exact = bindings.get(getBindingKey(namespace, id));
275
- return exact ? [exact] : [];
276
- }
277
-
278
- const matches: Set<string>[] = [];
279
- for (const [key, members] of bindings) {
280
- const parsed = parseBindingKey(key);
281
- if (parsed.id === id) {
282
- matches.push(members);
283
- }
284
- }
285
-
286
- return matches;
287
- }
288
-
289
- private removeRoomBinding(namespace: string, roomId: WebSocketRoomId, sessionId: string): void {
290
- const key = getBindingKey(namespace, roomId);
291
- const members = this.rooms.get(key);
292
- if (!members) {
293
- return;
294
- }
295
-
296
- members.delete(sessionId);
297
- if (members.size === 0) {
298
- this.rooms.delete(key);
299
- }
103
+ getRoomMemberCount(roomId: WebSocketRoomId, namespace?: string): number {
104
+ return this.index.getRoomMemberCount(roomId, namespace);
300
105
  }
301
106
 
302
- private removeUserBinding(namespace: string, userId: WebSocketUserId, sessionId: string): void {
303
- const key = getBindingKey(namespace, String(userId));
304
- const members = this.users.get(key);
305
- if (!members) {
306
- return;
307
- }
308
-
309
- members.delete(sessionId);
310
- if (members.size === 0) {
311
- this.users.delete(key);
312
- }
107
+ // 복사본을 반환하므로 호출자가 값을 바꿔도 store 상태는 그대로다
108
+ getConnection(sessionId: string): WebSocketSessionPresence | undefined {
109
+ return this.index.getConnection(sessionId);
313
110
  }
314
111
  }
315
-
316
- function getBindingKey(namespace: string, id: string): string {
317
- return `${namespace}::${id}`;
318
- }
319
-
320
- interface PresenceBinding {
321
- namespace: string;
322
- id: string;
323
- }
324
-
325
- function parseBindingKey(key: string): PresenceBinding {
326
- const [namespace, ...rest] = key.split("::");
327
- return {
328
- namespace,
329
- id: rest.join("::"),
330
- };
331
- }