spacetimedb 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/browser/react/index.mjs +129 -57
  2. package/dist/browser/react/index.mjs.map +1 -1
  3. package/dist/browser/solid/index.mjs +120 -50
  4. package/dist/browser/solid/index.mjs.map +1 -1
  5. package/dist/browser/svelte/index.mjs.map +1 -1
  6. package/dist/index.browser.mjs +10 -2
  7. package/dist/index.browser.mjs.map +1 -1
  8. package/dist/index.cjs +10 -2
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.mjs +10 -2
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/lib/type_builders.d.ts +18 -3
  13. package/dist/lib/type_builders.d.ts.map +1 -1
  14. package/dist/min/index.browser.mjs +1 -1
  15. package/dist/min/index.browser.mjs.map +1 -1
  16. package/dist/min/react/index.mjs +1 -1
  17. package/dist/min/react/index.mjs.map +1 -1
  18. package/dist/min/sdk/index.browser.mjs +1 -1
  19. package/dist/min/sdk/index.browser.mjs.map +1 -1
  20. package/dist/react/index.cjs +129 -57
  21. package/dist/react/index.cjs.map +1 -1
  22. package/dist/react/index.mjs +129 -57
  23. package/dist/react/index.mjs.map +1 -1
  24. package/dist/react/useTable.d.ts.map +1 -1
  25. package/dist/sdk/connection_manager.d.ts +8 -0
  26. package/dist/sdk/connection_manager.d.ts.map +1 -1
  27. package/dist/sdk/db_connection_impl.d.ts +7 -0
  28. package/dist/sdk/db_connection_impl.d.ts.map +1 -1
  29. package/dist/sdk/event_context.d.ts +3 -3
  30. package/dist/sdk/event_context.d.ts.map +1 -1
  31. package/dist/sdk/index.browser.mjs +10 -2
  32. package/dist/sdk/index.browser.mjs.map +1 -1
  33. package/dist/sdk/index.cjs +10 -2
  34. package/dist/sdk/index.cjs.map +1 -1
  35. package/dist/sdk/index.mjs +10 -2
  36. package/dist/sdk/index.mjs.map +1 -1
  37. package/dist/sdk/procedures.d.ts +2 -2
  38. package/dist/sdk/procedures.d.ts.map +1 -1
  39. package/dist/sdk/reducers.d.ts +3 -4
  40. package/dist/sdk/reducers.d.ts.map +1 -1
  41. package/dist/sdk/table_cache.d.ts.map +1 -1
  42. package/dist/sdk/type_utils.d.ts +3 -3
  43. package/dist/sdk/type_utils.d.ts.map +1 -1
  44. package/dist/sdk/websocket_test_adapter.d.ts +2 -1
  45. package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
  46. package/dist/server/index.mjs.map +1 -1
  47. package/dist/server/views.d.ts +4 -2
  48. package/dist/server/views.d.ts.map +1 -1
  49. package/dist/solid/index.cjs +120 -50
  50. package/dist/solid/index.cjs.map +1 -1
  51. package/dist/solid/index.mjs +120 -50
  52. package/dist/solid/index.mjs.map +1 -1
  53. package/dist/svelte/index.cjs.map +1 -1
  54. package/dist/svelte/index.mjs.map +1 -1
  55. package/dist/svelte/useReducer.d.ts +2 -2
  56. package/dist/svelte/useReducer.d.ts.map +1 -1
  57. package/dist/tanstack/index.cjs +120 -50
  58. package/dist/tanstack/index.cjs.map +1 -1
  59. package/dist/tanstack/index.mjs +120 -50
  60. package/dist/tanstack/index.mjs.map +1 -1
  61. package/package.json +22 -21
  62. package/src/lib/type_builders.test-d.ts +2 -0
  63. package/src/lib/type_builders.ts +39 -3
  64. package/src/react/useTable.ts +16 -11
  65. package/src/sdk/connection_manager.ts +173 -58
  66. package/src/sdk/db_connection_impl.ts +22 -6
  67. package/src/sdk/event_context.ts +3 -3
  68. package/src/sdk/procedures.ts +6 -2
  69. package/src/sdk/reducers.ts +3 -4
  70. package/src/sdk/table_cache.ts +2 -1
  71. package/src/sdk/type_utils.ts +3 -3
  72. package/src/sdk/websocket_test_adapter.ts +8 -1
  73. package/src/server/views.ts +5 -2
  74. package/src/svelte/useReducer.ts +2 -2
  75. package/LICENSE.txt +0 -759
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacetimedb",
3
- "version": "2.5.0",
3
+ "version": "2.6.1",
4
4
  "description": "API and ABI bindings for the SpacetimeDB TypeScript module library",
