@gencow/core 0.1.24 → 0.1.26

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 (75) hide show
  1. package/dist/crud.d.ts +2 -2
  2. package/dist/crud.js +225 -208
  3. package/dist/index.d.ts +5 -5
  4. package/dist/index.js +2 -2
  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.js +4 -11
  16. package/dist/workflows-api.js +5 -12
  17. package/package.json +45 -42
  18. package/src/__tests__/auth.test.ts +90 -86
  19. package/src/__tests__/crons.test.ts +69 -67
  20. package/src/__tests__/crud-codegen-integration.test.ts +164 -170
  21. package/src/__tests__/crud-owner-rls.test.ts +308 -301
  22. package/src/__tests__/crud.test.ts +694 -711
  23. package/src/__tests__/dist-exports.test.ts +120 -120
  24. package/src/__tests__/fixtures/basic/auth.ts +16 -16
  25. package/src/__tests__/fixtures/basic/drizzle.config.ts +1 -4
  26. package/src/__tests__/fixtures/basic/index.ts +1 -1
  27. package/src/__tests__/fixtures/basic/schema.ts +1 -1
  28. package/src/__tests__/fixtures/basic/tasks.ts +4 -4
  29. package/src/__tests__/fixtures/common/auth-schema.ts +38 -34
  30. package/src/__tests__/helpers/basic-rls-fixture.ts +80 -78
  31. package/src/__tests__/helpers/pglite-migrations.ts +2 -5
  32. package/src/__tests__/helpers/pglite-rls-session.ts +13 -16
  33. package/src/__tests__/helpers/seed-like-fill.ts +47 -41
  34. package/src/__tests__/helpers/test-gencow-ctx-rls.ts +4 -7
  35. package/src/__tests__/httpaction.test.ts +91 -91
  36. package/src/__tests__/image-optimization.test.ts +570 -574
  37. package/src/__tests__/load.test.ts +321 -308
  38. package/src/__tests__/network-sim.test.ts +238 -215
  39. package/src/__tests__/reactive.test.ts +380 -358
  40. package/src/__tests__/retry.test.ts +99 -84
  41. package/src/__tests__/rls-crud-basic.test.ts +172 -245
  42. package/src/__tests__/rls-crud-no-owner-rls-pglite.test.ts +81 -81
  43. package/src/__tests__/rls-custom-mutation-handlers.test.ts +47 -94
  44. package/src/__tests__/rls-custom-query-handlers.test.ts +92 -92
  45. package/src/__tests__/rls-db-leased-connection.test.ts +2 -6
  46. package/src/__tests__/rls-session-and-policies.test.ts +181 -199
  47. package/src/__tests__/scheduler-durable-v2.test.ts +199 -181
  48. package/src/__tests__/scheduler-durable.test.ts +117 -117
  49. package/src/__tests__/scheduler-exec.test.ts +258 -246
  50. package/src/__tests__/scheduler.test.ts +129 -111
  51. package/src/__tests__/storage.test.ts +282 -269
  52. package/src/__tests__/tsconfig.json +6 -6
  53. package/src/__tests__/validator.test.ts +236 -232
  54. package/src/__tests__/workflow.test.ts +309 -286
  55. package/src/__tests__/ws-integration.test.ts +223 -218
  56. package/src/__tests__/ws-scale.test.ts +168 -159
  57. package/src/auth-config.ts +18 -18
  58. package/src/auth.ts +106 -106
  59. package/src/crons.ts +77 -77
  60. package/src/crud.ts +523 -479
  61. package/src/index.ts +69 -5
  62. package/src/reactive.ts +357 -331
  63. package/src/retry.ts +51 -54
  64. package/src/rls-db.ts +195 -205
  65. package/src/rls.ts +33 -36
  66. package/src/scheduler.ts +237 -211
  67. package/src/server.ts +0 -1
  68. package/src/storage.ts +632 -593
  69. package/src/v.ts +119 -114
  70. package/src/workflow-types.ts +67 -70
  71. package/src/workflow.ts +99 -116
  72. package/src/workflows-api.ts +231 -241
  73. package/dist/db.d.ts +0 -13
  74. package/dist/db.js +0 -16
  75. package/src/db.ts +0 -18
