@gencow/core 0.1.27 → 0.1.29

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 (130) hide show
  1. package/dist/auth-config.d.ts +92 -5
  2. package/dist/config.d.ts +107 -0
  3. package/dist/config.js +12 -0
  4. package/dist/context.d.ts +139 -0
  5. package/dist/context.js +3 -0
  6. package/dist/crud.d.ts +5 -5
  7. package/dist/crud.js +19 -35
  8. package/dist/document-types.d.ts +65 -0
  9. package/dist/document-types.js +15 -0
  10. package/dist/grounded-answer-types.d.ts +62 -0
  11. package/dist/grounded-answer-types.js +6 -0
  12. package/dist/http-action.d.ts +77 -0
  13. package/dist/http-action.js +41 -0
  14. package/dist/index.d.ts +30 -5
  15. package/dist/index.js +15 -2
  16. package/dist/platform-capacity-profile.d.ts +19 -0
  17. package/dist/platform-capacity-profile.js +94 -0
  18. package/dist/procedure.d.ts +58 -0
  19. package/dist/procedure.js +115 -0
  20. package/dist/rag-ingest-types.d.ts +39 -0
  21. package/dist/rag-ingest-types.js +1 -0
  22. package/dist/rag-operations-types.d.ts +81 -0
  23. package/dist/rag-operations-types.js +1 -0
  24. package/dist/rag-schema.d.ts +1466 -0
  25. package/dist/rag-schema.js +87 -0
  26. package/dist/reactive-mutation-types.d.ts +11 -0
  27. package/dist/reactive-mutation-types.js +1 -0
  28. package/dist/reactive-mutation.d.ts +51 -0
  29. package/dist/reactive-mutation.js +75 -0
  30. package/dist/reactive-query-types.d.ts +12 -0
  31. package/dist/reactive-query-types.js +1 -0
  32. package/dist/reactive-query.d.ts +14 -0
  33. package/dist/reactive-query.js +28 -0
  34. package/dist/reactive-realtime.d.ts +48 -0
  35. package/dist/reactive-realtime.js +236 -0
  36. package/dist/reactive.d.ts +29 -5
  37. package/dist/reactive.js +65 -0
  38. package/dist/rls-db.d.ts +9 -2
  39. package/dist/runtime-env-policy.d.ts +5 -0
  40. package/dist/runtime-env-policy.js +56 -0
  41. package/dist/search-types.d.ts +83 -0
  42. package/dist/search-types.js +1 -0
  43. package/dist/server.d.ts +1 -2
  44. package/dist/server.js +0 -1
  45. package/dist/storage-metering.d.ts +13 -0
  46. package/dist/storage-metering.js +18 -0
  47. package/dist/storage-shared.d.ts +36 -0
  48. package/dist/storage-shared.js +39 -0
  49. package/dist/storage.d.ts +5 -27
  50. package/dist/storage.js +30 -22
  51. package/dist/wake-app-result.d.ts +22 -0
  52. package/dist/wake-app-result.js +11 -0
  53. package/dist/workflow-types.d.ts +16 -2
  54. package/dist/workflow.d.ts +1 -1
  55. package/dist/workflow.js +136 -11
  56. package/dist/workflows-api.js +71 -3
  57. package/package.json +11 -7
  58. package/src/auth-config.ts +104 -3
  59. package/src/config.ts +119 -0
  60. package/src/context.ts +152 -0
  61. package/src/crud.ts +18 -35
  62. package/src/document-types.ts +102 -0
  63. package/src/grounded-answer-types.ts +78 -0
  64. package/src/http-action.ts +101 -0
  65. package/src/index.ts +142 -19
  66. package/src/platform-capacity-profile.ts +114 -0
  67. package/src/procedure.ts +283 -0
  68. package/src/rag-ingest-types.ts +52 -0
  69. package/src/rag-operations-types.ts +90 -0
  70. package/src/rag-schema.ts +94 -0
  71. package/src/reactive-mutation-types.ts +13 -0
  72. package/src/reactive-mutation.ts +115 -0
  73. package/src/reactive-query-types.ts +14 -0
  74. package/src/reactive-query.ts +48 -0
  75. package/src/reactive-realtime.ts +267 -0
  76. package/src/rls-db.ts +9 -4
  77. package/src/runtime-env-policy.ts +66 -0
  78. package/src/search-types.ts +91 -0
  79. package/src/server.ts +6 -2
  80. package/src/storage-metering.ts +35 -0
  81. package/src/storage-shared.ts +74 -0
  82. package/src/storage.ts +44 -53
  83. package/src/wake-app-result.ts +37 -0
  84. package/src/workflow-types.ts +16 -2
  85. package/src/workflow.ts +166 -12
  86. package/src/workflows-api.ts +82 -3
  87. package/src/__tests__/auth.test.ts +0 -118
  88. package/src/__tests__/crons.test.ts +0 -83
  89. package/src/__tests__/crud-codegen-integration.test.ts +0 -246
  90. package/src/__tests__/crud-owner-rls.test.ts +0 -387
  91. package/src/__tests__/crud.test.ts +0 -930
  92. package/src/__tests__/dist-exports.test.ts +0 -176
  93. package/src/__tests__/fixtures/basic/auth.ts +0 -32
  94. package/src/__tests__/fixtures/basic/drizzle.config.ts +0 -12
  95. package/src/__tests__/fixtures/basic/index.ts +0 -6
  96. package/src/__tests__/fixtures/basic/migrations/0000_last_warstar.sql +0 -75
  97. package/src/__tests__/fixtures/basic/migrations/meta/0000_snapshot.json +0 -497
  98. package/src/__tests__/fixtures/basic/migrations/meta/_journal.json +0 -13
  99. package/src/__tests__/fixtures/basic/schema.ts +0 -51
  100. package/src/__tests__/fixtures/basic/tasks.ts +0 -15
  101. package/src/__tests__/fixtures/common/auth-schema.ts +0 -67
  102. package/src/__tests__/helpers/basic-rls-fixture.ts +0 -135
  103. package/src/__tests__/helpers/pglite-migrations.ts +0 -32
  104. package/src/__tests__/helpers/pglite-rls-session.ts +0 -51
  105. package/src/__tests__/helpers/seed-like-fill.ts +0 -202
  106. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +0 -50
  107. package/src/__tests__/httpaction.test.ts +0 -122
  108. package/src/__tests__/image-optimization.test.ts +0 -648
  109. package/src/__tests__/load.test.ts +0 -389
  110. package/src/__tests__/network-sim.test.ts +0 -319
  111. package/src/__tests__/reactive.test.ts +0 -479
  112. package/src/__tests__/retry.test.ts +0 -113
  113. package/src/__tests__/rls-crud-basic.test.ts +0 -317
  114. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +0 -117
  115. package/src/__tests__/rls-custom-mutation-handlers.test.ts +0 -142
  116. package/src/__tests__/rls-custom-query-handlers.test.ts +0 -128
  117. package/src/__tests__/rls-db-leased-connection.test.ts +0 -118
  118. package/src/__tests__/rls-session-and-policies.test.ts +0 -228
  119. package/src/__tests__/scheduler-durable-v2.test.ts +0 -288
  120. package/src/__tests__/scheduler-durable.test.ts +0 -173
  121. package/src/__tests__/scheduler-exec.test.ts +0 -328
  122. package/src/__tests__/scheduler.test.ts +0 -187
  123. package/src/__tests__/storage.test.ts +0 -334
  124. package/src/__tests__/tsconfig.json +0 -8
  125. package/src/__tests__/validator.test.ts +0 -323
  126. package/src/__tests__/workflow.test.ts +0 -606
  127. package/src/__tests__/ws-integration.test.ts +0 -309
  128. package/src/__tests__/ws-scale.test.ts +0 -241
  129. package/src/auth.ts +0 -155
  130. package/src/reactive.ts +0 -580