5
5
  "homepage": "https://github.com/clockworklabs/SpacetimeDB#readme",
6
6
  "bugs": {
@@ -22,6 +22,26 @@
22
22
  "sideEffects": [
23
23
  "./src/server/polyfills.ts"
24
24
  ],
25
+ "scripts": {
26
+ "build:js": "tsup",
27
+ "build:types": "tsc -p tsconfig.build.json",
28
+ "build": "pnpm -s build:js && pnpm -s build:types",
29
+ "format": "prettier . --write --ignore-path ../../.prettierignore",
30
+ "lint": "eslint . && prettier . --check --ignore-path ../../.prettierignore",
31
+ "test": "vitest run",
32
+ "test:typecheck": "vitest typecheck --run",
33
+ "coverage": "vitest run --coverage",
34
+ "brotli-size": "brotli-size dist/index.js",
35
+ "size": "pnpm -s build && size-limit",
36
+ "generate:moduledef": "cargo run -p spacetimedb-codegen --example regen-typescript-moduledef && prettier --write src/lib/autogen",
37
+ "generate:client-api": "cargo run -p generate-client-api && prettier --write src/sdk/client_api",
38
+ "generate:test-app": "pnpm --filter @clockworklabs/test-app generate",
39
+ "generate:examples:chat-react-ts": "pnpm --filter @clockworklabs/chat-react-ts generate",
40
+ "generate:examples:react-ts": "pnpm --filter @clockworklabs/react-ts generate",
41
+ "generate:examples:empty": "pnpm --filter @clockworklabs/empty-client generate",
42
+ "generate": "pnpm generate:moduledef && pnpm generate:client-api && pnpm generate:test-app && pnpm generate:examples:chat-react-ts && pnpm generate:examples:react-ts && pnpm generate:examples:empty",
43
+ "prepublishOnly": "pnpm run build && pnpm run test && pnpm run size"
44
+ },
25
45
  "main": "dist/index.cjs",
26
46
  "module": "dist/index.mjs",
27
47
  "types": "dist/index.d.ts",
@@ -228,24 +248,5 @@
228
248
  "typescript-eslint": "^8.18.2",
229
249
  "vite": "^7.1.5",
230
250
  "vitest": "^3.2.4"
231
- },
232
- "scripts": {
233
- "build:js": "tsup",
234
- "build:types": "tsc -p tsconfig.build.json",
235
- "build": "pnpm -s build:js && pnpm -s build:types",
236
- "format": "prettier . --write --ignore-path ../../.prettierignore",
237
- "lint": "eslint . && prettier . --check --ignore-path ../../.prettierignore",
238
- "test": "vitest run",
239
- "test:typecheck": "vitest typecheck --run",
240
- "coverage": "vitest run --coverage",
241
- "brotli-size": "brotli-size dist/index.js",
242
- "size": "pnpm -s build && size-limit",
243
- "generate:moduledef": "cargo run -p spacetimedb-codegen --example regen-typescript-moduledef && prettier --write src/lib/autogen",
244
- "generate:client-api": "cargo run -p generate-client-api && prettier --write src/sdk/client_api",
245
- "generate:test-app": "pnpm --filter @clockworklabs/test-app generate",
246
- "generate:examples:chat-react-ts": "pnpm --filter @clockworklabs/chat-react-ts generate",
247
- "generate:examples:react-ts": "pnpm --filter @clockworklabs/react-ts generate",
248
- "generate:examples:empty": "pnpm --filter @clockworklabs/empty-client generate",
249
- "generate": "pnpm generate:moduledef && pnpm generate:client-api && pnpm generate:test-app && pnpm generate:examples:chat-react-ts && pnpm generate:examples:react-ts && pnpm generate:examples:empty"
250
251
  }
251
- }
252
+ }
@@ -35,6 +35,8 @@ const rowOptionOptional = {
35
35
  };
36
36
  type RowOptionOptional = InferTypeOfRow<typeof rowOptionOptional>;