@@ -65,25 +65,13 @@ describe("fixtures/basic + PGlite + CRUD + RLS", () => {
65
65
  db = env.db;
66
66
  seededTaskRows = env.taskRows;
67
67
  listDef = env.defs.list;
68
- listHandler = env.defs.list!.handler as (
69
- ctx: unknown,
70
- args: unknown
71
- ) => Promise<TaskListResult>;
72
- getHandler = env.defs.get!.handler as (
73
- ctx: unknown,
74
- args: unknown
75
- ) => Promise<TaskRow | null>;
76
- createHandler = env.defs.create!.handler as (
77
- ctx: unknown,
78
- args: unknown
79
- ) => Promise<TaskRow>;
80
- updateHandler = env.defs.update!.handler as (
81
- ctx: unknown,
82
- args: unknown
83
- ) => Promise<TaskRow | undefined>;
68
+ listHandler = env.defs.list!.handler as (ctx: unknown, args: unknown) => Promise<TaskListResult>;
69
+ getHandler = env.defs.get!.handler as (ctx: unknown, args: unknown) => Promise<TaskRow | null>;
70
+ createHandler = env.defs.create!.handler as (ctx: unknown, args: unknown) => Promise<TaskRow>;
71
+ updateHandler = env.defs.update!.handler as (ctx: unknown, args: unknown) => Promise<TaskRow | undefined>;
84
72
  removeHandler = env.defs.remove!.handler as (
85
73
  ctx: unknown,
86
- args: unknown
74
+ args: unknown,
87
75
  ) => Promise<{ success: boolean }>;
88
76
  });
89
77
 
@@ -102,109 +90,81 @@ describe("fixtures/basic + PGlite + CRUD + RLS", () => {
102
90
  tenantId: "tenant_fixture",
103
91
  vars: { "app.note": "ok" },
104
92
  });
105
- const otherUserRows = await scoped
106
- .select()
107
- .from(tasks)
108
- .where(eq(tasks.userId, fixtureUsers[1].id));
93
+ const otherUserRows = await scoped.select().from(tasks).where(eq(tasks.userId, fixtureUsers[1].id));
109
94
  expect(otherUserRows.length).toBe(0);
110
95
 
111
- const ownRows = await scoped
112
- .select()
113
- .from(tasks)
114
- .where(eq(tasks.userId, user0Identity.id));
115
- expect(ownRows.length).toBe(
116
- seededTaskRows.filter((r) => r.userId === user0Identity.id).length
117
- );
96
+ const ownRows = await scoped.select().from(tasks).where(eq(tasks.userId, user0Identity.id));
97
+ expect(ownRows.length).toBe(seededTaskRows.filter((r) => r.userId === user0Identity.id).length);
118
98
  });
119
99
 
