@gencow/core 0.1.23 → 0.1.25

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 (77) hide show
  1. package/dist/crud.d.ts +2 -2
  2. package/dist/crud.js +225 -208
  3. package/dist/index.d.ts +7 -3
  4. package/dist/index.js +4 -1
  5. package/dist/reactive.js +10 -3
  6. package/dist/retry.js +1 -1
  7. package/dist/rls-db.d.ts +2 -2
  8. package/dist/rls-db.js +1 -5
  9. package/dist/scheduler.d.ts +2 -0
  10. package/dist/scheduler.js +16 -6
  11. package/dist/server.d.ts +0 -1
  12. package/dist/server.js +0 -1
  13. package/dist/storage.js +29 -22
  14. package/dist/v.d.ts +2 -2
  15. package/dist/workflow-types.d.ts +81 -0
  16. package/dist/workflow-types.js +12 -0
  17. package/dist/workflow.d.ts +30 -0
  18. package/dist/workflow.js +150 -0
  19. package/dist/workflows-api.d.ts +13 -0
  20. package/dist/workflows-api.js +321 -0
  21. package/package.json +46 -42
  22. package/src/__tests__/auth.test.ts +90 -86
  23. package/src/__tests__/crons.test.ts +69 -67
  24. package/src/__tests__/crud-codegen-integration.test.ts +164 -170
  25. package/src/__tests__/crud-owner-rls.test.ts +308 -301
  26. package/src/__tests__/crud.test.ts +694 -711
  27. package/src/__tests__/dist-exports.test.ts +120 -114
  28. package/src/__tests__/fixtures/basic/auth.ts +16 -16
  29. package/src/__tests__/fixtures/basic/drizzle.config.ts +1 -4
  30. package/src/__tests__/fixtures/basic/index.ts +1 -1
  31. package/src/__tests__/fixtures/basic/schema.ts +1 -1
  32. package/src/__tests__/fixtures/basic/tasks.ts +4 -4
  33. package/src/__tests__/fixtures/common/auth-schema.ts +38 -34
  34. package/src/__tests__/helpers/basic-rls-fixture.ts +80 -78
  35. package/src/__tests__/helpers/pglite-migrations.ts +2 -5
  36. package/src/__tests__/helpers/pglite-rls-session.ts +13 -16
  37. package/src/__tests__/helpers/seed-like-fill.ts +50 -44
  38. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +4 -7
  39. package/src/__tests__/httpaction.test.ts +91 -91
  40. package/src/__tests__/image-optimization.test.ts +570 -574
  41. package/src/__tests__/load.test.ts +321 -308
  42. package/src/__tests__/network-sim.test.ts +238 -215
  43. package/src/__tests__/reactive.test.ts +380 -358
  44. package/src/__tests__/retry.test.ts +99 -84
  45. package/src/__tests__/rls-crud-basic.test.ts +172 -245
  46. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +81 -81
  47. package/src/__tests__/rls-custom-mutation-handlers.test.ts +47 -94
  48. package/src/__tests__/rls-custom-query-handlers.test.ts +92 -92
  49. package/src/__tests__/rls-db-leased-connection.test.ts +2 -6
  50. package/src/__tests__/rls-session-and-policies.test.ts +181 -199
  51. package/src/__tests__/scheduler-durable-v2.test.ts +199 -181
  52. package/src/__tests__/scheduler-durable.test.ts +117 -117
  53. package/src/__tests__/scheduler-exec.test.ts +258 -246
  54. package/src/__tests__/scheduler.test.ts +129 -111
  55. package/src/__tests__/storage.test.ts +282 -269
  56. package/src/__tests__/tsconfig.json +6 -6
  57. package/src/__tests__/validator.test.ts +236 -232
  58. package/src/__tests__/workflow.test.ts +606 -0
  59. package/src/__tests__/ws-integration.test.ts +223 -218
  60. package/src/__tests__/ws-scale.test.ts +168 -159
  61. package/src/auth-config.ts +18 -18
  62. package/src/auth.ts +106 -106
  63. package/src/crons.ts +77 -77
  64. package/src/crud.ts +523 -479
  65. package/src/index.ts +71 -6
  66. package/src/reactive.ts +357 -331
  67. package/src/retry.ts +51 -54
  68. package/src/rls-db.ts +195 -205
  69. package/src/rls.ts +33 -36
  70. package/src/scheduler.ts +237 -211
  71. package/src/server.ts +0 -1
  72. package/src/storage.ts +632 -593
  73. package/src/v.ts +119 -114
  74. package/src/workflow-types.ts +108 -0
  75. package/src/workflow.ts +188 -0
  76. package/src/workflows-api.ts +415 -0
  77. package/src/db.ts +0 -18