37
37
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
+ const _rowOptionOptionalOmitted: RowOptionOptional = {};
39
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
40
  const _rowOptionOptionalNone: RowOptionOptional = {
39
41
  foo: undefined,
40
42
  };
@@ -39,9 +39,45 @@ export type Infer<T> = T extends RowObj
39
39
  /**
40
40
  * Helper type to extract the type of a row from an object.
41
41
  */
42
- export type InferTypeOfRow<T extends RowObj> = {
43
- [K in keyof T & string]: InferTypeOfTypeBuilder<CollapseColumn<T[K]>>;
44
- };
42
+ type OptionalRowKeys<T extends RowObj> = {
43
+ [K in keyof T & string]-?: CollapseColumn<T[K]> extends OptionBuilder<any>
44
+ ? K
45
+ : never;
46
+ }[keyof T & string];
47
+
48
+ type RequiredRowKeys<T extends RowObj> = Exclude<
49
+ keyof T & string,
50
+ OptionalRowKeys<T>
51
+ >;
52
+
53
+ export type InferTypeOfRow<T extends RowObj> = Prettify<
54
+ {
55
+ [K in RequiredRowKeys<T>]: InferTypeOfTypeBuilder<CollapseColumn<T[K]>>;
56
+ } & {
57
+ [K in OptionalRowKeys<T>]?: InferTypeOfTypeBuilder<CollapseColumn<T[K]>>;
58
+ }
59
+ >;
60
+
61
+ type OptionalParamKeys<T extends RowObj> = {
62
+ [K in keyof T & string]-?: undefined extends InferTypeOfTypeBuilder<
63
+ CollapseColumn<T[K]>
64
+ >
65
+ ? K
66
+ : never;
67
+ }[keyof T & string];
68
+
69
+ type RequiredParamKeys<T extends RowObj> = Exclude<
70
+ keyof T & string,
71
+ OptionalParamKeys<T>
72
+ >;
73
+
74
+ export type InferTypeOfParams<T extends RowObj> = Prettify<
75
+ {
76
+ [K in RequiredParamKeys<T>]: InferTypeOfTypeBuilder<CollapseColumn<T[K]>>;
77
+ } & {
78
+ [K in OptionalParamKeys<T>]?: InferTypeOfTypeBuilder<CollapseColumn<T[K]>>;
79
+ }
80
+ >;
45
81
 
46
82
  /**
47
83
  * Helper type to extract the type of a row from an object.
@@ -127,18 +127,23 @@ export function useTable<TableDef extends UntypedTableDef>(
127
127
  setSubscribeApplied(false);
128
128
  return;
129
129
  }
130
- const connection = connectionState.getConnection()!;
131
- if (connectionState.isActive && connection) {
132
- const cancel = connection
133
- .subscriptionBuilder()
134
- .onApplied(() => {
135
- setSubscribeApplied(true);
136
- })
137
- .subscribe(querySql);
138
- return () => {
139
- cancel.unsubscribe();
140
- };
130
+ const connection = connectionState.getConnection();
131
+ if (!connectionState.isActive || !connection) {
132
+ // The connection dropped (or was replaced and has not reconnected
133
+ // yet), so any previously applied subscription no longer reflects the
134
+ // current cache. Report not-ready until the new subscription applies.
135
+ setSubscribeApplied(false);
136
+ return;
141
137
  }
138
+ const cancel = connection
139
+ .subscriptionBuilder()
140
+ .onApplied(() => {
141
+ setSubscribeApplied(true);
142
+ })
143
+ .subscribe(querySql);
144
+ return () => {
145
+ cancel.unsubscribe();
146
+ };
142
147
  }, [querySql, connectionState.isActive, connectionState, enabled]);
143
148
 
144
149
  const subscribe = useCallback(
@@ -46,12 +46,30 @@ export type ConnectionState = {
46
46
 
47
47
  type Listener = () => void;
48
48
 
49
+ export const CONNECTION_MANAGER_RECONNECT_BASE_DELAY_MS = 1000;
50
+ export const CONNECTION_MANAGER_RECONNECT_MAX_DELAY_MS = 30_000;
51
+
52
+ /**
53
+ * Computes the reconnect delay for the given attempt (0-based) using
54
+ * exponential backoff: the base delay doubles with each consecutive failed
55
+ * attempt, capped at the maximum delay.
56
+ */
57
+ export function connectionManagerReconnectDelayMs(attempt: number): number {
58
+ return Math.min(
59
+ CONNECTION_MANAGER_RECONNECT_BASE_DELAY_MS * 2 ** attempt,
60
+ CONNECTION_MANAGER_RECONNECT_MAX_DELAY_MS
61
+ );
62
+ }
63
+
49
64
  type ManagedConnection = {
50
65
  connection?: DbConnectionImpl<any>;
66
+ builder?: DbConnectionBuilder<any>;
51
67
  refCount: number;
52
68
  state: ConnectionState;
53
69
  listeners: Set<Listener>;
54
70
  pendingRelease: ReturnType<typeof setTimeout> | null;
71
+ reconnectTimer: ReturnType<typeof setTimeout> | null;
72
+ reconnectAttempt: number;
55
73
  onConnect?: (conn: DbConnectionImpl<any>) => void;
56
74
  onDisconnect?: (ctx: ErrorContextInterface<any>, error?: Error) => void;
57
75
  onConnectError?: (ctx: ErrorContextInterface<any>, error: Error) => void;
@@ -91,10 +109,13 @@ class ConnectionManagerImpl {
91
109
  }
92
110
  const managed: ManagedConnection = {
93
111
  connection: undefined,
112
+ builder: undefined,
94
113
  refCount: 0,
95
114
  state: defaultState(),
96
115
  listeners: new Set(),
97
116
  pendingRelease: null,
117
+ reconnectTimer: null,
118
+ reconnectAttempt: 0,
98
119
  };
99
120
  this.#connections.set(key, managed);
100
121
  return managed;
@@ -106,47 +127,25 @@ class ConnectionManagerImpl {
106
127
  }
107
128
  }
