spacetimedb 2.0.4 → 2.2.0

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 (116) hide show
  1. package/LICENSE.txt +2 -2
  2. package/dist/angular/index.cjs +9 -3
  3. package/dist/angular/index.cjs.map +1 -1
  4. package/dist/angular/index.mjs +9 -3
  5. package/dist/angular/index.mjs.map +1 -1
  6. package/dist/browser/angular/index.mjs +9 -3
  7. package/dist/browser/angular/index.mjs.map +1 -1
  8. package/dist/browser/react/index.mjs +62 -7
  9. package/dist/browser/react/index.mjs.map +1 -1
  10. package/dist/browser/svelte/index.mjs +9 -3
  11. package/dist/browser/svelte/index.mjs.map +1 -1
  12. package/dist/browser/vue/index.mjs +9 -3
  13. package/dist/browser/vue/index.mjs.map +1 -1
  14. package/dist/index.browser.mjs +481 -146
  15. package/dist/index.browser.mjs.map +1 -1
  16. package/dist/index.cjs +481 -146
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.mjs +481 -146
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/lib/algebraic_type.d.ts.map +1 -1
  21. package/dist/lib/binary_reader.d.ts +1 -1
  22. package/dist/lib/binary_reader.d.ts.map +1 -1
  23. package/dist/lib/binary_writer.d.ts +2 -1
  24. package/dist/lib/binary_writer.d.ts.map +1 -1
  25. package/dist/lib/filter.d.ts +2 -1
  26. package/dist/lib/filter.d.ts.map +1 -1
  27. package/dist/lib/query.d.ts +10 -5
  28. package/dist/lib/query.d.ts.map +1 -1
  29. package/dist/lib/table.d.ts +12 -1
  30. package/dist/lib/table.d.ts.map +1 -1
  31. package/dist/min/index.browser.mjs +1 -1
  32. package/dist/min/index.browser.mjs.map +1 -1
  33. package/dist/min/react/index.mjs +1 -1
  34. package/dist/min/react/index.mjs.map +1 -1
  35. package/dist/min/sdk/index.browser.mjs +1 -1
  36. package/dist/min/sdk/index.browser.mjs.map +1 -1
  37. package/dist/react/index.cjs +62 -6
  38. package/dist/react/index.cjs.map +1 -1
  39. package/dist/react/index.d.ts +1 -0
  40. package/dist/react/index.d.ts.map +1 -1
  41. package/dist/react/index.mjs +62 -7
  42. package/dist/react/index.mjs.map +1 -1
  43. package/dist/react/useProcedure.d.ts +4 -0
  44. package/dist/react/useProcedure.d.ts.map +1 -0
  45. package/dist/react/useTable.d.ts +2 -0
  46. package/dist/react/useTable.d.ts.map +1 -1
  47. package/dist/sdk/db_connection_builder.d.ts +3 -3
  48. package/dist/sdk/db_connection_builder.d.ts.map +1 -1
  49. package/dist/sdk/db_connection_impl.d.ts +3 -3
  50. package/dist/sdk/db_connection_impl.d.ts.map +1 -1
  51. package/dist/sdk/decompress.d.ts +1 -1
  52. package/dist/sdk/decompress.d.ts.map +1 -1
  53. package/dist/sdk/index.browser.mjs +477 -144
  54. package/dist/sdk/index.browser.mjs.map +1 -1
  55. package/dist/sdk/index.cjs +477 -144
  56. package/dist/sdk/index.cjs.map +1 -1
  57. package/dist/sdk/index.mjs +477 -144
  58. package/dist/sdk/index.mjs.map +1 -1
  59. package/dist/sdk/table_cache.d.ts +1 -0
  60. package/dist/sdk/table_cache.d.ts.map +1 -1
  61. package/dist/sdk/type_utils.d.ts +4 -1
  62. package/dist/sdk/type_utils.d.ts.map +1 -1
  63. package/dist/sdk/websocket_decompress_adapter.d.ts +5 -21
  64. package/dist/sdk/websocket_decompress_adapter.d.ts.map +1 -1
  65. package/dist/sdk/websocket_protocols.d.ts +6 -0
  66. package/dist/sdk/websocket_protocols.d.ts.map +1 -0
  67. package/dist/sdk/websocket_test_adapter.d.ts +14 -18
  68. package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
  69. package/dist/sdk/websocket_v3_frames.d.ts +9 -0
  70. package/dist/sdk/websocket_v3_frames.d.ts.map +1 -0
  71. package/dist/sdk/ws.d.ts +26 -1
  72. package/dist/sdk/ws.d.ts.map +1 -1
  73. package/dist/server/http_internal.d.ts.map +1 -1
  74. package/dist/server/index.d.ts +2 -1
  75. package/dist/server/index.d.ts.map +1 -1
  76. package/dist/server/index.mjs +76 -15
  77. package/dist/server/index.mjs.map +1 -1
  78. package/dist/server/runtime.d.ts +29 -2
  79. package/dist/server/runtime.d.ts.map +1 -1
  80. package/dist/svelte/index.cjs +9 -3
  81. package/dist/svelte/index.cjs.map +1 -1
  82. package/dist/svelte/index.mjs +9 -3
  83. package/dist/svelte/index.mjs.map +1 -1
  84. package/dist/tanstack/index.cjs +9 -3
  85. package/dist/tanstack/index.cjs.map +1 -1
  86. package/dist/tanstack/index.mjs +9 -3
  87. package/dist/tanstack/index.mjs.map +1 -1
  88. package/dist/vue/index.cjs +9 -3
  89. package/dist/vue/index.cjs.map +1 -1
  90. package/dist/vue/index.mjs +9 -3
  91. package/dist/vue/index.mjs.map +1 -1
  92. package/package.json +2 -2
  93. package/src/lib/algebraic_type.ts +5 -1
  94. package/src/lib/binary_reader.ts +5 -2
  95. package/src/lib/binary_writer.ts +7 -1
  96. package/src/lib/filter.ts +12 -1
  97. package/src/lib/query.ts +60 -19
  98. package/src/lib/table.ts +15 -2
  99. package/src/react/index.ts +1 -0
  100. package/src/react/useProcedure.ts +60 -0
  101. package/src/react/useTable.ts +22 -2
  102. package/src/sdk/db_connection_builder.ts +16 -7
  103. package/src/sdk/db_connection_impl.ts +404 -89
  104. package/src/sdk/decompress.ts +7 -23
  105. package/src/sdk/table_cache.ts +5 -5
  106. package/src/sdk/type_utils.ts +10 -1
  107. package/src/sdk/websocket_decompress_adapter.ts +15 -77
  108. package/src/sdk/websocket_protocols.ts +25 -0
  109. package/src/sdk/websocket_test_adapter.ts +65 -29
  110. package/src/sdk/websocket_v3_frames.ts +126 -0
  111. package/src/sdk/ws.ts +81 -3
  112. package/src/server/http_internal.ts +10 -1
  113. package/src/server/index.ts +2 -1
  114. package/src/server/runtime.ts +39 -1
  115. package/src/server/sys.d.ts +4 -0
  116. package/src/server/view.test-d.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacetimedb",
