@gencow/core 0.1.24 → 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 (73) 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 +46 -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 +50 -44
  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/src/db.ts +0 -18
package/src/v.ts CHANGED
@@ -11,106 +11,111 @@
11
11
  * without fragile string-matching on the error message.
12
12
  */
13
13
  export class GencowValidationError extends Error {
14
- readonly statusCode = 400;
15
- constructor(message: string) {
16
- super(message);
17
- this.name = "GencowValidationError";
18
- }
14
+ readonly statusCode = 400;
15
+ constructor(message: string) {
16
+ super(message);
17
+ this.name = "GencowValidationError";
18
+ }
19
19
  }
20
20
 
21
21
  export type Validator<T = any> = {
22
- parse(val: any): T;
23
- _type: T; // Ghost property for TypeScript inference
22
+ parse(val: any): T;
23
+ _type: T; // Ghost property for TypeScript inference
24
24
  };
25
25
 
26
26
  export type Infer<T> = T extends Validator<infer U> ? U : never;
27
27
 
28
28
  export const v = {
29
- string(): Validator<string> {
30
- return {
31
- parse: (val) => {
32
- if (typeof val !== "string") throw new Error(`Expected string, got ${typeof val}`);
33
- return val;
34
- },
35
- _type: "" as string,
36
- };
37
- },
38
- number(): Validator<number> {
39
- return {
40
- parse: (val) => {
41
- // Accept numeric strings (e.g. from URL params / form data)
42
- const n = typeof val === "string" ? Number(val) : val;
43
- if (typeof n !== "number" || isNaN(n)) throw new Error(`Expected number, got ${typeof val}`);
44
- return n;
45
- },
46
- _type: 0 as number,
47
- };
48
- },
49
- boolean(): Validator<boolean> {
50
- return {
51
- parse: (val) => {
52
- if (typeof val !== "boolean") throw new Error(`Expected boolean, got ${typeof val}`);
53
- return val;
54
- },
55
- _type: false as boolean,
56
- };
57
- },
58
- any(): Validator<any> {
59
- return {
60
- parse: (val) => val,
61
- _type: null as any,
62
- };
63
- },
64
- optional<T>(inner: Validator<T>): Validator<T | undefined> {
65
- return {
66
- parse: (val) => {
67
- if (val === undefined || val === null) return undefined;
68
- return inner.parse(val);
69
- },
70
- _type: undefined as T | undefined,
71
- };
72
- },
73
- object<T extends Record<string, Validator>>(fields: T): Validator<{ [K in keyof T]: Infer<T[K]> }> {
74
- return {
75
- parse: (val) => {
76
- if (typeof val !== "object" || val === null) throw new Error("Expected object");
77
- const result: any = {};
78
- for (const key in fields) {
79
- result[key] = fields[key].parse((val as any)[key]);
80
- }
81
- return result;
82
- },
83
- _type: {} as any,
84
- };
85
- },
86
- array<T>(inner: Validator<T>): Validator<T[]> {
87
- return {
88
- parse: (val) => {
89
- if (!Array.isArray(val)) throw new Error("Expected array");
90
- return val.map((item) => inner.parse(item));
91
- },
92
- _type: [] as T[],
93
- };
94
- },
29
+ string(): Validator<string> {
30
+ return {
31
+ parse: (val) => {
32
+ if (typeof val !== "string") throw new Error(`Expected string, got ${typeof val}`);
33
+ return val;
34
+ },
35
+ _type: "" as string,
36
+ };
37
+ },
38
+ number(): Validator<number> {
39
+ return {
40
+ parse: (val) => {
41
+ // Accept numeric strings (e.g. from URL params / form data)
42
+ const n = typeof val === "string" ? Number(val) : val;
43
+ if (typeof n !== "number" || isNaN(n)) throw new Error(`Expected number, got ${typeof val}`);
44
+ return n;
45
+ },
46
+ _type: 0 as number,
47
+ };
48
+ },
49
+ boolean(): Validator<boolean> {
50
+ return {
51
+ parse: (val) => {
52
+ if (typeof val !== "boolean") throw new Error(`Expected boolean, got ${typeof val}`);
53
+ return val;
54
+ },
55
+ _type: false as boolean,
56
+ };
57
+ },
58
+ any(): Validator<any> {
59
+ return {
60
+ parse: (val) => val,
61
+ _type: null as any,
62
+ };
63
+ },
64
+ optional<T>(inner: Validator<T>): Validator<T | undefined> {
65
+ return {
66
+ parse: (val) => {
67
+ if (val === undefined || val === null) return undefined;
68
+ return inner.parse(val);
69
+ },
70
+ _type: undefined as T | undefined,
71
+ };
72
+ },
73
+ object<T extends Record<string, Validator>>(fields: T): Validator<{ [K in keyof T]: Infer<T[K]> }> {
74
+ return {
75
+ parse: (val) => {
76
+ if (typeof val !== "object" || val === null) throw new Error("Expected object");
77
+ const result: any = {};
78
+ for (const key in fields) {
79
+ result[key] = fields[key].parse((val as any)[key]);
80
+ }
81
+ return result;
82
+ },
83
+ _type: {} as any,
84
+ };
85
+ },
86
+ array<T>(inner: Validator<T>): Validator<T[]> {
87
+ return {
88
+ parse: (val) => {
89
+ if (!Array.isArray(val)) throw new Error("Expected array");
90
+ return val.map((item) => inner.parse(item));
91
+ },
92
+ _type: [] as T[],
93
+ };
94
+ },
95
95
  };
