Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions crates/bindings-typescript/src/server/constraints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import type { ColumnMetadata } from './type_builders';
*/
export type AllUnique<
TableDef extends UntypedTableDef,
Columns extends Array<keyof TableDef['columns']>,
> = {
[i in keyof Columns]: ColumnIsUnique<
TableDef['columns'][Columns[i]]['columnMetadata']
>;
} extends true[]
? true
: false;
Columns extends ReadonlyArray<keyof TableDef['columns']>,
> = Columns extends readonly [
infer Head extends keyof TableDef['columns'],
...infer Tail extends ReadonlyArray<keyof TableDef['columns']>,
]
? ColumnIsUnique<TableDef['columns'][Head]['columnMetadata']> extends true
? AllUnique<TableDef, Tail>
: false
: true;

/**
* A helper type to determine if a column is unique based on its metadata.
Expand Down
43 changes: 26 additions & 17 deletions crates/bindings-typescript/src/server/indexes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@ export type IndexOpts<AllowedCol extends string> = {
/**
* An untyped representation of an index definition.
*/
type UntypedIndex<AllowedCol extends string> = {
export type UntypedIndex<AllowedCol extends string> = {
name: string;
unique: boolean;
algorithm: 'btree' | 'direct';
columns: AllowedCol[];
columns: readonly AllowedCol[];
};

/**
* A helper type to extract the column names from an index definition.
*/
export type IndexColumns<I extends IndexOpts<any>> = I extends {
columns: string[];
columns: readonly string[];
}
? I['columns']
: I extends { column: string }
? [I['column']]
? readonly [...I['columns']]
: I extends { column: infer Name extends string }
? readonly [Name]
: never;

/**
Expand Down Expand Up @@ -95,9 +95,18 @@ export type IndexVal<
/**
* A helper type to extract the types of the columns that make up an index.
*/
type _IndexVal<TableDef extends UntypedTableDef, Columns extends string[]> = {
[i in keyof Columns]: TableDef['columns'][Columns[i]]['typeBuilder']['type'];
};
type _IndexVal<
TableDef extends UntypedTableDef,
Columns extends readonly string[],
> = Columns extends readonly [
infer Head extends string,
...infer Tail extends readonly string[],
]
? [
TableDef['columns'][Head]['typeBuilder']['type'],
..._IndexVal<TableDef, Tail>,
]
: [];

/**
* A helper type to define the bounds for scanning an index.
Expand All @@ -115,7 +124,9 @@ export type IndexScanRangeBounds<
* It supports omitting trailing columns if the index is multi-column.
* This version only allows omitting the array if the index is single-column to avoid ambiguity.
*/
type _IndexScanRangeBounds<Columns extends any[]> = Columns extends [infer Term]
type _IndexScanRangeBounds<Columns extends readonly any[]> = Columns extends [
infer Term,
]
? Term | Range<Term>
: _IndexScanRangeBoundsCase<Columns>;

Expand All @@ -124,12 +135,10 @@ type _IndexScanRangeBounds<Columns extends any[]> = Columns extends [infer Term]
* This type allows for specifying exact values or ranges for each column in the index.
* It supports omitting trailing columns if the index is multi-column.
*/
type _IndexScanRangeBoundsCase<Columns extends any[]> = Columns extends [
...infer Prefix,
infer Term,
]
? [...Prefix, Term | Range<Term>] | _IndexScanRangeBounds<Prefix>
: never;
type _IndexScanRangeBoundsCase<Columns extends readonly any[]> =
Columns extends [...infer Prefix, infer Term]
? readonly [...Prefix, Term | Range<Term>] | _IndexScanRangeBounds<Prefix>
: never;

/**
* A helper type representing a column index definition.
Expand All @@ -141,7 +150,7 @@ export type ColumnIndex<
{
name: Name;
unique: ColumnIsUnique<M>;
columns: [Name];
columns: readonly [Name];
algorithm: 'btree' | 'direct';
} & (M extends {
indexType: infer I extends NonNullable<IndexTypes>;
Expand Down
48 changes: 48 additions & 0 deletions crates/bindings-typescript/src/server/schema.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { schema } from './schema';
import { table } from './table';
import t from './type_builders';

const person = table(
{
name: 'person',
indexes: [
{
name: 'id_name_idx',
algorithm: 'btree',
columns: ['id', 'name'] as const,
},
{
name: 'id_name2_idx',
algorithm: 'btree',
columns: ['id', 'name2'] as const,
},
{
name: 'name_idx',
algorithm: 'btree',
columns: ['name'] as const,
},
],
},
{
id: t.u32().primaryKey(),
name: t.string(),
name2: t.string().unique(),
married: t.bool(),
id2: t.identity(),
age: t.u32(),
age2: t.u16(),
}
);

const spacetimedb = schema(person);

spacetimedb.init(ctx => {
ctx.db.person.id_name_idx.filter(1);
ctx.db.person.id_name_idx.filter([1, 'aname']);
// ctx.db.person.id_name2_idx.find

// @ts-expect-error id2 is not indexed, so this should not exist at all.
const _id2 = ctx.db.person.id2;

ctx.db.person.id.find(2);
});
45 changes: 34 additions & 11 deletions crates/bindings-typescript/src/server/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,26 +54,49 @@ type CoerceArray<X extends IndexOpts<any>[]> = X;
export type UntypedTableDef = {
name: string;
columns: Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>;
indexes: IndexOpts<any>[];
indexes: readonly IndexOpts<any>[];
};

/**
* A type representing the indexes defined on a table.
*/
export type TableIndexes<TableDef extends UntypedTableDef> = {
[k in keyof TableDef['columns'] & string]: ColumnIndex<
k,
TableDef['columns'][k]['columnMetadata']
>;
[K in keyof TableDef['columns'] & string as ColumnIndex<
K,
TableDef['columns'][K]['columnMetadata']
> extends never
? never
: K]: ColumnIndex<K, TableDef['columns'][K]['columnMetadata']>;
} & {
[I in TableDef['indexes'][number] as I['name'] & {}]: {
name: I['name'];
unique: AllUnique<TableDef, IndexColumns<I>>;
algorithm: Lowercase<I['algorithm']>;
columns: IndexColumns<I>;
};
[I in TableDef['indexes'][number] as I['name'] & {}]: TableIndexFromDef<
TableDef,
I
>;
};

type TableIndexFromDef<
TableDef extends UntypedTableDef,
I extends IndexOpts<keyof TableDef['columns'] & string>,
> =
NormalizeIndexColumns<TableDef, I> extends infer Cols extends ReadonlyArray<
keyof TableDef['columns'] & string
>
? {
name: I['name'];
unique: AllUnique<TableDef, Cols>;
algorithm: Lowercase<I['algorithm']>;
columns: Cols;
}
: never;

type NormalizeIndexColumns<
TableDef extends UntypedTableDef,
I extends IndexOpts<keyof TableDef['columns'] & string>,
> =
IndexColumns<I> extends ReadonlyArray<keyof TableDef['columns'] & string>
? IndexColumns<I>
: never;

/**
* Options for configuring a database table.
* - `name`: The name of the table.
Expand Down
Loading