3
- "version": "2.0.4",
3
+ "version": "2.2.0",
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": {
@@ -94,7 +94,7 @@
94
94
  "name": "esm (gzip)",
95
95
  "path": "dist/index.mjs",
96
96
  "gzip": true,
97
- "limit": "40 kB"
97
+ "limit": "48 kB"
98
98
  },
99
99
  {
100
100
  "name": "esm (uncompressed)",
@@ -535,7 +535,11 @@ const view = reader.view;
535
535
  ${ty.elements
536
536
  .map(({ name, algebraicType: { tag } }) =>
537
537
  tag in primitiveJSName
538
- ? `\
538
+ ? tag === 'Bool'
539
+ ? `\
540
+ result.${name} = view.getUint8(reader.offset) !== 0;
541
+ reader.offset += 1;`
542
+ : `\
539
543
  result.${name} = view.get${primitiveJSName[tag as JSPrimitives]}(reader.offset, ${primitiveSizes[tag] > 1 ? 'true' : ''});
540
544
  reader.offset += ${primitiveSizes[tag]};`
541
545
  : `result.${name} = reader.read${tag}();`
@@ -25,8 +25,11 @@ export default class BinaryReader {
25
25
  this.offset = 0;
26
26
  }
27
27
 
28
- reset(view: DataView) {
29
- this.view = view;
28
+ reset(input: Uint8Array | DataView) {
29
+ this.view =
30
+ input instanceof DataView
31
+ ? input
32
+ : new DataView(input.buffer, input.byteOffset, input.byteLength);
30
33
  this.offset = 0;
31
34
  }
32
35
 
@@ -63,7 +63,7 @@ export default class BinaryWriter {
63
63
  return fromByteArray(this.getBuffer());
64
64
  }
65
65
 
66
- getBuffer(): Uint8Array {
66
+ getBuffer(): Uint8Array<ArrayBuffer> {
67
67
  return new Uint8Array(this.buffer.buffer, 0, this.offset);
68
68
  }
69
69
 
@@ -93,6 +93,12 @@ export default class BinaryWriter {
93
93
  this.offset += 1;
94
94
  }
95
95
 
96
+ writeBytes(value: Uint8Array): void {
97
+ this.expandBuffer(value.length);
98
+ new Uint8Array(this.buffer.buffer, this.offset, value.length).set(value);
99
+ this.offset += value.length;
100
+ }
101
+
96
102
  writeI8(value: number): void {
97
103
  this.expandBuffer(1);
98
104
  this.view.setInt8(this.offset, value);
package/src/lib/filter.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import type { RowType, UntypedTableDef } from './table';
2
+ import { Timestamp } from './timestamp';
2
3
  import { Uuid } from './uuid';
3
4
 
4
- export type Value = string | number | boolean | Uuid;
5
+ export type Value = string | number | boolean | Uuid | Timestamp;
5
6
 
6
7
  export type Expr<Column extends string> =
7
8
  | { type: 'eq'; key: Column; value: Value }
@@ -77,6 +78,13 @@ export function evaluate<Column extends string>(
77
78
  if (v instanceof Uuid && typeof expr.value === 'string') {
78
79
  return v.toString() === expr.value;
79
80
  }
81
+
82
+ if (v instanceof Timestamp) {
83
+ // Value of the Column and passed Value are both Timestamps so compare microseconds.
84
+ if (expr.value instanceof Timestamp) {
85
+ return v.microsSinceUnixEpoch === expr.value.microsSinceUnixEpoch;
86
+ }
87
+ }
80
88
  }
81
89
  return false;
82
90
  }
@@ -103,6 +111,9 @@ function formatValue(v: Value): string {
103
111
  if (v instanceof Uuid) {
104
112
  return `'${v.toString()}'`;
105
113
  }
114
+ if (v instanceof Timestamp) {
115
+ return `'${v.toISOString()}'`;
116
+ }
106
117
 
107
118
  return '';
108
119
  }
package/src/lib/query.ts CHANGED
@@ -348,7 +348,8 @@ function createRowExpr<TableDef extends TypedTableDef>(
348
348
  columnBuilder.typeBuilder.algebraicType as InferSpacetimeTypeOfColumn<
349
349
  TableDef,
350
350
  typeof columnName
351
- >
351
+ >,
352
+ columnBuilder.columnMetadata.name
352
353
  );
353
354
  row[columnName] = Object.freeze(column);
354
355
  }
@@ -438,7 +439,10 @@ export class ColumnExpression<
438
439
  ColumnName extends ColumnNames<TableDef>,
439
440
  > {
440
441
  readonly type = 'column' as const;
442
+ // This is the column accessor
441
443
  readonly column: ColumnName;
444
+ // The name of the column in the database.
445
+ readonly columnName: string;
442
446
  readonly table: TableDef['sourceName'];
443
447
  // phantom: actual runtime value is undefined
444
448
  readonly tsValueType?: RowType<TableDef>[ColumnName];
@@ -447,10 +451,12 @@ export class ColumnExpression<
447
451
  constructor(
448
452
  table: TableDef['sourceName'],
449
453
  column: ColumnName,
450
- spacetimeType: InferSpacetimeTypeOfColumn<TableDef, ColumnName>
454
+ spacetimeType: InferSpacetimeTypeOfColumn<TableDef, ColumnName>,
455
+ columnName?: string
451
456
  ) {
452
457
  this.table = table;
453
458
  this.column = column;
459
+ this.columnName = columnName || column;
454
460
  this.spacetimeType = spacetimeType;
455
461
  }
456
462
 
@@ -710,15 +716,38 @@ type BooleanExprData<Table extends TypedTableDef> = (
710
716
  _tableType?: Table;
711
717
  };
712
718
 
719
+ type AndOrMixedTableScopeError = {
720
+ readonly 'Cannot combine predicates from different table scopes with and/or. In semijoin on(...), keep only the join equality and move extra predicates to .where(...).': never;
721
+ };
722
+
723
+ type RequireSameAndOrTable<
724
+ Expected extends TypedTableDef,
725
+ Actual extends TypedTableDef,
726
+ > = [Expected] extends [Actual]
727
+ ? [Actual] extends [Expected]
728
+ ? unknown
729
+ : AndOrMixedTableScopeError
730
+ : AndOrMixedTableScopeError;
731
+
713
732
  export class BooleanExpr<Table extends TypedTableDef> {
714
733
  constructor(readonly data: BooleanExprData<Table>) {}
715
734
 
716
- and(other: BooleanExpr<Table>): BooleanExpr<Table> {
717
- return new BooleanExpr({ type: 'and', clauses: [this.data, other.data] });
735
+ and<OtherTable extends TypedTableDef>(
736
+ other: BooleanExpr<OtherTable> & RequireSameAndOrTable<Table, OtherTable>
737
+ ): BooleanExpr<Table> {
738
+ return new BooleanExpr({
739
+ type: 'and',
740
+ clauses: [this.data, other.data as BooleanExprData<Table>],
741
+ });
718
742
  }
719
743
 
720
- or(other: BooleanExpr<Table>): BooleanExpr<Table> {
721
- return new BooleanExpr({ type: 'or', clauses: [this.data, other.data] });
744
+ or<OtherTable extends TypedTableDef>(
745
+ other: BooleanExpr<OtherTable> & RequireSameAndOrTable<Table, OtherTable>
746
+ ): BooleanExpr<Table> {
747
+ return new BooleanExpr({
748
+ type: 'or',
749
+ clauses: [this.data, other.data as BooleanExprData<Table>],
750
+ });
722
751
  }
723
752
 
724
753
  not(): BooleanExpr<Table> {
@@ -732,28 +761,40 @@ export function not<T extends TypedTableDef>(
732
761
  return new BooleanExpr({ type: 'not', clause: clause.data });
733
762
  }
734
763
 
735
- export function and<T extends TypedTableDef>(
736
- ...clauses: readonly [BooleanExpr<T>, BooleanExpr<T>, ...BooleanExpr<T>[]]
737
- ): BooleanExpr<T> {
764
+ export function and<
765
+ Table extends TypedTableDef,
766
+ OtherTable extends TypedTableDef,
767
+ >(
768
+ first: BooleanExpr<Table>,
769
+ second: BooleanExpr<OtherTable> & RequireSameAndOrTable<Table, OtherTable>,
770
+ ...rest: readonly BooleanExpr<Table>[]
771
+ ): BooleanExpr<Table> {
772
+ const clauses = [first, second, ...rest];
738
773
  return new BooleanExpr({
739
774
  type: 'and',
740
775
  clauses: clauses.map(c => c.data) as [
741
- BooleanExprData<T>,
742
- BooleanExprData<T>,
743
- ...BooleanExprData<T>[],
776
+ BooleanExprData<Table>,
777
+ BooleanExprData<Table>,
778
+ ...BooleanExprData<Table>[],
744
779
  ],
745
780
  });
746
781
  }
747
782
 
748
- export function or<T extends TypedTableDef>(
749
- ...clauses: readonly [BooleanExpr<T>, BooleanExpr<T>, ...BooleanExpr<T>[]]
750
- ): BooleanExpr<T> {
783
+ export function or<
784
+ Table extends TypedTableDef,
785
+ OtherTable extends TypedTableDef,
786
+ >(
787
+ first: BooleanExpr<Table>,
788
+ second: BooleanExpr<OtherTable> & RequireSameAndOrTable<Table, OtherTable>,
789
+ ...rest: readonly BooleanExpr<Table>[]
790
+ ): BooleanExpr<Table> {
791
+ const clauses = [first, second, ...rest];
751
792
  return new BooleanExpr({
752
793
  type: 'or',
753
794
  clauses: clauses.map(c => c.data) as [
754
- BooleanExprData<T>,
755
- BooleanExprData<T>,
756
- ...BooleanExprData<T>[],
795
+ BooleanExprData<Table>,
796
+ BooleanExprData<Table>,
797
+ ...BooleanExprData<Table>[],
757
798
  ],
758
799
  });
759
800
  }
@@ -803,7 +844,7 @@ function valueExprToSql<Table extends TypedTableDef>(
803
844
  return literalValueToSql(expr.value);
804
845
  }
805
846
  const table = tableAlias ?? expr.table;
806
- return `${quoteIdentifier(table)}.${quoteIdentifier(expr.column)}`;
847
+ return `${quoteIdentifier(table)}.${quoteIdentifier(expr.columnName)}`;
807
848
  }
808
849
 
809
850
  function literalValueToSql(value: unknown): string {
package/src/lib/table.ts CHANGED
@@ -239,7 +239,12 @@ export type ReadonlyTable<TableDef extends UntypedTableDef> = Prettify<
239
239
  >;
240
240
 
241
241
  export interface ReadonlyTableMethods<TableDef extends UntypedTableDef> {
242
- /** Returns the number of rows in the TX state. */
242
+ /**
243
+ * Returns the number of rows in this table.
244
+ *
245
+ * This reads datastore metadata, so it runs in constant time.
246
+ * It also takes into account modifications by the current transaction.
247
+ */
243
248
  count(): bigint;
244
249
 
245
250
  /** Iterate over all rows in the TX state. Rust Iterator<Item=Row> → TS IterableIterator<Row>. */
@@ -263,6 +268,13 @@ export interface TableMethods<TableDef extends UntypedTableDef>
263
268
 
264
269
  /** Delete a row equal to `row`. Returns true if something was deleted. */
265
270
  delete(row: Prettify<RowType<TableDef>>): boolean;
271
+
272
+ /**
273
+ * Clears the table of all rows.
274
+ * Returns the number of rows deleted,
275
+ * i.e., the return value of `this.count()` before this call.
276
+ */
277
+ clear(): bigint;
266
278
  }
267
279
 
268
280
  /**
@@ -399,7 +411,8 @@ export function table<Row extends RowObj, const Opts extends TableOpts<Row>>(
399
411
  });
400
412
  }
401
413
 
402
- if (meta.defaultValue) {
414
+ // Check for defaultValue on the property to allow for 0, false, '', and undefined as defaults
415
+ if (Object.prototype.hasOwnProperty.call(meta, 'defaultValue')) {
403
416
  const writer = new BinaryWriter(16);
404
417
  builder.serialize(writer, meta.defaultValue);
405
418
  defaultValues.push({
@@ -2,3 +2,4 @@ export * from './SpacetimeDBProvider.ts';
2
2
  export { useSpacetimeDB } from './useSpacetimeDB.ts';
3
3
  export { useTable } from './useTable.ts';
4
4
  export { useReducer } from './useReducer.ts';
5
+ export { useProcedure } from './useProcedure.ts';
@@ -0,0 +1,60 @@
1
+ import { useCallback, useEffect, useRef } from 'react';
2
+ import type { UntypedProcedureDef } from '../sdk/procedures';
3
+ import { useSpacetimeDB } from './useSpacetimeDB';
4
+ import type {
5
+ ProcedureParamsType,
6
+ ProcedureReturnType,
7
+ } from '../sdk/type_utils';
8
+
9
+ export function useProcedure<ProcedureDef extends UntypedProcedureDef>(
10
+ procedureDef: ProcedureDef
11
+ ): (
12
+ ...params: ProcedureParamsType<ProcedureDef>
13
+ ) => Promise<ProcedureReturnType<ProcedureDef>> {
14
+ const { getConnection, isActive } = useSpacetimeDB();
15
+ const procedureName = procedureDef.accessorName;
16
+
17
+ // Holds calls made before the connection exists
18
+ const queueRef = useRef<
19
+ {
20
+ params: ProcedureParamsType<ProcedureDef>;
21
+ resolve: (val: any) => void;
22
+ reject: (err: unknown) => void;
23
+ }[]
24
+ >([]);
25
+
26
+ // Flush when we finally have a connection
27
+ useEffect(() => {
28
+ const conn = getConnection();
29
+ if (!conn) {
30
+ return;
31
+ }
32
+ const fn = (conn.procedures as any)[procedureName] as (
33
+ ...p: ProcedureParamsType<ProcedureDef>
34
+ ) => Promise<ProcedureReturnType<ProcedureDef>>;
35
+ if (queueRef.current.length) {
36
+ const pending = queueRef.current.splice(0);
37
+ for (const item of pending) {
38
+ fn(...item.params).then(item.resolve, item.reject);
39
+ }
40
+ }
41
+ }, [getConnection, procedureName, isActive]);
42
+
43
+ return useCallback(
44
+ (...params: ProcedureParamsType<ProcedureDef>) => {
45
+ const conn = getConnection();
46
+ if (!conn) {
47
+ return new Promise<ProcedureReturnType<ProcedureDef>>(
48
+ (resolve, reject) => {
49
+ queueRef.current.push({ params, resolve, reject });
50
+ }
51
+ );
52
+ }
53
+ const fn = (conn.procedures as any)[procedureName] as (
54
+ ...p: ProcedureParamsType<ProcedureDef>
55
+ ) => Promise<ProcedureReturnType<ProcedureDef>>;
56
+ return fn(...params);
57
+ },
58
+ [getConnection, procedureName]
59
+ );
60
+ }
@@ -24,6 +24,8 @@ export interface UseTableCallbacks<RowType> {
24
24
  onInsert?: (row: RowType) => void;
25
25
  onDelete?: (row: RowType) => void;
26
26
  onUpdate?: (oldRow: RowType, newRow: RowType) => void;
27
+ /** Whether the subscription is active. Defaults to `true`. */
28
+ enabled?: boolean;
27
29
  }
28
30
 
29
31
  type MembershipChange = 'enter' | 'leave' | 'stayIn' | 'stayOut';
@@ -67,6 +69,7 @@ export function useTable<TableDef extends UntypedTableDef>(
67
69
  callbacks?: UseTableCallbacks<Prettify<RowType<TableDef>>>
68
70
  ): [readonly Prettify<RowType<TableDef>>[], boolean] {
69
71
  type UseTableRowType = RowType<TableDef>;
72
+ const enabled = callbacks?.enabled ?? true;
70
73
  const accessorName = getQueryAccessorName(query);
71
74
  const whereExpr = getQueryWhereClause(query);
72
75
 
@@ -93,6 +96,9 @@ export function useTable<TableDef extends UntypedTableDef>(
93
96
  readonly Prettify<UseTableRowType>[],
94
97
  boolean,
95
98
  ] => {
99
+ if (!enabled) {
100
+ return [[], true];
101
+ }
96
102
  const connection = connectionState.getConnection();
97
103
  if (!connection) {
98
104
  return [[], false];
@@ -104,8 +110,10 @@ export function useTable<TableDef extends UntypedTableDef>(
104
110
  ) as Prettify<UseTableRowType>[])
105
111
  : (Array.from(table.iter()) as Prettify<UseTableRowType>[]);
106
112
  return [result, subscribeApplied];
113
+ // TODO: investigating refactoring so that this is no longer necessary, as we have had genuine bugs with missed deps.
114
+ // See https://github.com/clockworklabs/SpacetimeDB/pull/4580.
107
115
  // eslint-disable-next-line react-hooks/exhaustive-deps
108
- }, [connectionState, accessorName, querySql, subscribeApplied]);
116
+ }, [connectionState, accessorName, querySql, subscribeApplied, enabled]);
109
117
 
110
118
  // Invalidate the cached snapshot when computeSnapshot changes (e.g. when
111
119
  // subscribeApplied flips to true) so getSnapshot() recomputes on the next
@@ -115,6 +123,10 @@ export function useTable<TableDef extends UntypedTableDef>(
115
123
  }, [computeSnapshot]);
116
124
 
117
125
  useEffect(() => {
126
+ if (!enabled) {
127
+ setSubscribeApplied(false);
128
+ return;
129
+ }
118
130
  const connection = connectionState.getConnection()!;
119
131
  if (connectionState.isActive && connection) {
120
132
  const cancel = connection
@@ -127,10 +139,14 @@ export function useTable<TableDef extends UntypedTableDef>(
127
139
  cancel.unsubscribe();
128
140
  };
129
141
  }
130
- }, [querySql, connectionState.isActive, connectionState]);
142
+ }, [querySql, connectionState.isActive, connectionState, enabled]);
131
143
 
132
144
  const subscribe = useCallback(
133
145
  (onStoreChange: () => void) => {
146
+ if (!enabled) {
147
+ return () => {};
148
+ }
149
+
134
150
  const onInsert = (
135
151
  ctx: EventContextInterface<UntypedRemoteModule>,
136
152
  row: any
@@ -205,14 +221,18 @@ export function useTable<TableDef extends UntypedTableDef>(
205
221
  table.removeOnUpdate?.(onUpdate);
206
222
  };
207
223
  },
224
+ // TODO: investigating refactoring so that this is no longer necessary, as we have had genuine bugs with missed deps.
225
+ // See https://github.com/clockworklabs/SpacetimeDB/pull/4580.
208
226
  // eslint-disable-next-line react-hooks/exhaustive-deps
209
227
  [
210
228
  connectionState,
211
229
  accessorName,
212
230
  querySql,
231
+ computeSnapshot,
213
232
  callbacks?.onDelete,
214
233
  callbacks?.onInsert,
215
234
  callbacks?.onUpdate,
235
+ enabled,
216
236
  ]
217
237
  );
218
238
 
@@ -8,6 +8,7 @@ import type {
8
8
  } from '../';
9
9
  import { ensureMinimumVersionOrThrow } from './version';
10
10
  import { WebsocketDecompressAdapter } from './websocket_decompress_adapter';
11
+ import type { WebSocketFactory } from './ws';
11
12
 
12
13
  /**
13
14
  * The database client connection to a SpacetimeDB server.
@@ -23,10 +24,10 @@ export class DbConnectionBuilder<DbConnection extends DbConnectionImpl<any>> {
23
24
  #identity?: Identity;
24
25
  #token?: string;
25
26
  #emitter: EventEmitter<ConnectionEvent> = new EventEmitter();
26
- #compression: 'gzip' | 'none' = 'gzip';
27
+ #compression: 'gzip' | 'brotli' | 'none' = 'gzip';
27
28
  #lightMode: boolean = false;
28
29
  #confirmedReads?: boolean;
29
- #createWSFn: typeof WebsocketDecompressAdapter.createWebSocketFn;
30
+ #createWSFn: WebSocketFactory;
30
31
 
31
32
  /**
32
33
  * Creates a new `DbConnectionBuilder` database client and set the initial parameters.
@@ -42,7 +43,7 @@ export class DbConnectionBuilder<DbConnection extends DbConnectionImpl<any>> {
42
43
  config: DbConnectionConfig<RemoteModuleOf<DbConnection>>
43
44
  ) => DbConnection
44
45
  ) {
45
- this.#createWSFn = WebsocketDecompressAdapter.createWebSocketFn;
46
+ this.#createWSFn = WebsocketDecompressAdapter.openWebSocket;
46
47
  }
47
48
 
48
49
  /**
@@ -82,9 +83,7 @@ export class DbConnectionBuilder<DbConnection extends DbConnectionImpl<any>> {
82
83
  return this;
83
84
  }
84
85
 
85
- withWSFn(
86
- createWSFn: typeof WebsocketDecompressAdapter.createWebSocketFn
87
- ): this {
86
+ withWSFn(createWSFn: WebSocketFactory): this {
88
87
  this.#createWSFn = createWSFn;
89
88
  return this;
90
89
  }
@@ -94,7 +93,17 @@ export class DbConnectionBuilder<DbConnection extends DbConnectionImpl<any>> {
94
93
  *
95
94
  * @param compression The compression algorithm to use for the connection.
96
95
  */
97
- withCompression(compression: 'gzip' | 'none'): this {
96
+ withCompression(compression: 'gzip' | 'brotli' | 'none'): this {
97
+ if (compression === 'brotli') {
98
+ try {
99
+ new DecompressionStream('brotli' as CompressionFormat);
100
+ } catch (e) {
101
+ throw new TypeError(
102
+ `Brotli compression is not supported by the runtime. Please choose a different compression method.`,
103
+ { cause: e }
104
+ );
105
+ }
106
+ }
98
107
  this.#compression = compression;
99
108
  return this;
100
109
  }