@gencow/core 0.1.26 → 0.1.28
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/crud.d.ts +12 -0
- package/dist/crud.js +16 -0
- package/dist/db.d.ts +13 -0
- package/dist/db.js +16 -0
- package/dist/document-types.d.ts +65 -0
- package/dist/document-types.js +15 -0
- package/dist/grounded-answer-types.d.ts +62 -0
- package/dist/grounded-answer-types.js +6 -0
- package/dist/index.d.ts +12 -2
- package/dist/index.js +5 -1
- package/dist/rag-ingest-types.d.ts +39 -0
- package/dist/rag-ingest-types.js +1 -0
- package/dist/rag-operations-types.d.ts +81 -0
- package/dist/rag-operations-types.js +1 -0
- package/dist/rag-schema.d.ts +1557 -0
- package/dist/rag-schema.js +87 -0
- package/dist/reactive.d.ts +13 -0
- package/dist/rls-db.d.ts +9 -2
- package/dist/runtime-env-policy.d.ts +5 -0
- package/dist/runtime-env-policy.js +56 -0
- package/dist/search-types.d.ts +83 -0
- package/dist/search-types.js +1 -0
- package/dist/server.d.ts +1 -2
- package/dist/server.js +0 -1
- package/dist/storage-shared.d.ts +36 -0
- package/dist/storage-shared.js +39 -0
- package/dist/storage.d.ts +2 -26
- package/dist/storage.js +19 -15
- package/dist/workflow-types.d.ts +3 -1
- package/package.json +1 -1
- package/src/crud.ts +33 -0
- package/src/document-types.ts +95 -0
- package/src/grounded-answer-types.ts +78 -0
- package/src/index.ts +68 -2
- package/src/rag-ingest-types.ts +52 -0
- package/src/rag-operations-types.ts +90 -0
- package/src/rag-schema.ts +94 -0
- package/src/reactive.ts +13 -0
- package/src/rls-db.ts +9 -4
- package/src/runtime-env-policy.ts +66 -0
- package/src/search-types.ts +91 -0
- package/src/server.ts +1 -2
- package/src/storage-shared.ts +74 -0
- package/src/storage.ts +29 -46
- package/src/workflow-types.ts +3 -1
- package/src/__tests__/auth.test.ts +0 -118
- package/src/__tests__/crons.test.ts +0 -83
- package/src/__tests__/crud-codegen-integration.test.ts +0 -246
- package/src/__tests__/crud-owner-rls.test.ts +0 -387
- package/src/__tests__/crud.test.ts +0 -930
- package/src/__tests__/dist-exports.test.ts +0 -176
- package/src/__tests__/fixtures/basic/auth.ts +0 -32
- package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
- package/src/__tests__/fixtures/basic/index.ts +0 -6
- package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
- package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
- package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
- package/src/__tests__/fixtures/basic/schema.ts +0 -51
- package/src/__tests__/fixtures/basic/tasks.ts +0 -15
- package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
- package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
- package/src/__tests__/helpers/pglite-migrations.ts +0 -32
- package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
- package/src/__tests__/helpers/seed-like-fill.ts +0 -202
- package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
- package/src/__tests__/httpaction.test.ts +0 -122
- package/src/__tests__/image-optimization.test.ts +0 -648
- package/src/__tests__/load.test.ts +0 -389
- package/src/__tests__/network-sim.test.ts +0 -319
- package/src/__tests__/reactive.test.ts +0 -479
- package/src/__tests__/retry.test.ts +0 -113
- package/src/__tests__/rls-crud-basic.test.ts +0 -317
- package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
- package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
- package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
- package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
- package/src/__tests__/rls-session-and-policies.test.ts +0 -228
- package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
- package/src/__tests__/scheduler-durable.test.ts +0 -173
- package/src/__tests__/scheduler-exec.test.ts +0 -328
- package/src/__tests__/scheduler.test.ts +0 -187
- package/src/__tests__/storage.test.ts +0 -334
- package/src/__tests__/tsconfig.json +0 -8
- package/src/__tests__/validator.test.ts +0 -323
- package/src/__tests__/workflow.test.ts +0 -606
- package/src/__tests__/ws-integration.test.ts +0 -309
- package/src/__tests__/ws-scale.test.ts +0 -241
- package/src/auth.ts +0 -155
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* packages/core/src/__tests__/network-sim.test.ts
|
|
3
|
-
*
|
|
4
|
-
* 네트워크 I/O 특성을 시뮬레이션한 부하 테스트.
|
|
5
|
-
*
|
|
6
|
-
* mock `send()`에 실제 네트워크 조건을 모델링:
|
|
7
|
-
* - LAN : P50 = 0.5ms, P99 = 2ms, loss = 0%
|
|
8
|
-
* - WAN : P50 = 30ms, P99 = 120ms, loss = 0.1%
|
|
9
|
-
* - Mobile: P50 = 80ms, P99 = 400ms, loss = 2%
|
|
10
|
-
* - 혼잡 : P50 = 200ms, P99 = 2000ms, loss = 5%
|
|
11
|
-
*
|
|
12
|
-
* 측정 지표:
|
|
13
|
-
* - 전체 delivery 시간 (P50 / P95 / P99)
|
|
14
|
-
* - 패킷 손실 후 실제 수신률
|
|
15
|
-
* - slow client(back-pressure)가 전체에 미치는 영향
|
|
16
|
-
*
|
|
17
|
-
* Run: bun test packages/core/src/__tests__/network-sim.test.ts
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { describe, it, expect } from "bun:test";
|
|
21
|
-
import { buildRealtimeCtx, subscribe, deregisterClient } from "../reactive.js";
|
|
22
|
-
|
|
23
|
-
// ─── 네트워크 조건 정의 ──────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
interface NetworkProfile {
|
|
26
|
-
name: string;
|
|
27
|
-
p50LatencyMs: number; // 중간값 지연
|
|
28
|
-
p99LatencyMs: number; // 99번째 백분위 지연
|
|
29
|
-
lossRate: number; // 패킷 손실률 0~1
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const PROFILES: Record<string, NetworkProfile> = {
|
|
33
|
-
LAN: { name: "LAN", p50LatencyMs: 0.5, p99LatencyMs: 2, lossRate: 0.0 },
|
|
34
|
-
WAN: { name: "WAN", p50LatencyMs: 30, p99LatencyMs: 120, lossRate: 0.001 },
|
|
35
|
-
Mobile: { name: "Mobile 4G", p50LatencyMs: 80, p99LatencyMs: 400, lossRate: 0.02 },
|
|
36
|
-
Congested: { name: "혼잡", p50LatencyMs: 200, p99LatencyMs: 2000, lossRate: 0.05 },
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// ─── 네트워크 지연 샘플러 (log-normal 분포 근사) ────────────────────────────
|
|
40
|
-
// log-normal은 실제 네트워크 지연 분포와 유사함
|
|
41
|
-
|
|
42
|
-
function sampleLatency(p50: number, p99: number): number {
|
|
43
|
-
// log-normal: μ = ln(p50), σ 역산
|
|
44
|
-
const mu = Math.log(p50);
|
|
45
|
-
const sigma = (Math.log(p99) - mu) / 2.326; // z=2.326 = 99th percentile
|
|
46
|
-
// Box-Muller transform
|
|
47
|
-
const u1 = Math.random();
|
|
48
|
-
const u2 = Math.random();
|
|
49
|
-
const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
|
|
50
|
-
return Math.max(0, Math.exp(mu + sigma * z));
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ─── 네트워크 시뮬레이션 WS mock ─────────────────────────────────────────────
|
|
54
|
-
|
|
55
|
-
function makeNetworkWs(profile: NetworkProfile) {
|
|
56
|
-
const deliveryTimes: number[] = [];
|
|
57
|
-
let dropped = 0;
|
|
58
|
-
let sendCallCount = 0;
|
|
59
|
-
const sendStart = performance.now();
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
send: (msg: string) => {
|
|
63
|
-
sendCallCount++;
|
|
64
|
-
// 패킷 손실 시뮬레이션
|
|
65
|
-
if (Math.random() < profile.lossRate) {
|
|
66
|
-
dropped++;
|
|
67
|
-
return; // 손실 — 전달 안 됨
|
|
68
|
-
}
|
|
69
|
-
// 지연 시뮬레이션 (비동기 — 실제 I/O 기다림)
|
|
70
|
-
const latency = sampleLatency(profile.p50LatencyMs, profile.p99LatencyMs);
|
|
71
|
-
setTimeout(() => {
|
|
72
|
-
deliveryTimes.push(performance.now() - sendStart);
|
|
73
|
-
}, latency);
|
|
74
|
-
},
|
|
75
|
-
readyState: 1,
|
|
76
|
-
get deliveryTimes() {
|
|
77
|
-
return deliveryTimes;
|
|
78
|
-
},
|
|
79
|
-
get dropped() {
|
|
80
|
-
return dropped;
|
|
81
|
-
},
|
|
82
|
-
get sendCallCount() {
|
|
83
|
-
return sendCallCount;
|
|
84
|
-
},
|
|
85
|
-
} as any;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── 퍼센타일 계산 ───────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
function percentile(sorted: number[], p: number): number {
|
|
91
|
-
if (sorted.length === 0) return 0;
|
|
92
|
-
const idx = Math.ceil((p / 100) * sorted.length) - 1;
|
|
93
|
-
return sorted[Math.max(0, idx)];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function stats(times: number[]) {
|
|
97
|
-
const sorted = [...times].sort((a, b) => a - b);
|
|
98
|
-
return {
|
|
99
|
-
count: sorted.length,
|
|
100
|
-
p50: percentile(sorted, 50),
|
|
101
|
-
p95: percentile(sorted, 95),
|
|
102
|
-
p99: percentile(sorted, 99),
|
|
103
|
-
max: sorted[sorted.length - 1] ?? 0,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
108
|
-
function key() {
|
|
109
|
-
return `net.${Math.random().toString(36).slice(2)}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ─── 1. LAN 환경 ─────────────────────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
describe("[NetSim] LAN 환경 (P50=0.5ms, 손실 0%)", () => {
|
|
115
|
-
it("1,000 구독자 전체 delivery P99 < 10ms", async () => {
|
|
116
|
-
const N = 1_000;
|
|
117
|
-
const k = key();
|
|
118
|
-
const profile = PROFILES.LAN;
|
|
119
|
-
const clients = Array.from({ length: N }, () => makeNetworkWs(profile));
|
|
120
|
-
clients.forEach((ws) => subscribe(k, ws));
|
|
121
|
-
|
|
122
|
-
buildRealtimeCtx().emit(k, [{ id: 1 }]);
|
|
123
|
-
await wait(60 + 20); // debounce + max LAN latency
|
|
124
|
-
|
|
125
|
-
const allTimes = clients.flatMap((ws) => ws.deliveryTimes);
|
|
126
|
-
const s = stats(allTimes);
|
|
127
|
-
const successRate = (s.count / N) * 100;
|
|
128
|
-
|
|
129
|
-
console.log(
|
|
130
|
-
`[LAN] delivered=${s.count}/${N} (${successRate.toFixed(1)}%), P50=${s.p50.toFixed(1)}ms P95=${s.p95.toFixed(1)}ms P99=${s.p99.toFixed(1)}ms`,
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
expect(s.count).toBe(N); // 손실 없음
|
|
134
|
-
expect(s.p99).toBeLessThan(200); // debounce(50) + LAN P99(2) + margin
|
|
135
|
-
clients.forEach((ws) => deregisterClient(ws));
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
// ─── 2. WAN 환경 ─────────────────────────────────────────────────────────────
|
|
140
|
-
|
|
141
|
-
describe("[NetSim] WAN 환경 (P50=30ms, 손실 0.1%)", () => {
|
|
142
|
-
it("1,000 구독자 전체 delivery P95 < 300ms, 수신률 99% 이상", async () => {
|
|
143
|
-
const N = 1_000;
|
|
144
|
-
const k = key();
|
|
145
|
-
const profile = PROFILES.WAN;
|
|
146
|
-
const clients = Array.from({ length: N }, () => makeNetworkWs(profile));
|
|
147
|
-
clients.forEach((ws) => subscribe(k, ws));
|
|
148
|
-
|
|
149
|
-
buildRealtimeCtx().emit(k, [{ id: 1 }]);
|
|
150
|
-
await wait(60 + 300); // debounce + WAN P99
|
|
151
|
-
|
|
152
|
-
const allTimes = clients.flatMap((ws) => ws.deliveryTimes);
|
|
153
|
-
const s = stats(allTimes);
|
|
154
|
-
const successRate = (s.count / N) * 100;
|
|
155
|
-
const dropped = clients.reduce((a, ws) => a + ws.dropped, 0);
|
|
156
|
-
|
|
157
|
-
console.log(
|
|
158
|
-
`[WAN] delivered=${s.count}/${N} (${successRate.toFixed(2)}%), dropped=${dropped}, P50=${s.p50.toFixed(1)}ms P95=${s.p95.toFixed(1)}ms P99=${s.p99.toFixed(1)}ms`,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
expect(successRate).toBeGreaterThan(99); // 0.1% 손실 → 99% 이상 수신
|
|
162
|
-
expect(s.p95).toBeLessThan(300);
|
|
163
|
-
clients.forEach((ws) => deregisterClient(ws));
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// ─── 3. 모바일 환경 ──────────────────────────────────────────────────────────
|
|
168
|
-
|
|
169
|
-
describe("[NetSim] Mobile 4G 환경 (P50=80ms, 손실 2%)", () => {
|
|
170
|
-
it("500 구독자 전체 delivery P95 < 700ms, 수신률 95% 이상", async () => {
|
|
171
|
-
const N = 500;
|
|
172
|
-
const k = key();
|
|
173
|
-
const profile = PROFILES.Mobile;
|
|
174
|
-
const clients = Array.from({ length: N }, () => makeNetworkWs(profile));
|
|
175
|
-
clients.forEach((ws) => subscribe(k, ws));
|
|
176
|
-
|
|
177
|
-
buildRealtimeCtx().emit(k, [{ id: 1 }]);
|
|
178
|
-
await wait(60 + 600); // debounce + Mobile P99
|
|
179
|
-
|
|
180
|
-
const allTimes = clients.flatMap((ws) => ws.deliveryTimes);
|
|
181
|
-
const s = stats(allTimes);
|
|
182
|
-
const successRate = (s.count / N) * 100;
|
|
183
|
-
const dropped = clients.reduce((a, ws) => a + ws.dropped, 0);
|
|
184
|
-
|
|
185
|
-
console.log(
|
|
186
|
-
`[Mobile] delivered=${s.count}/${N} (${successRate.toFixed(2)}%), dropped=${dropped}, P50=${s.p50.toFixed(1)}ms P95=${s.p95.toFixed(1)}ms P99=${s.p99.toFixed(1)}ms`,
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
expect(successRate).toBeGreaterThan(95); // 2% 손실 → 95%+ 수신
|
|
190
|
-
expect(s.p95).toBeLessThan(700);
|
|
191
|
-
clients.forEach((ws) => deregisterClient(ws));
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// ─── 4. Slow Client (back-pressure) 격리 ─────────────────────────────────────
|
|
196
|
-
//
|
|
197
|
-
// 일부 클라이언트가 매우 느릴 때 다른 클라이언트에 영향을 주지 않아야 함.
|
|
198
|
-
// (현재 구현: for loop의 send()가 동기적으로 setTimeout만 등록하므로 격리됨)
|
|
199
|
-
|
|
200
|
-
describe("[NetSim] Slow client back-pressure 격리", () => {
|
|
201
|
-
it("일부 slow client가 있어도 fast client 전달 시간은 영향 없다", async () => {
|
|
202
|
-
const N_FAST = 900;
|
|
203
|
-
const N_SLOW = 100;
|
|
204
|
-
const k = key();
|
|
205
|
-
|
|
206
|
-
const fastClients = Array.from({ length: N_FAST }, () => makeNetworkWs(PROFILES.LAN));
|
|
207
|
-
const slowClients = Array.from({ length: N_SLOW }, () => makeNetworkWs(PROFILES.Congested));
|
|
208
|
-
|
|
209
|
-
fastClients.forEach((ws) => subscribe(k, ws));
|
|
210
|
-
slowClients.forEach((ws) => subscribe(k, ws));
|
|
211
|
-
|
|
212
|
-
const emitStart = performance.now();
|
|
213
|
-
buildRealtimeCtx().emit(k, [{ id: 1 }]);
|
|
214
|
-
await wait(60 + 30); // debounce + LAN P99만 기다림 (slow client는 아직 미도달)
|
|
215
|
-
const elapsed = performance.now() - emitStart;
|
|
216
|
-
|
|
217
|
-
const fastDelivered = fastClients.flatMap((ws) => ws.deliveryTimes).length;
|
|
218
|
-
const slowDelivered = slowClients.flatMap((ws) => ws.deliveryTimes).length;
|
|
219
|
-
|
|
220
|
-
const fastRate = (fastDelivered / N_FAST) * 100;
|
|
221
|
-
const slowRate = (slowDelivered / N_SLOW) * 100;
|
|
222
|
-
|
|
223
|
-
console.log(
|
|
224
|
-
`[back-pressure] fast: ${fastDelivered}/${N_FAST} (${fastRate.toFixed(1)}%) in ${elapsed.toFixed(1)}ms`,
|
|
225
|
-
);
|
|
226
|
-
console.log(
|
|
227
|
-
`[back-pressure] slow: ${slowDelivered}/${N_SLOW} (${slowRate.toFixed(1)}%) — still in flight`,
|
|
228
|
-
);
|
|
229
|
-
|
|
230
|
-
// fast client는 LAN latency 이내에 거의 모두 도달
|
|
231
|
-
expect(fastRate).toBeGreaterThan(95);
|
|
232
|
-
// slow client는 혼잡 환경이라 아직 미도달 (격리 확인)
|
|
233
|
-
expect(slowRate).toBeLessThan(50); // 혼잡 환경 P50=200ms, 아직 30ms만 기다렸으므로
|
|
234
|
-
|
|
235
|
-
fastClients.forEach((ws) => deregisterClient(ws));
|
|
236
|
-
slowClients.forEach((ws) => deregisterClient(ws));
|
|
237
|
-
});
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// ─── 5. 혼합 환경: LAN + WAN + Mobile 혼재 ──────────────────────────────────
|
|
241
|
-
|
|
242
|
-
describe("[NetSim] 혼합 환경 (LAN + WAN + Mobile)", () => {
|
|
243
|
-
it("500명 혼합 구독자 전체 delivery → 그룹별 지연 분포 확인", async () => {
|
|
244
|
-
const k = key();
|
|
245
|
-
const groups = {
|
|
246
|
-
lan: { n: 200, profile: PROFILES.LAN, clients: [] as any[] },
|
|
247
|
-
wan: { n: 200, profile: PROFILES.WAN, clients: [] as any[] },
|
|
248
|
-
mobile: { n: 100, profile: PROFILES.Mobile, clients: [] as any[] },
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
for (const g of Object.values(groups)) {
|
|
252
|
-
g.clients = Array.from({ length: g.n }, () => makeNetworkWs(g.profile));
|
|
253
|
-
g.clients.forEach((ws: any) => subscribe(k, ws));
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
buildRealtimeCtx().emit(k, [{ id: 1 }]);
|
|
257
|
-
await wait(60 + 600); // 가장 느린 Mobile P99까지 대기
|
|
258
|
-
|
|
259
|
-
for (const [name, g] of Object.entries(groups)) {
|
|
260
|
-
const allTimes = g.clients.flatMap((ws: any) => ws.deliveryTimes);
|
|
261
|
-
const s = stats(allTimes);
|
|
262
|
-
const dropped = g.clients.reduce((a: number, ws: any) => a + ws.dropped, 0);
|
|
263
|
-
const successRate = (s.count / g.n) * 100;
|
|
264
|
-
console.log(
|
|
265
|
-
`[mixed/${name}] ${s.count}/${g.n} (${successRate.toFixed(1)}%), dropped=${dropped}, P50=${s.p50.toFixed(1)}ms P95=${s.p95.toFixed(1)}ms`,
|
|
266
|
-
);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// 전체 수신률
|
|
270
|
-
const totalDelivered = Object.values(groups).reduce(
|
|
271
|
-
(a, g) => a + g.clients.flatMap((ws: any) => ws.deliveryTimes).length,
|
|
272
|
-
0,
|
|
273
|
-
);
|
|
274
|
-
const totalN = Object.values(groups).reduce((a, g) => a + g.n, 0);
|
|
275
|
-
const overallRate = (totalDelivered / totalN) * 100;
|
|
276
|
-
|
|
277
|
-
console.log(`[mixed/total] ${totalDelivered}/${totalN} (${overallRate.toFixed(1)}%)`);
|
|
278
|
-
expect(overallRate).toBeGreaterThan(97); // Mobile 2% 손실 반영
|
|
279
|
-
|
|
280
|
-
for (const g of Object.values(groups)) {
|
|
281
|
-
g.clients.forEach((ws: any) => deregisterClient(ws));
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
// ─── 6. debounce + 네트워크 지연 조합 효과 ──────────────────────────────────
|
|
287
|
-
|
|
288
|
-
describe("[NetSim] debounce + 네트워크 지연 조합", () => {
|
|
289
|
-
it("100번 rapid emit 후 단 1회 WAN 전송으로 전체 delivery 완료된다", async () => {
|
|
290
|
-
const N = 200;
|
|
291
|
-
const k = key();
|
|
292
|
-
const clients = Array.from({ length: N }, () => makeNetworkWs(PROFILES.WAN));
|
|
293
|
-
clients.forEach((ws) => subscribe(k, ws));
|
|
294
|
-
|
|
295
|
-
const rt = buildRealtimeCtx();
|
|
296
|
-
// 30ms 간격으로 10번 emit (각 emit 사이에 짧은 간격, 50ms debounce 이내)
|
|
297
|
-
for (let i = 0; i < 10; i++) {
|
|
298
|
-
rt.emit(k, [{ id: i, title: `Update ${i}` }]);
|
|
299
|
-
await wait(4); // 4ms 간격 → 총 40ms < 50ms debounce window
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// 마지막 emit 후 debounce(50ms) + WAN P99(120ms) 대기
|
|
303
|
-
await wait(50 + 200);
|
|
304
|
-
|
|
305
|
-
const allSendCounts = clients.map((ws) => ws.sendCallCount);
|
|
306
|
-
const allDelivered = clients.flatMap((ws) => ws.deliveryTimes);
|
|
307
|
-
|
|
308
|
-
// send()는 debounce로 인해 1번만 호출됨
|
|
309
|
-
expect(allSendCounts.every((c) => c === 1)).toBe(true);
|
|
310
|
-
|
|
311
|
-
const successRate = (allDelivered.length / N) * 100;
|
|
312
|
-
console.log(
|
|
313
|
-
`[debounce+net] sends: 1 per client, WAN delivered: ${allDelivered.length}/${N} (${successRate.toFixed(1)}%)`,
|
|
314
|
-
);
|
|
315
|
-
|
|
316
|
-
expect(successRate).toBeGreaterThan(99); // WAN 0.1% 손실
|
|
317
|
-
clients.forEach((ws) => deregisterClient(ws));
|
|
318
|
-
});
|
|
319
|
-
});
|