@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
@@ -1,81 +1,83 @@
1
1
  import { describe, test, expect } from "bun:test";
2
- import { cronJobs } from "../crons";
2
+ import { cronJobs } from "../crons.js";
3
3
 
4
4
  describe("cronJobs 빌더", () => {
5
- test("interval — minutes → cron 패턴 변환", () => {
6
- const crons = cronJobs();
7
- crons.interval("test", { minutes: 15 }, "test.action");
8
- const jobs = crons.getJobs();
9
- expect(jobs).toHaveLength(1);
10
- expect(jobs[0].name).toBe("test");
11
- expect(jobs[0].pattern).toBe("*/15 * * * *");
12
- expect(jobs[0].action).toBe("test.action");
13
- });
5
+ test("interval — minutes → cron 패턴 변환", () => {
6
+ const crons = cronJobs();
7
+ crons.interval("test", { minutes: 15 }, "test.action");
8
+ const jobs = crons.getJobs();
9
+ expect(jobs).toHaveLength(1);
10
+ expect(jobs[0].name).toBe("test");
11
+ expect(jobs[0].pattern).toBe("*/15 * * * *");
12
+ expect(jobs[0].action).toBe("test.action");
13
+ });
14
14
 
15
- test("interval — hours → cron 패턴 변환", () => {
16
- const crons = cronJobs();
17
- crons.interval("hourly", { hours: 6 }, "cleanup");
18
- expect(crons.getJobs()[0].pattern).toBe("0 */6 * * *");
19
- });
15
+ test("interval — hours → cron 패턴 변환", () => {
16
+ const crons = cronJobs();
17
+ crons.interval("hourly", { hours: 6 }, "cleanup");
18
+ expect(crons.getJobs()[0].pattern).toBe("0 */6 * * *");
19
+ });
20
20
 
21
- test("interval — seconds → 6자리 cron 패턴", () => {
22
- const crons = cronJobs();
23
- crons.interval("fast", { seconds: 30 }, "tick");
24
- expect(crons.getJobs()[0].pattern).toBe("*/30 * * * * *");
25
- });
21
+ test("interval — seconds → 6자리 cron 패턴", () => {
22
+ const crons = cronJobs();
23
+ crons.interval("fast", { seconds: 30 }, "tick");
24
+ expect(crons.getJobs()[0].pattern).toBe("*/30 * * * * *");
25
+ });
26
26
 
27
- test("daily — 특정 시각", () => {
28
- const crons = cronJobs();
29
- crons.daily("report", { hour: 9, minute: 30 }, "reports.gen");
30
- const job = crons.getJobs()[0];
31
- expect(job.pattern).toBe("30 9 * * *");
32
- });
27
+ test("daily — 특정 시각", () => {
28
+ const crons = cronJobs();
29
+ crons.daily("report", { hour: 9, minute: 30 }, "reports.gen");
30
+ const job = crons.getJobs()[0];
31
+ expect(job.pattern).toBe("30 9 * * *");
32
+ });
33
33
 
34
- test("daily — minute 기본값 0", () => {
35
- const crons = cronJobs();
36
- crons.daily("cleanup", { hour: 2 }, "admin.cleanup");
37
- expect(crons.getJobs()[0].pattern).toBe("0 2 * * *");
38
- });
34
+ test("daily — minute 기본값 0", () => {
35
+ const crons = cronJobs();
36
+ crons.daily("cleanup", { hour: 2 }, "admin.cleanup");
37
+ expect(crons.getJobs()[0].pattern).toBe("0 2 * * *");
38
+ });
39
39
 
40
- test("weekly — 요일 + 시각", () => {
41
- const crons = cronJobs();
42
- crons.weekly("monday-report", { dayOfWeek: 1, hour: 9 }, "reports.weekly");
43
- expect(crons.getJobs()[0].pattern).toBe("0 9 * * 1");
44
- });
40
+ test("weekly — 요일 + 시각", () => {
41
+ const crons = cronJobs();
42
+ crons.weekly("monday-report", { dayOfWeek: 1, hour: 9 }, "reports.weekly");
43
+ expect(crons.getJobs()[0].pattern).toBe("0 9 * * 1");
44
+ });
45
45
 
46
- test("cron — 직접 패턴 지정", () => {
47
- const crons = cronJobs();
48
- crons.cron("custom", "0 */2 * * *", "custom.handler");
49
- expect(crons.getJobs()[0].pattern).toBe("0 */2 * * *");
50
- });
46
+ test("cron — 직접 패턴 지정", () => {
47
+ const crons = cronJobs();
48
+ crons.cron("custom", "0 */2 * * *", "custom.handler");
49
+ expect(crons.getJobs()[0].pattern).toBe("0 */2 * * *");
50
+ });
51
51
 
52
- test("체이닝 지원", () => {
53
- const crons = cronJobs();
54
- crons
55
- .interval("a", { minutes: 5 }, "a.action")
56
- .daily("b", { hour: 3 }, "b.action")
57
- .cron("c", "0 * * * *", "c.action");
58
- expect(crons.getJobs()).toHaveLength(3);
59
- });
52
+ test("체이닝 지원", () => {
53
+ const crons = cronJobs();
54
+ crons
55
+ .interval("a", { minutes: 5 }, "a.action")
56
+ .daily("b", { hour: 3 }, "b.action")
57
+ .cron("c", "0 * * * *", "c.action");
58
+ expect(crons.getJobs()).toHaveLength(3);
59
+ });
60
60
 
61
- test("인라인 핸들러 지원", () => {
62
- const crons = cronJobs();
63
- const fn = async () => { /* noop */ };
64
- crons.interval("inline", { minutes: 1 }, fn);
65
- expect(typeof crons.getJobs()[0].action).toBe("function");
66
- });
61
+ test("인라인 핸들러 지원", () => {
62
+ const crons = cronJobs();
63
+ const fn = async () => {
64
+ /* noop */
65
+ };
66
+ crons.interval("inline", { minutes: 1 }, fn);
67
+ expect(typeof crons.getJobs()[0].action).toBe("function");
68
+ });
67
69
 
68
- test("getJobs는 복사본 반환 (원본 보호)", () => {
69
- const crons = cronJobs();
70
- crons.interval("test", { minutes: 1 }, "test");
71
- const jobs1 = crons.getJobs();
72
- const jobs2 = crons.getJobs();
73
- expect(jobs1).not.toBe(jobs2); // 서로 다른 참조
74
- expect(jobs1).toEqual(jobs2); // 내용은 동일
75
- });
70
+ test("getJobs는 복사본 반환 (원본 보호)", () => {
71
+ const crons = cronJobs();
72
+ crons.interval("test", { minutes: 1 }, "test");
73
+ const jobs1 = crons.getJobs();
74
+ const jobs2 = crons.getJobs();
75
+ expect(jobs1).not.toBe(jobs2); // 서로 다른 참조
76
+ expect(jobs1).toEqual(jobs2); // 내용은 동일
77
+ });
76
78
 
77
- test("interval — 옵션 없으면 에러", () => {
78
- const crons = cronJobs();
79
- expect(() => crons.interval("bad", {}, "bad")).toThrow("minutes, hours, 또는 seconds");
80
- });
79
+ test("interval — 옵션 없으면 에러", () => {
80
+ const crons = cronJobs();
81
+ expect(() => crons.interval("bad", {}, "bad")).toThrow("minutes, hours, 또는 seconds");
82
+ });
81
83
  });