120
100
  it("tasks.list returns only the current user (us_000) rows per RLS and total matches", async () => {
121
101
  expect(listDef).toBeDefined();
122
102
 
123
- await runWithRollbackTestGencowCtxWithRls(
124
- db,
125
- user0Identity,
126
- async (ctx) => {
127
- const listResult = await listHandler(ctx, {});
128
-
129
- const expected = seededTaskRows.filter(
130
- (r) => r.userId === fixtureUsers[0].id
131
- );
132
-
133
- expect(listResult).toHaveProperty("data");
134
- expect(listResult).toHaveProperty("total");
135
- expect(listResult.total).toBe(expected.length);
136
-
137
- const rows = listResult.data;
138
-
139
- const byId = new Map(rows.map((r) => [r.id, r]));
140
- expect(byId.size).toBe(expected.length);
141
-
142
- for (const seeded of expected) {
143
- const row = byId.get(seeded.id);
144
- expect(row).toBeDefined();
145
- expect(row!.id).toBe(seeded.id);
146
- expect(row!.userId).toBe(seeded.userId);
147
- expect(row!.done).toBe(seeded.done);
148
- expect(row!.title).toBe(seeded.title);
149
- }
103
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
104
+ const listResult = await listHandler(ctx, {});
105
+
106
+ const expected = seededTaskRows.filter((r) => r.userId === fixtureUsers[0].id);
107
+
108
+ expect(listResult).toHaveProperty("data");
109
+ expect(listResult).toHaveProperty("total");
110
+ expect(listResult.total).toBe(expected.length);
111
+
112
+ const rows = listResult.data;
113
+
114
+ const byId = new Map(rows.map((r) => [r.id, r]));
115
+ expect(byId.size).toBe(expected.length);
116
+
117
+ for (const seeded of expected) {
118
+ const row = byId.get(seeded.id);
119
+ expect(row).toBeDefined();
120
+ expect(row!.id).toBe(seeded.id);
121
+ expect(row!.userId).toBe(seeded.userId);
122
+ expect(row!.done).toBe(seeded.done);
123
+ expect(row!.title).toBe(seeded.title);
150
124
  }
151
- );
125
+ });
152
126
  });
153
127
 
154
128
  it("search parameter applies to title/description ilike search", async () => {
155
129
  expect(listDef).toBeDefined();
156
130
 
157
- await runWithRollbackTestGencowCtxWithRls(
158
- db,
159
- user0Identity,
160
- async (ctx) => {
161
- const sharedSubstring = "Project Alpha";
162
- const expectedForUser0 = seededTaskRows.filter(
163
- (r) =>
164
- r.userId === user0Identity.id &&
165
- r.title.toLowerCase().includes(sharedSubstring.toLowerCase())
166
- );
167
- expect(expectedForUser0.length).toBe(2);
168
-
169
- const result = await listHandler(ctx, { search: sharedSubstring });
170
- const rows = result.data;
171
- expect(rows.length).toBe(expectedForUser0.length);
172
-
173
- const byId = new Map(rows.map((r) => [r.id, r]));
174
- for (const seeded of expectedForUser0) {
175
- expect(byId.get(seeded.id)?.title).toBe(seeded.title);
176
- }
131
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
132
+ const sharedSubstring = "Project Alpha";
133
+ const expectedForUser0 = seededTaskRows.filter(
134
+ (r) => r.userId === user0Identity.id && r.title.toLowerCase().includes(sharedSubstring.toLowerCase()),
135
+ );
136
+ expect(expectedForUser0.length).toBe(2);
137
+
138
+ const result = await listHandler(ctx, { search: sharedSubstring });
139
+ const rows = result.data;
140
+ expect(rows.length).toBe(expectedForUser0.length);
141
+
142
+ const byId = new Map(rows.map((r) => [r.id, r]));
143
+ for (const seeded of expectedForUser0) {
144
+ expect(byId.get(seeded.id)?.title).toBe(seeded.title);
177
145
  }
178
- );
146
+ });
179
147
  });
180
148
 
181
149
  it("tasks.get succeeds when current user reads own task", async () => {
182
150
  const ownTaskId = "tk-003";
183
151
 
184
- await runWithRollbackTestGencowCtxWithRls(
185
- db,
186
- user0Identity,
187
- async (ctx) => {
188
- const task = await getHandler(ctx, { id: ownTaskId });
152
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
153
+ const task = await getHandler(ctx, { id: ownTaskId });
189
154
 
190
- expect(task).toBeDefined();
191
- expect(task?.id).toBe(ownTaskId);
192
- expect(task?.userId).toBe(user0Identity.id);
193
- }
194
- );
155
+ expect(task).toBeDefined();
156
+ expect(task?.id).toBe(ownTaskId);
157
+ expect(task?.userId).toBe(user0Identity.id);
158
+ });
195
159
  });
196
160
 