96
96
 
97
97
  /**
98
98
  * Infer TypeScript type from a validator schema or a shorthand record of validators.
99
99
  * Correctly handles optional properties (Validator<T | undefined> → optional key).
100
100
  */
101
- export type InferArgs<T> = T extends Validator<infer U>
101
+ export type InferArgs<T> =
102
+ T extends Validator<infer U>
102
103
  ? U
103
104
  : T extends Record<string, Validator>
104
- ? {
105
- [K in keyof T as T[K] extends Validator<infer U>
106
- ? (undefined extends U ? never : K)
107
- : K]: T[K] extends Validator<infer U> ? U : any;
108
- } & {
109
- [K in keyof T as T[K] extends Validator<infer U>
110
- ? (undefined extends U ? K : never)
111
- : never]?: T[K] extends Validator<infer U> ? U : any;
112
- }
113
- : T;
105
+ ? {
106
+ [K in keyof T as T[K] extends Validator<infer U>
107
+ ? undefined extends U
108
+ ? never
109
+ : K
110
+ : K]: T[K] extends Validator<infer U> ? U : any;
111
+ } & {
112
+ [K in keyof T as T[K] extends Validator<infer U>
113
+ ? undefined extends U
114
+ ? K
115
+ : never
116
+ : never]?: T[K] extends Validator<infer U> ? U : any;
117
+ }
118
+ : T;
114
119
 
115
120
  /**
116
121
  * Validates and parses arguments against a schema at runtime.
@@ -122,41 +127,41 @@ export type InferArgs<T> = T extends Validator<infer U>
122
127
  * Throws `GencowValidationError` (HTTP 400) on validation failure.
123
128
  */