108
129
 
109
- /**
110
- * Retains a connection, incrementing its reference count.
111
- * Creates the connection on first call; returns existing connection on subsequent calls.
112
- * Cancels any pending release if the connection was about to be cleaned up.
113
- *
114
- * @param key - Unique identifier for the connection (use getKey to generate)
115
- * @param builder - Connection builder to create the connection if needed
116
- * @returns The managed connection instance
117
- */
118
- retain<T extends DbConnectionImpl<any>>(
119
- key: string,
120
- builder: DbConnectionBuilder<T>
121
- ): T {
122
- const managed = this.#ensureEntry(key);
123
- if (managed.pendingRelease) {
124
- clearTimeout(managed.pendingRelease);
125
- managed.pendingRelease = null;
126
- }
127
- managed.refCount += 1;
128
- if (managed.connection) {
129
- return managed.connection as T;
130
- }
131
-
132
- const connection = builder.build();
133
- managed.connection = connection;
134
-
135
- const updateState = (updates: Partial<ConnectionState>) => {
136
- managed.state = { ...managed.state, ...updates };
137
- this.#notify(managed);
138
- };
130
+ #updateState(
131
+ managed: ManagedConnection,
132
+ updates: Partial<ConnectionState>
133
+ ): void {
134
+ managed.state = { ...managed.state, ...updates };
135
+ this.#notify(managed);
136
+ }
139
137
 
140
- updateState({
141
- isActive: connection.isActive,
142
- identity: connection.identity,
143
- token: connection.token,
144
- connectionId: connection.connectionId,
145
- connectionError: undefined,
146
- });
138
+ #ensureCallbacks(managed: ManagedConnection): void {
139
+ if (managed.onConnect) {
140
+ return;
141
+ }
147
142
 