197
161
  it("tasks.get fails when current user tries to read another user's task", async () => {
198
162
  const user1TaskId = "tk-001";
199
163
 
200
- await runWithRollbackTestGencowCtxWithRls(
201
- db,
202
- user0Identity,
203
- async (ctx) => {
204
- const task = await getHandler(ctx, { id: user1TaskId });
205
- expect(task).toBeNull();
206
- }
207
- );
164
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
165
+ const task = await getHandler(ctx, { id: user1TaskId });
166
+ expect(task).toBeNull();
167
+ });
208
168
  });
209
169
 
210
170
  it("tasks.update succeeds when updating a task owned by current user", async () => {
@@ -212,179 +172,146 @@ describe("fixtures/basic + PGlite + CRUD + RLS", () => {
212
172
  const updatedTitle = "Project Alpha — updated by owner";
213
173
  const updatedDone = true;
214
174
 
215
- await runWithRollbackTestGencowCtxWithRls(
216
- db,
217
- user0Identity,
218
- async (ctx) => {
219
- const before = await getHandler(ctx, {
220
- id: ownTaskId,
221
- });
222
- expect(before?.title).not.toBe(updatedTitle);
223
- expect(before?.done).not.toBe(updatedDone);
224
-
225
- const updated = await updateHandler(ctx, {
226
- id: ownTaskId,
227
- title: updatedTitle,
228
- done: updatedDone,
229
- });
230
-
231
- expect(updated).toBeDefined();
232
- expect(updated?.id).toBe(ownTaskId);
233
- expect(updated?.userId).toBe(user0Identity.id);
234
- expect(updated?.title).toBe(updatedTitle);
235
- expect(updated?.done).toBe(updatedDone);
236
-
237
- const fetched = await getHandler(ctx, {
238
- id: ownTaskId,
239
- });
240
- expect(fetched?.id).toBe(ownTaskId);
241
- expect(fetched?.title).toBe(updatedTitle);
242
- expect(fetched?.done).toBe(updatedDone);
243
- }
244
- );
175
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (ctx) => {
176
+ const before = await getHandler(ctx, {
177
+ id: ownTaskId,
178
+ });
179
+ expect(before?.title).not.toBe(updatedTitle);
180
+ expect(before?.done).not.toBe(updatedDone);
181
+
182
+ const updated = await updateHandler(ctx, {
183
+ id: ownTaskId,
184
+ title: updatedTitle,
185
+ done: updatedDone,
186
+ });
187
+
188
+ expect(updated).toBeDefined();
189
+ expect(updated?.id).toBe(ownTaskId);
190
+ expect(updated?.userId).toBe(user0Identity.id);
191
+ expect(updated?.title).toBe(updatedTitle);
192
+ expect(updated?.done).toBe(updatedDone);
193
+
194
+ const fetched = await getHandler(ctx, {
195
+ id: ownTaskId,
196
+ });
197
+ expect(fetched?.id).toBe(ownTaskId);
198
+ expect(fetched?.title).toBe(updatedTitle);
199
+ expect(fetched?.done).toBe(updatedDone);
200
+ });
245
201
  });
246
202
 
247
203
  it("tasks.create succeeds when creating a task with current userId", async () => {
248
204
  const ownTaskId = "tk-own-create";
249
205
  const ownTaskTitle = "Project Delta — created by owner";
250
206
 
251
- await runWithRollbackTestGencowCtxWithRls(
252
- db,
253
- user0Identity,
254
- async (user0Ctx) => {
255
- const created = await createHandler(user0Ctx, {
256
- id: ownTaskId,
257
- userId: user0Identity.id,
258
- title: ownTaskTitle,
259
- done: false,
260
- });
261
-
262
- expect(created).toBeDefined();
263
- expect(created.id).toBe(ownTaskId);
264
- expect(created.userId).toBe(user0Identity.id);
265
- expect(created.title).toBe(ownTaskTitle);
266
- expect(created.done).toBe(false);
267
-
268
- const fetched = await getHandler(user0Ctx, {
269
- id: ownTaskId,
270
- });
271
- expect(fetched).toBeDefined();
272
- expect(fetched?.id).toBe(ownTaskId);
273
- expect(fetched?.userId).toBe(user0Identity.id);
274
- }
275
- );
207
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (user0Ctx) => {
208
+ const created = await createHandler(user0Ctx, {
209
+ id: ownTaskId,
210
+ userId: user0Identity.id,
211
+ title: ownTaskTitle,
212
+ done: false,
213
+ });
214
+
215
+ expect(created).toBeDefined();
216
+ expect(created.id).toBe(ownTaskId);
217
+ expect(created.userId).toBe(user0Identity.id);
218
+ expect(created.title).toBe(ownTaskTitle);
219
+ expect(created.done).toBe(false);
220
+
221
+ const fetched = await getHandler(user0Ctx, {
222
+ id: ownTaskId,
223
+ });
224
+ expect(fetched).toBeDefined();
225
+ expect(fetched?.id).toBe(ownTaskId);
226
+ expect(fetched?.userId).toBe(user0Identity.id);
227
+ });
276
228
  });
