@pylonsync/functions 0.3.331 → 0.3.333

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/define.d.ts CHANGED
@@ -20,9 +20,9 @@
20
20
  * forgetting an auth check — when `auth: "user"` is in effect,
21
21
  * an anonymous request never reaches the handler at all.
22
22
  */
23
- import type { FnDefinition, QueryCtx, MutationCtx, ActionCtx, Validator } from "./types";
24
- interface CommonDef {
25
- args?: Record<string, Validator>;
23
+ import type { FnDefinition, QueryCtx, MutationCtx, ActionCtx, ValidatorSchema, InferArgs } from "./types";
24
+ interface CommonDef<TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> {
25
+ args?: TSchema;
26
26
  /**
27
27
  * When true, the function is callable only via `ctx.runQuery()` /
28
28
  * `ctx.runMutation()` / `ctx.runAction()` from another function
@@ -56,28 +56,28 @@ interface CommonDef {
56
56
  */
57
57
  timeout?: number;
58
58
  }
59
- interface QueryDefRequired<TArgs, TReturn> extends CommonDef {
59
+ interface QueryDefRequired<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
60
60
  /** Defaults to `"user"`. See [`AuthMode`] for the full surface. */
61
61
  auth?: "user" | "admin";
62
62
  handler: (ctx: QueryCtx<"required">, args: TArgs) => Promise<TReturn>;
63
63
  }
64
- interface QueryDefOptional<TArgs, TReturn> extends CommonDef {
64
+ interface QueryDefOptional<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
65
65
  auth: "public" | "guest";
66
66
  handler: (ctx: QueryCtx<"optional">, args: TArgs) => Promise<TReturn>;
67
67
  }
68
- interface MutationDefRequired<TArgs, TReturn> extends CommonDef {
68
+ interface MutationDefRequired<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
69
69
  auth?: "user" | "admin";
70
70
  handler: (ctx: MutationCtx<"required">, args: TArgs) => Promise<TReturn>;
71
71
  }
72
- interface MutationDefOptional<TArgs, TReturn> extends CommonDef {
72
+ interface MutationDefOptional<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
73
73
  auth: "public" | "guest";
74
74
  handler: (ctx: MutationCtx<"optional">, args: TArgs) => Promise<TReturn>;
75
75
  }
76
- interface ActionDefRequired<TArgs, TReturn> extends CommonDef {
76
+ interface ActionDefRequired<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
77
77
  auth?: "user" | "admin";
78
78
  handler: (ctx: ActionCtx<"required">, args: TArgs) => Promise<TReturn>;
79
79
  }
80
- interface ActionDefOptional<TArgs, TReturn> extends CommonDef {
80
+ interface ActionDefOptional<TArgs, TReturn, TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined> extends CommonDef<TSchema> {
81
81
  auth: "public" | "guest";
82
82
  handler: (ctx: ActionCtx<"optional">, args: TArgs) => Promise<TReturn>;
83
83
  }
@@ -113,6 +113,14 @@ interface ActionDefOptional<TArgs, TReturn> extends CommonDef {
113
113
  * });
114
114
  * ```
115
115
  */
116
+ export declare function query<const TSchema extends ValidatorSchema, TReturn, TAuth extends "user" | "admin" | undefined>(def: QueryDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
117
+ args: TSchema;
118
+ auth?: TAuth;
119
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
120
+ export declare function query<const TSchema extends ValidatorSchema, TReturn, TAuth extends "public" | "guest">(def: QueryDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
121
+ args: TSchema;
122
+ auth: TAuth;
123
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
116
124
  export declare function query<TArgs = Record<string, unknown>, TReturn = unknown>(def: QueryDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
117
125
  export declare function query<TArgs = Record<string, unknown>, TReturn = unknown>(def: QueryDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
118
126
  /**
@@ -144,6 +152,14 @@ export declare function query<TArgs = Record<string, unknown>, TReturn = unknown
144
152
  * });
145
153
  * ```
146
154
  */
155
+ export declare function mutation<const TSchema extends ValidatorSchema, TReturn, TAuth extends "user" | "admin" | undefined>(def: MutationDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
156
+ args: TSchema;
157
+ auth?: TAuth;
158
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
159
+ export declare function mutation<const TSchema extends ValidatorSchema, TReturn, TAuth extends "public" | "guest">(def: MutationDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
160
+ args: TSchema;
161
+ auth: TAuth;
162
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
147
163
  export declare function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(def: MutationDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
148
164
  export declare function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(def: MutationDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
149
165
  /**
@@ -190,6 +206,14 @@ export declare function mutation<TArgs = Record<string, unknown>, TReturn = unkn
190
206
  * });
191
207
  * ```
192
208
  */
209
+ export declare function action<const TSchema extends ValidatorSchema, TReturn, TAuth extends "user" | "admin" | undefined>(def: ActionDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
210
+ args: TSchema;
211
+ auth?: TAuth;
212
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
213
+ export declare function action<const TSchema extends ValidatorSchema, TReturn, TAuth extends "public" | "guest">(def: ActionDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
214
+ args: TSchema;
215
+ auth: TAuth;
216
+ }): FnDefinition<InferArgs<TSchema>, TReturn>;
193
217
  export declare function action<TArgs = Record<string, unknown>, TReturn = unknown>(def: ActionDefRequired<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
194
218
  export declare function action<TArgs = Record<string, unknown>, TReturn = unknown>(def: ActionDefOptional<TArgs, TReturn>): FnDefinition<TArgs, TReturn>;
195
219
  export {};
package/dist/index.d.ts CHANGED
@@ -22,4 +22,4 @@ export { v } from "./validators";
22
22
  export { resetDb, installTestIsolation } from "./testing";
23
23
  export { slugifyName, availableSlug } from "./slugify";
24
24
  export type { SsrResponse, SsrCookieOptions, SsrMetadata, Sitemap, SitemapEntry, Robots, RobotsRule, } from "./ssr-runtime";
25
- export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, RequireMember, RequireMemberOptions, MemberRow, } from "./types";
25
+ export type { QueryCtx, MutationCtx, ActionCtx, DbReader, DbWriter, Stream, Scheduler, AuthInfo, AuthMode, AuthRequirement, FnDefinition, Validator, AnyValidator, ValidatorSchema, InferValidator, InferArgs, RequireMember, RequireMemberOptions, MemberRow, } from "./types";
package/dist/types.d.ts CHANGED
@@ -545,9 +545,19 @@ export interface FnDefinition<TArgs = unknown, TReturn = unknown> {
545
545
  */
546
546
  timeout?: number;
547
547
  }
548
- export interface Validator {
548
+ declare const validatorType: unique symbol;
549
+ declare const validatorOptional: unique symbol;
550
+ /**
551
+ * Runtime validator carrying its validated TypeScript type.
552
+ *
553
+ * The symbol properties are type-only markers: they emit no runtime data and
554
+ * let the function constructors derive handler arguments from an `args` schema.
555
+ */
556
+ export interface Validator<T = unknown, TOptional extends boolean = boolean> {
549
557
  type: string;
550
558
  optional?: boolean;
559
+ readonly [validatorType]?: T;
560
+ readonly [validatorOptional]?: TOptional;
551
561
  /** For v.id("tableName") */
552
562
  table?: string;
553
563
  /** For v.array(v.string()) */
@@ -559,3 +569,20 @@ export interface Validator {
559
569
  /** For v.literal("value") */
560
570
  value?: unknown;
561
571
  }
572
+ /** Any validator accepted in an argument schema. */
573
+ export type AnyValidator = Validator<any, boolean>;
574
+ /** A function argument schema keyed by argument name. */
575
+ export type ValidatorSchema = Record<string, AnyValidator>;
576
+ /** Extract the value accepted by a validator. */
577
+ export type InferValidator<TValidator extends AnyValidator> = TValidator extends Validator<infer TValue, boolean> ? TValue : never;
578
+ type OptionalValidatorKeys<TSchema extends ValidatorSchema> = {
579
+ [TKey in keyof TSchema]-?: TSchema[TKey] extends Validator<any, true> ? TKey : never;
580
+ }[keyof TSchema];
581
+ type RequiredValidatorKeys<TSchema extends ValidatorSchema> = Exclude<keyof TSchema, OptionalValidatorKeys<TSchema>>;
582
+ /** Derive a handler argument object from a validator schema. */
583
+ export type InferArgs<TSchema extends ValidatorSchema> = {
584
+ [TKey in RequiredValidatorKeys<TSchema>]: InferValidator<TSchema[TKey]>;
585
+ } & {
586
+ [TKey in OptionalValidatorKeys<TSchema>]?: InferValidator<TSchema[TKey]>;
587
+ };
588
+ export {};
@@ -21,52 +21,52 @@
21
21
  * });
22
22
  * ```
23
23
  */
24
- import type { Validator } from "./types";
24
+ import type { AnyValidator, InferArgs, InferValidator, Validator, ValidatorSchema } from "./types";
25
25
  export declare const v: {
26
26
  /** String value. */
27
- string: () => Validator;
27
+ string: () => Validator<string, false>;
28
28
  /** Number (float64). Same as `v.float()`. */
29
- number: () => Validator;
29
+ number: () => Validator<number, false>;
30
30
  /**
31
31
  * 64-bit float. Alias for `v.number()` so the validator API matches the
32
32
  * schema DSL (which uses `field.float()`). Prefer this in new code.
33
33
  */
34
- float: () => Validator;
34
+ float: () => Validator<number, false>;
35
35
  /** Integer. */
36
- int: () => Validator;
36
+ int: () => Validator<number, false>;
37
37
  /** Boolean. Same as `v.bool()`. */
38
- boolean: () => Validator;
38
+ boolean: () => Validator<boolean, false>;
39
39
  /**
40
40
  * Boolean. Alias for `v.boolean()` so the validator API matches the
41
41
  * schema DSL (which uses `field.bool()`). Prefer this in new code.
42
42
  */
43
- bool: () => Validator;
43
+ bool: () => Validator<boolean, false>;
44
44
  /**
45
45
  * ISO-8601 datetime string. Validates the shape of a string value; the
46
46
  * stored column type comes from the schema (`field.datetime()`).
47
47
  */
48
- datetime: () => Validator;
48
+ datetime: () => Validator<string, false>;
49
49
  /**
50
50
  * Richtext string. Same runtime validation as `v.string()`; named
51
51
  * explicitly so server functions read as the matching schema type.
52
52
  */
53
- richtext: () => Validator;
53
+ richtext: () => Validator<string, false>;
54
54
  /** ID reference to another entity. */
55
- id: (table: string) => Validator;
55
+ id: (table: string) => Validator<string, false>;
56
56
  /** Null value. */
57
- null: () => Validator;
57
+ null: () => Validator<null, false>;
58
58
  /** Array of values. */
59
- array: (items: Validator) => Validator;
59
+ array: <TValidator extends AnyValidator>(items: TValidator) => Validator<InferValidator<TValidator>[], false>;
60
60
  /** Object with typed fields. */
61
- object: (fields: Record<string, Validator>) => Validator;
61
+ object: <TSchema extends ValidatorSchema>(fields: TSchema) => Validator<InferArgs<TSchema>, false>;
62
62
  /** Optional value (may be omitted). */
63
- optional: (inner: Validator) => Validator;
63
+ optional: <TValidator extends AnyValidator>(inner: TValidator) => Validator<InferValidator<TValidator>, true>;
64
64
  /** Union of multiple types. */
65
- union: (...variants: Validator[]) => Validator;
65
+ union: <TVariants extends AnyValidator[]>(...variants: TVariants) => Validator<InferValidator<TVariants[number]>, false>;
66
66
  /** Exact literal value. */
67
- literal: (value: string | number | boolean) => Validator;
67
+ literal: <const TValue extends string | number | boolean>(value: TValue) => Validator<TValue, false>;
68
68
  /** Any valid JSON value. */
69
- any: () => Validator;
69
+ any: () => Validator<any, false>;
70
70
  };
71
71
  export declare function validateArgs(args: unknown, schema: Record<string, Validator>): {
72
72
  valid: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pylonsync/functions",
3
- "version": "0.3.331",
3
+ "version": "0.3.333",
4
4
  "description": "TypeScript function runtime for pylon — defines server-side queries, mutations, and actions.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,6 +32,7 @@
32
32
  "README.md"
33
33
  ],
34
34
  "scripts": {
35
+ "check": "tsc --noEmit",
35
36
  "typecheck": "tsc --noEmit",
36
37
  "build": "tsc -p tsconfig.build.json",
37
38
  "test": "bun test",
package/src/define.ts CHANGED
@@ -26,15 +26,18 @@ import type {
26
26
  QueryCtx,
27
27
  MutationCtx,
28
28
  ActionCtx,
29
- Validator,
29
+ ValidatorSchema,
30
+ InferArgs,
30
31
  AuthMode,
31
32
  } from "./types";
32
33
 
33
34
  /** Default auth mode applied when a definition omits `auth`. */
34
35
  const DEFAULT_AUTH: AuthMode = "user";
35
36
 
36
- interface CommonDef {
37
- args?: Record<string, Validator>;
37
+ interface CommonDef<
38
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
39
+ > {
40
+ args?: TSchema;
38
41
  /**
39
42
  * When true, the function is callable only via `ctx.runQuery()` /
40
43
  * `ctx.runMutation()` / `ctx.runAction()` from another function
@@ -69,7 +72,11 @@ interface CommonDef {
69
72
  timeout?: number;
70
73
  }
71
74
 
72
- interface QueryDefRequired<TArgs, TReturn> extends CommonDef {
75
+ interface QueryDefRequired<
76
+ TArgs,
77
+ TReturn,
78
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
79
+ > extends CommonDef<TSchema> {
73
80
  /** Defaults to `"user"`. See [`AuthMode`] for the full surface. */
74
81
  auth?: "user" | "admin";
75
82
  handler: (
@@ -78,7 +85,11 @@ interface QueryDefRequired<TArgs, TReturn> extends CommonDef {
78
85
  ) => Promise<TReturn>;
79
86
  }
80
87
 
81
- interface QueryDefOptional<TArgs, TReturn> extends CommonDef {
88
+ interface QueryDefOptional<
89
+ TArgs,
90
+ TReturn,
91
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
92
+ > extends CommonDef<TSchema> {
82
93
  auth: "public" | "guest";
83
94
  handler: (
84
95
  ctx: QueryCtx<"optional">,
@@ -86,7 +97,11 @@ interface QueryDefOptional<TArgs, TReturn> extends CommonDef {
86
97
  ) => Promise<TReturn>;
87
98
  }
88
99
 
89
- interface MutationDefRequired<TArgs, TReturn> extends CommonDef {
100
+ interface MutationDefRequired<
101
+ TArgs,
102
+ TReturn,
103
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
104
+ > extends CommonDef<TSchema> {
90
105
  auth?: "user" | "admin";
91
106
  handler: (
92
107
  ctx: MutationCtx<"required">,
@@ -94,7 +109,11 @@ interface MutationDefRequired<TArgs, TReturn> extends CommonDef {
94
109
  ) => Promise<TReturn>;
95
110
  }
96
111
 
97
- interface MutationDefOptional<TArgs, TReturn> extends CommonDef {
112
+ interface MutationDefOptional<
113
+ TArgs,
114
+ TReturn,
115
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
116
+ > extends CommonDef<TSchema> {
98
117
  auth: "public" | "guest";
99
118
  handler: (
100
119
  ctx: MutationCtx<"optional">,
@@ -102,7 +121,11 @@ interface MutationDefOptional<TArgs, TReturn> extends CommonDef {
102
121
  ) => Promise<TReturn>;
103
122
  }
104
123
 
105
- interface ActionDefRequired<TArgs, TReturn> extends CommonDef {
124
+ interface ActionDefRequired<
125
+ TArgs,
126
+ TReturn,
127
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
128
+ > extends CommonDef<TSchema> {
106
129
  auth?: "user" | "admin";
107
130
  handler: (
108
131
  ctx: ActionCtx<"required">,
@@ -110,7 +133,11 @@ interface ActionDefRequired<TArgs, TReturn> extends CommonDef {
110
133
  ) => Promise<TReturn>;
111
134
  }
112
135
 
113
- interface ActionDefOptional<TArgs, TReturn> extends CommonDef {
136
+ interface ActionDefOptional<
137
+ TArgs,
138
+ TReturn,
139
+ TSchema extends ValidatorSchema | undefined = ValidatorSchema | undefined,
140
+ > extends CommonDef<TSchema> {
114
141
  auth: "public" | "guest";
115
142
  handler: (
116
143
  ctx: ActionCtx<"optional">,
@@ -150,6 +177,26 @@ interface ActionDefOptional<TArgs, TReturn> extends CommonDef {
150
177
  * });
151
178
  * ```
152
179
  */
180
+ export function query<
181
+ const TSchema extends ValidatorSchema,
182
+ TReturn,
183
+ TAuth extends "user" | "admin" | undefined,
184
+ >(
185
+ def: QueryDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
186
+ args: TSchema;
187
+ auth?: TAuth;
188
+ },
189
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
190
+ export function query<
191
+ const TSchema extends ValidatorSchema,
192
+ TReturn,
193
+ TAuth extends "public" | "guest",
194
+ >(
195
+ def: QueryDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
196
+ args: TSchema;
197
+ auth: TAuth;
198
+ },
199
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
153
200
  export function query<TArgs = Record<string, unknown>, TReturn = unknown>(
154
201
  def: QueryDefRequired<TArgs, TReturn>,
155
202
  ): FnDefinition<TArgs, TReturn>;
@@ -200,6 +247,26 @@ export function query<TArgs, TReturn>(
200
247
  * });
201
248
  * ```
202
249
  */
250
+ export function mutation<
251
+ const TSchema extends ValidatorSchema,
252
+ TReturn,
253
+ TAuth extends "user" | "admin" | undefined,
254
+ >(
255
+ def: MutationDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
256
+ args: TSchema;
257
+ auth?: TAuth;
258
+ },
259
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
260
+ export function mutation<
261
+ const TSchema extends ValidatorSchema,
262
+ TReturn,
263
+ TAuth extends "public" | "guest",
264
+ >(
265
+ def: MutationDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
266
+ args: TSchema;
267
+ auth: TAuth;
268
+ },
269
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
203
270
  export function mutation<TArgs = Record<string, unknown>, TReturn = unknown>(
204
271
  def: MutationDefRequired<TArgs, TReturn>,
205
272
  ): FnDefinition<TArgs, TReturn>;
@@ -265,6 +332,26 @@ export function mutation<TArgs, TReturn>(
265
332
  * });
266
333
  * ```
267
334
  */
335
+ export function action<
336
+ const TSchema extends ValidatorSchema,
337
+ TReturn,
338
+ TAuth extends "user" | "admin" | undefined,
339
+ >(
340
+ def: ActionDefRequired<InferArgs<TSchema>, TReturn, TSchema> & {
341
+ args: TSchema;
342
+ auth?: TAuth;
343
+ },
344
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
345
+ export function action<
346
+ const TSchema extends ValidatorSchema,
347
+ TReturn,
348
+ TAuth extends "public" | "guest",
349
+ >(
350
+ def: ActionDefOptional<InferArgs<TSchema>, TReturn, TSchema> & {
351
+ args: TSchema;
352
+ auth: TAuth;
353
+ },
354
+ ): FnDefinition<InferArgs<TSchema>, TReturn>;
268
355
  export function action<TArgs = Record<string, unknown>, TReturn = unknown>(
269
356
  def: ActionDefRequired<TArgs, TReturn>,
270
357
  ): FnDefinition<TArgs, TReturn>;
@@ -0,0 +1,109 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ import { action, mutation, query } from "./define";
4
+ import type { FnDefinition } from "./types";
5
+ import { v } from "./validators";
6
+
7
+ type Equal<TLeft, TRight> =
8
+ (<T>() => T extends TLeft ? 1 : 2) extends
9
+ (<T>() => T extends TRight ? 1 : 2)
10
+ ? true
11
+ : false;
12
+ type Expect<T extends true> = T;
13
+ type ArgsOf<TDefinition> =
14
+ TDefinition extends FnDefinition<infer TArgs, unknown> ? TArgs : never;
15
+ type IsAny<T> = 0 extends 1 & T ? true : false;
16
+
17
+ const inferredMutation = mutation({
18
+ args: {
19
+ required: v.string(),
20
+ optional: v.optional(v.number()),
21
+ list: v.array(v.int()),
22
+ nested: v.object({
23
+ enabled: v.boolean(),
24
+ label: v.optional(v.string()),
25
+ }),
26
+ union: v.union(v.literal("open"), v.literal(404), v.null()),
27
+ literal: v.literal("fixed"),
28
+ anything: v.any(),
29
+ },
30
+ async handler(_ctx, args) {
31
+ const required: string = args.required;
32
+ const optional: number | undefined = args.optional;
33
+ const list: number[] = args.list;
34
+ const nested: { enabled: boolean; label?: string } = args.nested;
35
+ const union: "open" | 404 | null = args.union;
36
+ const literal: "fixed" = args.literal;
37
+ const anything: any = args.anything;
38
+
39
+ return { required, optional, list, nested, union, literal, anything };
40
+ },
41
+ });
42
+
43
+ type InferredArgs = ArgsOf<typeof inferredMutation>;
44
+ type _RequiredValue = Expect<Equal<InferredArgs["required"], string>>;
45
+ type _RequiredKey = Expect<
46
+ Equal<{} extends Pick<InferredArgs, "required"> ? true : false, false>
47
+ >;
48
+ type _OptionalValue = Expect<
49
+ Equal<InferredArgs["optional"], number | undefined>
50
+ >;
51
+ type _OptionalKey = Expect<
52
+ Equal<{} extends Pick<InferredArgs, "optional"> ? true : false, true>
53
+ >;
54
+ type _Array = Expect<Equal<InferredArgs["list"], number[]>>;
55
+ type _ObjectRequired = Expect<
56
+ Equal<InferredArgs["nested"]["enabled"], boolean>
57
+ >;
58
+ type _ObjectOptional = Expect<
59
+ Equal<InferredArgs["nested"]["label"], string | undefined>
60
+ >;
61
+ type _Union = Expect<Equal<InferredArgs["union"], "open" | 404 | null>>;
62
+ type _Literal = Expect<Equal<InferredArgs["literal"], "fixed">>;
63
+ type _Any = Expect<IsAny<InferredArgs["anything"]>>;
64
+
65
+ const publicQuery = query({
66
+ auth: "public",
67
+ args: { id: v.id("Item") },
68
+ async handler(ctx, args) {
69
+ const userId: string | null = ctx.auth.userId;
70
+ const id: string = args.id;
71
+ return { id, userId };
72
+ },
73
+ });
74
+
75
+ const guestAction = action({
76
+ auth: "guest",
77
+ args: { mode: v.union(v.literal("fast"), v.literal("safe")) },
78
+ async handler(ctx, args) {
79
+ const userId: string | null = ctx.auth.userId;
80
+ const mode: "fast" | "safe" = args.mode;
81
+ return { mode, userId };
82
+ },
83
+ });
84
+
85
+ type ExplicitArgs = { name: string; note?: string };
86
+ type ExplicitReturn = { ok: boolean };
87
+
88
+ const explicitMutation = mutation<ExplicitArgs, ExplicitReturn>({
89
+ args: {
90
+ name: v.string(),
91
+ note: v.optional(v.string()),
92
+ },
93
+ async handler(_ctx, args) {
94
+ const explicit: ExplicitArgs = args;
95
+ return { ok: explicit.name.length > 0 };
96
+ },
97
+ });
98
+
99
+ type _ExplicitArgs = Expect<
100
+ Equal<ArgsOf<typeof explicitMutation>, ExplicitArgs>
101
+ >;
102
+
103
+ test("validator schemas remain available at runtime", () => {
104
+ expect(inferredMutation.args?.required.type).toBe("string");
105
+ expect(inferredMutation.args?.optional.optional).toBe(true);
106
+ expect(publicQuery.auth).toBe("public");
107
+ expect(guestAction.auth).toBe("guest");
108
+ expect(explicitMutation.type).toBe("mutation");
109
+ });
package/src/index.ts CHANGED
@@ -46,6 +46,11 @@ export type {
46
46
  AuthMode,
47
47
  AuthRequirement,
48
48
  FnDefinition,
49
+ Validator,
50
+ AnyValidator,
51
+ ValidatorSchema,
52
+ InferValidator,
53
+ InferArgs,
49
54
  RequireMember,
50
55
  RequireMemberOptions,
51
56
  MemberRow,
package/src/types.ts CHANGED
@@ -658,9 +658,23 @@ export interface FnDefinition<TArgs = unknown, TReturn = unknown> {
658
658
  // Validators
659
659
  // ---------------------------------------------------------------------------
660
660
 
661
- export interface Validator {
661
+ declare const validatorType: unique symbol;
662
+ declare const validatorOptional: unique symbol;
663
+
664
+ /**
665
+ * Runtime validator carrying its validated TypeScript type.
666
+ *
667
+ * The symbol properties are type-only markers: they emit no runtime data and
668
+ * let the function constructors derive handler arguments from an `args` schema.
669
+ */
670
+ export interface Validator<
671
+ T = unknown,
672
+ TOptional extends boolean = boolean,
673
+ > {
662
674
  type: string;
663
675
  optional?: boolean;
676
+ readonly [validatorType]?: T;
677
+ readonly [validatorOptional]?: TOptional;
664
678
  /** For v.id("tableName") */
665
679
  table?: string;
666
680
  /** For v.array(v.string()) */
@@ -672,3 +686,31 @@ export interface Validator {
672
686
  /** For v.literal("value") */
673
687
  value?: unknown;
674
688
  }
689
+
690
+ /** Any validator accepted in an argument schema. */
691
+ export type AnyValidator = Validator<any, boolean>;
692
+
693
+ /** A function argument schema keyed by argument name. */
694
+ export type ValidatorSchema = Record<string, AnyValidator>;
695
+
696
+ /** Extract the value accepted by a validator. */
697
+ export type InferValidator<TValidator extends AnyValidator> =
698
+ TValidator extends Validator<infer TValue, boolean> ? TValue : never;
699
+
700
+ type OptionalValidatorKeys<TSchema extends ValidatorSchema> = {
701
+ [TKey in keyof TSchema]-?: TSchema[TKey] extends Validator<any, true>
702
+ ? TKey
703
+ : never;
704
+ }[keyof TSchema];
705
+
706
+ type RequiredValidatorKeys<TSchema extends ValidatorSchema> = Exclude<
707
+ keyof TSchema,
708
+ OptionalValidatorKeys<TSchema>
709
+ >;
710
+
711
+ /** Derive a handler argument object from a validator schema. */
712
+ export type InferArgs<TSchema extends ValidatorSchema> = {
713
+ [TKey in RequiredValidatorKeys<TSchema>]: InferValidator<TSchema[TKey]>;
714
+ } & {
715
+ [TKey in OptionalValidatorKeys<TSchema>]?: InferValidator<TSchema[TKey]>;
716
+ };
package/src/validators.ts CHANGED
@@ -22,75 +22,99 @@
22
22
  * ```
23
23
  */
24
24
 
25
- import type { Validator } from "./types";
26
-
27
- function validator(type: string, extra?: Partial<Validator>): Validator {
25
+ import type {
26
+ AnyValidator,
27
+ InferArgs,
28
+ InferValidator,
29
+ Validator,
30
+ ValidatorSchema,
31
+ } from "./types";
32
+
33
+ function validator<T>(
34
+ type: string,
35
+ extra?: Partial<Validator<T, false>>,
36
+ ): Validator<T, false> {
28
37
  return { type, ...extra };
29
38
  }
30
39
 
31
40
  export const v = {
32
41
  /** String value. */
33
- string: (): Validator => validator("string"),
42
+ string: (): Validator<string, false> => validator("string"),
34
43
 
35
44
  /** Number (float64). Same as `v.float()`. */
36
- number: (): Validator => validator("number"),
45
+ number: (): Validator<number, false> => validator("number"),
37
46
 
38
47
  /**
39
48
  * 64-bit float. Alias for `v.number()` so the validator API matches the
40
49
  * schema DSL (which uses `field.float()`). Prefer this in new code.
41
50
  */
42
- float: (): Validator => validator("number"),
51
+ float: (): Validator<number, false> => validator("number"),
43
52
 
44
53
  /** Integer. */
45
- int: (): Validator => validator("int"),
54
+ int: (): Validator<number, false> => validator("int"),
46
55
 
47
56
  /** Boolean. Same as `v.bool()`. */
48
- boolean: (): Validator => validator("boolean"),
57
+ boolean: (): Validator<boolean, false> => validator("boolean"),
49
58
 
50
59
  /**
51
60
  * Boolean. Alias for `v.boolean()` so the validator API matches the
52
61
  * schema DSL (which uses `field.bool()`). Prefer this in new code.
53
62
  */
54
- bool: (): Validator => validator("boolean"),
63
+ bool: (): Validator<boolean, false> => validator("boolean"),
55
64
 
56
65
  /**
57
66
  * ISO-8601 datetime string. Validates the shape of a string value; the
58
67
  * stored column type comes from the schema (`field.datetime()`).
59
68
  */
60
- datetime: (): Validator => validator("string"),
69
+ datetime: (): Validator<string, false> => validator("string"),
61
70
 
62
71
  /**
63
72
  * Richtext string. Same runtime validation as `v.string()`; named
64
73
  * explicitly so server functions read as the matching schema type.
65
74
  */
66
- richtext: (): Validator => validator("string"),
75
+ richtext: (): Validator<string, false> => validator("string"),
67
76
 
68
77
  /** ID reference to another entity. */
69
- id: (table: string): Validator => validator("id", { table }),
78
+ id: (table: string): Validator<string, false> =>
79
+ validator("id", { table }),
70
80
 
71
81
  /** Null value. */
72
- null: (): Validator => validator("null"),
82
+ null: (): Validator<null, false> => validator("null"),
73
83
 
74
84
  /** Array of values. */
75
- array: (items: Validator): Validator => validator("array", { items }),
85
+ array: <TValidator extends AnyValidator>(
86
+ items: TValidator,
87
+ ): Validator<InferValidator<TValidator>[], false> =>
88
+ validator("array", { items }),
76
89
 
77
90
  /** Object with typed fields. */
78
- object: (fields: Record<string, Validator>): Validator =>
91
+ object: <TSchema extends ValidatorSchema>(
92
+ fields: TSchema,
93
+ ): Validator<InferArgs<TSchema>, false> =>
79
94
  validator("object", { fields }),
80
95
 
81
96
  /** Optional value (may be omitted). */
82
- optional: (inner: Validator): Validator => ({ ...inner, optional: true }),
97
+ optional: <TValidator extends AnyValidator>(
98
+ inner: TValidator,
99
+ ): Validator<InferValidator<TValidator>, true> =>
100
+ ({
101
+ ...inner,
102
+ optional: true,
103
+ }) as Validator<InferValidator<TValidator>, true>,
83
104
 
84
105
  /** Union of multiple types. */
85
- union: (...variants: Validator[]): Validator =>
106
+ union: <TVariants extends AnyValidator[]>(
107
+ ...variants: TVariants
108
+ ): Validator<InferValidator<TVariants[number]>, false> =>
86
109
  validator("union", { variants }),
87
110
 
88
111
  /** Exact literal value. */
89
- literal: (value: string | number | boolean): Validator =>
90
- validator("literal", { value }),
112
+ literal: <const TValue extends string | number | boolean>(
113
+ value: TValue,
114
+ ): Validator<TValue, false> => validator("literal", { value }),
91
115
 
92
116
  /** Any valid JSON value. */
93
- any: (): Validator => validator("any"),
117
+ any: (): Validator<any, false> => validator("any"),
94
118
  };
95
119
 
96
120
  // ---------------------------------------------------------------------------