Skip to content

Commit 74f1f57

Browse files
sshaderConvex, Inc.
authored andcommitted
Make validator types shorter and more readable (#27144)
The goal is having the types on hover for stuff like `v.object({ a: v.string(), b: v.number() })` look reasonable. I shortened `FooValidator` to `VFoo`, which looks a little better, and avoids confusion with built in types like `Array` or `Object`. I also switched `IsOptional extends boolean` to `IsOptional extends "required" | "optional"` for readability (but serialized format is the same). I dropped a few type params that didn't seem necessary (e.g. only using `FieldPaths` on object and union types, since I think this is used to represent all indexable fields, eliminating the TableName type param on `VId`) GitOrigin-RevId: 743c026572c27c6f282dee51822476cec21c1005
1 parent 2ac52d0 commit 74f1f57

File tree

11 files changed

+249
-207
lines changed

11 files changed

+249
-207
lines changed

crates/isolate/src/tests/args_validation.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -132,20 +132,23 @@ async fn test_correct_arg(rt: TestRuntime) -> anyhow::Result<()> {
132132
#[convex_macro::test_runtime]
133133
async fn test_record(rt: TestRuntime) -> anyhow::Result<()> {
134134
UdfTest::run_test_with_isolate2(rt, async move |t: UdfTestType| {
135-
let args_obj = assert_obj!(
136-
"foo" => 0.,
137-
"bar" => 1.,
138-
"baz" => 2.,
135+
must_let!(
136+
let ConvexValue::Object(record) = t
137+
.mutation(
138+
"args_validation:returnRecord",
139+
assert_obj!(),
140+
)
141+
.await?
139142
);
140143
must_let!(
141144
let ConvexValue::Object(result) = t
142145
.query(
143146
"args_validation:recordArg",
144-
assert_obj!("arg" => args_obj.clone()),
147+
assert_obj!("arg" => record.clone()),
145148
)
146149
.await?
147150
);
148-
assert_eq!(result, args_obj);
151+
assert_eq!(result, record);
149152
Ok(())
150153
})
151154
.await

npm-packages/convex/scripts/postpack.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ function rewriteDtsToRemoveInternal(dirname) {
120120
dirname,
121121
"values/validator.d.ts",
122122
`/** @internal */
123-
record<Key extends Validator<any, boolean, any>, Value extends Validator<any, boolean, any>>(keys: Key, values: Value): RecordValidator<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, false, never>;`,
123+
record<Key extends Validator<any, "required", any>, Value extends Validator<any, "required", any>>(keys: Key, values: Value): VRecord<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>;`,
124124
`/* @internal
125-
record<Key extends Validator<any, boolean, any>, Value extends Validator<any, boolean, any>>(keys: Key, values: Value): RecordValidator<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, false, never>; */`,
125+
record<Key extends Validator<any, "required", any>, Value extends Validator<any, "required", any>>(keys: Key, values: Value): VRecord<Value["isOptional"] extends true ? { [key in Infer<Key>]?: Value["type"] | undefined; } : Record<Infer<Key>, Value["type"]>, Key, Value, "required", string>; */`,
126126
);
127127
auditForInternal(path.join(dirname, "dist", "cjs-types"));
128128
auditForInternal(path.join(dirname, "dist", "esm-types"));

npm-packages/convex/src/server/impl/registration_impl.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
ConvexError,
33
convexToJson,
4+
GenericValidator,
45
jsonToConvex,
56
v,
67
Validator,
@@ -126,14 +127,14 @@ function assertNotBrowser() {
126127
type FunctionDefinition =
127128
| ((ctx: any, args: DefaultFunctionArgs) => any)
128129
| {
129-
args?: Record<string, Validator<any, boolean>>;
130-
returns?: Validator<any, boolean>;
130+
args?: Record<string, GenericValidator>;
131+
returns?: GenericValidator;
131132
handler: (ctx: any, args: DefaultFunctionArgs) => any;
132133
};
133134

134135
function exportArgs(functionDefinition: FunctionDefinition) {
135136
return () => {
136-
let args: Validator<any, boolean, any> = v.any();
137+
let args: GenericValidator = v.any();
137138
if (
138139
typeof functionDefinition === "object" &&
139140
functionDefinition.args !== undefined

npm-packages/convex/src/server/schema.test.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ describe("DataModelFromSchemaDefinition", () => {
5353
| "string"
5454
| "bytes"
5555
| "array"
56-
| "record";
56+
| "record"
57+
| `record.${string}`;
5758

5859
type ExpectedDataModel = {
5960
table: {
@@ -287,7 +288,11 @@ describe("DataModelFromSchemaDefinition", () => {
287288
_creationTime: number;
288289
property: Record<GenericId<"reference">, string>;
289290
};
290-
type ExpectedFieldPaths = "_id" | "_creationTime" | "property";
291+
type ExpectedFieldPaths =
292+
| "_id"
293+
| "_creationTime"
294+
| "property"
295+
| `property.${string}`;
291296
type ExpectedDataModel = {
292297
table: {
293298
document: ExpectedDocument;
@@ -303,22 +308,20 @@ describe("DataModelFromSchemaDefinition", () => {
303308
test("defineSchema handles records with type unions", () => {
304309
const schema = defineSchema({
305310
table: defineTable({
306-
property: v.record(
307-
v.union(v.literal("foo"), v.literal("bla")),
308-
v.optional(v.string()),
309-
),
311+
property: v.record(v.union(v.id("foo"), v.id("bla")), v.string()),
310312
}),
311313
});
312314
type DataModel = DataModelFromSchemaDefinition<typeof schema>;
313315
type ExpectedDocument = {
314316
_id: GenericId<"table">;
315317
_creationTime: number;
316-
property: {
317-
foo?: string;
318-
bla?: string;
319-
};
318+
property: Record<GenericId<"foo"> | GenericId<"bla">, string>;
320319
};
321-
type ExpectedFieldPaths = "_id" | "_creationTime" | "property";
320+
type ExpectedFieldPaths =
321+
| "_id"
322+
| "_creationTime"
323+
| "property"
324+
| `property.${string}`;
322325
type ExpectedDataModel = {
323326
table: {
324327
document: ExpectedDocument;

npm-packages/convex/src/server/schema.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,13 @@ import {
4242
SystemIndexes,
4343
} from "../server/system_fields.js";
4444
import { Expand } from "../type_utils.js";
45-
import { ObjectType, isValidator, v } from "../values/validator.js";
46-
import { ObjectValidator, Validator } from "../values/validators.js";
45+
import {
46+
GenericValidator,
47+
ObjectType,
48+
isValidator,
49+
v,
50+
} from "../values/validator.js";
51+
import { VObject, Validator } from "../values/validators.js";
4752

4853
/**
4954
* Extract all of the index field paths within a {@link Validator}.
@@ -343,7 +348,7 @@ export class TableDefinition<
343348
* @public
344349
*/
345350
export function defineTable<
346-
DocumentSchema extends Validator<Record<string, any>, false, any>,
351+
DocumentSchema extends Validator<Record<string, any>, "required", any>,
347352
>(documentSchema: DocumentSchema): TableDefinition<DocumentSchema>;
348353
/**
349354
* Define a table in a schema.
@@ -371,14 +376,14 @@ export function defineTable<
371376
* @public
372377
*/
373378
export function defineTable<
374-
DocumentSchema extends Record<string, Validator<any, any, any>>,
379+
DocumentSchema extends Record<string, GenericValidator>,
375380
>(
376381
documentSchema: DocumentSchema,
377-
): TableDefinition<ObjectValidator<ObjectType<DocumentSchema>, DocumentSchema>>;
382+
): TableDefinition<VObject<ObjectType<DocumentSchema>, DocumentSchema>>;
378383
export function defineTable<
379384
DocumentSchema extends
380-
| Validator<Record<string, any>, false, any>
381-
| Record<string, Validator<any, any, any>>,
385+
| Validator<Record<string, any>, "required", any>
386+
| Record<string, GenericValidator>,
382387
>(documentSchema: DocumentSchema): TableDefinition<any, any, any> {
383388
if (isValidator(documentSchema)) {
384389
return new TableDefinition(documentSchema);

npm-packages/convex/src/values/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,16 @@ export type {
1414
NumericValue,
1515
} from "./value.js";
1616
export { v } from "./validator.js";
17-
export type { PropertyValidators, ObjectType } from "./validator.js";
17+
export type {
18+
PropertyValidators,
19+
ObjectType,
20+
GenericValidator,
21+
} from "./validator.js";
1822
export type {
1923
ValidatorJSON,
2024
ObjectFieldType,
2125
Validator,
26+
OptionalProperty,
2227
} from "./validators.js";
2328
import * as Base64 from "./base64.js";
2429
export { Base64 };
Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,32 @@
11
import { Expand } from "../type_utils.js";
22
import { GenericId } from "./index.js";
3-
4-
// Binding this module to `V` makes autocompletion pretty crummy in this file.
53
import {
6-
AnyValidator,
7-
ArrayValidator,
8-
BooleanValidator,
9-
BytesValidator,
10-
Float64Validator,
11-
IdValidator,
12-
Int64Validator,
13-
LiteralValidator,
14-
NullValidator,
15-
ObjectValidator,
16-
OptionalValidator,
17-
RecordValidator,
18-
StringValidator,
19-
UnionValidator,
4+
OptionalProperty,
5+
VAny,
6+
VArray,
7+
VBoolean,
8+
VBytes,
9+
VFloat64,
10+
VId,
11+
VInt64,
12+
VLiteral,
13+
VNull,
14+
VObject,
15+
VOptional,
16+
VRecord,
17+
VString,
18+
VUnion,
2019
Validator,
2120
} from "./validators.js";
2221

23-
export function isValidator(v: any): v is Validator<any, boolean, any> {
22+
/**
23+
* The type that all validators must extend.
24+
*
25+
* @public
26+
*/
27+
export type GenericValidator = Validator<any, any, any>;
28+
29+
export function isValidator(v: any): v is GenericValidator {
2430
return !!v.isValidator;
2531
}
2632

@@ -36,82 +42,81 @@ export function isValidator(v: any): v is Validator<any, boolean, any> {
3642
*/
3743
export const v = {
3844
id<TableName extends string>(tableName: TableName) {
39-
return new IdValidator<GenericId<TableName>, TableName>({
40-
isOptional: false,
45+
return new VId<GenericId<TableName>>({
46+
isOptional: "required",
4147
tableName,
4248
});
4349
},
4450
null() {
45-
return new NullValidator({ isOptional: false });
51+
return new VNull({ isOptional: "required" });
4652
},
4753
/**
4854
* Alias for `v.float64()`
4955
*/
5056
number() {
51-
return new Float64Validator({ isOptional: false });
57+
return new VFloat64({ isOptional: "required" });
5258
},
5359
float64() {
54-
return new Float64Validator({ isOptional: false });
60+
return new VFloat64({ isOptional: "required" });
5561
},
5662
/**
5763
* @deprecated Use `v.int64()` instead
5864
*/
5965
bigint() {
60-
return new Int64Validator({ isOptional: false });
66+
return new VInt64({ isOptional: "required" });
6167
},
6268
int64() {
63-
return new Int64Validator({ isOptional: false });
69+
return new VInt64({ isOptional: "required" });
6470
},
6571
boolean() {
66-
return new BooleanValidator({ isOptional: false });
72+
return new VBoolean({ isOptional: "required" });
6773
},
6874
string() {
69-
return new StringValidator({ isOptional: false });
75+
return new VString({ isOptional: "required" });
7076
},
7177
bytes() {
72-
return new BytesValidator({ isOptional: false });
78+
return new VBytes({ isOptional: "required" });
7379
},
74-
// this could be expanded for more kinds of literals
7580
literal<T extends string | number | bigint | boolean>(literal: T) {
76-
return new LiteralValidator<T, T>({ isOptional: false, value: literal });
81+
return new VLiteral<T>({ isOptional: "required", value: literal });
7782
},
78-
array<T extends Validator<any, false, any>>(element: T) {
79-
return new ArrayValidator<T["type"][], T>({ isOptional: false, element });
83+
array<T extends Validator<any, "required", any>>(element: T) {
84+
return new VArray<T["type"][], T>({ isOptional: "required", element });
8085
},
8186
object<T extends PropertyValidators>(fields: T) {
82-
return new ObjectValidator<ObjectType<T>, T>({ isOptional: false, fields });
87+
return new VObject<ObjectType<T>, T>({ isOptional: "required", fields });
8388
},
8489

8590
/** @internal */
8691
record<
87-
Key extends Validator<any, boolean, any>,
88-
Value extends Validator<any, boolean, any>,
92+
Key extends Validator<any, "required", any>,
93+
Value extends Validator<any, "required", any>,
8994
>(keys: Key, values: Value) {
9095
// TODO enforce that Infer<key> extends string
91-
return new RecordValidator<
96+
return new VRecord<
9297
Value["isOptional"] extends true
9398
? { [key in Infer<Key>]?: Value["type"] }
9499
: Record<Infer<Key>, Value["type"]>,
95100
Key,
96101
Value
97102
>({
98-
isOptional: false,
103+
isOptional: "required",
99104
key: keys,
100105
value: values,
101106
});
102107
},
103108

104-
union<T extends Validator<any, false, any>[]>(...members: T) {
105-
return new UnionValidator<T[number]["type"], T>({
106-
isOptional: false,
109+
union<T extends Validator<any, "required", any>[]>(...members: T) {
110+
return new VUnion<T[number]["type"], T>({
111+
isOptional: "required",
107112
members,
108113
});
109114
},
110115
any() {
111-
return new AnyValidator({ isOptional: false });
116+
return new VAny({ isOptional: "required" });
112117
},
113-
optional<T extends Validator<any, boolean, any>>(value: T) {
114-
return value.optional() as OptionalValidator<T>;
118+
optional<T extends GenericValidator>(value: T) {
119+
return value.optional() as VOptional<T>;
115120
},
116121
};
117122

@@ -123,7 +128,10 @@ export const v = {
123128
*
124129
* @public
125130
*/
126-
export type PropertyValidators = Record<string, Validator<any, boolean, any>>;
131+
export type PropertyValidators = Record<
132+
string,
133+
Validator<any, OptionalProperty, any>
134+
>;
127135

128136
/**
129137
* Compute the type of an object from {@link PropertyValidators}.
@@ -140,17 +148,15 @@ export type ObjectType<Fields extends PropertyValidators> = Expand<
140148
}
141149
>;
142150

143-
type OptionalKeys<
144-
PropertyValidators extends Record<string, Validator<any, boolean, any>>,
145-
> = {
146-
[Property in keyof PropertyValidators]: PropertyValidators[Property]["isOptional"] extends true
147-
? Property
148-
: never;
149-
}[keyof PropertyValidators];
151+
type OptionalKeys<PropertyValidators extends Record<string, GenericValidator>> =
152+
{
153+
[Property in keyof PropertyValidators]: PropertyValidators[Property]["isOptional"] extends "optional"
154+
? Property
155+
: never;
156+
}[keyof PropertyValidators];
150157

151-
type RequiredKeys<
152-
PropertyValidators extends Record<string, Validator<any, boolean, any>>,
153-
> = Exclude<keyof PropertyValidators, OptionalKeys<PropertyValidators>>;
158+
type RequiredKeys<PropertyValidators extends Record<string, GenericValidator>> =
159+
Exclude<keyof PropertyValidators, OptionalKeys<PropertyValidators>>;
154160

155161
/**
156162
* Extract a TypeScript type from a validator.
@@ -166,4 +172,4 @@ type RequiredKeys<
166172
*
167173
* @public
168174
*/
169-
export type Infer<T extends Validator<any, boolean, any>> = T["type"];
175+
export type Infer<T extends Validator<any, OptionalProperty, any>> = T["type"];

0 commit comments

Comments
 (0)