@@ -24,41 +24,35 @@
24
24
  import { describe, it, expect, beforeAll } from "bun:test";
25
25
  import { pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
26
26
 
27
- import { crud } from "../crud";
28
- import {
29
- query,
30
- mutation,
31
- getRegisteredQueries,
32
- getRegisteredMutations,
33
- getQueryDef,
34
- } from "../reactive";
27
+ import { crud } from "../crud.js";
28
+ import { query, mutation, getRegisteredQueries, getRegisteredMutations, getQueryDef } from "../reactive.js";
35
29
 
36
30
  // ─── 테스트용 테이블 정의 ─────────────────────────────────────────────
37
31
 
38
32
  const keywords = pgTable("cg_keywords", {
39
- id: serial("id").primaryKey(),
40
- keyword: text("keyword").notNull(),
41
- userId: text("user_id"),
42
- createdAt: timestamp("created_at").defaultNow(),
33
+ id: serial("id").primaryKey(),
34
+ keyword: text("keyword").notNull(),
35
+ userId: text("user_id"),
36
+ createdAt: timestamp("created_at").defaultNow(),
43
37
  });
44
38
 
45
39
  const crawlLogs = pgTable("cg_crawl_logs", {
46
- id: serial("id").primaryKey(),
47
- status: text("status"),
48
- createdAt: timestamp("created_at").defaultNow(),
40
+ id: serial("id").primaryKey(),
41
+ status: text("status"),
42
+ createdAt: timestamp("created_at").defaultNow(),
49
43
  });
50
44
 
51
45
  const digests = pgTable("cg_digests", {
52
- id: serial("id").primaryKey(),
53
- title: text("title"),
54
- content: text("content"),
55
- createdAt: timestamp("created_at").defaultNow(),
46
+ id: serial("id").primaryKey(),
47
+ title: text("title"),
48
+ content: text("content"),
49
+ createdAt: timestamp("created_at").defaultNow(),
56
50
  });
57
51
 
58
52
  const appSettings = pgTable("cg_app_settings", {
59
- id: serial("id").primaryKey(),
60
- key: text("key"),
61
- value: text("value"),
53
+ id: serial("id").primaryKey(),
54
+ key: text("key"),
55
+ value: text("value"),
62
56
  });
63
57
 
64
58
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -66,46 +60,46 @@ const appSettings = pgTable("cg_app_settings", {
66
60
  // ═══════════════════════════════════════════════════════════════════════════════
67
61
 
68
62
  describe("crud() → codegen 통합 — 다중 테이블", () => {
69
- beforeAll(() => {
70
- // test032 패턴 재현: 4개 모듈이 각각 crud() 호출
71
- crud(keywords, { public: true });
72
- crud(crawlLogs, { public: true });
73
- crud(digests, { public: true });
74
- crud(appSettings, { public: true });
75
- });
76
-
77
- it("4개 테이블의 list/get query가 모두 레지스트리에 등록된다", () => {
78
- const queries = getRegisteredQueries();
79
-
80
- // 각 테이블의 list + get = 8개
81
- expect(queries).toContain("cg_keywords.list");
82
- expect(queries).toContain("cg_keywords.get");
83
- expect(queries).toContain("cg_crawl_logs.list");
84
- expect(queries).toContain("cg_crawl_logs.get");
85
- expect(queries).toContain("cg_digests.list");
86
- expect(queries).toContain("cg_digests.get");
87
- expect(queries).toContain("cg_app_settings.list");
88
- expect(queries).toContain("cg_app_settings.get");
89
- });
90
-
91
- it("4개 테이블의 create/update/remove mutation이 모두 레지스트리에 등록된다", () => {
92
- const mutations = getRegisteredMutations();
93
- const names = mutations.map((m) => m.name);
94
-
95
- // 각 테이블의 create + update + remove = 12개
96
- expect(names).toContain("cg_keywords.create");
97
- expect(names).toContain("cg_keywords.update");
98
- expect(names).toContain("cg_keywords.remove");
99
- expect(names).toContain("cg_crawl_logs.create");
100
- expect(names).toContain("cg_crawl_logs.update");
101
- expect(names).toContain("cg_crawl_logs.remove");
102
- expect(names).toContain("cg_digests.create");
103
- expect(names).toContain("cg_digests.update");
104
- expect(names).toContain("cg_digests.remove");
105
- expect(names).toContain("cg_app_settings.create");
106
- expect(names).toContain("cg_app_settings.update");
107
- expect(names).toContain("cg_app_settings.remove");
108
- });
63
+ beforeAll(() => {
64
+ // test032 패턴 재현: 4개 모듈이 각각 crud() 호출
65
+ crud(keywords, { public: true });
66
+ crud(crawlLogs, { public: true });
67
+ crud(digests, { public: true });
68
+ crud(appSettings, { public: true });
69
+ });
70
+
71
+ it("4개 테이블의 list/get query가 모두 레지스트리에 등록된다", () => {
72
+ const queries = getRegisteredQueries();
73
+
74
+ // 각 테이블의 list + get = 8개
75
+ expect(queries).toContain("cg_keywords.list");
76
+ expect(queries).toContain("cg_keywords.get");
77
+ expect(queries).toContain("cg_crawl_logs.list");
78
+ expect(queries).toContain("cg_crawl_logs.get");
79
+ expect(queries).toContain("cg_digests.list");
80
+ expect(queries).toContain("cg_digests.get");
81
+ expect(queries).toContain("cg_app_settings.list");
82
+ expect(queries).toContain("cg_app_settings.get");
83
+ });
84
+
85
+ it("4개 테이블의 create/update/remove mutation이 모두 레지스트리에 등록된다", () => {
86
+ const mutations = getRegisteredMutations();
87
+ const names = mutations.map((m) => m.name);
88
+
89
+ // 각 테이블의 create + update + remove = 12개
90
+ expect(names).toContain("cg_keywords.create");
91
+ expect(names).toContain("cg_keywords.update");
92
+ expect(names).toContain("cg_keywords.remove");
93
+ expect(names).toContain("cg_crawl_logs.create");
94
+ expect(names).toContain("cg_crawl_logs.update");
95
+ expect(names).toContain("cg_crawl_logs.remove");
96
+ expect(names).toContain("cg_digests.create");
97
+ expect(names).toContain("cg_digests.update");
98
+ expect(names).toContain("cg_digests.remove");
99
+ expect(names).toContain("cg_app_settings.create");
100
+ expect(names).toContain("cg_app_settings.update");
101
+ expect(names).toContain("cg_app_settings.remove");
102
+ });
109
103
  });
110
104
 
111
105
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -113,54 +107,54 @@ describe("crud() → codegen 통합 — 다중 테이블", () => {
113
107
  // ═══════════════════════════════════════════════════════════════════════════════
114
108
 
115
109
  describe("crud() + 수동 query/mutation 혼용", () => {
116
- beforeAll(() => {
117
- // crud로 자동 등록
118
- const articlesTable = pgTable("cg_articles", {
119
- id: serial("id").primaryKey(),
120
- title: text("title"),
121
- createdAt: timestamp("created_at").defaultNow(),
122
- });
123
- crud(articlesTable, { public: true });
124
-
125
- // 수동으로 추가 등록 (test032의 digestsModule.generate 패턴)
126
- query("cg_digests.latest", {
127
- public: true,
128
- handler: async (ctx) => {
129
- return { id: 1, title: "latest" };
130
- },
131
- });
132
-
133
- mutation("cg_digests.generate", {
134
- public: true,
135
- handler: async (ctx) => {
136
- return { success: true };
137
- },
138
- });
110
+ beforeAll(() => {
111
+ // crud로 자동 등록
112
+ const articlesTable = pgTable("cg_articles", {
113
+ id: serial("id").primaryKey(),
114
+ title: text("title"),
115
+ createdAt: timestamp("created_at").defaultNow(),
116
+ });
117
+ crud(articlesTable, { public: true });
118
+
119
+ // 수동으로 추가 등록 (test032의 digestsModule.generate 패턴)
120
+ query("cg_digests.latest", {
121
+ public: true,
122
+ handler: async (ctx) => {
123
+ return { id: 1, title: "latest" };
124
+ },
139
125
  });
140
126
 
141
- it("crud 등록 + 수동 등록이 모두 getRegisteredQueries()에 포함된다", () => {
142
- const queries = getRegisteredQueries();
127
+ mutation("cg_digests.generate", {
128
+ public: true,
129
+ handler: async (ctx) => {
130
+ return { success: true };
131
+ },
132
+ });
133
+ });
143
134
 
144
- // crud 자동 등록
145
- expect(queries).toContain("cg_articles.list");
146
- expect(queries).toContain("cg_articles.get");
135
+ it("crud 등록 + 수동 등록이 모두 getRegisteredQueries()에 포함된다", () => {
136
+ const queries = getRegisteredQueries();
147
137
 
148
- // 수동 등록
149
- expect(queries).toContain("cg_digests.latest");
150
- });
138
+ // crud 자동 등록
139
+ expect(queries).toContain("cg_articles.list");
140
+ expect(queries).toContain("cg_articles.get");
151
141
 
152
- it("crud 등록 + 수동 등록이 모두 getRegisteredMutations()에 포함된다", () => {
153
- const mutations = getRegisteredMutations();
154
- const names = mutations.map((m) => m.name);
142
+ // 수동 등록
143
+ expect(queries).toContain("cg_digests.latest");
144
+ });
155
145
 
156
- // crud 자동 등록
157
- expect(names).toContain("cg_articles.create");
158
- expect(names).toContain("cg_articles.update");
159
- expect(names).toContain("cg_articles.remove");
146
+ it("crud 등록 + 수동 등록이 모두 getRegisteredMutations()에 포함된다", () => {
147
+ const mutations = getRegisteredMutations();
148
+ const names = mutations.map((m) => m.name);
160
149
 
161
- // 수동 등록
162
- expect(names).toContain("cg_digests.generate");
163
- });
150
+ // crud 자동 등록
151
+ expect(names).toContain("cg_articles.create");
152
+ expect(names).toContain("cg_articles.update");
153
+ expect(names).toContain("cg_articles.remove");
154
+
155
+ // 수동 등록
156
+ expect(names).toContain("cg_digests.generate");
157
+ });
164
158
  });
165
159
 
166
160
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -168,43 +162,43 @@ describe("crud() + 수동 query/mutation 혼용", () => {
168
162
  // ═══════════════════════════════════════════════════════════════════════════════
169
163
 
170
164
  describe("crud() isPublic 상태 — codegen auth 분기 정확성", () => {
171
- beforeAll(() => {
172
- const publicTable = pgTable("cg_public_data", {
173
- id: serial("id").primaryKey(),
174
- name: text("name"),
175
- });
176
- const privateTable = pgTable("cg_private_data", {
177
- id: serial("id").primaryKey(),
178
- name: text("name"),
179
- });
180
-
181
- crud(publicTable, { public: true });
182
- crud(privateTable); // default: auth 필수
165
+ beforeAll(() => {
166
+ const publicTable = pgTable("cg_public_data", {
167
+ id: serial("id").primaryKey(),
168
+ name: text("name"),
169
+ });
170
+ const privateTable = pgTable("cg_private_data", {
171
+ id: serial("id").primaryKey(),
172
+ name: text("name"),
183
173
  });
184
174
 
185
- it("public: true 테이블의 모든 엔드포인트가 isPublic === true", () => {
186
- const listDef = getQueryDef("cg_public_data.list");
187
- const getDef = getQueryDef("cg_public_data.get");
175
+ crud(publicTable, { public: true });
176
+ crud(privateTable); // default: auth 필수
177
+ });
188
178
 
189
- expect(listDef!.isPublic).toBe(true);
190
- expect(getDef!.isPublic).toBe(true);
179
+ it("public: true 테이블의 모든 엔드포인트가 isPublic === true", () => {
180
+ const listDef = getQueryDef("cg_public_data.list");
181
+ const getDef = getQueryDef("cg_public_data.get");
191
182
 
192
- const mutations = getRegisteredMutations();
193
- const createDef = mutations.find((m) => m.name === "cg_public_data.create");
194
- expect(createDef!.isPublic).toBe(true);
195
- });
183
+ expect(listDef!.isPublic).toBe(true);
184
+ expect(getDef!.isPublic).toBe(true);
196
185
 
197
- it("기본(private) 테이블의 모든 엔드포인트가 isPublic === false", () => {
198
- const listDef = getQueryDef("cg_private_data.list");
199
- const getDef = getQueryDef("cg_private_data.get");
186
+ const mutations = getRegisteredMutations();
187
+ const createDef = mutations.find((m) => m.name === "cg_public_data.create");
188
+ expect(createDef!.isPublic).toBe(true);
189
+ });
200
190
 
201
- expect(listDef!.isPublic).toBe(false);
202
- expect(getDef!.isPublic).toBe(false);
191
+ it("기본(private) 테이블의 모든 엔드포인트가 isPublic === false", () => {
192
+ const listDef = getQueryDef("cg_private_data.list");
193
+ const getDef = getQueryDef("cg_private_data.get");
203
194
 
204
- const mutations = getRegisteredMutations();
205
- const createDef = mutations.find((m) => m.name === "cg_private_data.create");
206
- expect(createDef!.isPublic).toBe(false);
207
- });
195
+ expect(listDef!.isPublic).toBe(false);
196
+ expect(getDef!.isPublic).toBe(false);
197
+
198
+ const mutations = getRegisteredMutations();
199
+ const createDef = mutations.find((m) => m.name === "cg_private_data.create");
200
+ expect(createDef!.isPublic).toBe(false);
201
+ });
208
202
  });
209
203
 
210
204
  // ═══════════════════════════════════════════════════════════════════════════════
@@ -212,41 +206,41 @@ describe("crud() isPublic 상태 — codegen auth 분기 정확성", () => {
212
206
  // ═══════════════════════════════════════════════════════════════════════════════
213
207
 
214
208
  describe("codegen 시뮬레이션 — api.ts 생성 가능 여부", () => {
215
- it("getRegisteredQueries()로 모든 query key를 열거할 수 있다", () => {
216
- const queries = getRegisteredQueries();
217
- expect(queries.length).toBeGreaterThan(0);
218
-
219
- // 모든 query key가 "namespace.action" 패턴이어야 함
220
- for (const key of queries) {
221
- expect(key).toMatch(/^[a-z_]+\.[a-z_]+$/i);
222
- }
223
- });
224
-
225
- it("getRegisteredMutations()로 모든 mutation을 열거할 수 있다", () => {
226
- const mutations = getRegisteredMutations();
227
- expect(mutations.length).toBeGreaterThan(0);
228
-
229
- // 모든 mutation이 name + handler를 가져야 함
230
- for (const mut of mutations) {
231
- expect(mut.name).toBeTruthy();
232
- expect(typeof mut.handler).toBe("function");
233
- }
234
- });
235
-
236
- it("getQueryDef()로 개별 query의 argsSchema에 접근할 수 있다", () => {
237
- const listDef = getQueryDef("cg_keywords.list");
238
- expect(listDef).toBeDefined();
239
- // list handler에는 argsSchema가 있음 (limit, offset, sort, filters)
240
- expect(listDef!.handler).toBeDefined();
241
- });
242
-
243
- it("codegen 대상 query/mutation 총 개수가 올바르다", () => {
244
- const queries = getRegisteredQueries();
245
- const mutations = getRegisteredMutations();
246
-
247
- // 최소 8개 query (4테이블 × list+get) + 수동 1개 + 이전 테스트들
248
- expect(queries.length).toBeGreaterThanOrEqual(8);
249
- // 최소 12개 mutation (4테이블 × create+update+remove) + 수동 1개
250
- expect(mutations.length).toBeGreaterThanOrEqual(12);
251
- });
209
+ it("getRegisteredQueries()로 모든 query key를 열거할 수 있다", () => {
210
+ const queries = getRegisteredQueries();
211
+ expect(queries.length).toBeGreaterThan(0);
212
+
213
+ // 모든 query key가 "namespace.action" 패턴이어야 함
214
+ for (const key of queries) {
215
+ expect(key).toMatch(/^[a-z_]+\.[a-z_]+$/i);
216
+ }
217
+ });
218
+
219
+ it("getRegisteredMutations()로 모든 mutation을 열거할 수 있다", () => {
220
+ const mutations = getRegisteredMutations();
221
+ expect(mutations.length).toBeGreaterThan(0);
222
+
223
+ // 모든 mutation이 name + handler를 가져야 함
224
+ for (const mut of mutations) {
225
+ expect(mut.name).toBeTruthy();
226
+ expect(typeof mut.handler).toBe("function");
227
+ }
228
+ });
229
+
230
+ it("getQueryDef()로 개별 query의 argsSchema에 접근할 수 있다", () => {
231
+ const listDef = getQueryDef("cg_keywords.list");
232
+ expect(listDef).toBeDefined();
233
+ // list handler에는 argsSchema가 있음 (limit, offset, sort, filters)
234
+ expect(listDef!.handler).toBeDefined();
235
+ });
236
+
237
+ it("codegen 대상 query/mutation 총 개수가 올바르다", () => {
238
+ const queries = getRegisteredQueries();
239
+ const mutations = getRegisteredMutations();
240
+
241
+ // 최소 8개 query (4테이블 × list+get) + 수동 1개 + 이전 테스트들
242
+ expect(queries.length).toBeGreaterThanOrEqual(8);
243
+ // 최소 12개 mutation (4테이블 × create+update+remove) + 수동 1개
244
+ expect(mutations.length).toBeGreaterThanOrEqual(12);
245
+ });
252
246
  });