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.
- package/dist/_virtual/_rolldown/runtime.js +1 -2
- package/dist/ai/agents/agent.js +2 -3
- package/dist/ai/agents/index.js +1 -2
- package/dist/ai/index.js +1 -2
- package/dist/ai/providers/rtzr/api.js +1 -2
- package/dist/ai/providers/rtzr/error.js +2 -3
- package/dist/ai/providers/rtzr/model.js +1 -2
- package/dist/ai/providers/rtzr/options.js +1 -2
- package/dist/ai/providers/rtzr/provider.js +1 -2
- package/dist/ai/providers/rtzr/utils.js +1 -2
- package/dist/api/base-frame.js +4 -4
- package/dist/api/caster.d.ts +1 -1
- package/dist/api/caster.d.ts.map +1 -1
- package/dist/api/caster.js +9 -9
- package/dist/api/code-converters.js +6 -5
- package/dist/api/config.d.ts +1 -1
- package/dist/api/config.d.ts.map +1 -1
- package/dist/api/config.js +2 -3
- package/dist/api/context.d.ts +2 -3
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -3
- package/dist/api/decorators.js +2 -3
- package/dist/api/http-validator.js +2 -3
- package/dist/api/index.js +8 -3
- package/dist/api/secret.js +1 -2
- package/dist/api/sonamu.d.ts +3 -5
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +30 -24
- package/dist/api/validator.js +1 -2
- package/dist/api/websocket-helpers.js +2 -3
- package/dist/auth/audit-log/builders.js +1 -2
- package/dist/auth/audit-log/client-ip.js +2 -3
- package/dist/auth/audit-log/events.js +1 -2
- package/dist/auth/audit-log/plugin.d.ts.map +1 -1
- package/dist/auth/audit-log/plugin.js +2 -3
- package/dist/auth/audit-log-ingestor.js +2 -3
- package/dist/auth/auth-generator.js +2 -3
- package/dist/auth/better-auth-entities.js +1 -2
- package/dist/auth/better-auth-request.js +2 -3
- package/dist/auth/index.js +2 -3
- package/dist/auth/knex-adapter.js +2 -3
- package/dist/auth/plugins/entity-definitions/admin.js +1 -2
- package/dist/auth/plugins/entity-definitions/anonymous.js +1 -2
- package/dist/auth/plugins/entity-definitions/api-key.js +1 -2
- package/dist/auth/plugins/entity-definitions/audit-log.js +1 -2
- package/dist/auth/plugins/entity-definitions/index.js +1 -2
- package/dist/auth/plugins/entity-definitions/jwt.js +1 -2
- package/dist/auth/plugins/entity-definitions/organization.js +1 -2
- package/dist/auth/plugins/entity-definitions/passkey.js +1 -2
- package/dist/auth/plugins/entity-definitions/phone-number.js +1 -2
- package/dist/auth/plugins/entity-definitions/sso.js +1 -2
- package/dist/auth/plugins/entity-definitions/two-factor.js +1 -2
- package/dist/auth/plugins/entity-definitions/username.js +1 -2
- package/dist/auth/plugins/wrappers/admin.d.ts +33 -32
- package/dist/auth/plugins/wrappers/admin.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/admin.js +2 -3
- package/dist/auth/plugins/wrappers/anonymous.js +2 -3
- package/dist/auth/plugins/wrappers/api-key.js +2 -3
- package/dist/auth/plugins/wrappers/jwt.js +2 -3
- package/dist/auth/plugins/wrappers/organization.js +2 -3
- package/dist/auth/plugins/wrappers/passkey.js +2 -3
- package/dist/auth/plugins/wrappers/phone-number.d.ts +43 -1
- package/dist/auth/plugins/wrappers/phone-number.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/phone-number.js +2 -3
- package/dist/auth/plugins/wrappers/sso.d.ts +138 -187
- package/dist/auth/plugins/wrappers/sso.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/sso.js +1 -2
- package/dist/auth/plugins/wrappers/two-factor.d.ts +51 -10
- package/dist/auth/plugins/wrappers/two-factor.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/two-factor.js +2 -3
- package/dist/auth/plugins/wrappers/username.d.ts +39 -5
- package/dist/auth/plugins/wrappers/username.d.ts.map +1 -1
- package/dist/auth/plugins/wrappers/username.js +2 -3
- package/dist/bin/build-config.js +5 -5
- package/dist/bin/compiler-policy.js +2 -3
- package/dist/bin/fixture.js +7 -5
- package/dist/bin/hmr-hook-register.js +1 -2
- package/dist/bin/migrate-targets.js +2 -3
- package/dist/bin/test-command.js +2 -3
- package/dist/bin/ts-loader-register.js +2 -3
- package/dist/bin/ts-loader-registration.js +2 -3
- package/dist/bin/typecheck-web.js +2 -3
- package/dist/cache/cache-manager.js +1 -2
- package/dist/cache/decorator.js +2 -3
- package/dist/cache/drivers.js +1 -2
- package/dist/cache/index.js +2 -3
- package/dist/cache-control/cache-control.js +1 -2
- package/dist/code-formatting/index.js +2 -3
- package/dist/code-formatting/oxfmt.js +2 -3
- package/dist/code-formatting/oxlint.js +2 -3
- package/dist/compress/compress.js +1 -2
- package/dist/compress/index.js +2 -2
- package/dist/compress/types.d.ts +1 -1
- package/dist/compress/types.d.ts.map +1 -1
- package/dist/cone/cone-generator.js +2 -3
- package/dist/database/_batch_update.js +1 -2
- package/dist/database/base-model.js +4 -4
- package/dist/database/db.js +4 -4
- package/dist/database/knex.js +2 -3
- package/dist/database/puri-wrapper.js +2 -3
- package/dist/database/puri.js +23 -22
- package/dist/database/puri.types.js +1 -2
- package/dist/database/transaction-context.js +1 -2
- package/dist/database/upsert-builder.js +2 -3
- package/dist/dict/en.js +2 -3
- package/dist/dict/index.js +3 -3
- package/dist/dict/ko.js +2 -3
- package/dist/dict/rc-keys.js +1 -2
- package/dist/dict/sd.js +2 -3
- package/dist/dict/sonamu-dictionary.js +7 -6
- package/dist/dict/utils.js +2 -3
- package/dist/entity/entity-manager.d.ts +66 -66
- package/dist/entity/entity-manager.js +4 -4
- package/dist/entity/entity-template-cone.js +2 -3
- package/dist/entity/entity.js +21 -18
- package/dist/env.js +1 -2
- package/dist/exceptions/error-handler.d.ts.map +1 -1
- package/dist/exceptions/error-handler.js +2 -3
- package/dist/exceptions/so-exceptions.js +2 -3
- package/dist/filter/index.js +3 -2
- package/dist/filter/types.js +1 -2
- package/dist/filter/utils.js +7 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +42 -6
- package/dist/logger/category.js +2 -3
- package/dist/logger/configure.js +5 -5
- package/dist/migration/code-generation.js +23 -16
- package/dist/migration/index-where-predicate.js +2 -3
- package/dist/migration/migration-set.js +2 -3
- package/dist/migration/migrator.js +2 -3
- package/dist/migration/postgresql-schema-reader.js +6 -9
- package/dist/migration/slack-confirm.js +2 -3
- package/dist/naite/naite-reporter.js +2 -3
- package/dist/naite/naite.js +10 -8
- package/dist/runtime-type.js +2 -3
- package/dist/ssr/hmr-opt-out.js +1 -2
- package/dist/ssr/index.js +7 -4
- package/dist/ssr/registry.js +1 -2
- package/dist/ssr/renderer.js +2 -3
- package/dist/storage/base-file.js +1 -2
- package/dist/storage/buffered-file.js +2 -3
- package/dist/storage/drivers.js +1 -2
- package/dist/storage/index.js +2 -3
- package/dist/storage/s3-driver.js +1 -2
- package/dist/storage/storage-manager.js +2 -3
- package/dist/storage/uploaded-file.js +2 -3
- package/dist/stream/__tests__/ws-test-helpers.d.ts +108 -0
- package/dist/stream/__tests__/ws-test-helpers.d.ts.map +1 -0
- package/dist/stream/__tests__/ws-test-helpers.js +259 -0
- package/dist/stream/index.d.ts +1 -0
- package/dist/stream/index.d.ts.map +1 -1
- package/dist/stream/index.js +15 -4
- package/dist/stream/sse.js +1 -2
- package/dist/stream/ws-audience-resolver.d.ts +8 -5
- package/dist/stream/ws-audience-resolver.d.ts.map +1 -1
- package/dist/stream/ws-audience-resolver.js +17 -15
- package/dist/stream/ws-audience.js +1 -2
- package/dist/stream/ws-cluster-bus.d.ts +9 -4
- package/dist/stream/ws-cluster-bus.d.ts.map +1 -1
- package/dist/stream/ws-cluster-bus.js +2 -3
- package/dist/stream/ws-core.d.ts +10 -0
- package/dist/stream/ws-core.d.ts.map +1 -1
- package/dist/stream/ws-delivery.d.ts +30 -4
- package/dist/stream/ws-delivery.d.ts.map +1 -1
- package/dist/stream/ws-delivery.js +200 -54
- package/dist/stream/ws-frame.d.ts +15 -0
- package/dist/stream/ws-frame.d.ts.map +1 -0
- package/dist/stream/ws-frame.js +28 -0
- package/dist/stream/ws-local-connection-store.d.ts +4 -4
- package/dist/stream/ws-local-connection-store.d.ts.map +1 -1
- package/dist/stream/ws-local-connection-store.js +3 -4
- package/dist/stream/ws-presence-index.d.ts +36 -0
- package/dist/stream/ws-presence-index.d.ts.map +1 -0
- package/dist/stream/ws-presence-index.js +176 -0
- package/dist/stream/ws-presence-store.d.ts +39 -39
- package/dist/stream/ws-presence-store.d.ts.map +1 -1
- package/dist/stream/ws-presence-store.js +23 -154
- package/dist/stream/ws-registry.d.ts +45 -17
- package/dist/stream/ws-registry.d.ts.map +1 -1
- package/dist/stream/ws-registry.js +170 -62
- package/dist/stream/ws-session-id.d.ts +14 -0
- package/dist/stream/ws-session-id.d.ts.map +1 -0
- package/dist/stream/ws-session-id.js +36 -0
- package/dist/stream/ws-telemetry-memory.js +3 -4
- package/dist/stream/ws-telemetry-trace.js +1 -2
- package/dist/stream/ws-telemetry.js +2 -3
- package/dist/stream/ws.d.ts +20 -10
- package/dist/stream/ws.d.ts.map +1 -1
- package/dist/stream/ws.js +60 -38
- package/dist/syncer/api-parser.js +2 -3
- package/dist/syncer/checksum.js +4 -4
- package/dist/syncer/code-generator.js +4 -4
- package/dist/syncer/entity-operations.js +2 -3
- package/dist/syncer/event-batcher.js +1 -2
- package/dist/syncer/file-copy.js +2 -3
- package/dist/syncer/file-patterns.js +2 -3
- package/dist/syncer/file-tracking.js +2 -3
- package/dist/syncer/filesystem-dependencies.js +1 -2
- package/dist/syncer/index.js +8 -2
- package/dist/syncer/module-loader.js +8 -6
- package/dist/syncer/syncer-actions.js +8 -7
- package/dist/syncer/syncer.js +13 -8
- package/dist/syncer/watcher.js +2 -3
- package/dist/tasks/decorator.js +1 -2
- package/dist/tasks/step-wrapper.js +2 -3
- package/dist/tasks/workflow-manager.js +2 -3
- package/dist/template/entity-converter.js +17 -16
- package/dist/template/helpers.js +2 -3
- package/dist/template/implementations/entity.template.js +2 -3
- package/dist/template/implementations/entry-server.template.js +2 -3
- package/dist/template/implementations/generated.template.js +2 -3
- package/dist/template/implementations/generated_http.template.js +6 -6
- package/dist/template/implementations/generated_sso.template.js +2 -3
- package/dist/template/implementations/http_validators.template.js +2 -3
- package/dist/template/implementations/init_types.template.js +2 -3
- package/dist/template/implementations/model.template.js +4 -4
- package/dist/template/implementations/model_test.template.js +2 -3
- package/dist/template/implementations/queries.template.js +2 -3
- package/dist/template/implementations/sd.template.js +2 -3
- package/dist/template/implementations/services.template.js +2 -3
- package/dist/template/implementations/view_form.template.js +12 -9
- package/dist/template/implementations/view_id_all_select.template.js +2 -3
- package/dist/template/implementations/view_list.template.d.ts.map +1 -1
- package/dist/template/implementations/view_list.template.js +19 -17
- package/dist/template/implementations/view_search_input.template.js +2 -3
- package/dist/template/index.js +9 -3
- package/dist/template/template-manager.js +2 -3
- package/dist/template/template-types.js +1 -2
- package/dist/template/template.js +2 -3
- package/dist/template/zod-converter.js +54 -47
- package/dist/testing/_relation-graph.js +2 -3
- package/dist/testing/bootstrap.js +2 -3
- package/dist/testing/data-explorer.js +2 -3
- package/dist/testing/dev-test-routes.js +2 -3
- package/dist/testing/dev-vitest-manager.js +15 -12
- package/dist/testing/faker-mappings.js +1 -2
- package/dist/testing/filesystem.js +2 -3
- package/dist/testing/fixture-generator.js +2 -3
- package/dist/testing/fixture-loader.js +1 -2
- package/dist/testing/fixture-manager.js +12 -9
- package/dist/testing/global-setup.js +5 -5
- package/dist/testing/index.js +6 -3
- package/dist/testing/naite-vitest-reporter.js +1 -2
- package/dist/testing/parallel-db-manager.js +2 -3
- package/dist/testing/vitest-helpers.js +2 -3
- package/dist/tooling/__tests__/helpers/sonamu-test-root.js +2 -3
- package/dist/tooling/cdd-service.js +1 -2
- package/dist/tooling/cli-tooling.js +1 -2
- package/dist/tooling/fixture-service.js +1 -2
- package/dist/tooling/index.js +1 -2
- package/dist/tooling/test-run-client.js +2 -3
- package/dist/tsdown.api.config.js +1 -2
- package/dist/types/types.js +2 -3
- package/dist/ui/ai-api.d.ts.map +1 -1
- package/dist/ui/ai-api.js +4 -5
- package/dist/ui/ai-client.d.ts +2 -2
- package/dist/ui/ai-client.d.ts.map +1 -1
- package/dist/ui/ai-client.js +9 -8
- package/dist/ui/api.d.ts.map +1 -1
- package/dist/ui/api.js +11 -8
- package/dist/ui/async-route.js +1 -2
- package/dist/ui/cdd-service.js +22 -21
- package/dist/ui/migrations-api.js +6 -5
- package/dist/ui-web/assets/index-QDMyy31K.js +180 -0
- package/dist/ui-web/assets/index-dV7akMFB.css +1 -0
- package/dist/ui-web/index.html +2 -2
- package/dist/utils/async-utils.js +1 -2
- package/dist/utils/class-name.js +1 -2
- package/dist/utils/console-util.js +1 -2
- package/dist/utils/controller.js +2 -3
- package/dist/utils/esm-utils.js +1 -2
- package/dist/utils/fs-utils.js +2 -3
- package/dist/utils/lodash-able.js +1 -2
- package/dist/utils/model.js +1 -2
- package/dist/utils/object-utils.js +2 -3
- package/dist/utils/path-utils.js +2 -3
- package/dist/utils/process-utils.js +2 -3
- package/dist/utils/runtime-value.js +1 -2
- package/dist/utils/sql-parser.js +2 -3
- package/dist/utils/type-utils.js +8 -8
- package/dist/utils/utils.js +2 -3
- package/dist/utils/zod-error.js +2 -3
- package/dist/vector/chunking.js +1 -2
- package/dist/vector/config.js +1 -2
- package/dist/vector/embedding.js +4 -4
- package/package.json +63 -63
- package/src/ai/agents/agent.ts +1 -1
- package/src/api/__tests__/sonamu.plugins.test.ts +11 -1
- package/src/api/__tests__/sonamu.websocket-routes.test.ts +234 -0
- package/src/api/__tests__/sonamu.websocket.test.ts +241 -24
- package/src/api/config.ts +1 -1
- package/src/api/context.ts +2 -8
- package/src/api/sonamu.ts +46 -28
- package/src/auth/audit-log/plugin.ts +2 -0
- package/src/compress/types.ts +1 -1
- package/src/exceptions/error-handler.ts +3 -2
- package/src/index.ts +1 -0
- package/src/stream/__tests__/ws-contracts.test.ts +483 -265
- package/src/stream/__tests__/ws-multinode.test.ts +326 -0
- package/src/stream/__tests__/ws-registry-async.test.ts +855 -0
- package/src/stream/__tests__/ws-test-helpers.ts +360 -0
- package/src/stream/__tests__/ws.test.ts +342 -145
- package/src/stream/index.ts +1 -0
- package/src/stream/ws-audience-resolver.ts +21 -19
- package/src/stream/ws-cluster-bus.ts +12 -4
- package/src/stream/ws-core.ts +14 -0
- package/src/stream/ws-delivery.ts +321 -97
- package/src/stream/ws-frame.ts +39 -0
- package/src/stream/ws-local-connection-store.ts +10 -9
- package/src/stream/ws-presence-index.ts +273 -0
- package/src/stream/ws-presence-store.ts +64 -284
- package/src/stream/ws-registry.ts +272 -85
- package/src/stream/ws-session-id.ts +39 -0
- package/src/stream/ws.ts +91 -50
- package/src/template/implementations/model.template.ts +3 -3
- package/src/template/implementations/view_list.template.ts +12 -13
- package/src/ui/ai-api.ts +4 -2
- package/src/ui/ai-client.ts +2 -2
- package/src/ui/api.ts +58 -62
- package/dist/ui-web/assets/index-BMdIyKXS.css +0 -1
- package/dist/ui-web/assets/index-LpE8u19O.js +0 -178
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import Fastify, { type FastifyInstance } from "fastify";
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
import { RecordingClusterBus, flushAsync } from "../../stream/__tests__/ws-test-helpers";
|
|
10
|
+
import { createWebSocketRuntime, type WebSocketRuntime } from "../../stream/ws";
|
|
11
|
+
import { InMemoryWebSocketPresenceStore } from "../../stream/ws-presence-store";
|
|
12
|
+
import { type SonamuFastifyConfig } from "../../types/types";
|
|
13
|
+
import { type ExtendedApi } from "../decorators";
|
|
14
|
+
import { Sonamu } from "../sonamu";
|
|
15
|
+
|
|
16
|
+
const ChatEventsV1 = z.object({
|
|
17
|
+
onMessage: z.object({ text: z.string() }),
|
|
18
|
+
});
|
|
19
|
+
// HMR로 바뀐 스키마: 기본값 필드가 추가돼 인코딩 결과가 달라진다
|
|
20
|
+
const ChatEventsV2 = z.object({
|
|
21
|
+
onMessage: z.object({ text: z.string(), edited: z.boolean().default(true) }),
|
|
22
|
+
});
|
|
23
|
+
const NoticeEvents = z.object({
|
|
24
|
+
onNotice: z.object({ level: z.number() }),
|
|
25
|
+
});
|
|
26
|
+
const AlertEvents = z.object({
|
|
27
|
+
onAlert: z.object({ message: z.string() }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function createWebSocketApi(input: {
|
|
31
|
+
modelName: string;
|
|
32
|
+
methodName: string;
|
|
33
|
+
path: string;
|
|
34
|
+
outEvents: z.ZodObject;
|
|
35
|
+
}): ExtendedApi {
|
|
36
|
+
return {
|
|
37
|
+
modelName: input.modelName,
|
|
38
|
+
methodName: input.methodName,
|
|
39
|
+
path: input.path,
|
|
40
|
+
options: {},
|
|
41
|
+
websocketOptions: {
|
|
42
|
+
outEvents: input.outEvents,
|
|
43
|
+
inEvents: z.object({}),
|
|
44
|
+
namespace: "chat",
|
|
45
|
+
},
|
|
46
|
+
typeParameters: [],
|
|
47
|
+
parameters: [],
|
|
48
|
+
returnType: "void",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const chatApi = (outEvents: z.ZodObject) =>
|
|
53
|
+
createWebSocketApi({
|
|
54
|
+
modelName: "ChatFrame",
|
|
55
|
+
methodName: "subscribe",
|
|
56
|
+
path: "/chat/subscribe",
|
|
57
|
+
outEvents,
|
|
58
|
+
});
|
|
59
|
+
const noticeApi = createWebSocketApi({
|
|
60
|
+
modelName: "NoticeFrame",
|
|
61
|
+
methodName: "subscribe",
|
|
62
|
+
path: "/notice/subscribe",
|
|
63
|
+
outEvents: NoticeEvents,
|
|
64
|
+
});
|
|
65
|
+
const alertApi = createWebSocketApi({
|
|
66
|
+
modelName: "AlertFrame",
|
|
67
|
+
methodName: "subscribe",
|
|
68
|
+
path: "/alert/subscribe",
|
|
69
|
+
outEvents: AlertEvents,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 테스트가 서버 측에서 publish하는 payload 모양
|
|
73
|
+
type ServerPublishData = { text: string; extra?: number } | { level: number } | { message: string };
|
|
74
|
+
|
|
75
|
+
const config = /* SAFETY: route 등록 단계는 SonamuFastifyConfig의 함수를 호출하지 않는다. */ {
|
|
76
|
+
contextProvider(defaultContext) {
|
|
77
|
+
return defaultContext;
|
|
78
|
+
},
|
|
79
|
+
guardHandler() {},
|
|
80
|
+
} as SonamuFastifyConfig;
|
|
81
|
+
|
|
82
|
+
describe("Sonamu websocket route table 등록", () => {
|
|
83
|
+
const originalState = Sonamu.captureTestingSnapshot();
|
|
84
|
+
const originalInitialized = Sonamu.isInitialized;
|
|
85
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
86
|
+
const servers: FastifyInstance[] = [];
|
|
87
|
+
let tempRoot: string;
|
|
88
|
+
let runtime: WebSocketRuntime;
|
|
89
|
+
let clusterBus: RecordingClusterBus;
|
|
90
|
+
|
|
91
|
+
beforeEach(async () => {
|
|
92
|
+
tempRoot = await mkdtemp(path.join(os.tmpdir(), "sonamu-ws-routes-"));
|
|
93
|
+
// web, web-dist가 없는 api 루트를 만들어 dev/prod 모두 정적 서빙 없이 route만 등록하게 한다
|
|
94
|
+
Reflect.set(Sonamu, "apiRootPathValue", path.join(tempRoot, "api"));
|
|
95
|
+
Reflect.set(Sonamu, "configValue", {
|
|
96
|
+
sync: { targets: [] },
|
|
97
|
+
api: { route: { prefix: "/api" } },
|
|
98
|
+
server: {},
|
|
99
|
+
validation: { zodCompiler: false },
|
|
100
|
+
});
|
|
101
|
+
Sonamu.isInitialized = true;
|
|
102
|
+
|
|
103
|
+
// 원격 노드 node-b의 세션이 room-1에 있고, 이 노드(node-a)에는 로컬 연결이 하나도 없다
|
|
104
|
+
const presenceStore = new InMemoryWebSocketPresenceStore();
|
|
105
|
+
await presenceStore.register({
|
|
106
|
+
sessionId: "remote-1",
|
|
107
|
+
nodeId: "node-b",
|
|
108
|
+
namespace: "chat",
|
|
109
|
+
active: true,
|
|
110
|
+
connectedAt: new Date(),
|
|
111
|
+
});
|
|
112
|
+
await presenceStore.join("remote-1", "room-1");
|
|
113
|
+
clusterBus = new RecordingClusterBus();
|
|
114
|
+
runtime = createWebSocketRuntime({ nodeId: "node-a", presenceStore, clusterBus });
|
|
115
|
+
Sonamu.websocketRuntime = runtime;
|
|
116
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(async () => {
|
|
120
|
+
await Promise.all(servers.splice(0).map((server) => server.close()));
|
|
121
|
+
Sonamu.restoreTestingSnapshot(originalState);
|
|
122
|
+
Sonamu.isInitialized = originalInitialized;
|
|
123
|
+
if (originalNodeEnv === undefined) {
|
|
124
|
+
delete process.env.NODE_ENV;
|
|
125
|
+
} else {
|
|
126
|
+
process.env.NODE_ENV = originalNodeEnv;
|
|
127
|
+
}
|
|
128
|
+
vi.restoreAllMocks();
|
|
129
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
function setApis(apis: ExtendedApi[]): void {
|
|
133
|
+
Reflect.set(Sonamu, "syncerValue", {
|
|
134
|
+
apis,
|
|
135
|
+
models: { ChatFrame: {}, NoticeFrame: {}, AlertFrame: {} },
|
|
136
|
+
types: {},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function startServer(environment: "production" | "test"): Promise<FastifyInstance> {
|
|
141
|
+
process.env.NODE_ENV = environment;
|
|
142
|
+
const server = Fastify();
|
|
143
|
+
servers.push(server);
|
|
144
|
+
await Sonamu.withFastify(server, config, { doSilent: true, enableSync: false });
|
|
145
|
+
return server;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 서버 측 publish 결과로 이 노드의 route table을 관찰한다
|
|
149
|
+
async function publishAndCollectFrames(
|
|
150
|
+
event: string,
|
|
151
|
+
data: ServerPublishData,
|
|
152
|
+
): Promise<Record<string, string> | undefined> {
|
|
153
|
+
const before = clusterBus.published.length;
|
|
154
|
+
await runtime.publishToRoom("room-1", event, data, "chat");
|
|
155
|
+
await flushAsync();
|
|
156
|
+
const published = clusterBus.published.slice(before);
|
|
157
|
+
expect(published.length).toBeLessThanOrEqual(1);
|
|
158
|
+
return published[0]?.frames;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const environment of ["production", "test"] as const) {
|
|
162
|
+
const label =
|
|
163
|
+
environment === "production" ? "프로덕션 route 등록 경로" : "dev/HMR catch-all 경로";
|
|
164
|
+
|
|
165
|
+
it(`${label}: 시작 시 모든 @websocket route의 outEvents가 api.path 키로 등록돼, 로컬 연결이 없어도 원격 수신자용 frame을 만든다`, async () => {
|
|
166
|
+
setApis([chatApi(ChatEventsV1), noticeApi]);
|
|
167
|
+
|
|
168
|
+
const server = await startServer(environment);
|
|
169
|
+
|
|
170
|
+
// 의도한 등록 경로를 실제로 탔는지 확인한다 (프로덕션은 route별 등록, dev는 catch-all)
|
|
171
|
+
if (environment === "production") {
|
|
172
|
+
expect(server.hasRoute({ method: "GET", url: "/api/chat/subscribe" })).toBe(true);
|
|
173
|
+
expect(server.hasRoute({ method: "GET", url: "/api/notice/subscribe" })).toBe(true);
|
|
174
|
+
} else {
|
|
175
|
+
expect(server.hasRoute({ method: "GET", url: "/api/*" })).toBe(true);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const chatFrames = await publishAndCollectFrames("onMessage", { text: "hi", extra: 1 });
|
|
179
|
+
expect(chatFrames).toBeDefined();
|
|
180
|
+
expect(Object.keys(chatFrames ?? {})).toEqual(["/chat/subscribe"]);
|
|
181
|
+
expect(JSON.parse(chatFrames?.["/chat/subscribe"] ?? "null")).toEqual({
|
|
182
|
+
event: "onMessage",
|
|
183
|
+
data: { text: "hi" },
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const noticeFrames = await publishAndCollectFrames("onNotice", { level: 2 });
|
|
187
|
+
expect(Object.keys(noticeFrames ?? {})).toEqual(["/notice/subscribe"]);
|
|
188
|
+
expect(JSON.parse(noticeFrames?.["/notice/subscribe"] ?? "null")).toEqual({
|
|
189
|
+
event: "onNotice",
|
|
190
|
+
data: { level: 2 },
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
it("HMR refresh 뒤에는 바뀐 스키마로 인코딩하고, 제거된 route는 빠지고, 추가된 route는 포함된다", async () => {
|
|
196
|
+
setApis([chatApi(ChatEventsV1), noticeApi]);
|
|
197
|
+
await startServer("test");
|
|
198
|
+
|
|
199
|
+
// syncer HMR 사이클과 같은 순서: API를 다시 읽은 뒤 refreshHttpValidators를 호출한다
|
|
200
|
+
setApis([chatApi(ChatEventsV2), alertApi]);
|
|
201
|
+
await Sonamu.refreshHttpValidators();
|
|
202
|
+
|
|
203
|
+
const chatFrames = await publishAndCollectFrames("onMessage", { text: "hi" });
|
|
204
|
+
expect(JSON.parse(chatFrames?.["/chat/subscribe"] ?? "null")).toEqual({
|
|
205
|
+
event: "onMessage",
|
|
206
|
+
data: { text: "hi", edited: true },
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const noticeFrames = await publishAndCollectFrames("onNotice", { level: 2 });
|
|
210
|
+
expect(noticeFrames?.["/notice/subscribe"]).toBeUndefined();
|
|
211
|
+
|
|
212
|
+
const alertFrames = await publishAndCollectFrames("onAlert", { message: "fire" });
|
|
213
|
+
expect(Object.keys(alertFrames ?? {})).toEqual(["/alert/subscribe"]);
|
|
214
|
+
expect(JSON.parse(alertFrames?.["/alert/subscribe"] ?? "null")).toEqual({
|
|
215
|
+
event: "onAlert",
|
|
216
|
+
data: { message: "fire" },
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it("HMR refresh로 스키마가 바뀌면 이전 스키마로만 유효한 payload는 더 이상 인코딩되지 않는다", async () => {
|
|
221
|
+
const StrictV2 = z.object({ onMessage: z.object({ text: z.string().max(3) }) });
|
|
222
|
+
setApis([chatApi(ChatEventsV1)]);
|
|
223
|
+
await startServer("test");
|
|
224
|
+
|
|
225
|
+
const before = await publishAndCollectFrames("onMessage", { text: "long text" });
|
|
226
|
+
expect(before?.["/chat/subscribe"]).toBeDefined();
|
|
227
|
+
|
|
228
|
+
setApis([chatApi(StrictV2)]);
|
|
229
|
+
await Sonamu.refreshHttpValidators();
|
|
230
|
+
|
|
231
|
+
const after = await publishAndCollectFrames("onMessage", { text: "long text" });
|
|
232
|
+
expect(after?.["/chat/subscribe"]).toBeUndefined();
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -3,17 +3,16 @@ import { describe, expect, it, vi } from "vitest";
|
|
|
3
3
|
import { type WebSocket } from "ws";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
type AnyWebSocketConnection,
|
|
8
|
-
type WebSocketConnection,
|
|
9
|
-
WebSocketRuntime,
|
|
10
|
-
} from "../../stream/ws";
|
|
6
|
+
import { type WebSocketConnection, WebSocketRuntime } from "../../stream/ws";
|
|
11
7
|
import { type WebSocketTelemetryConnectionContext } from "../../stream/ws-telemetry";
|
|
12
8
|
import { type SonamuFastifyConfig } from "../../types/types";
|
|
13
9
|
import { type WebSocketContext } from "../context";
|
|
14
10
|
import { type ExtendedApi } from "../decorators";
|
|
15
11
|
import { Sonamu } from "../sonamu";
|
|
16
12
|
|
|
13
|
+
// runtime.registerConnection이 돌려주는 연결 타입. spy 대역도 이 타입으로 만들어야 한다
|
|
14
|
+
type RuntimeWebSocketConnection = Awaited<ReturnType<WebSocketRuntime["registerConnection"]>>;
|
|
15
|
+
|
|
17
16
|
interface TestWebSocketMessage {
|
|
18
17
|
roomId?: string;
|
|
19
18
|
}
|
|
@@ -25,7 +24,7 @@ interface TestWebSocketEvents {
|
|
|
25
24
|
describe("Sonamu websocket context scoping", () => {
|
|
26
25
|
it("웹소켓 컨텍스트를 생성한 뒤 해당 컨텍스트 안에서 guard를 실행한다", async () => {
|
|
27
26
|
const executionOrder: string[] = [];
|
|
28
|
-
const rawWs:
|
|
27
|
+
const rawWs: RuntimeWebSocketConnection = {
|
|
29
28
|
id: "ws-guard",
|
|
30
29
|
namespace: "chat",
|
|
31
30
|
transport: "ws" as const,
|
|
@@ -38,10 +37,18 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
38
37
|
waitForClose() {
|
|
39
38
|
return Promise.resolve();
|
|
40
39
|
},
|
|
41
|
-
join() {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
join() {
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
},
|
|
43
|
+
leave() {
|
|
44
|
+
return Promise.resolve();
|
|
45
|
+
},
|
|
46
|
+
setUserId() {
|
|
47
|
+
return Promise.resolve();
|
|
48
|
+
},
|
|
49
|
+
clearUserId() {
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
},
|
|
45
52
|
};
|
|
46
53
|
const runtime = new WebSocketRuntime({ nodeId: "websocket-guard-test" });
|
|
47
54
|
const originalState = Sonamu.captureTestingSnapshot();
|
|
@@ -49,8 +56,8 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
49
56
|
let guardContext: WebSocketContext | null = null;
|
|
50
57
|
let guardTransport: WebSocketContext["transport"] | null = null;
|
|
51
58
|
|
|
52
|
-
vi.spyOn(runtime, "registerConnection").
|
|
53
|
-
vi.spyOn(runtime, "activateConnection").mockImplementation(() => {
|
|
59
|
+
vi.spyOn(runtime, "registerConnection").mockResolvedValue(rawWs);
|
|
60
|
+
vi.spyOn(runtime, "activateConnection").mockImplementation(async () => {
|
|
54
61
|
executionOrder.push("activate");
|
|
55
62
|
});
|
|
56
63
|
vi.spyOn(Sonamu, "createWebSocketContext").mockImplementation(async (_config, request, ws) => {
|
|
@@ -104,10 +111,7 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
104
111
|
Reflect.set(Sonamu, "syncerValue", { types: {} });
|
|
105
112
|
|
|
106
113
|
await Sonamu.createWebSocketHandlerForTesting(api, config)(
|
|
107
|
-
{
|
|
108
|
-
socket:
|
|
109
|
-
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ {} as WebSocket,
|
|
110
|
-
},
|
|
114
|
+
/* SAFETY: API 데코레이터와 Zod 검증기 등록 계약이 이 값의 타입을 보장한다. */ {} as WebSocket,
|
|
111
115
|
request,
|
|
112
116
|
);
|
|
113
117
|
|
|
@@ -144,10 +148,18 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
144
148
|
waitForClose() {
|
|
145
149
|
return Promise.resolve();
|
|
146
150
|
},
|
|
147
|
-
join() {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
join() {
|
|
152
|
+
return Promise.resolve();
|
|
153
|
+
},
|
|
154
|
+
leave() {
|
|
155
|
+
return Promise.resolve();
|
|
156
|
+
},
|
|
157
|
+
setUserId() {
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
},
|
|
160
|
+
clearUserId() {
|
|
161
|
+
return Promise.resolve();
|
|
162
|
+
},
|
|
151
163
|
};
|
|
152
164
|
|
|
153
165
|
let context: WebSocketContext<TestWebSocketEvents, TestWebSocketEvents> | null = null;
|
|
@@ -201,10 +213,18 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
201
213
|
waitForClose() {
|
|
202
214
|
return Promise.resolve();
|
|
203
215
|
},
|
|
204
|
-
join() {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
216
|
+
join() {
|
|
217
|
+
return Promise.resolve();
|
|
218
|
+
},
|
|
219
|
+
leave() {
|
|
220
|
+
return Promise.resolve();
|
|
221
|
+
},
|
|
222
|
+
setUserId() {
|
|
223
|
+
return Promise.resolve();
|
|
224
|
+
},
|
|
225
|
+
clearUserId() {
|
|
226
|
+
return Promise.resolve();
|
|
227
|
+
},
|
|
208
228
|
};
|
|
209
229
|
|
|
210
230
|
let context: WebSocketContext<TestWebSocketEvents, TestWebSocketEvents> | null = null;
|
|
@@ -243,4 +263,201 @@ describe("Sonamu websocket context scoping", () => {
|
|
|
243
263
|
|
|
244
264
|
expect(seenTraceContext).toEqual(messageTraceContext);
|
|
245
265
|
});
|
|
266
|
+
|
|
267
|
+
it("핸들러는 registerConnection과 activateConnection이 끝날 때까지 기다리며 context → guard → activate → handler 순서를 지킨다", async () => {
|
|
268
|
+
const executionOrder: string[] = [];
|
|
269
|
+
const rawWs: RuntimeWebSocketConnection = {
|
|
270
|
+
id: "ws-async",
|
|
271
|
+
namespace: "chat",
|
|
272
|
+
transport: "ws" as const,
|
|
273
|
+
closed: false,
|
|
274
|
+
publishUntyped() {},
|
|
275
|
+
close() {},
|
|
276
|
+
onClose() {},
|
|
277
|
+
onMessage() {},
|
|
278
|
+
publish() {},
|
|
279
|
+
waitForClose() {
|
|
280
|
+
return Promise.resolve();
|
|
281
|
+
},
|
|
282
|
+
join() {
|
|
283
|
+
return Promise.resolve();
|
|
284
|
+
},
|
|
285
|
+
leave() {
|
|
286
|
+
return Promise.resolve();
|
|
287
|
+
},
|
|
288
|
+
setUserId() {
|
|
289
|
+
return Promise.resolve();
|
|
290
|
+
},
|
|
291
|
+
clearUserId() {
|
|
292
|
+
return Promise.resolve();
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
const runtime = new WebSocketRuntime({ nodeId: "websocket-async-test" });
|
|
296
|
+
const originalState = Sonamu.captureTestingSnapshot();
|
|
297
|
+
let releaseRegister!: () => void;
|
|
298
|
+
let releaseActivate!: () => void;
|
|
299
|
+
const registerGate = new Promise<void>((resolve) => {
|
|
300
|
+
releaseRegister = resolve;
|
|
301
|
+
});
|
|
302
|
+
const activateGate = new Promise<void>((resolve) => {
|
|
303
|
+
releaseActivate = resolve;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const registerSpy = vi.spyOn(runtime, "registerConnection").mockImplementation(async () => {
|
|
307
|
+
executionOrder.push("register:start");
|
|
308
|
+
await registerGate;
|
|
309
|
+
executionOrder.push("register:end");
|
|
310
|
+
return rawWs;
|
|
311
|
+
});
|
|
312
|
+
const activateSpy = vi
|
|
313
|
+
.spyOn(runtime, "activateConnection")
|
|
314
|
+
.mockImplementation(async (connectionId) => {
|
|
315
|
+
executionOrder.push(`activate:start:${connectionId}`);
|
|
316
|
+
await activateGate;
|
|
317
|
+
executionOrder.push("activate:end");
|
|
318
|
+
});
|
|
319
|
+
vi.spyOn(Sonamu, "createWebSocketContext").mockImplementation(async (_config, request, ws) => {
|
|
320
|
+
executionOrder.push("context");
|
|
321
|
+
return {
|
|
322
|
+
transport: "ws",
|
|
323
|
+
request,
|
|
324
|
+
headers: request.headers,
|
|
325
|
+
ws,
|
|
326
|
+
naiteStore: new Map(),
|
|
327
|
+
locale: "ko",
|
|
328
|
+
user: null,
|
|
329
|
+
session: null,
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
vi.spyOn(Sonamu, "invokeModelMethod").mockImplementation(async () => {
|
|
333
|
+
executionOrder.push("handler");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const config = /* SAFETY: 테스트는 guardHandler와 contextProvider만 사용한다. */ {
|
|
337
|
+
guardHandler() {
|
|
338
|
+
executionOrder.push("guard");
|
|
339
|
+
},
|
|
340
|
+
contextProvider(defaultContext) {
|
|
341
|
+
return defaultContext;
|
|
342
|
+
},
|
|
343
|
+
} as SonamuFastifyConfig;
|
|
344
|
+
const api: ExtendedApi = {
|
|
345
|
+
modelName: "ChatFrame",
|
|
346
|
+
methodName: "subscribe",
|
|
347
|
+
path: "/chat/subscribe",
|
|
348
|
+
options: { guards: ["user"] },
|
|
349
|
+
websocketOptions: {
|
|
350
|
+
outEvents: z.object({}),
|
|
351
|
+
inEvents: z.object({}),
|
|
352
|
+
namespace: "chat",
|
|
353
|
+
},
|
|
354
|
+
typeParameters: [],
|
|
355
|
+
parameters: [],
|
|
356
|
+
returnType: "void",
|
|
357
|
+
};
|
|
358
|
+
const request = /* SAFETY: 테스트는 headers와 query만 읽는다. */ {
|
|
359
|
+
headers: {},
|
|
360
|
+
query: {},
|
|
361
|
+
} as FastifyRequest;
|
|
362
|
+
const socket =
|
|
363
|
+
/* SAFETY: registerConnection이 mock이라 소켓 메서드는 호출되지 않는다. */ {} as WebSocket;
|
|
364
|
+
try {
|
|
365
|
+
Sonamu.websocketRuntime = runtime;
|
|
366
|
+
Reflect.set(Sonamu, "syncerValue", { types: {} });
|
|
367
|
+
|
|
368
|
+
let handlerDone = false;
|
|
369
|
+
const handling = Sonamu.createWebSocketHandlerForTesting(api, config)(socket, request).then(
|
|
370
|
+
() => {
|
|
371
|
+
handlerDone = true;
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
375
|
+
|
|
376
|
+
// 등록이 끝나기 전에는 context를 만들지 않는다
|
|
377
|
+
expect(executionOrder).toEqual(["register:start"]);
|
|
378
|
+
|
|
379
|
+
releaseRegister();
|
|
380
|
+
await new Promise<void>((resolve) => setImmediate(resolve));
|
|
381
|
+
|
|
382
|
+
// 활성화가 끝나기 전에는 handler를 실행하지 않는다
|
|
383
|
+
expect(executionOrder).toEqual([
|
|
384
|
+
"register:start",
|
|
385
|
+
"register:end",
|
|
386
|
+
"context",
|
|
387
|
+
"guard",
|
|
388
|
+
"activate:start:ws-async",
|
|
389
|
+
]);
|
|
390
|
+
expect(handlerDone).toBe(false);
|
|
391
|
+
|
|
392
|
+
releaseActivate();
|
|
393
|
+
await handling;
|
|
394
|
+
|
|
395
|
+
expect(executionOrder).toEqual([
|
|
396
|
+
"register:start",
|
|
397
|
+
"register:end",
|
|
398
|
+
"context",
|
|
399
|
+
"guard",
|
|
400
|
+
"activate:start:ws-async",
|
|
401
|
+
"activate:end",
|
|
402
|
+
"handler",
|
|
403
|
+
]);
|
|
404
|
+
expect(registerSpy).toHaveBeenCalledWith(
|
|
405
|
+
socket,
|
|
406
|
+
expect.objectContaining({
|
|
407
|
+
routeKey: "/chat/subscribe",
|
|
408
|
+
namespace: "chat",
|
|
409
|
+
active: false,
|
|
410
|
+
}),
|
|
411
|
+
);
|
|
412
|
+
expect(activateSpy).toHaveBeenCalledTimes(1);
|
|
413
|
+
} finally {
|
|
414
|
+
Sonamu.restoreTestingSnapshot(originalState);
|
|
415
|
+
vi.restoreAllMocks();
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it("scoped 연결의 join/leave/setUserId/clearUserId는 원본 연결의 promise를 그대로 반환한다", async () => {
|
|
420
|
+
const joinPromise = Promise.resolve();
|
|
421
|
+
const leavePromise = Promise.resolve();
|
|
422
|
+
const setUserIdPromise = Promise.resolve();
|
|
423
|
+
const clearUserIdPromise = Promise.resolve();
|
|
424
|
+
const calls: string[] = [];
|
|
425
|
+
const rawWs: WebSocketConnection<TestWebSocketEvents, TestWebSocketEvents> = {
|
|
426
|
+
id: "ws-scoped",
|
|
427
|
+
namespace: "chat",
|
|
428
|
+
transport: "ws" as const,
|
|
429
|
+
closed: false,
|
|
430
|
+
publishUntyped() {},
|
|
431
|
+
close() {},
|
|
432
|
+
onClose() {},
|
|
433
|
+
onMessage() {},
|
|
434
|
+
publish() {},
|
|
435
|
+
waitForClose() {
|
|
436
|
+
return Promise.resolve();
|
|
437
|
+
},
|
|
438
|
+
join(roomId) {
|
|
439
|
+
calls.push(`join:${roomId}`);
|
|
440
|
+
return joinPromise;
|
|
441
|
+
},
|
|
442
|
+
leave(roomId) {
|
|
443
|
+
calls.push(`leave:${roomId}`);
|
|
444
|
+
return leavePromise;
|
|
445
|
+
},
|
|
446
|
+
setUserId(userId) {
|
|
447
|
+
calls.push(`setUserId:${JSON.stringify(userId)}`);
|
|
448
|
+
return setUserIdPromise;
|
|
449
|
+
},
|
|
450
|
+
clearUserId() {
|
|
451
|
+
calls.push("clearUserId");
|
|
452
|
+
return clearUserIdPromise;
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
const scopedWs = Sonamu.createScopedWebSocketConnectionForTesting(rawWs, () => null);
|
|
456
|
+
|
|
457
|
+
expect(scopedWs.join("room-1")).toBe(joinPromise);
|
|
458
|
+
expect(scopedWs.leave("room-1")).toBe(leavePromise);
|
|
459
|
+
expect(scopedWs.setUserId(1)).toBe(setUserIdPromise);
|
|
460
|
+
expect(scopedWs.clearUserId()).toBe(clearUserIdPromise);
|
|
461
|
+
expect(calls).toEqual(["join:room-1", "leave:room-1", "setUserId:1", "clearUserId"]);
|
|
462
|
+
});
|
|
246
463
|
});
|
package/src/api/config.ts
CHANGED
|
@@ -201,7 +201,7 @@ export type SonamuServerOptions = {
|
|
|
201
201
|
// 프로젝트 외부에서 접근할 수 있는 URL. 기본값은 {server.listen.host}:{server.listen.port} 입니다.
|
|
202
202
|
baseUrl?: string;
|
|
203
203
|
|
|
204
|
-
fastify?: Omit<FastifyServerOptions, "logger">;
|
|
204
|
+
fastify?: Omit<FastifyServerOptions, "logger" | "loggerInstance">;
|
|
205
205
|
|
|
206
206
|
listen?: {
|
|
207
207
|
port: number;
|
package/src/api/context.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type IncomingHttpHeaders,
|
|
3
|
-
type IncomingMessage,
|
|
4
|
-
type Server,
|
|
5
|
-
type ServerResponse,
|
|
6
|
-
} from "http";
|
|
1
|
+
import { type IncomingHttpHeaders } from "http";
|
|
7
2
|
|
|
8
3
|
import { type Session, type User } from "better-auth";
|
|
9
4
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
|
10
|
-
import { type RouteGenericInterface } from "fastify/types/route";
|
|
11
5
|
import { type ZodObject } from "zod";
|
|
12
6
|
|
|
13
7
|
import { type NaiteStore } from "../naite/naite";
|
|
@@ -32,7 +26,7 @@ type BaseContext = {
|
|
|
32
26
|
|
|
33
27
|
export type Context = BaseContext & {
|
|
34
28
|
transport: "http";
|
|
35
|
-
reply: FastifyReply
|
|
29
|
+
reply: FastifyReply;
|
|
36
30
|
createSSE: <T extends ZodObject>(events: T) => ReturnType<typeof createSSEFactory<T>>;
|
|
37
31
|
/** buffer 모드에서 업로드된 파일 */
|
|
38
32
|
bufferedFiles?: BufferedFile[];
|