Skip to content

Commit 655a04c

Browse files
committed
Typescript module bindings for views
1 parent 557ba31 commit 655a04c

File tree

9 files changed

+309
-58
lines changed

9 files changed

+309
-58
lines changed

crates/bindings-typescript/src/server/indexes.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ export type Indexes<
4646
[k in keyof I]: Index<TableDef, I[k]>;
4747
};
4848

49+
export type ReadonlyIndexes<
50+
TableDef extends UntypedTableDef,
51+
I extends Record<string, UntypedIndex<keyof TableDef['columns'] & string>>,
52+
> = {
53+
[k in keyof I]: ReadonlyIndex<TableDef, I[k]>;
54+
};
55+
4956
/**
5057
* A type representing a database index, which can be either unique or ranged.
5158
*/
@@ -56,32 +63,51 @@ export type Index<
5663
? UniqueIndex<TableDef, I>
5764
: RangedIndex<TableDef, I>;
5865

66+
export type ReadonlyIndex<
67+
TableDef extends UntypedTableDef,
68+
I extends UntypedIndex<keyof TableDef['columns'] & string>,
69+
> = I['unique'] extends true
70+
? ReadonlyUniqueIndex<TableDef, I>
71+
: ReadonlyRangedIndex<TableDef, I>;
72+
73+
export interface ReadonlyUniqueIndex<
74+
TableDef extends UntypedTableDef,
75+
I extends UntypedIndex<keyof TableDef['columns'] & string>,
76+
> {
77+
find(col_val: IndexVal<TableDef, I>): RowType<TableDef> | null;
78+
}
79+
5980
/**
6081
* A type representing a unique index on a database table.
6182
* Unique indexes enforce that the indexed columns contain unique values.
6283
*/
63-
export type UniqueIndex<
84+
export interface UniqueIndex<
6485
TableDef extends UntypedTableDef,
6586
I extends UntypedIndex<keyof TableDef['columns'] & string>,
66-
> = {
67-
find(col_val: IndexVal<TableDef, I>): RowType<TableDef> | null;
87+
> extends ReadonlyUniqueIndex<TableDef, I> {
6888
delete(col_val: IndexVal<TableDef, I>): boolean;
6989
update(col_val: RowType<TableDef>): RowType<TableDef>;
70-
};
90+
}
91+
92+
export interface ReadonlyRangedIndex<
93+
TableDef extends UntypedTableDef,
94+
I extends UntypedIndex<keyof TableDef['columns'] & string>,
95+
> {
96+
filter(
97+
range: IndexScanRangeBounds<TableDef, I>
98+
): IterableIterator<RowType<TableDef>>;
99+
}
71100

72101
/**
73102
* A type representing a ranged index on a database table.
74103
* Ranged indexes allow for range queries on the indexed columns.
75104
*/
76-
export type RangedIndex<
105+
export interface RangedIndex<
77106
TableDef extends UntypedTableDef,
78107
I extends UntypedIndex<keyof TableDef['columns'] & string>,
79-
> = {
80-
filter(
81-
range: IndexScanRangeBounds<TableDef, I>
82-
): IterableIterator<RowType<TableDef>>;
108+
> extends ReadonlyRangedIndex<TableDef, I> {
83109
delete(range: IndexScanRangeBounds<TableDef, I>): number;
84-
};
110+
}
85111