277
229
 
278
230
  it("tasks.create fails when current user tries to create with another userId", async () => {
279
231
  const unauthorizedTaskId = "tk-other-user-create";
280
- await runWithRollbackTestGencowCtxWithRls(
281
- db,
282
- user0Identity,
283
- async (user0Ctx) => {
284
- let thrown: unknown;
285
- try {
286
- await createHandler(user0Ctx, {
287
- id: unauthorizedTaskId,
288
- userId: user1Identity.id,
289
- title: "Unauthorized create attempt",
290
- done: false,
291
- });
292
- } catch (e) {
293
- thrown = e;
294
- }
295
- expect(thrown).toBeInstanceOf(Error);
296
-
297
- const user1Ctx = makeTestGencowCtxWithRls(
298
- user0Ctx.unsafeDb as any,
299
- user1Identity
300
- );
301
- const after = await getHandler(user1Ctx, { id: unauthorizedTaskId });
302
- expect(after).toBeNull();
232
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (user0Ctx) => {
233
+ let thrown: unknown;
234
+ try {
235
+ await createHandler(user0Ctx, {
236
+ id: unauthorizedTaskId,
237
+ userId: user1Identity.id,
238
+ title: "Unauthorized create attempt",
239
+ done: false,
240
+ });
241
+ } catch (e) {
242
+ thrown = e;
303
243
  }
304
- );
244
+ expect(thrown).toBeInstanceOf(Error);
245
+
246
+ const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
247
+ const after = await getHandler(user1Ctx, { id: unauthorizedTaskId });
248
+ expect(after).toBeNull();
249
+ });
305
250
  });
306
251
 
307
252
  it("tasks.update fails when current user tries to update user1 task", async () => {
308
253
  const user1TaskId = "tk-001";
309
- await runWithRollbackTestGencowCtxWithRls(
310
- db,
311
- user0Identity,
312
- async (user0Ctx) => {
313
- const user1Ctx = makeTestGencowCtxWithRls(
314
- user0Ctx.unsafeDb as any,
315
- user1Identity
316
- );
317
-
318
- const before = await getHandler(user1Ctx, {
319
- id: user1TaskId,
320
- });
321
- expect(before).toBeDefined();
322
- const beforeTitle = before?.title;
323
- const beforeDone = before?.done ?? false;
324
-
325
- const unauthorizedUpdate = await updateHandler(user0Ctx, {
326
- id: user1TaskId,
327
- title: "Unauthorized update attempt",
328
- done: !beforeDone,
329
- });
330
-
331
- expect(unauthorizedUpdate).toBeUndefined();
332
-
333
- const after = await getHandler(user1Ctx, {
334
- id: user1TaskId,
335
- });
336
- expect(after).toBeDefined();
337
- expect(after?.title).toBe(beforeTitle);
338
- expect(after?.done).toBe(beforeDone);
339
- }
340
- );
254
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (user0Ctx) => {
255
+ const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
256
+
257
+ const before = await getHandler(user1Ctx, {
258
+ id: user1TaskId,
259
+ });
260
+ expect(before).toBeDefined();
261
+ const beforeTitle = before?.title;
262
+ const beforeDone = before?.done ?? false;
263
+
264
+ const unauthorizedUpdate = await updateHandler(user0Ctx, {
265
+ id: user1TaskId,
266
+ title: "Unauthorized update attempt",
267
+ done: !beforeDone,
268
+ });
269
+
270
+ expect(unauthorizedUpdate).toBeUndefined();
271
+
272
+ const after = await getHandler(user1Ctx, {
273
+ id: user1TaskId,
274
+ });
275
+ expect(after).toBeDefined();
276
+ expect(after?.title).toBe(beforeTitle);
277
+ expect(after?.done).toBe(beforeDone);
278
+ });
341
279
  });
