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