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
12 changes: 5 additions & 7 deletions crates/bindings-typescript/src/lib/table.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ProductType } from './algebraic_type';
import type RawConstraintDefV9 from './autogen/raw_constraint_def_v_9_type';
import RawIndexAlgorithm from './autogen/raw_index_algorithm_type';
import type RawIndexDefV9 from './autogen/raw_index_def_v_9_type';
Expand Down Expand Up @@ -66,7 +65,8 @@ export type UntypedTableDef = {
name: string;
accessorName: string;
columns: Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>;
rowType: ProductType;
// This is really just a ProductType where all the elements have names.
rowType: RowBuilder<RowObj>['algebraicType']['value'];
indexes: readonly IndexOpts<any>[];
constraints: readonly ConstraintOpts<any>[];
};
Expand Down Expand Up @@ -365,11 +365,9 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
tableAccess: { tag: isPublic ? 'Public' : 'Private' },
};

const productType = {
elements: row.algebraicType.value.elements.map(elem => {
return { name: elem.name, algebraicType: elem.algebraicType };
}),
};
const productType = row.algebraicType.value as RowBuilder<
CoerceRow<Row>
>['algebraicType']['value'];

return {
rowType: row as RowBuilder<CoerceRow<Row>>,
Expand Down
3 changes: 1 addition & 2 deletions crates/bindings-typescript/src/lib/table_schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { ProductType } from './algebraic_type';
import type RawTableDefV9 from './autogen/raw_table_def_v_9_type';
import type { IndexOpts } from './indexes';
import type { ColumnBuilder, Infer, RowBuilder } from './type_builders';
Expand All @@ -24,7 +23,7 @@ export type TableSchema<
/**
* The {@link ProductType} representing the structure of a row in the table.
*/
readonly rowSpacetimeType: ProductType;
readonly rowSpacetimeType: RowBuilder<Row>['algebraicType']['value'];

/**
* The {@link RawTableDefV9} of the configured table
Expand Down
4 changes: 2 additions & 2 deletions crates/bindings-typescript/src/lib/type_builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1275,10 +1275,10 @@ class UnitBuilder extends TypeBuilder<
}

export class RowBuilder<Row extends RowObj> extends TypeBuilder<
RowType<Row>,
RowType<CoerceRow<Row>>,
{
tag: 'Product';
value: { elements: ElementsArrayFromRowObj<Row> };
value: { elements: ElementsArrayFromRowObj<CoerceRow<Row>> };
}
> {
readonly row: CoerceRow<Row>;
Expand Down
14 changes: 12 additions & 2 deletions crates/bindings-typescript/src/lib/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { ReadonlyTable } from './table';
import {
RowBuilder,
type Infer,
type InferSpacetimeTypeOfTypeBuilder,
type InferTypeOfRow,
type TypeBuilder,
} from './type_builders';
Expand Down Expand Up @@ -58,7 +59,7 @@ export type ViewFn<
| ((
ctx: ViewCtx<S>,
params: InferTypeOfRow<Params>
) => RowTypedQuery<FlattenedArray<Infer<Ret>>>);
) => RowTypedQuery<FlattenedArray<Infer<Ret>>, ExtractArrayProduct<Ret>>);

export type AnonymousViewFn<
S extends UntypedSchemaDef,
Expand All @@ -69,7 +70,7 @@ export type AnonymousViewFn<
| ((
ctx: AnonymousViewCtx<S>,
params: InferTypeOfRow<Params>
) => RowTypedQuery<FlattenedArray<Infer<Ret>>>);
) => RowTypedQuery<FlattenedArray<Infer<Ret>>, ExtractArrayProduct<Ret>>);

export type ViewReturnTypeBuilder =
| TypeBuilder<
Expand Down Expand Up @@ -146,3 +147,12 @@ type ViewInfo<F> = {

export const VIEWS: ViewInfo<ViewFn<any, any, any>>[] = [];
export const ANON_VIEWS: ViewInfo<AnonymousViewFn<any, any, any>>[] = [];

// A helper to get the product type out of a type builder.
// This is only non-never if the type builder is an array.
type ExtractArrayProduct<T extends TypeBuilder<any, any>> =
InferSpacetimeTypeOfTypeBuilder<T> extends { tag: 'Array'; value: infer V }
? V extends { tag: 'Product'; value: infer P }
? P
: never
: never;
26 changes: 14 additions & 12 deletions crates/bindings-typescript/src/server/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { TableSchema } from '../lib/table_schema';
import type {
ColumnBuilder,
ColumnMetadata,
RowBuilder,
TypeBuilder,
} from '../lib/type_builders';

Expand All @@ -30,24 +31,19 @@ export interface TableTypedQuery<TableDef extends TypedTableDef> {
readonly __table?: TableDef;
}

export interface RowTypedQuery<Row> {
export interface RowTypedQuery<Row, ST> {
readonly [QueryBrand]: true;
// Phantom type to track the row type.
readonly __row?: Row;
readonly __algebraicType?: ST;
}

type RowFromTableQuery<Q> =
Q extends TableTypedQuery<infer TD> ? RowType<TD> : never;

export type ToRowQuery<Q> = RowTypedQuery<RowFromTableQuery<Q>>;

// export type Query<TableDef extends TypedTableDef> = TableTypedQuery<TableDef>;
// export type Query<TableDef extends TypedTableDef> = TableTypedQuery<TableDef>;
export type Query<TableDef extends TypedTableDef> = RowTypedQuery<
RowType<TableDef>
RowType<TableDef>,
TableDef['rowType']
>;

export const isRowTypedQuery = (val: unknown): val is RowTypedQuery<any> =>
export const isRowTypedQuery = (val: unknown): val is RowTypedQuery<any, any> =>
!!val && typeof val === 'object' && QueryBrand in (val as object);

export const isTypedQuery = (val: unknown): val is TableTypedQuery<any> =>
Expand Down Expand Up @@ -356,10 +352,16 @@ function renderSelectSqlWithJoins<Table extends TypedTableDef>(
}

// TODO: Just use UntypedTableDef if they end up being the same.
export type TypedTableDef = {
export type TypedTableDef<
Columns extends Record<
string,
ColumnBuilder<any, any, ColumnMetadata<any>>
> = Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>,
> = {
name: string;
columns: Record<string, ColumnBuilder<any, any, ColumnMetadata<any>>>;
columns: Columns;
indexes: readonly IndexOpts<any>[];
rowType: RowBuilder<Columns>['algebraicType']['value'];
};

export type TableSchemaAsTableDef<
Expand Down
83 changes: 78 additions & 5 deletions crates/bindings-typescript/src/server/view.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ const personWithExtra = table(
}
);

const personWithMissing = table(
{ name: 'personWithMissing' },
{
id: t.u32(),
}
);

const personReordered = table(
{ name: 'personReordered' },
{
name: t.string(),
id: t.u32(),
}
);

const order = table(
{
name: 'order',
Expand All @@ -46,14 +61,46 @@ const order = table(
}
);

const spacetime = schema(person, order, personWithExtra);
const spacetime = schema(
person,
order,
personWithExtra,
personReordered,
personWithMissing
);

const arrayRetValue = t.array(person.rowType);
const optionalPerson = t.option(person.rowType);

spacetime.anonymousView({ name: 'v1', public: true }, arrayRetValue, ctx => {
return ctx.from.person.build();
});

spacetime.anonymousView(
{ name: 'optionalPerson', public: true },
optionalPerson,
ctx => {
return ctx.db.person.iter().next().value;
}
);

spacetime.anonymousView(
{ name: 'optionalPersonWrong', public: true },
optionalPerson,
ctx => {
return ctx.db.order.iter().next().value;
}
);

// Extra fields are only an issue for queries.
spacetime.anonymousView(
{ name: 'optionalPersonWithExtra', public: true },
optionalPerson,
ctx => {
return ctx.db.personWithExtra.iter().next().value;
}
);

spacetime.anonymousView(
{ name: 'v2', public: true },
arrayRetValue,
Expand All @@ -63,10 +110,36 @@ spacetime.anonymousView(
}
);

// We should eventually make this fail.
spacetime.anonymousView({ name: 'v3', public: true }, arrayRetValue, ctx => {
return ctx.from.personWithExtra.build();
});
// For queries, we can't return rows with extra fields.
spacetime.anonymousView(
{ name: 'v3', public: true },
arrayRetValue,
// @ts-expect-error returns a query of the wrong type.
ctx => {
return ctx.from.personWithExtra.build();
}
);

// Ideally this would fail, since we depend on the field ordering for serialization.
spacetime.anonymousView(
{ name: 'reorderedPerson', public: true },
arrayRetValue,
// Comment this out if we can fix the types.
// // @ts-expect-error returns a query of the wrong type.
ctx => {
return ctx.from.personReordered.build();
}
);

// Fails because it is missing a field.
spacetime.anonymousView(
{ name: 'missingField', public: true },
arrayRetValue,
// @ts-expect-error returns a query of the wrong type.
ctx => {
return ctx.from.personWithMissing.build();
}
);

spacetime.anonymousView({ name: 'v4', public: true }, arrayRetValue, ctx => {
// @ts-expect-error returns a query of the wrong type.
Expand Down