@@ -10,160 +10,178 @@ import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test";
10
10
  import { createScheduler, getSchedulerInfo } from "../scheduler";
11
11
 
12
12
  describe("createScheduler()", () => {
13
- let scheduler: ReturnType<typeof createScheduler>;
13
+ let scheduler: ReturnType<typeof createScheduler>;
14
14
 
15
- beforeEach(() => {
16
- scheduler = createScheduler();
15
+ beforeEach(() => {
16
+ scheduler = createScheduler();
17
+ });
18
+
19
+ // ─── registerAction + executeAction ──────────────────
20
+
21
+ describe("registerAction / executeAction", () => {
22
+ it("등록된 액션 실행", async () => {
23
+ const fn = mock(async (args: any) => {});
24
+ scheduler.registerAction("test.action", fn);
25
+
26
+ await scheduler.executeAction("test.action", { foo: 1 });
27
+
28
+ expect(fn).toHaveBeenCalledTimes(1);
29
+ expect(fn.mock.calls[0][0]).toEqual({ foo: 1 });
17
30
  });
18
31
 
19
- // ─── registerAction + executeAction ──────────────────
32
+ it("미등록 액션 실행 console.error만 출력 (throw 안 함)", async () => {
33
+ const errorSpy = mock(() => {});
34
+ const orig = console.error;
35
+ console.error = errorSpy;
20
36
 
21
- describe("registerAction / executeAction", () => {
22
- it("등록된 액션 실행", async () => {
23
- const fn = mock(async (args: any) => {});
24
- scheduler.registerAction("test.action", fn);
37
+ await scheduler.executeAction("nonexistent.action");
25
38
 
26
- await scheduler.executeAction("test.action", { foo: 1 });
39
+ expect(errorSpy).toHaveBeenCalled();
40
+ expect(String(errorSpy.mock.calls[0][0])).toContain("nonexistent.action");
27
41
 
28
- expect(fn).toHaveBeenCalledTimes(1);
29
- expect(fn.mock.calls[0][0]).toEqual({ foo: 1 });
30
- });
42
+ console.error = orig;
43
+ });
44
+
45
+ it("액션 핸들러 에러 시 console.error 출력 (throw 안 함)", async () => {
46
+ scheduler.registerAction("failing.action", async () => {
47
+ throw new Error("action failed");
48
+ });
31
49
 
32
- it("미등록 액션 실행 시 console.error만 출력 (throw 안 함)", async () => {
33
- const errorSpy = mock(() => {});
34
- const orig = console.error;
35
- console.error = errorSpy;
50
+ const errorSpy = mock((..._args: any[]) => {});
51
+ const orig = console.error;
52
+ console.error = errorSpy;
36
53
 
37
- await scheduler.executeAction("nonexistent.action");
54
+ await scheduler.executeAction("failing.action");
38
55
 
39
- expect(errorSpy).toHaveBeenCalled();
40
- expect(String(errorSpy.mock.calls[0][0])).toContain("nonexistent.action");
56
+ expect(errorSpy).toHaveBeenCalled();
41
57
 
42
- console.error = orig;
43
- });
58
+ console.error = orig;
59
+ });
44
60
 
45
- it("액션 핸들러 에러 console.error 출력 (throw 안 함)", async () => {
46
- scheduler.registerAction("failing.action", async () => {
47
- throw new Error("action failed");
48
- });
61
+ it("executeActionStrict는 등록된 액션의 args를 그대로 전달한다", async () => {
62
+ const fn = mock(async (_args: any) => {});
63
+ scheduler.registerAction("strict.action", fn);
49
64
 
50
- const errorSpy = mock((..._args: any[]) => {});
51
- const orig = console.error;
52
- console.error = errorSpy;
65
+ await scheduler.executeActionStrict("strict.action", { workflowId: "wf-1" });
53
66
 
54
- await scheduler.executeAction("failing.action");
67
+ expect(fn).toHaveBeenCalledTimes(1);
68
+ expect(fn.mock.calls[0][0]).toEqual({ workflowId: "wf-1" });
69
+ });
55
70
 
56
- expect(errorSpy).toHaveBeenCalled();
71
+ it("executeActionStrict는 액션 에러를 호출자에게 다시 던진다", async () => {
72
+ scheduler.registerAction("strict.fail", async () => {
73
+ throw new Error("strict failed");
74
+ });
57
75
 
58
- console.error = orig;
59
- });
76
+ await expect(scheduler.executeActionStrict("strict.fail")).rejects.toThrow("strict failed");
60
77
  });
78
+ });
61
79
 
62
- // ─── runAfter ───────────────────────────────────────
80
+ // ─── runAfter ───────────────────────────────────────
63
81
 
64
- describe("runAfter()", () => {
65
- it("jobId를 반환한다", () => {
66
- const jobId = scheduler.runAfter(10000, "test.action");
67
- expect(typeof jobId).toBe("string");
68
- expect(jobId).toContain("job_");
82
+ describe("runAfter()", () => {
83
+ it("jobId를 반환한다", () => {
84
+ const jobId = scheduler.runAfter(10000, "test.action");
85
+ expect(typeof jobId).toBe("string");
86
+ expect(jobId).toContain("job_");
69
87
 
70
- // cleanup
71
- scheduler.cancel(jobId);
72
- });
88
+ // cleanup
89
+ scheduler.cancel(jobId);
90
+ });
73
91
 
74
- it("지정 시간 후 액션을 실행한다", async () => {
75
- const fn = mock(async () => {});
76
- scheduler.registerAction("delayed.action", fn);
92
+ it("지정 시간 후 액션을 실행한다", async () => {
93
+ const fn = mock(async () => {});
94
+ scheduler.registerAction("delayed.action", fn);
77
95
 
78
- scheduler.runAfter(50, "delayed.action", { key: "val" });
96
+ scheduler.runAfter(50, "delayed.action", { key: "val" });
79
97
 
80
- // 액션은 아직 실행되지 않아야 함
81
- expect(fn).not.toHaveBeenCalled();
98
+ // 액션은 아직 실행되지 않아야 함
99
+ expect(fn).not.toHaveBeenCalled();
82
100
 
83
- // 100ms 대기 후 실행됨
84
- await new Promise(r => setTimeout(r, 100));
85
- expect(fn).toHaveBeenCalledTimes(1);
86
- expect(fn.mock.calls[0][0]).toEqual({ key: "val" });
87
- });
101
+ // 100ms 대기 후 실행됨
102
+ await new Promise((r) => setTimeout(r, 100));
103
+ expect(fn).toHaveBeenCalledTimes(1);
104
+ expect(fn.mock.calls[0][0]).toEqual({ key: "val" });
105
+ });
88
106
 
89
- it("pendingJobs에 등록된다", () => {
90
- const jobId = scheduler.runAfter(60000, "test.long");
91
- const info = getSchedulerInfo();
92
- const found = info.pendingJobs.find(j => j.id === jobId);
93
- expect(found).toBeDefined();
94
- expect(found!.action).toBe("test.long");
107
+ it("pendingJobs에 등록된다", () => {
108
+ const jobId = scheduler.runAfter(60000, "test.long");
109
+ const info = getSchedulerInfo();
110
+ const found = info.pendingJobs.find((j) => j.id === jobId);
111
+ expect(found).toBeDefined();
112
+ expect(found!.action).toBe("test.long");
95
113
 
96
- // cleanup
97
- scheduler.cancel(jobId);
98
- });
114
+ // cleanup
115
+ scheduler.cancel(jobId);
99
116
  });
117
+ });
100
118
 
101
- // ─── cancel ─────────────────────────────────────────
119
+ // ─── cancel ─────────────────────────────────────────
102
120
 
103
- describe("cancel()", () => {
104
- it("예약된 작업을 취소한다", () => {
105
- const fn = mock(async () => {});
106
- scheduler.registerAction("cancel.test", fn);
121
+ describe("cancel()", () => {
122
+ it("예약된 작업을 취소한다", () => {
123
+ const fn = mock(async () => {});
124
+ scheduler.registerAction("cancel.test", fn);
107
125
 
108
- const jobId = scheduler.runAfter(50, "cancel.test");
109
- const cancelled = scheduler.cancel(jobId);
126
+ const jobId = scheduler.runAfter(50, "cancel.test");
127
+ const cancelled = scheduler.cancel(jobId);
110
128
 
111
- expect(cancelled).toBe(true);
112
- });
129
+ expect(cancelled).toBe(true);
130
+ });
113
131
 
114
- it("존재하지 않는 jobId → false 반환", () => {
115
- expect(scheduler.cancel("nonexistent_id")).toBe(false);
116
- });
132
+ it("존재하지 않는 jobId → false 반환", () => {
133
+ expect(scheduler.cancel("nonexistent_id")).toBe(false);
134
+ });
117
135
 
118
- it("취소된 작업은 실행되지 않는다", async () => {
119
- const fn = mock(async () => {});
120
- scheduler.registerAction("cancel.verify", fn);
136
+ it("취소된 작업은 실행되지 않는다", async () => {
137
+ const fn = mock(async () => {});
138
+ scheduler.registerAction("cancel.verify", fn);
121
139
 
122
- const jobId = scheduler.runAfter(30, "cancel.verify");
123
- scheduler.cancel(jobId);
140
+ const jobId = scheduler.runAfter(30, "cancel.verify");
141
+ scheduler.cancel(jobId);
124
142
 
125
- await new Promise(r => setTimeout(r, 100));
126
- expect(fn).not.toHaveBeenCalled();
127
- });
143
+ await new Promise((r) => setTimeout(r, 100));
144
+ expect(fn).not.toHaveBeenCalled();
128
145
  });
146
+ });
129
147
 
130
- // ─── runAt ──────────────────────────────────────────
148
+ // ─── runAt ──────────────────────────────────────────
131
149
 
132
- describe("runAt()", () => {
133
- it("Date 객체로 예약 가능", () => {
134
- const future = new Date(Date.now() + 60000);
135
- const jobId = scheduler.runAt(future, "test.runat");
136
- expect(typeof jobId).toBe("string");
137
- scheduler.cancel(jobId);
138
- });
150
+ describe("runAt()", () => {
151
+ it("Date 객체로 예약 가능", () => {
152
+ const future = new Date(Date.now() + 60000);
153
+ const jobId = scheduler.runAt(future, "test.runat");
154
+ expect(typeof jobId).toBe("string");
155
+ scheduler.cancel(jobId);
156
+ });
139
157
 
140
- it("timestamp(ms)로 예약 가능", () => {
141
- const ts = Date.now() + 60000;
142
- const jobId = scheduler.runAt(ts, "test.runat.ts");
143
- expect(typeof jobId).toBe("string");
144
- scheduler.cancel(jobId);
145
- });
158
+ it("timestamp(ms)로 예약 가능", () => {
159
+ const ts = Date.now() + 60000;
160
+ const jobId = scheduler.runAt(ts, "test.runat.ts");
161
+ expect(typeof jobId).toBe("string");
162
+ scheduler.cancel(jobId);
163
+ });
146
164
 
147
- it("과거 시각 → 즉시 실행 (ms=0)", async () => {
148
- const fn = mock(async () => {});
149
- scheduler.registerAction("past.action", fn);
165
+ it("과거 시각 → 즉시 실행 (ms=0)", async () => {
166
+ const fn = mock(async () => {});
167
+ scheduler.registerAction("past.action", fn);
150
168
 
151
- scheduler.runAt(Date.now() - 10000, "past.action");
169
+ scheduler.runAt(Date.now() - 10000, "past.action");
152
170
 
153
- await new Promise(r => setTimeout(r, 50));
154
- expect(fn).toHaveBeenCalledTimes(1);
155
- });
171
+ await new Promise((r) => setTimeout(r, 50));
172
+ expect(fn).toHaveBeenCalledTimes(1);
156
173
  });
174
+ });
157
175
 
158
- // ─── getSchedulerInfo ───────────────────────────────
176
+ // ─── getSchedulerInfo ───────────────────────────────
159
177
 
160
- describe("getSchedulerInfo()", () => {
161
- it("crons와 pendingJobs를 반환한다", () => {
162
- const info = getSchedulerInfo();
163
- expect(info).toHaveProperty("crons");
164
- expect(info).toHaveProperty("pendingJobs");
165
- expect(Array.isArray(info.crons)).toBe(true);
166
- expect(Array.isArray(info.pendingJobs)).toBe(true);
167
- });
178
+ describe("getSchedulerInfo()", () => {
179
+ it("crons와 pendingJobs를 반환한다", () => {
180
+ const info = getSchedulerInfo();
181
+ expect(info).toHaveProperty("crons");
182
+ expect(info).toHaveProperty("pendingJobs");
183
+ expect(Array.isArray(info.crons)).toBe(true);
184
+ expect(Array.isArray(info.pendingJobs)).toBe(true);
168
185
  });
186
+ });
169
187
  });