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,360 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
|
|
3
|
+
import { type WebSocketTransport } from "../ws";
|
|
4
|
+
import { type WebSocketAudience } from "../ws-audience";
|
|
5
|
+
import {
|
|
6
|
+
type WebSocketClusterBus,
|
|
7
|
+
type WebSocketClusterEnvelope,
|
|
8
|
+
type WebSocketClusterEnvelopeHandler,
|
|
9
|
+
} from "../ws-cluster-bus";
|
|
10
|
+
import { type WebSocketRoomId, type WebSocketUserId } from "../ws-core";
|
|
11
|
+
import { InMemoryWebSocketPresenceStore } from "../ws-presence-store";
|
|
12
|
+
|
|
13
|
+
// setImmediate 단위로 큐를 비워 fan-out/outbound flush/cluster 전달이 끝나기를 기다린다
|
|
14
|
+
export async function flushAsync(rounds: number = 5): Promise<void> {
|
|
15
|
+
for (let index = 0; index < rounds; index += 1) {
|
|
16
|
+
await new Promise<void>((resolve) => {
|
|
17
|
+
setImmediate(resolve);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 조건이 만족될 때까지 setImmediate 단위로 기다린다. 끝내 만족하지 않으면 그대로 반환해 assertion에서 실패하게 한다
|
|
23
|
+
export async function flushUntil(predicate: () => boolean, maxRounds: number = 50): Promise<void> {
|
|
24
|
+
for (let index = 0; index < maxRounds && !predicate(); index += 1) {
|
|
25
|
+
await flushAsync(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type Deferred<T> = {
|
|
30
|
+
promise: Promise<T>;
|
|
31
|
+
resolve: (value: T) => void;
|
|
32
|
+
reject: (error: Error) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function createDeferred<T = void>(): Deferred<T> {
|
|
36
|
+
let resolve!: (value: T) => void;
|
|
37
|
+
let reject!: (error: Error) => void;
|
|
38
|
+
const promise = new Promise<T>((innerResolve, innerReject) => {
|
|
39
|
+
resolve = innerResolve;
|
|
40
|
+
reject = innerReject;
|
|
41
|
+
});
|
|
42
|
+
return { promise, resolve, reject };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// promise가 아직 settle되지 않았는지 확인한다
|
|
46
|
+
export async function isSettled<T>(promise: Promise<T>): Promise<boolean> {
|
|
47
|
+
let settled = false;
|
|
48
|
+
promise.then(
|
|
49
|
+
() => {
|
|
50
|
+
settled = true;
|
|
51
|
+
},
|
|
52
|
+
() => {
|
|
53
|
+
settled = true;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
await flushAsync(2);
|
|
57
|
+
return settled;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class FakeWebSocket extends EventEmitter {
|
|
61
|
+
readyState: 0 | 1 | 2 | 3 = 1;
|
|
62
|
+
bufferedAmount = 0;
|
|
63
|
+
sentMessages: string[] = [];
|
|
64
|
+
pingCount = 0;
|
|
65
|
+
closedWith: { code?: number; reason?: string } | null = null;
|
|
66
|
+
|
|
67
|
+
send(data: string): void {
|
|
68
|
+
this.sentMessages.push(data);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
close(code?: number, reason?: string): void {
|
|
72
|
+
this.closedWith = { code, reason };
|
|
73
|
+
this.readyState = 3;
|
|
74
|
+
this.emit("close", code ?? 1000, Buffer.from(reason ?? "", "utf-8"));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
ping(): void {
|
|
78
|
+
this.pingCount += 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
terminate(): void {
|
|
82
|
+
this.readyState = 3;
|
|
83
|
+
this.emit("close", 1006, Buffer.alloc(0));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function asWebSocket(socket: FakeWebSocket): WebSocketTransport {
|
|
88
|
+
return {
|
|
89
|
+
get bufferedAmount() {
|
|
90
|
+
return socket.bufferedAmount;
|
|
91
|
+
},
|
|
92
|
+
get readyState() {
|
|
93
|
+
return socket.readyState;
|
|
94
|
+
},
|
|
95
|
+
close(code, reason) {
|
|
96
|
+
socket.close(code, reason?.toString());
|
|
97
|
+
},
|
|
98
|
+
off(...args) {
|
|
99
|
+
socket.off(...args);
|
|
100
|
+
},
|
|
101
|
+
on(...args) {
|
|
102
|
+
socket.on(...args);
|
|
103
|
+
},
|
|
104
|
+
ping() {
|
|
105
|
+
socket.ping();
|
|
106
|
+
},
|
|
107
|
+
send(data) {
|
|
108
|
+
socket.send(data.toString());
|
|
109
|
+
},
|
|
110
|
+
terminate() {
|
|
111
|
+
socket.terminate();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 테스트 데이터 직렬화 결과를 되돌린다. 깨진 JSON이면 원문을 포함한 에러로 테스트를 실패시킨다
|
|
117
|
+
function parseTestJson<T>(raw: string): T {
|
|
118
|
+
try {
|
|
119
|
+
return /* SAFETY: 테스트가 직접 직렬화했거나 runtime이 직렬화한 값만 넘긴다. */ JSON.parse(
|
|
120
|
+
raw,
|
|
121
|
+
) as T;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`테스트 JSON 파싱 실패: ${raw}`, { cause: error });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function parseFrames(socket: FakeWebSocket): Array<{ event: string; data: object }> {
|
|
128
|
+
return socket.sentMessages.map((message) =>
|
|
129
|
+
parseTestJson<{ event: string; data: object }>(message),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
type ScriptedMethod =
|
|
134
|
+
| "register"
|
|
135
|
+
| "activate"
|
|
136
|
+
| "unregister"
|
|
137
|
+
| "setUserId"
|
|
138
|
+
| "clearUserId"
|
|
139
|
+
| "join"
|
|
140
|
+
| "leave"
|
|
141
|
+
| "resolveRoute"
|
|
142
|
+
| "getRoomMemberCount"
|
|
143
|
+
| "shutdown";
|
|
144
|
+
|
|
145
|
+
// 다음 호출 직전에 끼워넣을 동작. 동기 throw, reject, 지연된 promise를 그대로 흉내낸다
|
|
146
|
+
type ScriptedInterceptor = () => void | Promise<void>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* InMemoryWebSocketPresenceStore를 감싸 호출 순서를 기록하고, 메서드별로 다음 호출의 동작을 스크립트할 수 있는 store.
|
|
150
|
+
* 호출 기록은 store가 호출을 "받은" 시점에 남긴다.
|
|
151
|
+
*/
|
|
152
|
+
export class ScriptedPresenceStore {
|
|
153
|
+
readonly calls: string[] = [];
|
|
154
|
+
readonly inner: InMemoryWebSocketPresenceStore;
|
|
155
|
+
nodeId?: string;
|
|
156
|
+
private readonly interceptors = new Map<ScriptedMethod, ScriptedInterceptor[]>();
|
|
157
|
+
|
|
158
|
+
constructor(inner: InMemoryWebSocketPresenceStore = new InMemoryWebSocketPresenceStore()) {
|
|
159
|
+
this.inner = inner;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
next(method: ScriptedMethod, interceptor: ScriptedInterceptor): this {
|
|
163
|
+
const queue = this.interceptors.get(method) ?? [];
|
|
164
|
+
queue.push(interceptor);
|
|
165
|
+
this.interceptors.set(method, queue);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 이후 모든 호출에 같은 동작을 적용한다
|
|
170
|
+
always(method: ScriptedMethod, interceptor: ScriptedInterceptor): this {
|
|
171
|
+
for (let index = 0; index < 1_000; index += 1) {
|
|
172
|
+
this.next(method, interceptor);
|
|
173
|
+
}
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
register(input: {
|
|
178
|
+
sessionId: string;
|
|
179
|
+
nodeId: string;
|
|
180
|
+
namespace: string;
|
|
181
|
+
active: boolean;
|
|
182
|
+
connectedAt: Date;
|
|
183
|
+
}): void | Promise<void> {
|
|
184
|
+
this.calls.push(`register:${input.sessionId}`);
|
|
185
|
+
return this.run("register", () => this.inner.register(input));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
activate(sessionId: string): void | Promise<void> {
|
|
189
|
+
this.calls.push(`activate:${sessionId}`);
|
|
190
|
+
return this.run("activate", () => this.inner.activate(sessionId));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
unregister(sessionId: string): void | Promise<void> {
|
|
194
|
+
this.calls.push(`unregister:${sessionId}`);
|
|
195
|
+
return this.run("unregister", () => this.inner.unregister(sessionId));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
setUserId(sessionId: string, userId: WebSocketUserId): void | Promise<void> {
|
|
199
|
+
this.calls.push(`setUserId:${sessionId}:${JSON.stringify(userId)}`);
|
|
200
|
+
return this.run("setUserId", () => this.inner.setUserId(sessionId, userId));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
clearUserId(sessionId: string): void | Promise<void> {
|
|
204
|
+
this.calls.push(`clearUserId:${sessionId}`);
|
|
205
|
+
return this.run("clearUserId", () => this.inner.clearUserId(sessionId));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
join(sessionId: string, roomId: WebSocketRoomId): void | Promise<void> {
|
|
209
|
+
this.calls.push(`join:${sessionId}:${roomId}`);
|
|
210
|
+
return this.run("join", () => this.inner.join(sessionId, roomId));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
leave(sessionId: string, roomId: WebSocketRoomId): void | Promise<void> {
|
|
214
|
+
this.calls.push(`leave:${sessionId}:${roomId}`);
|
|
215
|
+
return this.run("leave", () => this.inner.leave(sessionId, roomId));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
resolveRoute(
|
|
219
|
+
audience: WebSocketAudience,
|
|
220
|
+
nodeId: string,
|
|
221
|
+
):
|
|
222
|
+
| { localSessionIds: string[]; remoteNodeIds: string[] }
|
|
223
|
+
| Promise<{ localSessionIds: string[]; remoteNodeIds: string[] }> {
|
|
224
|
+
this.calls.push(`resolveRoute:${nodeId}:${audience.type}`);
|
|
225
|
+
return this.run("resolveRoute", () => this.inner.resolveRoute(audience, nodeId));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
getRoomMemberCount(roomId: WebSocketRoomId, namespace?: string): number | Promise<number> {
|
|
229
|
+
this.calls.push(`getRoomMemberCount:${roomId}`);
|
|
230
|
+
return this.run("getRoomMemberCount", () => this.inner.getRoomMemberCount(roomId, namespace));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
shutdown(): void | Promise<void> {
|
|
234
|
+
this.calls.push("shutdown");
|
|
235
|
+
return this.run("shutdown", () => undefined);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
callsOf(prefix: string): string[] {
|
|
239
|
+
return this.calls.filter((call) => call.startsWith(prefix));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private run<T>(method: ScriptedMethod, delegate: () => T | Promise<T>): T | Promise<T> {
|
|
243
|
+
const interceptor = this.interceptors.get(method)?.shift();
|
|
244
|
+
if (!interceptor) {
|
|
245
|
+
return delegate();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = interceptor();
|
|
249
|
+
if (result instanceof Promise) {
|
|
250
|
+
return result.then(() => delegate());
|
|
251
|
+
}
|
|
252
|
+
return delegate();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* 한 프로세스 안에서 targetNodeIds 기준으로 envelope을 라우팅하는 fake cluster 네트워크.
|
|
258
|
+
* 전송 구간을 흉내내기 위해 envelope을 JSON 직렬화/역직렬화한 뒤 다음 tick에 전달한다.
|
|
259
|
+
*/
|
|
260
|
+
export class InProcessClusterNetwork {
|
|
261
|
+
readonly published: WebSocketClusterEnvelope[] = [];
|
|
262
|
+
private readonly handlers = new Map<string, Set<WebSocketClusterEnvelopeHandler>>();
|
|
263
|
+
|
|
264
|
+
bus(nodeId: string): InProcessClusterBus {
|
|
265
|
+
return new InProcessClusterBus(nodeId, this);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
subscribe(nodeId: string, handler: WebSocketClusterEnvelopeHandler): () => void {
|
|
269
|
+
const handlers = this.handlers.get(nodeId) ?? new Set();
|
|
270
|
+
handlers.add(handler);
|
|
271
|
+
this.handlers.set(nodeId, handlers);
|
|
272
|
+
return () => {
|
|
273
|
+
handlers.delete(handler);
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
deliver(envelope: WebSocketClusterEnvelope): void {
|
|
278
|
+
const wire = JSON.stringify(envelope);
|
|
279
|
+
this.published.push(parseTestJson<WebSocketClusterEnvelope>(wire));
|
|
280
|
+
setImmediate(() => {
|
|
281
|
+
for (const targetNodeId of envelope.targetNodeIds ?? []) {
|
|
282
|
+
for (const handler of this.handlers.get(targetNodeId) ?? []) {
|
|
283
|
+
const received = parseTestJson<WebSocketClusterEnvelope>(wire);
|
|
284
|
+
void Promise.resolve(handler(received)).catch(() => {});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export class InProcessClusterBus implements WebSocketClusterBus {
|
|
292
|
+
shutdownCalls = 0;
|
|
293
|
+
unsubscribeCalls = 0;
|
|
294
|
+
|
|
295
|
+
constructor(
|
|
296
|
+
readonly nodeId: string,
|
|
297
|
+
private readonly network: InProcessClusterNetwork,
|
|
298
|
+
) {}
|
|
299
|
+
|
|
300
|
+
publish(envelope: WebSocketClusterEnvelope): void {
|
|
301
|
+
this.network.deliver(envelope);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 실제 adapter처럼 구독 완료를 promise로 알린다
|
|
305
|
+
async subscribe(handler: WebSocketClusterEnvelopeHandler): Promise<() => void> {
|
|
306
|
+
const unsubscribe = this.network.subscribe(this.nodeId, handler);
|
|
307
|
+
return () => {
|
|
308
|
+
this.unsubscribeCalls += 1;
|
|
309
|
+
unsubscribe();
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
shutdown(): void {
|
|
314
|
+
this.shutdownCalls += 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// 발행된 envelope을 기록하고 테스트가 직접 수신 envelope을 주입할 수 있는 bus
|
|
319
|
+
export class RecordingClusterBus implements WebSocketClusterBus {
|
|
320
|
+
readonly published: WebSocketClusterEnvelope[] = [];
|
|
321
|
+
readonly handlers = new Set<WebSocketClusterEnvelopeHandler>();
|
|
322
|
+
shutdownCalls = 0;
|
|
323
|
+
unsubscribeCalls = 0;
|
|
324
|
+
nodeId?: string;
|
|
325
|
+
|
|
326
|
+
publish(envelope: WebSocketClusterEnvelope): void | Promise<void> {
|
|
327
|
+
this.published.push(envelope);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
subscribe(handler: WebSocketClusterEnvelopeHandler): (() => void) | Promise<() => void> {
|
|
331
|
+
this.handlers.add(handler);
|
|
332
|
+
return () => {
|
|
333
|
+
this.unsubscribeCalls += 1;
|
|
334
|
+
this.handlers.delete(handler);
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async emit(envelope: WebSocketClusterEnvelope): Promise<void> {
|
|
339
|
+
for (const handler of this.handlers) {
|
|
340
|
+
await handler(envelope);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
shutdown(): void | Promise<void> {
|
|
345
|
+
this.shutdownCalls += 1;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 전역 unhandledRejection을 수집한다. 반환한 함수로 해제하고 수집 결과를 받는다
|
|
350
|
+
export function collectUnhandledRejections(): () => Error[] {
|
|
351
|
+
const collected: Error[] = [];
|
|
352
|
+
const listener = (reason: Error) => {
|
|
353
|
+
collected.push(reason);
|
|
354
|
+
};
|
|
355
|
+
process.on("unhandledRejection", listener);
|
|
356
|
+
return () => {
|
|
357
|
+
process.off("unhandledRejection", listener);
|
|
358
|
+
return collected;
|
|
359
|
+
};
|
|
360
|
+
}
|