86112
/**
87113
* A helper type to extract the value type of an index based on the table definition and index definition.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { register_hooks } from 'spacetime:sys@1.0';
2-
import { hooks } from './runtime';
2+
import { register_hooks as register_hooks_v1_1 } from 'spacetime:sys@1.1';
3+
import { hooks, hooks_v1_1 } from './runtime';
34

45
register_hooks(hooks);
6+
register_hooks_v1_1(hooks_v1_1);

crates/bindings-typescript/src/server/runtime.ts

Lines changed: 47 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ import { MODULE_DEF } from './schema';
2929

3030
import * as _syscalls from 'spacetime:sys@1.0';
3131
import type { u16, u32, ModuleHooks } from 'spacetime:sys@1.0';
32+
import {
33+
ANON_VIEWS,
34+
VIEWS,
35+
type AnonymousViewCtx,
36+
type ViewCtx,
37+
} from './views';
38+
import { bsatnBaseSize } from './util';
3239

3340
const { freeze } = Object;
3441

@@ -212,6 +219,46 @@ export const hooks: ModuleHooks = {
212219
},
213220
};
214221

222+
export const hooks_v1_1: import('spacetime:sys@1.1').ModuleHooks = {
223+
__call_view__(id, sender, argsBuf) {
224+
const { fn, params, returnType, returnTypeBaseSize } = VIEWS[id];
225+
const ctx: ViewCtx<any> = freeze({
226+
sender: new Identity(sender),
227+
// this is the non-readonly DbView, but the typing for the user will be
228+
// the readonly one, and if they do call mutating functions it will fail
229+
// at runtime
230+
db: getDbView(),
231+
});
232+
const args = AlgebraicType.deserializeValue(
233+
new BinaryReader(argsBuf),
234+
AlgebraicType.Product(params),
235+
MODULE_DEF.typespace
236+
);
237+
const ret = fn(ctx, args);
238+
const retBuf = new BinaryWriter(returnTypeBaseSize);
239+
AlgebraicType.serializeValue(retBuf, returnType, ret, MODULE_DEF.typespace);
240+
return retBuf.getBuffer();
241+
},
242+
__call_view_anon__(id, argsBuf) {
243+
const { fn, params, returnType, returnTypeBaseSize } = ANON_VIEWS[id];
244+
const ctx: AnonymousViewCtx<any> = freeze({
245+
// this is the non-readonly DbView, but the typing for the user will be
246+
// the readonly one, and if they do call mutating functions it will fail
247+
// at runtime
248+
db: getDbView(),
249+
});
250+
const args = AlgebraicType.deserializeValue(
251+
new BinaryReader(argsBuf),
252+
AlgebraicType.Product(params),
253+
MODULE_DEF.typespace
254+
);
255+
const ret = fn(ctx, args);
256+
const retBuf = new BinaryWriter(returnTypeBaseSize);
257+
AlgebraicType.serializeValue(retBuf, returnType, ret, MODULE_DEF.typespace);
258+
return retBuf.getBuffer();
259+
},
260+
};
261+
215262
let DB_VIEW: DbView<any> | null = null;
216263
function getDbView() {
217264
DB_VIEW ??= makeDbView(MODULE_DEF);
@@ -464,47 +511,6 @@ function makeTableView(typespace: Typespace, table: RawTableDefV9): Table<any> {
464511
return freeze(tableView);
465512
}
466513

467-
function bsatnBaseSize(typespace: Typespace, ty: AlgebraicType): number {
468-
const assumedArrayLength = 4;
469-
while (ty.tag === 'Ref') ty = typespace.types[ty.value];
470-
if (ty.tag === 'Product') {
471-
let sum = 0;
472-
for (const { algebraicType: elem } of ty.value.elements) {
473-
sum += bsatnBaseSize(typespace, elem);
474-
}
475-
return sum;
476-
} else if (ty.tag === 'Sum') {
477-
let min = Infinity;
478-
for (const { algebraicType: vari } of ty.value.variants) {
479-
const vSize = bsatnBaseSize(typespace, vari);
480-
if (vSize < min) min = vSize;
481-
}
482-
if (min === Infinity) min = 0;
483-
return 4 + min;
484-
} else if (ty.tag == 'Array') {
485-
return 4 + assumedArrayLength * bsatnBaseSize(typespace, ty.value);
486-
}
487-
return {
488-
String: 4 + assumedArrayLength,
489-
Sum: 1,
490-
Bool: 1,
491-
I8: 1,
492-
U8: 1,
493-
I16: 2,
494-
U16: 2,
495-
I32: 4,
496-
U32: 4,
497-
F32: 4,
498-
I64: 8,
499-
U64: 8,
500-
F64: 8,
501-
I128: 16,
502-
U128: 16,
503-
I256: 32,
504-
U256: 32,
505-
}[ty.tag];
506-
}
507-
508514
function hasOwn<K extends PropertyKey>(
509515
o: object,
510516
k: K

crates/bindings-typescript/src/server/schema.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ import {
2222
type AlgebraicTypeVariants,
2323
} from '../lib/algebraic_type';
2424
import type RawScopedTypeNameV9 from '../lib/autogen/raw_scoped_type_name_v_9_type';
25+
import {
26+
defineView,
27+
type AnonymousViewFn,
28+
type ViewFn,
29+
type ViewReturnTypeBuilder,
30+
} from './views';
2531

2632
/**
2733
* The global module definition that gets populated by calls to `reducer()` and lifecycle hooks.
@@ -285,6 +291,54 @@ class Schema<S extends UntypedSchemaDef> {
285291
clientDisconnected(name, {}, fn);
286292
}
287293

294+
view<Ret extends ViewReturnTypeBuilder>(
295+
name: string,
296+
ret: Ret,
297+
fn: ViewFn<S, {}, Ret>
298+
): void;
299+
view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
300+
name: string,
301+
params: Params,
302+
ret: Ret,
303+
fn: ViewFn<S, {}, Ret>
304+
): void;
305+
view<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
306+
name: string,
307+
paramsOrRet: Ret | Params,
308+
retOrFn: ViewFn<S, {}, Ret> | Ret,
309+
maybeFn?: ViewFn<S, Params, Ret>
310+
): void {
311+
if (typeof retOrFn === 'function') {
312+
defineView(name, false, {}, paramsOrRet as Ret, retOrFn);
313+
} else {
314+
defineView(name, false, paramsOrRet as Params, retOrFn, maybeFn!);
315+
}
316+
}
317+
318+
anyonymousView<Ret extends ViewReturnTypeBuilder>(
319+
name: string,
320+
ret: Ret,
321+
fn: AnonymousViewFn<S, {}, Ret>
322+
): void;
323+
anyonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
324+
name: string,
325+
params: Params,
326+
ret: Ret,
327+
fn: AnonymousViewFn<S, {}, Ret>
328+
): void;
329+
anyonymousView<Params extends ParamsObj, Ret extends ViewReturnTypeBuilder>(
330+
name: string,
331+
paramsOrRet: Ret | Params,
332+
retOrFn: AnonymousViewFn<S, {}, Ret> | Ret,
333+
maybeFn?: AnonymousViewFn<S, Params, Ret>
334+
): void {
335+
if (typeof retOrFn === 'function') {
336+
defineView(name, true, {}, paramsOrRet as Ret, retOrFn);
337+
} else {
338+
defineView(name, true, paramsOrRet as Params, retOrFn, maybeFn!);
339+
}
340+
}
341+
288342
clientVisibilityFilter = {
289343
sql(filter: string): void {
290344
MODULE_DEF.rowLevelSecurity.push({ sql: filter });

crates/bindings-typescript/src/server/sys.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,12 @@ declare module 'spacetime:sys@1.0' {
6666
export function identity(): { __identity__: u256 };
6767
export function get_jwt_payload(connection_id: u128): Uint8Array;
6868
}
69+
70+
declare module 'spacetime:sys@1.1' {
71+
export type ModuleHooks = {
72+
__call_view__(id: u32, sender: u256, args: Uint8Array): Uint8Array;
73+
__call_view_anon__(id: u32, args: Uint8Array): Uint8Array;
74+
};
75+
76+
export function register_hooks(hooks: ModuleHooks);
77+
}

crates/bindings-typescript/src/server/table.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import type RawIndexDefV9 from '../lib/autogen/raw_index_def_v_9_type';
55
import type RawSequenceDefV9 from '../lib/autogen/raw_sequence_def_v_9_type';
66
import type RawTableDefV9 from '../lib/autogen/raw_table_def_v_9_type';
77
import type { AllUnique } from './constraints';
8-
import type { ColumnIndex, IndexColumns, Indexes, IndexOpts } from './indexes';
8+
import type {
9+
ColumnIndex,
10+
IndexColumns,
11+
Indexes,
12+
IndexOpts,
13+
ReadonlyIndexes,
14+
} from './indexes';
915
import { MODULE_DEF, splitName } from './schema';
1016
import {
1117
RowBuilder,
@@ -108,17 +114,25 @@ export type Table<TableDef extends UntypedTableDef> = Prettify<
108114
TableMethods<TableDef> & Indexes<TableDef, TableIndexes<TableDef>>
109115
>;
110116

111-
/**
112-
* A type representing the methods available on a table.
113-
*/
114-
export type TableMethods<TableDef extends UntypedTableDef> = {
117+
export type ReadonlyTable<TableDef extends UntypedTableDef> = Prettify<
118+
ReadonlyTableMethods<TableDef> &
119+
ReadonlyIndexes<TableDef, TableIndexes<TableDef>>
120+
>;
121+
122+
export interface ReadonlyTableMethods<TableDef extends UntypedTableDef> {
115123
/** Returns the number of rows in the TX state. */
116124
count(): bigint;
117125

118126
/** Iterate over all rows in the TX state. Rust Iterator<Item=Row> → TS IterableIterator<Row>. */
119127
iter(): IterableIterator<RowType<TableDef>>;
120128
[Symbol.iterator](): IterableIterator<RowType<TableDef>>;
129+
}
121130

131+
/**
132+
* A type representing the methods available on a table.
133+
*/
134+
export interface TableMethods<TableDef extends UntypedTableDef>
135+
extends ReadonlyTableMethods<TableDef> {
122136
/**
123137
* Insert and return the inserted row (auto-increment fields filled).
124138
*
@@ -130,7 +144,7 @@ export type TableMethods<TableDef extends UntypedTableDef> = {
130144

131145
/** Delete a row equal to `row`. Returns true if something was deleted. */
132146
delete(row: RowType<TableDef>): boolean;
133-
};
147+
}
134148