342
280
 
343
281
  it("tasks.remove succeeds when deleting a task owned by current user", async () => {
344
282
  const ownTaskId = "tk-002";
345
- await runWithRollbackTestGencowCtxWithRls(
346
- db,
347
- user0Identity,
348
- async (user0Ctx) => {
349
- const before = await getHandler(user0Ctx, { id: ownTaskId });
350
- expect(before).toBeDefined();
351
-
352
- const removeResult = await removeHandler(user0Ctx, {
353
- id: ownTaskId,
354
- });
355
- expect(removeResult.success).toBe(true);
283
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (user0Ctx) => {
284
+ const before = await getHandler(user0Ctx, { id: ownTaskId });
285
+ expect(before).toBeDefined();
356
286
 
357
- const after = await getHandler(user0Ctx, { id: ownTaskId });
358
- expect(after).toBeNull();
359
- }
360
- );
287
+ const removeResult = await removeHandler(user0Ctx, {
288
+ id: ownTaskId,
289
+ });
290
+ expect(removeResult.success).toBe(true);
291
+
292
+ const after = await getHandler(user0Ctx, { id: ownTaskId });
293
+ expect(after).toBeNull();
294
+ });
361
295
  });
362
296
 
363
297
  it("tasks.remove cannot delete a task owned by another user", async () => {
364
298
  const user1TaskId = "tk-004";
365
- await runWithRollbackTestGencowCtxWithRls(
366
- db,
367
- user0Identity,
368
- async (user0Ctx) => {
369
- const user1Ctx = makeTestGencowCtxWithRls(
370
- user0Ctx.unsafeDb as any,
371
- user1Identity
372
- );
373
-
374
- const before = await getHandler(user1Ctx, { id: user1TaskId });
375
- expect(before).toBeDefined();
376
-
377
- const removeResult = await removeHandler(user0Ctx, {
378
- id: user1TaskId,
379
- });
380
- expect(removeResult.success).toBe(true);
381
-
382
- const after = await getHandler(user1Ctx, {
383
- id: user1TaskId,
384
- });
385
- expect(after).toBeDefined();
386
- expect(after?.id).toBe(user1TaskId);
387
- }
388
- );
299
+ await runWithRollbackTestGencowCtxWithRls(db, user0Identity, async (user0Ctx) => {
300
+ const user1Ctx = makeTestGencowCtxWithRls(user0Ctx.unsafeDb as any, user1Identity);
301
+
302
+ const before = await getHandler(user1Ctx, { id: user1TaskId });
303
+ expect(before).toBeDefined();
304
+
305
+ const removeResult = await removeHandler(user0Ctx, {
306
+ id: user1TaskId,
307
+ });
308
+ expect(removeResult.success).toBe(true);
309
+
310
+ const after = await getHandler(user1Ctx, {
311
+ id: user1TaskId,
312
+ });
313
+ expect(after).toBeDefined();
314
+ expect(after?.id).toBe(user1TaskId);
315
+ });
389
316
  });
390
317
  });