148
143
  managed.onConnect = conn => {
149
- updateState({
144
+ if (conn !== managed.connection) {
145
+ return;
146
+ }
147
+ managed.reconnectAttempt = 0;
148
+ this.#updateState(managed, {
150
149
  isActive: conn.isActive,
151
150
  identity: conn.identity,
152
151
  token: conn.token,
@@ -156,26 +155,145 @@ class ConnectionManagerImpl {
156
155
  };
157
156
 
158
157
  managed.onDisconnect = (ctx, error) => {
159
- updateState({
160
- isActive: ctx.isActive,
158
+ if (ctx !== managed.connection) {
159
+ return;
160
+ }
161
+ this.#updateState(managed, {
162
+ isActive: false,
161
163
  connectionError: error ?? undefined,
162
164
  });
165
+ this.#scheduleReconnect(managed);
163
166
  };
164
167
 
165
168
  managed.onConnectError = (ctx, error) => {
166
- updateState({
167
- isActive: ctx.isActive,
169
+ if (ctx !== managed.connection) {
170
+ return;
171
+ }
172
+ this.#updateState(managed, {
173
+ isActive: false,
168
174
  connectionError: error,
169
175
  });
176
+ this.#scheduleReconnect(managed);
170
177
  };
178
+ }
179
+
180
+ #attachCallbacks<T extends DbConnectionImpl<any>>(
181
+ managed: ManagedConnection,
182
+ builder: DbConnectionBuilder<T>
183
+ ): void {
184
+ this.#ensureCallbacks(managed);
185
+ builder.onConnect(managed.onConnect!);
186
+ builder.onDisconnect(managed.onDisconnect!);
187
+ builder.onConnectError(managed.onConnectError!);
188
+ }
189
+
190
+ #detachCallbacks(
191
+ managed: ManagedConnection,
192
+ connection: DbConnectionImpl<any>
193
+ ): void {
194
+ if (managed.onConnect) {
195
+ connection.removeOnConnect(managed.onConnect as any);
196
+ }
197
+ if (managed.onDisconnect) {
198
+ connection.removeOnDisconnect(managed.onDisconnect as any);
199
+ }
200
+ if (managed.onConnectError) {
201
+ connection.removeOnConnectError(managed.onConnectError as any);
202
+ }
203
+ }
171
204
 
172
- builder.onConnect(managed.onConnect);
173
- builder.onDisconnect(managed.onDisconnect);
174
- builder.onConnectError(managed.onConnectError);
205
+ #buildManagedConnection<T extends DbConnectionImpl<any>>(
206
+ managed: ManagedConnection,
207
+ builder: DbConnectionBuilder<T>
208
+ ): T {
209
+ managed.builder = builder;
210
+ const connection = builder.build();
211
+ managed.connection = connection;
212
+ this.#attachCallbacks(managed, builder);
213
+
214
+ this.#updateState(managed, {
215
+ isActive: connection.isActive,
216
+ identity: connection.identity,
217
+ token: connection.token,
218
+ connectionId: connection.connectionId,
219
+ connectionError: undefined,
220
+ });
175
221
 
176
222
  return connection as T;
177
223
  }
178
224
 