135149
/**
136150
* Represents a handle to a database table, including its name, row type, and row spacetime type.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { AlgebraicType } from '../lib/algebraic_type';
2+
import type Typespace from '../lib/autogen/typespace_type';
3+
4+
export function bsatnBaseSize(typespace: Typespace, ty: AlgebraicType): number {
5+
const assumedArrayLength = 4;
6+
while (ty.tag === 'Ref') ty = typespace.types[ty.value];
7+
if (ty.tag === 'Product') {
8+
let sum = 0;
9+
for (const { algebraicType: elem } of ty.value.elements) {
10+
sum += bsatnBaseSize(typespace, elem);
11+
}
12+
return sum;
13+
} else if (ty.tag === 'Sum') {
14+
let min = Infinity;
15+
for (const { algebraicType: vari } of ty.value.variants) {
16+
const vSize = bsatnBaseSize(typespace, vari);
17+
if (vSize < min) min = vSize;
18+
}
19+
if (min === Infinity) min = 0;
20+
return 4 + min;
21+
} else if (ty.tag == 'Array') {
22+
return 4 + assumedArrayLength * bsatnBaseSize(typespace, ty.value);
23+
}
24+
return {
25+
String: 4 + assumedArrayLength,
26+
Sum: 1,
27+
Bool: 1,
28+
I8: 1,
29+
U8: 1,
30+
I16: 2,
31+
U16: 2,
32+
I32: 4,
33+
U32: 4,
34+
F32: 4,
35+
I64: 8,
36+
U64: 8,
37+
F64: 8,
38+
I128: 16,
39+
U128: 16,
40+
I256: 32,
41+
U256: 32,
42+
}[ty.tag];
43+
}

0 commit comments

Comments
 (0)