From 40eeab574e7f27a4b30689c7977b9a71a8b99c5f Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 26 Nov 2025 12:57:48 -0600 Subject: [PATCH 1/2] Refactor typescript table iterators --- crates/bindings-typescript/src/lib/indexes.ts | 2 +- crates/bindings-typescript/src/lib/table.ts | 4 +- .../src/sdk/table_cache.ts | 17 +-- .../bindings-typescript/src/server/runtime.ts | 108 ++++++++++-------- 4 files changed, 72 insertions(+), 59 deletions(-) diff --git a/crates/bindings-typescript/src/lib/indexes.ts b/crates/bindings-typescript/src/lib/indexes.ts index ba9ad6a10dd..a7a00475006 100644 --- a/crates/bindings-typescript/src/lib/indexes.ts +++ b/crates/bindings-typescript/src/lib/indexes.ts @@ -108,7 +108,7 @@ export interface ReadonlyRangedIndex< > { filter( range: IndexScanRangeBounds - ): IterableIterator>>; + ): IteratorObject>>; } /** diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index 483239b46a1..11a34930e1a 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -166,8 +166,8 @@ export interface ReadonlyTableMethods { count(): bigint; /** Iterate over all rows in the TX state. Rust Iterator → TS IterableIterator. */ - iter(): IterableIterator>>; - [Symbol.iterator](): IterableIterator>>; + iter(): IteratorObject>>; + [Symbol.iterator](): IteratorObject>>; } /** diff --git a/crates/bindings-typescript/src/sdk/table_cache.ts b/crates/bindings-typescript/src/sdk/table_cache.ts index 2277f22b8b0..57b9882ea26 100644 --- a/crates/bindings-typescript/src/sdk/table_cache.ts +++ b/crates/bindings-typescript/src/sdk/table_cache.ts @@ -201,7 +201,7 @@ export class TableCacheImpl< return impl as ReadonlyIndex; } else { const impl: ReadonlyRangedIndex = { - *filter(range: any): IterableIterator { + *filter(range: any): IteratorObject { for (const row of self.iter()) { if (matchRange(row, range)) yield row; } @@ -221,16 +221,18 @@ export class TableCacheImpl< /** * @returns The values of the rows in the table */ - iter(): IterableIterator< - Prettify>> + iter(): IteratorObject< + Prettify>>, + void > { function* generator( rows: Map< ComparablePrimitive, [RowType>, number] > - ): IterableIterator< - Prettify>> + ): IteratorObject< + Prettify>>, + void > { for (const [row] of rows.values()) { yield row as Prettify< @@ -245,8 +247,9 @@ export class TableCacheImpl< * Allows iteration over the rows in the table * @returns An iterator over the rows in the table */ - [Symbol.iterator](): IterableIterator< - Prettify>> + [Symbol.iterator](): IteratorObject< + Prettify>>, + void > { return this.iter(); } diff --git a/crates/bindings-typescript/src/server/runtime.ts b/crates/bindings-typescript/src/server/runtime.ts index 2e91bcb1c6c..4bfdc4438e2 100644 --- a/crates/bindings-typescript/src/server/runtime.ts +++ b/crates/bindings-typescript/src/server/runtime.ts @@ -424,7 +424,7 @@ function makeTableView( const hasAutoIncrement = sequences.length > 0; const iter = () => - new TableIterator(sys.datastore_table_scan_bsatn(table_id), rowType); + tableIterator(sys.datastore_table_scan_bsatn(table_id), rowType); const integrateGeneratedColumns = hasAutoIncrement ? (row: RowType, ret_buf: Uint8Array) => { @@ -541,7 +541,7 @@ function makeTableView( find: (colVal: IndexVal): RowType | null => { if (numColumns === 1) colVal = [colVal]; const args = serializeBound(colVal); - const iter = new TableIterator( + const iter = tableIterator( sys.datastore_index_scan_range_bsatn(index_id, ...args), rowType ); @@ -613,10 +613,10 @@ function makeTableView( return [prefix, prefix_elems, rstart, rend]; }; index = { - filter: (range: any): IterableIterator> => { + filter: (range: any): IteratorObject> => { if (numColumns === 1) range = [range]; const args = serializeRange(range); - return new TableIterator( + return tableIterator( sys.datastore_index_scan_range_bsatn(index_id, ...args), rowType ); @@ -649,60 +649,70 @@ function hasOwn( return Object.hasOwn(o, k); } -class TableIterator implements IterableIterator { - #id: u32 | -1; - #reader: BinaryReader; - #ty: AlgebraicType; - constructor(id: u32, ty: AlgebraicType) { - this.#id = id; - this.#reader = new BinaryReader(new Uint8Array()); - this.#ty = ty; - } - [Symbol.iterator](): typeof this { - return this; - } - next(): IteratorResult { - while (true) { - if (this.#reader.remaining > 0) { - const value = AlgebraicType.deserializeValue( - this.#reader, - this.#ty, - MODULE_DEF.typespace - ); - return { value }; - } - if (this.#id === -1) { - return { value: undefined, done: true }; - } - this.#advance_iter(); +function* tableIterator(id: u32, ty: AlgebraicType): Generator { + using iter = new IteratorHandle(id); + const { typespace } = MODULE_DEF; + + let buf; + while ((buf = advanceIter(iter)) != null) { + const reader = new BinaryReader(buf); + while (reader.remaining > 0) { + yield AlgebraicType.deserializeValue(reader, ty, typespace); } } +} - #advance_iter() { - let buf_max_len = 0x10000; - while (true) { - try { - const { 0: done, 1: buf } = sys.row_iter_bsatn_advance( - this.#id, - buf_max_len - ); - if (done) this.#id = -1; - this.#reader = new BinaryReader(buf); - return; - } catch (e) { - if (e && typeof e === 'object' && hasOwn(e, '__buffer_too_small__')) { - buf_max_len = e.__buffer_too_small__ as number; - continue; - } - throw e; +function advanceIter(iter: IteratorHandle): Uint8Array | null { + let buf_max_len = 0x10000; + while (true) { + try { + return iter.advance(buf_max_len); + } catch (e) { + if (e && typeof e === 'object' && hasOwn(e, '__buffer_too_small__')) { + buf_max_len = e.__buffer_too_small__ as number; + continue; } + throw e; } } +} + +/** A class to manage the lifecycle of an iterator handle. */ +class IteratorHandle implements Disposable { + #id: u32 | -1; + + static #finalizationRegistry = new FinalizationRegistry( + sys.row_iter_bsatn_close + ); + + constructor(id: u32) { + this.#id = id; + IteratorHandle.#finalizationRegistry.register(this, id, this); + } + + /** Unregister this object with the finalization registry and return the id */ + #detach() { + const id = this.#id; + this.#id = -1; + IteratorHandle.#finalizationRegistry.unregister(this); + return id; + } + + /** Call `row_iter_bsatn_advance`, returning null if this iterator was already exhausted. */ + advance(buf_max_len: u32): Uint8Array | null { + if (this.#id === -1) return null; + const { 0: done, 1: buf } = sys.row_iter_bsatn_advance( + this.#id, + buf_max_len + ); + if (done) this.#detach(); + return buf; + } [Symbol.dispose]() { if (this.#id >= 0) { - this.#id = -1; - sys.row_iter_bsatn_close(this.#id); + const id = this.#detach(); + sys.row_iter_bsatn_close(id); } } } From 04790b2840074935045249ba469fd9a8c7b7c8e9 Mon Sep 17 00:00:00 2001 From: Noa Date: Wed, 17 Dec 2025 16:53:43 -0600 Subject: [PATCH 2/2] Refine return types --- crates/bindings-typescript/src/lib/indexes.ts | 2 +- crates/bindings-typescript/src/lib/table.ts | 4 ++-- crates/bindings-typescript/src/sdk/table_cache.ts | 8 ++++---- crates/bindings-typescript/src/server/runtime.ts | 2 +- crates/bindings-typescript/src/server/view.test-d.ts | 1 + 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/bindings-typescript/src/lib/indexes.ts b/crates/bindings-typescript/src/lib/indexes.ts index a7a00475006..392631875d6 100644 --- a/crates/bindings-typescript/src/lib/indexes.ts +++ b/crates/bindings-typescript/src/lib/indexes.ts @@ -108,7 +108,7 @@ export interface ReadonlyRangedIndex< > { filter( range: IndexScanRangeBounds - ): IteratorObject>>; + ): IteratorObject>, undefined>; } /** diff --git a/crates/bindings-typescript/src/lib/table.ts b/crates/bindings-typescript/src/lib/table.ts index 11a34930e1a..167c8fedd75 100644 --- a/crates/bindings-typescript/src/lib/table.ts +++ b/crates/bindings-typescript/src/lib/table.ts @@ -166,8 +166,8 @@ export interface ReadonlyTableMethods { count(): bigint; /** Iterate over all rows in the TX state. Rust Iterator → TS IterableIterator. */ - iter(): IteratorObject>>; - [Symbol.iterator](): IteratorObject>>; + iter(): IteratorObject>, undefined>; + [Symbol.iterator](): IteratorObject>, undefined>; } /** diff --git a/crates/bindings-typescript/src/sdk/table_cache.ts b/crates/bindings-typescript/src/sdk/table_cache.ts index 57b9882ea26..9021daa3c6f 100644 --- a/crates/bindings-typescript/src/sdk/table_cache.ts +++ b/crates/bindings-typescript/src/sdk/table_cache.ts @@ -201,7 +201,7 @@ export class TableCacheImpl< return impl as ReadonlyIndex; } else { const impl: ReadonlyRangedIndex = { - *filter(range: any): IteratorObject { + *filter(range: any): IteratorObject { for (const row of self.iter()) { if (matchRange(row, range)) yield row; } @@ -223,7 +223,7 @@ export class TableCacheImpl< */ iter(): IteratorObject< Prettify>>, - void + undefined > { function* generator( rows: Map< @@ -232,7 +232,7 @@ export class TableCacheImpl< > ): IteratorObject< Prettify>>, - void + undefined > { for (const [row] of rows.values()) { yield row as Prettify< @@ -249,7 +249,7 @@ export class TableCacheImpl< */ [Symbol.iterator](): IteratorObject< Prettify>>, - void + undefined > { return this.iter(); } diff --git a/crates/bindings-typescript/src/server/runtime.ts b/crates/bindings-typescript/src/server/runtime.ts index 4bfdc4438e2..70e501b5e8e 100644 --- a/crates/bindings-typescript/src/server/runtime.ts +++ b/crates/bindings-typescript/src/server/runtime.ts @@ -649,7 +649,7 @@ function hasOwn( return Object.hasOwn(o, k); } -function* tableIterator(id: u32, ty: AlgebraicType): Generator { +function* tableIterator(id: u32, ty: AlgebraicType): Generator { using iter = new IteratorHandle(id); const { typespace } = MODULE_DEF; diff --git a/crates/bindings-typescript/src/server/view.test-d.ts b/crates/bindings-typescript/src/server/view.test-d.ts index 4a10e14e450..a12c71a2528 100644 --- a/crates/bindings-typescript/src/server/view.test-d.ts +++ b/crates/bindings-typescript/src/server/view.test-d.ts @@ -87,6 +87,7 @@ spacetime.anonymousView( spacetime.anonymousView( { name: 'optionalPersonWrong', public: true }, optionalPerson, + // @ts-expect-error returns a value of the wrong type. ctx => { return ctx.db.order.iter().next().value; }