225
+ #scheduleReconnect(managed: ManagedConnection): void {
226
+ if (
227
+ managed.refCount <= 0 ||
228
+ managed.pendingRelease ||
229
+ managed.reconnectTimer ||
230
+ !managed.builder
231
+ ) {
232
+ return;
233
+ }
234
+
235
+ const connection = managed.connection;
236
+ if (connection) {
237
+ this.#detachCallbacks(managed, connection);
238
+ }
239
+ managed.connection = undefined;
240
+
241
+ // The application asked this connection to close; don't fight it. A
242
+ // subsequent retain() will still build a fresh connection.
243
+ if (connection?.isDisconnectRequested) {
244
+ return;
245
+ }
246
+
247
+ const delay = connectionManagerReconnectDelayMs(managed.reconnectAttempt);
248
+ managed.reconnectAttempt += 1;
249
+ managed.reconnectTimer = setTimeout(() => {
250
+ managed.reconnectTimer = null;
251
+ if (
252
+ managed.refCount <= 0 ||
253
+ managed.pendingRelease ||
254
+ managed.connection ||
255
+ !managed.builder
256
+ ) {
257
+ return;
258
+ }
259
+
260
+ this.#buildManagedConnection(managed, managed.builder);
261
+ }, delay);
262
+ }
263
+
264
+ /**
265
+ * Retains a connection, incrementing its reference count.
266
+ * Creates the connection on first call; returns existing connection on subsequent calls.
267
+ * Cancels any pending release if the connection was about to be cleaned up.
268
+ *
269
+ * @param key - Unique identifier for the connection (use getKey to generate)
270
+ * @param builder - Connection builder to create the connection if needed
271
+ * @returns The managed connection instance
272
+ */
273
+ retain<T extends DbConnectionImpl<any>>(
274
+ key: string,
275
+ builder: DbConnectionBuilder<T>
276
+ ): T {
277
+ const managed = this.#ensureEntry(key);
278
+ if (managed.pendingRelease) {
279
+ clearTimeout(managed.pendingRelease);
280
+ managed.pendingRelease = null;
281
+ }
282
+ if (managed.reconnectTimer) {
283
+ clearTimeout(managed.reconnectTimer);
284
+ managed.reconnectTimer = null;
285
+ }
286
+
287
+ managed.refCount += 1;
288
+ managed.builder = builder;
289
+
290
+ if (managed.connection) {
291
+ return managed.connection as T;
292
+ }
293
+
294
+ return this.#buildManagedConnection(managed, builder);
295
+ }
296
+
179
297
  release(key: string): void {
180
298
  const managed = this.#connections.get(key);
181
299
  if (!managed) {
@@ -187,24 +305,21 @@ class ConnectionManagerImpl {
187
305
  return;
188
306
  }
189
307
 
308
+ if (managed.reconnectTimer) {
309
+ clearTimeout(managed.reconnectTimer);
310
+ managed.reconnectTimer = null;
311
+ }
312
+
190
313
  managed.pendingRelease = setTimeout(() => {
191
314
  managed.pendingRelease = null;
192
315
  if (managed.refCount > 0) {
193
316
  return;
194
317
  }
195
- if (managed.connection) {
196
- if (managed.onConnect) {
197
- managed.connection.removeOnConnect(managed.onConnect as any);
198
- }
199
- if (managed.onDisconnect) {
200
- managed.connection.removeOnDisconnect(managed.onDisconnect as any);
201
- }
202
- if (managed.onConnectError) {
203
- managed.connection.removeOnConnectError(
204
- managed.onConnectError as any
205
- );
206
- }
207
- managed.connection.disconnect();
318
+ const connection = managed.connection;
319
+ managed.connection = undefined;
320
+ if (connection) {
321
+ this.#detachCallbacks(managed, connection);
322
+ connection.disconnect();
208
323
  }
209
324
  this.#connections.delete(key);
210
325
  }, 0);
@@ -23,7 +23,12 @@ import {
23
23
  type SubscriptionEventContextInterface,
24
24
  } from './event_context.ts';
25
25
  import { EventEmitter } from './event_emitter.ts';
26
- import type { Deserializer, Identity, InferTypeOfRow, Serializer } from '../';
26
+ import type {
27
+ Deserializer,
28
+ Identity,
29
+ InferTypeOfParams,
30
+ Serializer,
31
+ } from '../';
27
32
  import type {
28
33
  ProcedureResultMessage,
29
34
  ReducerResultMessage,
@@ -138,6 +143,14 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
138
143
  */
139
144
  isActive = false;
140
145
 
146
+ /**
147
+ * Whether `disconnect()` has been called on this connection.
148
+ * Once requested, the connection will not be reused: managed environments
149
+ * (such as the React `SpacetimeDBProvider`) use this to avoid reconnecting
150
+ * after an intentional disconnect.
151
+ */
152
+ isDisconnectRequested = false;
153
+
141
154
  /**
142
155
  * This connection's public identity.
143
156
  */
@@ -321,12 +334,12 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
321
334
  this.ws = v;
322
335
 
323
336
  this.ws.onclose = () => {
324
- this.#emitter.emit('disconnect', this);
325
337
  this.isActive = false;
338
+ this.#emitter.emit('disconnect', this);
326
339
  };
327
340
  this.ws.onerror = (e: ErrorEvent) => {
328
- this.#emitter.emit('connectError', this, e);
329
341
  this.isActive = false;
342
+ this.#emitter.emit('connectError', this, e);
330
343
  };
331
344
  this.ws.onopen = this.#handleOnOpen.bind(this);
332
345
  this.ws.onmessage = this.#handleOnMessage.bind(this);
@@ -375,7 +388,9 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
375
388
  const { serialize: serializeArgs } =
376
389
  this.#reducerArgsSerializers[reducerName];
377
390
 
378
- (out as any)[key] = (params: InferTypeOfRow<typeof reducer.params>) => {
391
+ (out as any)[key] = (
392
+ params: InferTypeOfParams<typeof reducer.params>
393
+ ) => {
379
394
  const writer = this.#reducerArgsEncoder;
380
395
  writer.clear();
381
396
  serializeArgs(writer, params);
@@ -406,7 +421,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
406
421
  this.#procedureSerializers[procedureName];
407
422
 
408
423
  (out as any)[key] = (
409
- params: InferTypeOfRow<typeof procedure.params>
424
+ params: InferTypeOfParams<typeof procedure.params>
410
425
  ): Promise<any> => {
411
426
  writer.clear();
412
427
  serializeArgs(writer, params);
@@ -428,7 +443,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
428
443
  event: Event<
429
444
  ReducerEventInfo<
430
445
  RemoteModule['reducers'][number]['name'],
431
- InferTypeOfRow<RemoteModule['reducers'][number]['params']>
446
+ InferTypeOfParams<RemoteModule['reducers'][number]['params']>
432
447
  >
433
448
  >
434
449
  ): EventContextInterface<RemoteModule> {
@@ -1292,6 +1307,7 @@ export class DbConnectionImpl<RemoteModule extends UntypedRemoteModule>
1292
1307
  * ```
1293
1308
  */
1294
1309
  disconnect(): void {
1310
+ this.isDisconnectRequested = true;
1295
1311
  this.wsPromise.then(ws => ws?.close());
1296
1312
  }
1297
1313
 
@@ -1,4 +1,4 @@
1
- import type { InferTypeOfRow } from '../lib/type_builders.ts';
1
+ import type { InferTypeOfParams } from '../lib/type_builders.ts';
2
2
  import type { DbContext } from './db_context';
3
3
  import type { Event } from './event.ts';
4
4
  import type { ReducerEvent } from './reducer_event.ts';
@@ -13,7 +13,7 @@ export interface EventContextInterface<RemoteModule extends UntypedRemoteModule>
13
13
  event: Event<
14
14
  ReducerEventInfo<
15
15
  RemoteModule['reducers'][number]['name'],
16
- InferTypeOfRow<RemoteModule['reducers'][number]['params']>
16
+ InferTypeOfParams<RemoteModule['reducers'][number]['params']>
17
17
  >
18
18
  >;
19
19
  }
@@ -25,7 +25,7 @@ export interface ReducerEventContextInterface<
25
25
  event: ReducerEvent<
26
26
  ReducerEventInfo<
27
27
  RemoteModule['reducers'][number]['name'],
28
- InferTypeOfRow<RemoteModule['reducers'][number]['params']>
28
+ InferTypeOfParams<RemoteModule['reducers'][number]['params']>
29
29
  >
30
30
  >;
31
31
  }
@@ -1,5 +1,9 @@
1
1
  import type { ParamsObj } from '../lib/reducers';
2
- import type { Infer, InferTypeOfRow, TypeBuilder } from '../lib/type_builders';
2
+ import type {
3
+ Infer,
4
+ InferTypeOfParams,
5
+ TypeBuilder,
6
+ } from '../lib/type_builders';
3
7
  import type { CamelCase } from '../lib/type_util';
4
8
  import { coerceParams, toCamelCase, type CoerceParams } from '../lib/util';
5
9
  import type { UntypedRemoteModule } from './spacetime_module';
@@ -20,7 +24,7 @@ export type ProceduresView<RemoteModule> = IfAny<
20
24
  ? // x: camelCase(name)
21
25
  {
22
26
  [K in RemoteModule['procedures'][number] as K['accessorName']]: (
23
- params: InferTypeOfRow<K['params']>
27
+ params: InferTypeOfParams<K['params']>
24
28
  ) => Promise<Infer<K['returnType']>>;
25
29
  }
26
30
  : never
@@ -1,8 +1,7 @@
1
1
  import type { ProductType } from '../lib/algebraic_type';
2
2
  import type { ReducerSchema } from '../lib/reducer_schema';
3
3
  import type { ParamsObj } from '../lib/reducers';
4
- import type { CoerceRow } from '../lib/table';
5
- import { RowBuilder, type InferTypeOfRow } from '../lib/type_builders';
4
+ import { RowBuilder, type InferTypeOfParams } from '../lib/type_builders';
6
5
  import { toCamelCase } from '../lib/util';
7
6
  import type { SubscriptionEventContextInterface } from './event_context';
8
7
  import type { UntypedRemoteModule } from './spacetime_module';
@@ -26,7 +25,7 @@ export type ReducersView<RemoteModule> = IfAny<
26
25
  RemoteModule extends UntypedRemoteModule
27
26
  ? {
28
27
  [K in RemoteModule['reducers'][number] as K['accessorName']]: (
29
- params: InferTypeOfRow<K['params']>
28
+ params: InferTypeOfParams<K['params']>
30
29
  ) => Promise<void>;
31
30
  }
32
31
  : never
@@ -43,7 +42,7 @@ export type ReducerEventInfo<
43
42
  export type UntypedReducerDef = {
44
43
  name: string;
45
44
  accessorName: string;
46
- params: CoerceRow<ParamsObj>;
45
+ params: ParamsObj;
47
46
  paramsType: ProductType;
48
47
  };
49
48
 
@@ -123,7 +123,8 @@ export class TableCacheImpl<
123
123
  const columns = idx.columns;
124
124
 
125
125
  // Extract the tuple key for this btree index (column order preserved)
126
- const getKey = (row: Row): readonly unknown[] => columns.map(c => row[c]);
126
+ const getKey = (row: Row): readonly unknown[] =>
127
+ columns.map(c => (row as Record<string, unknown>)[c]);
127
128
 
128
129
  // The server’s ranged scan fixes all prefix cols to equality and applies
129
130
  // the bound only to the *last* term. We mirror that.
@@ -1,4 +1,4 @@
1
- import type { Infer, InferTypeOfRow } from '.';
1
+ import type { Infer, InferTypeOfParams } from '.';
2
2
  import type { Prettify } from '../lib/type_util';
3
3
  import type { UntypedProcedureDef } from './procedures';
4
4
  import type { UntypedReducerDef } from './reducers';
@@ -7,11 +7,11 @@ export type IsEmptyObject<T> = [keyof T] extends [never] ? true : false;
7
7
  export type MaybeParams<T> = IsEmptyObject<T> extends true ? [] : [params: T];
8
8
 
9
9
  export type ParamsType<R extends UntypedReducerDef> = MaybeParams<
10
- Prettify<InferTypeOfRow<R['params']>>
10
+ Prettify<InferTypeOfParams<R['params']>>
11
11
  >;
12
12
 
13
13
  export type ProcedureParamsType<P extends UntypedProcedureDef> = MaybeParams<
14
- Prettify<InferTypeOfRow<P['params']>>
14
+ Prettify<InferTypeOfParams<P['params']>>
15
15
  >;
16
16
 
17
17
  export type ProcedureReturnType<P extends UntypedProcedureDef> = Infer<
@@ -19,6 +19,7 @@ class WebsocketTestAdapter implements WebSocketAdapter {
19
19
  #onclose: (ev: CloseEvent) => void = () => {};
20
20
  #onopen: () => void = () => {};
21
21
  #onmessage: (msg: { data: Uint8Array }) => void = () => {};
22
+ #onerror: (msg: ErrorEvent) => void = () => {};
22
23
 
23
24
  constructor() {
24
25
  this.messageQueue = [];
@@ -39,7 +40,13 @@ class WebsocketTestAdapter implements WebSocketAdapter {
39
40
  this.#onmessage = handler;
40
41
  }
41
42
 
42
- set onerror(_handler: (msg: ErrorEvent) => void) {}
43
+ set onerror(handler: (msg: ErrorEvent) => void) {
44
+ this.#onerror = handler;
45
+ }
46
+
47
+ error(error: Error): void {
48
+ this.#onerror(error as unknown as ErrorEvent);
49
+ }
43
50
 
44
51
  send(message: Uint8Array<ArrayBuffer>): void {
45
52
  const rawMessage = message.slice();
@@ -327,8 +327,11 @@ type ViewInfo<F> = {
327
327
  returnTypeBaseSize: number;
328
328
  };
329
329
 
330
- export type Views = ViewInfo<ViewFn<any, any, any>>[];
331
- export type AnonViews = ViewInfo<AnonymousViewFn<any, any, any>>[];
330
+ type AnyViewFn = (ctx: ViewCtx<any>, params: any) => any;
331
+ type AnyAnonymousViewFn = (ctx: AnonymousViewCtx<any>, params: any) => any;
332
+
333
+ export type Views = ViewInfo<AnyViewFn>[];
334
+ export type AnonViews = ViewInfo<AnyAnonymousViewFn>[];
332
335
 
333
336
  // A helper to get the product type out of a type builder.
334
337
  // This is only non-never if the type builder is an array.