Skip to content
Draft
Show file tree
Hide file tree
Changes from 20 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
91 changes: 91 additions & 0 deletions crates/bindings-typescript/src/server/query.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import type { U32 } from '../lib/autogen/algebraic_type_variants';
import type { Indexes, UniqueIndex } from './indexes';
import {
eq,
literal,
type RowExpr,
type TableNames,
type TableSchemaAsTableDef,
} from './query';
import { schema } from './schema';
import { table, type TableIndexes } 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: 'name_idx',
algorithm: 'btree',
columns: ['name'] as const,
},
] as const,
},
{
id: t.u32().primaryKey(),
name: t.string(),
married: t.bool(),
id2: t.identity(),
age: t.u32(),
age2: t.u16(),
}
);

const order = table(
{
name: 'order',
},
{
order_id: t.u32().primaryKey(),
item_name: t.string(),
person_id: t.u32().index(),
}
);

const spacetimedb = schema([person, order]);

const tableDef = {
name: person.tableName,
columns: person.rowType.row,
indexes: person.idxs, // keep the typed, literal tuples here
} as TableSchemaAsTableDef<typeof person>;

type PersonDef = typeof tableDef;

declare const row: RowExpr<PersonDef>;

const x: U32 = row.age.spacetimeType;

type SchemaTableNames = TableNames<(typeof spacetimedb)['schemaType']>;
const y: SchemaTableNames = 'person';

const orderDef = {
name: order.tableName,
columns: order.rowType.row,
indexes: order.idxs,
};

spacetimedb.init(ctx => {
const firstQuery = ctx.queryBuilder.query('person');
firstQuery.semijoinTo(
ctx.queryBuilder.order,
p => p.age,
o => o.item_name
);
Comment on lines 77 to 81
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we decided on

.semijoin(ctx.queryBuilder.order, (p, o) => eq(p.age, o.item_name));

const filteredQuery = ctx.queryBuilder
.query('person')
.filter(row => eq(row.age, literal(20)));

// Eventually this should not type check.
const _semijoin = filteredQuery.semijoinTo(
ctx.queryBuilder.order,
p => p.age,
o => o.item_name
);
});
Loading