@@ -1,389 +0,0 @@
1
- /**
2
- * packages/core/src/__tests__/load.test.ts
3
- *
4
- * Load / stress tests for ctx.realtime.emit() push model.
5
- *
6
- * Run: bun test packages/core/src/__tests__/load.test.ts
7
- */
8
-
9
- import { describe, it, expect } from "bun:test";
10
- import { buildRealtimeCtx, subscribe, registerClient, deregisterClient } from "../reactive.js";
11
- import type { GencowCtx } from "../reactive.js";
12
-
13
- // ─── Mock WSContext ───────────────────────────────────────────────────────────
14
-
15
- function makeMockWs(id = 0) {
16
- let sendCount = 0;
17
- let lastPayloadBytes = 0;
18
- return {
19
- send: (msg: string) => {
20
- sendCount++;
21
- lastPayloadBytes = msg.length;
22
- },
23
- readyState: 1,
24
- id,
25
- get sendCount() {
26
- return sendCount;
27
- },
28
- get lastPayloadBytes() {
29
- return lastPayloadBytes;
30
- },
31
- } as any;
32
- }
33
-
34
- // 메시지 타입을 추적하는 WS (비교 테스트용)
35
- function makeTrackedWs(id = 0) {
36
- type Msg = { type: string; hasData: boolean };
37
- const received: Msg[] = [];
38
- return {
39
- send: (msg: string) => {
40
- const parsed = JSON.parse(msg);
41
- received.push({
42
- type: parsed.type,
43
- hasData: parsed.data !== undefined,
44
- });
45
- },
46
- readyState: 1,
47
- id,
48
- get received() {
49
- return received;
50
- },
51
- get sendCount() {
52
- return received.length;
53
- },
54
- } as any;
55
- }
56
-
57
- const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));
58
-
59
- function makeUniqueKey(prefix: string) {
60
- return `${prefix}.${Math.random().toString(36).slice(2)}`;
61
- }
62
-
63
- // ─── 1. 다수 구독자 throughput ────────────────────────────────────────────────
64
-
65
- describe("[Load] 다수 구독자 emit 처리량", () => {
66
- it("1,000명 구독자에게 emit이 150ms 이내에 완료된다", async () => {
67
- const N = 1_000;
68
- const key = makeUniqueKey("load1k");
69
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
70
- clients.forEach((ws) => subscribe(key, ws));
71
-
72
- const rt = buildRealtimeCtx();
73
- const start = performance.now();
74
- rt.emit(key, [{ id: 1, title: "Task 1" }]);
75
- await wait(60);
76
- const elapsed = performance.now() - start;
77
-
78
- console.log(`[throughput] 1,000 subscribers → ${elapsed.toFixed(1)}ms`);
79
- expect(clients.filter((ws) => ws.sendCount === 1).length).toBe(N);
80
- expect(elapsed).toBeLessThan(150);
81
- clients.forEach((ws) => deregisterClient(ws));
82
- });
83
-
84
- it("5,000명 구독자에게 emit이 300ms 이내에 완료된다", async () => {
85
- const N = 5_000;
86
- const key = makeUniqueKey("load5k");
87
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
88
- clients.forEach((ws) => subscribe(key, ws));
89
-
90
- const rt = buildRealtimeCtx();
91
- const start = performance.now();
92
- rt.emit(key, [{ id: 1 }]);
93
- await wait(60);
94
- const elapsed = performance.now() - start;
95
-
96
- console.log(`[throughput] 5,000 subscribers → ${elapsed.toFixed(1)}ms`);
97
- expect(clients.filter((ws) => ws.sendCount === 1).length).toBe(N);
98
- expect(elapsed).toBeLessThan(300);
99
- clients.forEach((ws) => deregisterClient(ws));
100
- });
101
-
102
- it("10,000명 구독자에게 emit이 500ms 이내에 완료된다", async () => {
103
- const N = 10_000;
104
- const key = makeUniqueKey("load10k");
105
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
106
- clients.forEach((ws) => subscribe(key, ws));
107
-
108
- const rt = buildRealtimeCtx();
109
- const start = performance.now();
110
- rt.emit(key, [{ id: 1 }]);
111
- await wait(60);
112
- const elapsed = performance.now() - start;
113
-
114
- console.log(`[throughput] 10,000 subscribers → ${elapsed.toFixed(1)}ms`);
115
- expect(clients.filter((ws) => ws.sendCount === 1).length).toBe(N);
116
- expect(elapsed).toBeLessThan(500);
117
- clients.forEach((ws) => deregisterClient(ws));
118
- });
119
-
120
- it("50,000명 구독자에게 emit이 2,000ms 이내에 완료된다", async () => {
121
- const N = 50_000;
122
- const key = makeUniqueKey("load50k");
123
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
124
- clients.forEach((ws) => subscribe(key, ws));
125
-
126
- const rt = buildRealtimeCtx();
127
- const start = performance.now();
128
- rt.emit(key, [{ id: 1 }]);
129
- await wait(60);
130
- const elapsed = performance.now() - start;
131
-
132
- console.log(`[throughput] 50,000 subscribers → ${elapsed.toFixed(1)}ms`);
133
- expect(clients.filter((ws) => ws.sendCount === 1).length).toBe(N);
134
- expect(elapsed).toBeLessThan(2_000);
135
- clients.forEach((ws) => deregisterClient(ws));
136
- });
137
-
138
- it("100,000명 구독자에게 emit이 5,000ms 이내에 완료된다", async () => {
139
- const N = 100_000;
140
- const key = makeUniqueKey("load100k");
141
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
142
- clients.forEach((ws) => subscribe(key, ws));
143
-
144
- const rt = buildRealtimeCtx();
145
- const start = performance.now();
146
- rt.emit(key, [{ id: 1 }]);
147
- await wait(60);
148
- const elapsed = performance.now() - start;
149
-
150
- console.log(`[throughput]100,000 subscribers → ${elapsed.toFixed(1)}ms`);
151
- expect(clients.filter((ws) => ws.sendCount === 1).length).toBe(N);
152
- expect(elapsed).toBeLessThan(5_000);
153
- clients.forEach((ws) => deregisterClient(ws));
154
- });
155
- });
156
-
157
- // ─── 2. 동시 다수 queryKey emit 처리량 ─────────────────────────────────────
158
-
159
- describe("[Load] 동시 다수 queryKey emit", () => {
160
- it("100개 queryKey × 100명 구독자 emit을 200ms 이내에 처리한다", async () => {
161
- const KEYS = 100;
162
- const SUBS_PER_KEY = 100;
163
- const keyMap: { key: string; clients: any[] }[] = [];
164
-
165
- for (let k = 0; k < KEYS; k++) {
166
- const key = makeUniqueKey(`multi.${k}`);
167
- const clients = Array.from({ length: SUBS_PER_KEY }, (_, i) => makeMockWs(k * 1000 + i));
168
- clients.forEach((ws) => subscribe(key, ws));
169
- keyMap.push({ key, clients });
170
- }
171
-
172
- const rt = buildRealtimeCtx();
173
- const start = performance.now();
174
- for (const { key } of keyMap) {
175
- rt.emit(key, [{ id: Math.random() }]);
176
- }
177
- await wait(80);
178
- const elapsed = performance.now() - start;
179
-
180
- console.log(`[multi-key] 100 keys × 100 subs → ${elapsed.toFixed(1)}ms`);
181
-
182
- const totalReceived = keyMap.reduce(
183
- (acc, { clients }) => acc + clients.filter((ws) => ws.sendCount === 1).length,
184
- 0,
185
- );
186
- expect(totalReceived).toBe(KEYS * SUBS_PER_KEY);
187
- expect(elapsed).toBeLessThan(200);
188
-
189
- keyMap.forEach(({ clients }) => clients.forEach((ws) => deregisterClient(ws)));
190
- });
191
- });
192
-
193
- // ─── 3. debounce 효율 ────────────────────────────────────────────────────────
194
-
195
- describe("[Load] 빠른 연속 emit debounce 효율", () => {
196
- it("50ms 이내 100번 연속 emit은 단 1회만 전송된다", async () => {
197
- const key = makeUniqueKey("debounce.stress");
198
- const ws = makeMockWs();
199
- subscribe(key, ws);
200
-
201
- const rt = buildRealtimeCtx();
202
- for (let i = 0; i < 100; i++) {
203
- rt.emit(key, [{ id: i, title: `Task ${i}` }]);
204
- }
205
- await wait(80);
206
-
207
- console.log(`[debounce] 100 rapid emits → ${ws.sendCount} actual sends`);
208
- expect(ws.sendCount).toBe(1);
209
-
210
- deregisterClient(ws);
211
- });
212
-
213
- it("debounce 효율: 1,000번 emit → 전송 횟수 감소율 99% 이상", async () => {
214
- const key = makeUniqueKey("debounce.efficiency");
215
- const ws = makeMockWs();
216
- subscribe(key, ws);
217
-
218
- const rt = buildRealtimeCtx();
219
- const RAPID = 1_000;
220
- const emitStart = performance.now();
221
- for (let i = 0; i < RAPID; i++) {
222
- rt.emit(
223
- key,
224
- Array.from({ length: 10 }, (_, j) => ({ id: j, value: i })),
225
- );
226
- }
227
- const emitTime = performance.now() - emitStart;
228
- await wait(80);
229
-
230
- const reductionRate = ((RAPID - ws.sendCount) / RAPID) * 100;
231
- console.log(
232
- `[debounce] emits: ${RAPID}, sends: ${ws.sendCount}, reduction: ${reductionRate.toFixed(1)}%, loop: ${emitTime.toFixed(1)}ms`,
233
- );
234
-
235
- expect(reductionRate).toBeGreaterThanOrEqual(99);
236
- expect(ws.sendCount).toBe(1);
237
- expect(emitTime).toBeLessThan(100);
238
-
239
- deregisterClient(ws);
240
- });
241
- });
242
-
243
- // ─── 4. connect/disconnect 반복 안정성 ─────────────────────────────────────
244
-
245
- describe("[Load] 클라이언트 connect/disconnect 안정성", () => {
246
- it("10,000번 connect/disconnect 반복 후 메모리 안정적이다", () => {
247
- const CYCLES = 10_000;
248
- const key = makeUniqueKey("churn.list");
249
- const before = process.memoryUsage().heapUsed;
250
-
251
- for (let i = 0; i < CYCLES; i++) {
252
- const ws = makeMockWs(i);
253
- registerClient(ws);
254
- subscribe(key, ws);
255
- deregisterClient(ws);
256
- }
257
-
258
- const after = process.memoryUsage().heapUsed;
259
- const diffMB = (after - before) / 1024 / 1024;
260
- console.log(`[churn] 10k connect/disconnect → heap diff: ${diffMB.toFixed(2)}MB`);
261
-
262
- expect(diffMB).toBeLessThan(50);
263
- });
264
-
265
- it("zombie ws(send throw)는 emit 후 자동 정리되고 이후 정상 클라이언트는 영향 없다", async () => {
266
- const key = makeUniqueKey("dead.cleanup");
267
- let throwCount = 0;
268
-
269
- const zombieWs = {
270
- send: () => {
271
- throwCount++;
272
- throw new Error("WebSocket closed");
273
- },
274
- readyState: 3,
275
- } as any;
276
- subscribe(key, zombieWs);
277
-
278
- const rt = buildRealtimeCtx();
279
- rt.emit(key, [{ id: 1 }]);
280
- await wait(80);
281
- expect(throwCount).toBe(1);
282
-
283
- // zombie 정리 후 정상 클라이언트 추가
284
- const normalWs = makeMockWs();
285
- subscribe(key, normalWs);
286
-
287
- const rt2 = buildRealtimeCtx();
288
- rt2.emit(key, [{ id: 2 }]);
289
- await wait(80);
290
-
291
- expect(normalWs.sendCount).toBe(1);
292
- deregisterClient(normalWs);
293
- });
294
- });
295
-
296
- // ─── 5. 대용량 페이로드 성능 ─────────────────────────────────────────────────
297
-
298
- describe("[Load] 대용량 페이로드 emit 성능", () => {
299
- it("10,000행 데이터셋을 100명 구독자에게 500ms 이내에 전송한다", async () => {
300
- const N_ROWS = 10_000;
301
- const N_SUBS = 100;
302
- const key = makeUniqueKey("large.payload");
303
-
304
- const largeDataset = Array.from({ length: N_ROWS }, (_, i) => ({
305
- id: i,
306
- title: `Task ${i}`,
307
- description: `Description for task ${i} to simulate real content length`,
308
- done: i % 2 === 0,
309
- createdAt: new Date().toISOString(),
310
- tags: ["alpha", "beta", "gamma"],
311
- }));
312
-
313
- const clients = Array.from({ length: N_SUBS }, (_, i) => makeMockWs(i));
314
- clients.forEach((ws) => subscribe(key, ws));
315
-
316
- const rt = buildRealtimeCtx();
317
- const start = performance.now();
318
- rt.emit(key, largeDataset);
319
- await wait(80);
320
- const elapsed = performance.now() - start;
321
-
322
- const payloadKB = clients[0]?.lastPayloadBytes / 1024;
323
- console.log(
324
- `[large payload] ${N_ROWS} rows × ${N_SUBS} subs → ${elapsed.toFixed(1)}ms, payload: ${payloadKB.toFixed(1)}KB`,
325
- );
326
-
327
- const received = clients.filter((ws) => ws.sendCount === 1).length;
328
- expect(received).toBe(N_SUBS);
329
- expect(elapsed).toBeLessThan(500);
330
-
331
- clients.forEach((ws) => deregisterClient(ws));
332
- });
333
- });
334
-
335
- // ─── 6. emit broadcast 확장성 (컨넥튰드 클라이언트 기반) ─────────────────────────
336
-
337
- describe("[Load] emit broadcast 확장성", () => {
338
- it("1,000개 구독자에게 emit이 50ms 이내에 완료된다", async () => {
339
- const N = 1_000;
340
- const key = makeUniqueKey("broadcast1k");
341
- const clients = Array.from({ length: N }, (_, i) => makeMockWs(i));
342
- clients.forEach((ws) => subscribe(key, ws));
343
-
344
- const start = performance.now();
345
- buildRealtimeCtx().emit(key, [{ id: 1 }, { id: 2 }]);
346
- await wait(60);
347
- const elapsed = performance.now() - start;
348
-
349
- console.log(`[broadcast] 1,000 subscribers → ${elapsed.toFixed(1)}ms`);
350
-
351
- const received = clients.filter((ws) => ws.sendCount === 1).length;
352
- expect(received).toBe(N);
353
- expect(elapsed).toBeLessThan(100); // emit has 50ms debounce
354
-
355
- clients.forEach((ws) => deregisterClient(ws));
356
- });
357
- });
358
-
359
- // ─── 7. Push emit 포맷 검증 ───────────────────────────────────────────────
360
-
361
- describe("[Load] Push emit 포맷 검증", () => {
362
- it("emit(push)은 query:updated+data를 실제 페이로드와 함께 전달한다", async () => {
363
- const N_SUBS = 500;
364
- const keyPush = makeUniqueKey("compare.push");
365
-
366
- // push: 구독 클라이언트 (query:updated 수신 예상)
367
- const pushClients = Array.from({ length: N_SUBS }, (_, i) => makeTrackedWs(i));
368
- pushClients.forEach((ws) => subscribe(keyPush, ws));
369
-
370
- const data = Array.from({ length: 100 }, (_, i) => ({ id: i, title: `T${i}` }));
371
-
372
- // Push path: emit → 50ms debounce → query:updated 전송
373
- const pushStart = performance.now();
374
- buildRealtimeCtx().emit(keyPush, data);
375
- await wait(80);
376
- const pushElapsed = performance.now() - pushStart;
377
-
378
- const pushWithData = pushClients.filter((ws) =>
379
- ws.received.some((m: any) => m.type === "query:updated" && m.hasData),
380
- ).length;
381
-
382
- console.log(`[push] ${pushElapsed.toFixed(1)}ms → ${pushWithData}/${N_SUBS} with data (query:updated)`);
383
-
384
- // 핵심 검증
385
- expect(pushWithData).toBe(N_SUBS); // 500/500이 data 포함 메시지 수신
386
-
387
- pushClients.forEach((ws) => deregisterClient(ws));
388
- });
389
- });