124
129
  export function parseArgs(schema: any, args: any): any {
125
- if (!schema) return args;
130
+ if (!schema) return args;
126
131
 
127
- // Direct Validator (has a .parse method)
128
- if (typeof schema.parse === "function") {
129
- try {
130
- return schema.parse(args);
131
- } catch (e: any) {
132
- throw new GencowValidationError(e.message);
133
- }
132
+ // Direct Validator (has a .parse method)
133
+ if (typeof schema.parse === "function") {
134
+ try {
135
+ return schema.parse(args);
136
+ } catch (e: any) {
137
+ throw new GencowValidationError(e.message);
134
138
  }
139
+ }
135
140
 
136
- // Shorthand object — e.g. { id: v.number(), title: v.optional(v.string()) }
137
- if (typeof schema === "object" && schema !== null) {
138
- // Empty schema {} → passthrough all args (e.g. FormData with file field)
139
- const schemaKeys = Object.keys(schema);
140
- if (schemaKeys.length === 0) return args;
141
+ // Shorthand object — e.g. { id: v.number(), title: v.optional(v.string()) }
142
+ if (typeof schema === "object" && schema !== null) {
143
+ // Empty schema {} → passthrough all args (e.g. FormData with file field)
144
+ const schemaKeys = Object.keys(schema);
145
+ if (schemaKeys.length === 0) return args;
141
146
 
142
- if (typeof args !== "object" || args === null) {
143
- throw new GencowValidationError("Expected an object for arguments");
144
- }
145
- const result: any = {};
146
- for (const key of schemaKeys) {
147
- const validator = schema[key];
148
- if (validator && typeof validator.parse === "function") {
149
- try {
150
- result[key] = validator.parse(args[key]);
151
- } catch (e: any) {
152
- throw new GencowValidationError(`Argument "${key}": ${e.message}`);
153
- }
154
- } else {
155
- result[key] = args[key];
156
- }
147
+ if (typeof args !== "object" || args === null) {
148
+ throw new GencowValidationError("Expected an object for arguments");
149
+ }
150
+ const result: any = {};
151
+ for (const key of schemaKeys) {
152
+ const validator = schema[key];
153
+ if (validator && typeof validator.parse === "function") {
154
+ try {
155
+ result[key] = validator.parse(args[key]);
156
+ } catch (e: any) {
157
+ throw new GencowValidationError(`Argument "${key}": ${e.message}`);
157
158
  }
158
- return result;
159
+ } else {
160
+ result[key] = args[key];
161
+ }
159
162
  }
163
+ return result;
164
+ }
160
165
 
161
- return args;
166
+ return args;
162
167
  }
@@ -6,106 +6,103 @@ export type WorkflowDuration = number | string;
6
6
  export type WorkflowDerivedStatus = WorkflowStatus | "queued" | "waiting" | "sleeping";
7
7
 
8
8
  export function deriveWorkflowStatus(
9
- status: WorkflowStatus,
10
- currentStep: string | null | undefined
9
+ status: WorkflowStatus,
10
+ currentStep: string | null | undefined,
11
11
  ): WorkflowDerivedStatus {
12
- if (status !== "pending") {
13
- return status;
14
- }
15
- if (currentStep?.startsWith("sleep#")) {
16
- return "sleeping";
17
- }
18
- if (currentStep?.startsWith("wait:")) {
19
- return "waiting";
20
- }
21
- return "queued";
12
+ if (status !== "pending") {
13
+ return status;
14
+ }
15
+ if (currentStep?.startsWith("sleep#")) {
16
+ return "sleeping";
17
+ }
18
+ if (currentStep?.startsWith("wait:")) {
19
+ return "waiting";
20
+ }
21
+ return "queued";
22
22
  }
23
23
 
24
24
  export interface WorkflowSummary {
25
- id: string;
26
- name: string;
27
- status: WorkflowStatus;
28
- derivedStatus: WorkflowDerivedStatus;
29
- currentStep: string | null;
30
- error: string | null;
31
- retryCount: number;
32
- maxRetries: number;
33
- maxDurationMs: number;
34
- startedAt: string;
35
- updatedAt: string;
36
- completedAt: string | null;
25
+ id: string;
26
+ name: string;
27
+ status: WorkflowStatus;
28
+ derivedStatus: WorkflowDerivedStatus;
29
+ currentStep: string | null;
30
+ error: string | null;
31
+ retryCount: number;
32
+ maxRetries: number;
33
+ maxDurationMs: number;
34
+ startedAt: string;
35
+ updatedAt: string;
36
+ completedAt: string | null;
37
37
  }
38
38
 
39
39
  export interface WorkflowStepSnapshot {
40
- name: string;
41
- status: WorkflowStatus;
42
- output: unknown;
43
- error: string | null;
44
- startedAt: string | null;
45
- updatedAt: string;
46
- completedAt: string | null;
40
+ name: string;
41
+ status: WorkflowStatus;
42
+ output: unknown;
43
+ error: string | null;
44
+ startedAt: string | null;
45
+ updatedAt: string;
46
+ completedAt: string | null;
47
47
  }
48
48
 
49
49
  export interface WorkflowSnapshot extends WorkflowSummary {
50
- args: unknown;
51
- result: unknown;
52
- steps: WorkflowStepSnapshot[];
53
- /** Opaque realtime channel for exact-id workflow updates. */
54
- realtimeKey: string;
50
+ args: unknown;
51
+ result: unknown;
52
+ steps: WorkflowStepSnapshot[];
53
+ /** Opaque realtime channel for exact-id workflow updates. */
54
+ realtimeKey: string;
55
55
  }
56
56
 
57
57
  export interface WorkflowListArgs {
58
- limit?: number;
59
- status?: WorkflowDerivedStatus;
58
+ limit?: number;
59
+ status?: WorkflowDerivedStatus;
60
60
  }
61
61
 
62
62
  export interface WorkflowStartResult {
63
- id: string;
64
- name: string;
65
- status: "pending";
66
- scheduledJobId: string;
63
+ id: string;
64
+ name: string;
65
+ status: "pending";
66
+ scheduledJobId: string;
67
67
  }
68
68
 
69
69
  export interface WorkflowSignalResult {
70
- ok: boolean;
71
- workflowId: string;
72
- event: string;
73
- scheduledJobId: string | null;
70
+ ok: boolean;
71
+ workflowId: string;
72
+ event: string;
73
+ scheduledJobId: string | null;
74
74
  }
75
75
 
76
76
  export interface WorkflowResumePayload {
77
- workflowId: string;
77
+ workflowId: string;
78
78
  }
79
79
 
80
80
  export interface WorkflowCtx extends GencowCtx {
81
- workflowId: string;
82
- workflowName: string;
83
- step<TResult>(name: string, run: () => Promise<TResult>): Promise<TResult>;
84
- sleep(duration: WorkflowDuration): Promise<void>;
85
- waitForEvent<TPayload = unknown>(name: string, timeout?: WorkflowDuration): Promise<TPayload>;
86
- parallel<TTasks extends ReadonlyArray<() => Promise<any>>>(
87
- tasks: TTasks
88
- ): Promise<{ [K in keyof TTasks]: Awaited<ReturnType<TTasks[K]>> }>;
81
+ workflowId: string;
82
+ workflowName: string;
83
+ step<TResult>(name: string, run: () => Promise<TResult>): Promise<TResult>;
84
+ sleep(duration: WorkflowDuration): Promise<void>;
85
+ waitForEvent<TPayload = unknown>(name: string, timeout?: WorkflowDuration): Promise<TPayload>;
86
+ parallel<TTasks extends ReadonlyArray<() => Promise<any>>>(
87
+ tasks: TTasks,
88
+ ): Promise<{ [K in keyof TTasks]: Awaited<ReturnType<TTasks[K]>> }>;
89
89
  }
90
90
 
91
- export type WorkflowHandler<TArgs, TReturn> = (
92
- wf: WorkflowCtx,
93
- args: TArgs
94
- ) => Promise<TReturn>;
91
+ export type WorkflowHandler<TArgs, TReturn> = (wf: WorkflowCtx, args: TArgs) => Promise<TReturn>;
95
92
 
96
93
  export interface WorkflowOptions<TSchema = any, TReturn = any> {
97
- args?: TSchema;
98
- public?: boolean;
99
- maxDuration?: WorkflowDuration;
100
- retries?: number;
101
- handler: WorkflowHandler<InferArgs<TSchema>, TReturn>;
94
+ args?: TSchema;
95
+ public?: boolean;
96
+ maxDuration?: WorkflowDuration;
97
+ retries?: number;
98
+ handler: WorkflowHandler<InferArgs<TSchema>, TReturn>;
102
99
  }
103
100
 
104
101
  export interface WorkflowDef<TSchema = any, TReturn = any> {
105
- name: string;
106
- argsSchema?: TSchema;
107
- isPublic: boolean;
108
- maxDurationMs: number;
109
- maxRetries: number;
110
- handler: WorkflowHandler<InferArgs<TSchema>, TReturn>;
102
+ name: string;
103
+ argsSchema?: TSchema;
104
+ isPublic: boolean;
105
+ maxDurationMs: number;
106
+ maxRetries: number;
107
+ handler: WorkflowHandler<InferArgs<TSchema>, TReturn>;
111
108
  }