spacetimedb 2.1.0 → 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 (110) hide show
  1. package/LICENSE.txt +2 -2
  2. package/dist/angular/index.cjs +7 -2
  3. package/dist/angular/index.cjs.map +1 -1
  4. package/dist/angular/index.mjs +7 -2
  5. package/dist/angular/index.mjs.map +1 -1
  6. package/dist/browser/angular/index.mjs +7 -2
  7. package/dist/browser/angular/index.mjs.map +1 -1
  8. package/dist/browser/react/index.mjs +57 -6
  9. package/dist/browser/react/index.mjs.map +1 -1
  10. package/dist/browser/svelte/index.mjs +7 -2
  11. package/dist/browser/svelte/index.mjs.map +1 -1
  12. package/dist/browser/vue/index.mjs +7 -2
  13. package/dist/browser/vue/index.mjs.map +1 -1
  14. package/dist/index.browser.mjs +459 -138
  15. package/dist/index.browser.mjs.map +1 -1
  16. package/dist/index.cjs +459 -138
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.mjs +459 -138
  19. package/dist/index.mjs.map +1 -1
  20. package/dist/lib/binary_reader.d.ts +1 -1
  21. package/dist/lib/binary_reader.d.ts.map +1 -1
  22. package/dist/lib/binary_writer.d.ts +2 -1
  23. package/dist/lib/binary_writer.d.ts.map +1 -1
  24. package/dist/lib/filter.d.ts +2 -1
  25. package/dist/lib/filter.d.ts.map +1 -1
  26. package/dist/lib/table.d.ts +6 -0
  27. package/dist/lib/table.d.ts.map +1 -1
  28. package/dist/min/index.browser.mjs +1 -1
  29. package/dist/min/index.browser.mjs.map +1 -1
  30. package/dist/min/react/index.mjs +1 -1
  31. package/dist/min/react/index.mjs.map +1 -1
  32. package/dist/min/sdk/index.browser.mjs +1 -1
  33. package/dist/min/sdk/index.browser.mjs.map +1 -1
  34. package/dist/react/index.cjs +57 -5
  35. package/dist/react/index.cjs.map +1 -1
  36. package/dist/react/index.d.ts +1 -0
  37. package/dist/react/index.d.ts.map +1 -1
  38. package/dist/react/index.mjs +57 -6
  39. package/dist/react/index.mjs.map +1 -1
  40. package/dist/react/useProcedure.d.ts +4 -0
  41. package/dist/react/useProcedure.d.ts.map +1 -0
  42. package/dist/react/useTable.d.ts +2 -0
  43. package/dist/react/useTable.d.ts.map +1 -1
  44. package/dist/sdk/db_connection_builder.d.ts +3 -3
  45. package/dist/sdk/db_connection_builder.d.ts.map +1 -1
  46. package/dist/sdk/db_connection_impl.d.ts +3 -3
  47. package/dist/sdk/db_connection_impl.d.ts.map +1 -1
  48. package/dist/sdk/decompress.d.ts +1 -1
  49. package/dist/sdk/decompress.d.ts.map +1 -1
  50. package/dist/sdk/index.browser.mjs +459 -138
  51. package/dist/sdk/index.browser.mjs.map +1 -1
  52. package/dist/sdk/index.cjs +459 -138
  53. package/dist/sdk/index.cjs.map +1 -1
  54. package/dist/sdk/index.mjs +459 -138
  55. package/dist/sdk/index.mjs.map +1 -1
  56. package/dist/sdk/table_cache.d.ts +1 -0
  57. package/dist/sdk/table_cache.d.ts.map +1 -1
  58. package/dist/sdk/type_utils.d.ts +4 -1
  59. package/dist/sdk/type_utils.d.ts.map +1 -1
  60. package/dist/sdk/websocket_decompress_adapter.d.ts +5 -21
  61. package/dist/sdk/websocket_decompress_adapter.d.ts.map +1 -1
  62. package/dist/sdk/websocket_protocols.d.ts +6 -0
  63. package/dist/sdk/websocket_protocols.d.ts.map +1 -0
  64. package/dist/sdk/websocket_test_adapter.d.ts +14 -18
  65. package/dist/sdk/websocket_test_adapter.d.ts.map +1 -1
  66. package/dist/sdk/websocket_v3_frames.d.ts +9 -0
  67. package/dist/sdk/websocket_v3_frames.d.ts.map +1 -0
  68. package/dist/sdk/ws.d.ts +26 -1
  69. package/dist/sdk/ws.d.ts.map +1 -1
  70. package/dist/server/http_internal.d.ts.map +1 -1
  71. package/dist/server/index.d.ts +1 -1
  72. package/dist/server/index.d.ts.map +1 -1
  73. package/dist/server/index.mjs +53 -6
  74. package/dist/server/index.mjs.map +1 -1
  75. package/dist/server/runtime.d.ts +29 -2
  76. package/dist/server/runtime.d.ts.map +1 -1
  77. package/dist/svelte/index.cjs +7 -2
  78. package/dist/svelte/index.cjs.map +1 -1
  79. package/dist/svelte/index.mjs +7 -2
  80. package/dist/svelte/index.mjs.map +1 -1
  81. package/dist/tanstack/index.cjs +7 -2
  82. package/dist/tanstack/index.cjs.map +1 -1
  83. package/dist/tanstack/index.mjs +7 -2
  84. package/dist/tanstack/index.mjs.map +1 -1
  85. package/dist/vue/index.cjs +7 -2
  86. package/dist/vue/index.cjs.map +1 -1
  87. package/dist/vue/index.mjs +7 -2
  88. package/dist/vue/index.mjs.map +1 -1
  89. package/package.json +2 -2
  90. package/src/lib/binary_reader.ts +5 -2
  91. package/src/lib/binary_writer.ts +7 -1
  92. package/src/lib/filter.ts +12 -1
  93. package/src/lib/table.ts +9 -1
  94. package/src/react/index.ts +1 -0
  95. package/src/react/useProcedure.ts +60 -0
  96. package/src/react/useTable.ts +17 -2
  97. package/src/sdk/db_connection_builder.ts +16 -7
  98. package/src/sdk/db_connection_impl.ts +404 -89
  99. package/src/sdk/decompress.ts +7 -23
  100. package/src/sdk/table_cache.ts +5 -5
  101. package/src/sdk/type_utils.ts +10 -1
  102. package/src/sdk/websocket_decompress_adapter.ts +15 -77
  103. package/src/sdk/websocket_protocols.ts +25 -0
  104. package/src/sdk/websocket_test_adapter.ts +65 -29
  105. package/src/sdk/websocket_v3_frames.ts +126 -0
  106. package/src/sdk/ws.ts +81 -3
  107. package/src/server/http_internal.ts +10 -1
  108. package/src/server/index.ts +1 -1
  109. package/src/server/runtime.ts +39 -1
  110. package/src/server/sys.d.ts +4 -0
@@ -430,8 +430,8 @@ var BinaryReader = class {
430
430
  this.view = input instanceof DataView ? input : new DataView(input.buffer, input.byteOffset, input.byteLength);
431
431
  this.offset = 0;
432
432
  }
433
- reset(view) {
434
- this.view = view;
433
+ reset(input) {
434
+ this.view = input instanceof DataView ? input : new DataView(input.buffer, input.byteOffset, input.byteLength);
435
435
  this.offset = 0;
436
436
  }
437
437
  get remaining() {
@@ -623,6 +623,11 @@ var BinaryWriter = class {
623
623
  this.view.setUint8(this.offset, value);
624
624
  this.offset += 1;
625
625
  }
626
+ writeBytes(value) {
627
+ this.expandBuffer(value.length);
628
+ new Uint8Array(this.buffer.buffer, this.offset, value.length).set(value);
629
+ this.offset += value.length;
630
+ }
626
631
  writeI8(value) {
627
632
  this.expandBuffer(1);
628
633
  this.view.setInt8(this.offset, value);
@@ -4332,6 +4337,7 @@ var scalarCompare = (x, y) => {
4332
4337
  return x < y ? -1 : 1;
4333
4338
  };
4334
4339
  var TableCacheImpl = class {
4340
+ hasPrimaryKey;
4335
4341
  rows;
4336
4342
  tableDef;
4337
4343
  emitter;
@@ -4345,6 +4351,9 @@ var TableCacheImpl = class {
4345
4351
  this.tableDef = tableDef;
4346
4352
  this.rows = /* @__PURE__ */ new Map();
4347
4353
  this.emitter = new EventEmitter();
4354
+ this.hasPrimaryKey = Object.values(this.tableDef.columns).some(
4355
+ (col) => col.columnMetadata.isPrimaryKey === true
4356
+ );
4348
4357
  for (const idxDef of this.tableDef.resolvedIndexes) {
4349
4358
  const index = this.#makeReadonlyIndex(this.tableDef, idxDef);
4350
4359
  this[idxDef.name] = index;
@@ -4454,10 +4463,7 @@ var TableCacheImpl = class {
4454
4463
  }
4455
4464
  return pendingCallbacks;
4456
4465
  }
4457
- const hasPrimaryKey = Object.values(this.tableDef.columns).some(
4458
- (col) => col.columnMetadata.isPrimaryKey === true
4459
- );
4460
- if (hasPrimaryKey) {
4466
+ if (this.hasPrimaryKey) {
4461
4467
  const insertMap = /* @__PURE__ */ new Map();
4462
4468
  const deleteMap = /* @__PURE__ */ new Map();
4463
4469
  for (const op of operations) {
@@ -4865,27 +4871,17 @@ async function decompress(buffer, type, chunkSize = 128 * 1024) {
4865
4871
  });
4866
4872
  const decompressionStream = new DecompressionStream(type);
4867
4873
  const decompressedStream = readableStream.pipeThrough(decompressionStream);
4868
- const reader = decompressedStream.getReader();
4869
4874
  const chunks = [];
4870
- let totalLength = 0;
4871
- let result;
4872
- while (!(result = await reader.read()).done) {
4873
- chunks.push(result.value);
4874
- totalLength += result.value.length;
4875
- }
4876
- const decompressedArray = new Uint8Array(totalLength);
4877
- let chunkOffset = 0;
4878
- for (const chunk of chunks) {
4879
- decompressedArray.set(chunk, chunkOffset);
4880
- chunkOffset += chunk.length;
4881
- }
4882
- return decompressedArray;
4875
+ for await (const chunk of decompressedStream) {
4876
+ chunks.push(chunk);
4877
+ }
4878
+ return new Blob(chunks).bytes();
4883
4879
  }
4884
4880
 
4885
4881
  // src/sdk/ws.ts
4886
4882
  async function resolveWS() {
4887
- if (typeof globalThis.WebSocket !== "undefined") {
4888
- return globalThis.WebSocket;
4883
+ if (typeof WebSocket !== "undefined") {
4884
+ return WebSocket;
4889
4885
  }
4890
4886
  const dynamicImport = new Function("m", "return import(m)");
4891
4887
  try {
@@ -4899,9 +4895,54 @@ async function resolveWS() {
4899
4895
  throw err;
4900
4896
  }
4901
4897
  }
4898
+ async function openWebSocket({
4899
+ url,
4900
+ nameOrAddress,
4901
+ wsProtocol,
4902
+ authToken,
4903
+ compression,
4904
+ lightMode,
4905
+ confirmedReads
4906
+ }) {
4907
+ const headers = new Headers();
4908
+ const WS = await resolveWS();
4909
+ let temporaryAuthToken;
4910
+ if (authToken) {
4911
+ headers.set("Authorization", `Bearer ${authToken}`);
4912
+ const tokenUrl = new URL("v1/identity/websocket-token", url);
4913
+ tokenUrl.protocol = url.protocol === "wss:" ? "https:" : "http:";
4914
+ const response = await fetch(tokenUrl, { method: "POST", headers });
4915
+ if (response.ok) {
4916
+ const { token } = await response.json();
4917
+ temporaryAuthToken = token;
4918
+ } else {
4919
+ throw new Error(`Failed to verify token: ${response.statusText}`);
4920
+ }
4921
+ }
4922
+ const databaseUrl = new URL(`v1/database/${nameOrAddress}/subscribe`, url);
4923
+ if (temporaryAuthToken) {
4924
+ databaseUrl.searchParams.set("token", temporaryAuthToken);
4925
+ }
4926
+ databaseUrl.searchParams.set(
4927
+ "compression",
4928
+ { gzip: "Gzip", brotli: "Brotli", none: "None" }[compression] ?? "None"
4929
+ );
4930
+ if (lightMode) {
4931
+ databaseUrl.searchParams.set("light", "true");
4932
+ }
4933
+ if (confirmedReads !== void 0) {
4934
+ databaseUrl.searchParams.set("confirmed", confirmedReads.toString());
4935
+ }
4936
+ const ws = new WS(databaseUrl.toString(), wsProtocol);
4937
+ ws.binaryType = "arraybuffer";
4938
+ return ws;
4939
+ }
4902
4940
 
4903
4941
  // src/sdk/websocket_decompress_adapter.ts
4904
4942
  var WebsocketDecompressAdapter = class _WebsocketDecompressAdapter {
4943
+ get protocol() {
4944
+ return this.#ws.protocol;
4945
+ }
4905
4946
  set onclose(handler) {
4906
4947
  this.#ws.onclose = handler;
4907
4948
  }
@@ -4925,9 +4966,7 @@ var WebsocketDecompressAdapter = class _WebsocketDecompressAdapter {
4925
4966
  case 0:
4926
4967
  return data;
4927
4968
  case 1:
4928
- throw new Error(
4929
- "Brotli Compression not supported. Please use gzip or none compression in withCompression method on DbConnection."
4930
- );
4969
+ return await decompress(data, "brotli");
4931
4970
  case 2:
4932
4971
  return await decompress(data, "gzip");
4933
4972
  default:
@@ -4943,51 +4982,10 @@ var WebsocketDecompressAdapter = class _WebsocketDecompressAdapter {
4943
4982
  this.#ws.close();
4944
4983
  }
4945
4984
  constructor(ws) {
4946
- ws.binaryType = "arraybuffer";
4947
4985
  this.#ws = ws;
4948
4986
  }
4949
- static async createWebSocketFn({
4950
- url,
4951
- nameOrAddress,
4952
- wsProtocol,
4953
- authToken,
4954
- compression,
4955
- lightMode,
4956
- confirmedReads
4957
- }) {
4958
- const headers = new Headers();
4959
- const WS = await resolveWS();
4960
- let temporaryAuthToken = void 0;
4961
- if (authToken) {
4962
- headers.set("Authorization", `Bearer ${authToken}`);
4963
- const tokenUrl = new URL("v1/identity/websocket-token", url);
4964
- tokenUrl.protocol = url.protocol === "wss:" ? "https:" : "http:";
4965
- const response = await fetch(tokenUrl, { method: "POST", headers });
4966
- if (response.ok) {
4967
- const { token } = await response.json();
4968
- temporaryAuthToken = token;
4969
- } else {
4970
- return Promise.reject(
4971
- new Error(`Failed to verify token: ${response.statusText}`)
4972
- );
4973
- }
4974
- }
4975
- const databaseUrl = new URL(`v1/database/${nameOrAddress}/subscribe`, url);
4976
- if (temporaryAuthToken) {
4977
- databaseUrl.searchParams.set("token", temporaryAuthToken);
4978
- }
4979
- databaseUrl.searchParams.set(
4980
- "compression",
4981
- compression === "gzip" ? "Gzip" : "None"
4982
- );
4983
- if (lightMode) {
4984
- databaseUrl.searchParams.set("light", "true");
4985
- }
4986
- if (confirmedReads !== void 0) {
4987
- databaseUrl.searchParams.set("confirmed", confirmedReads.toString());
4988
- }
4989
- const ws = new WS(databaseUrl.toString(), wsProtocol);
4990
- return new _WebsocketDecompressAdapter(ws);
4987
+ static async openWebSocket(args) {
4988
+ return new _WebsocketDecompressAdapter(await openWebSocket(args));
4991
4989
  }
4992
4990
  };
4993
4991
 
@@ -5004,7 +5002,7 @@ var DbConnectionBuilder = class {
5004
5002
  constructor(remoteModule, dbConnectionCtor) {
5005
5003
  this.remoteModule = remoteModule;
5006
5004
  this.dbConnectionCtor = dbConnectionCtor;
5007
- this.#createWSFn = WebsocketDecompressAdapter.createWebSocketFn;
5005
+ this.#createWSFn = WebsocketDecompressAdapter.openWebSocket;
5008
5006
  }
5009
5007
  #uri;
5010
5008
  #nameOrAddress;
@@ -5059,6 +5057,16 @@ var DbConnectionBuilder = class {
5059
5057
  * @param compression The compression algorithm to use for the connection.
5060
5058
  */
5061
5059
  withCompression(compression) {
5060
+ if (compression === "brotli") {
5061
+ try {
5062
+ new DecompressionStream("brotli");
5063
+ } catch (e) {
5064
+ throw new TypeError(
5065
+ `Brotli compression is not supported by the runtime. Please choose a different compression method.`,
5066
+ { cause: e }
5067
+ );
5068
+ }
5069
+ }
5062
5070
  this.#compression = compression;
5063
5071
  return this;
5064
5072
  }
@@ -5422,6 +5430,100 @@ var SubscriptionHandleImpl = class {
5422
5430
  return this.#activeState;
5423
5431
  }
5424
5432
  };
5433
+
5434
+ // src/sdk/websocket_protocols.ts
5435
+ var V2_WS_PROTOCOL = "v2.bsatn.spacetimedb";
5436
+ var V3_WS_PROTOCOL = "v3.bsatn.spacetimedb";
5437
+ var PREFERRED_WS_PROTOCOLS = [V3_WS_PROTOCOL, V2_WS_PROTOCOL];
5438
+ function normalizeWsProtocol(protocol) {
5439
+ if (protocol === V3_WS_PROTOCOL) {
5440
+ return V3_WS_PROTOCOL;
5441
+ }
5442
+ if (protocol === "" || protocol === V2_WS_PROTOCOL) {
5443
+ return V2_WS_PROTOCOL;
5444
+ }
5445
+ stdbLogger(
5446
+ "warn",
5447
+ `Unexpected websocket subprotocol "${protocol}", falling back to ${V2_WS_PROTOCOL}.`
5448
+ );
5449
+ return V2_WS_PROTOCOL;
5450
+ }
5451
+
5452
+ // src/sdk/websocket_v3_frames.ts
5453
+ var EMPTY_V3_PAYLOAD_ERR = "v3 websocket payloads must contain at least one message";
5454
+ function ensureMessages(messages) {
5455
+ if (messages.length === 0) {
5456
+ throw new RangeError(EMPTY_V3_PAYLOAD_ERR);
5457
+ }
5458
+ }
5459
+ function ensureMessageCount(messages, messageCount) {
5460
+ ensureMessages(messages);
5461
+ if (messageCount < 1 || messageCount > messages.length) {
5462
+ throw new RangeError(
5463
+ `v3 websocket payload requested ${messageCount} messages from ${messages.length}`
5464
+ );
5465
+ }
5466
+ }
5467
+ function concatenateMessagesV3(writer, messages, messageCount = messages.length) {
5468
+ ensureMessageCount(messages, messageCount);
5469
+ writer.clear();
5470
+ for (let i = 0; i < messageCount; i++) {
5471
+ writer.writeBytes(messages[i]);
5472
+ }
5473
+ return writer.getBuffer();
5474
+ }
5475
+ function countClientMessagesForV3Frame(messages, maxFrameBytes) {
5476
+ ensureMessages(messages);
5477
+ const firstMessage = messages[0];
5478
+ if (firstMessage.length > maxFrameBytes) {
5479
+ return 1;
5480
+ }
5481
+ let count = 1;
5482
+ let frameSize = firstMessage.length;
5483
+ while (count < messages.length) {
5484
+ const nextMessage = messages[count];
5485
+ const nextFrameSize = frameSize + nextMessage.length;
5486
+ if (nextFrameSize > maxFrameBytes) {
5487
+ break;
5488
+ }
5489
+ frameSize = nextFrameSize;
5490
+ count += 1;
5491
+ }
5492
+ return count;
5493
+ }
5494
+ function encodeClientMessagesV3(writer, messages, messageCount = messages.length) {
5495
+ return concatenateMessagesV3(writer, messages, messageCount);
5496
+ }
5497
+ function forEachServerMessageV3(reader, data, visit) {
5498
+ reader.reset(data);
5499
+ if (reader.remaining === 0) {
5500
+ throw new RangeError(EMPTY_V3_PAYLOAD_ERR);
5501
+ }
5502
+ let count = 0;
5503
+ while (reader.remaining > 0) {
5504
+ visit(ServerMessage.deserialize(reader));
5505
+ count += 1;
5506
+ }
5507
+ return count;
5508
+ }
5509
+
5510
+ // src/sdk/db_connection_impl.ts
5511
+ var TEXT_ENCODER = new TextEncoder();
5512
+ function getClientMessageVariantTag(name) {
5513
+ if (ClientMessage.algebraicType.tag !== "Sum") {
5514
+ throw new TypeError("ClientMessage must be a sum type");
5515
+ }
5516
+ const tag = ClientMessage.algebraicType.value.variants.findIndex(
5517
+ (variant) => variant.name === name
5518
+ );
5519
+ if (tag === -1) {
5520
+ throw new RangeError(`Unknown ClientMessage variant: ${name}`);
5521
+ }
5522
+ return tag;
5523
+ }
5524
+ var CLIENT_MESSAGE_CALL_REDUCER_TAG = getClientMessageVariantTag("CallReducer");
5525
+ var CLIENT_MESSAGE_CALL_PROCEDURE_TAG = getClientMessageVariantTag("CallProcedure");
5526
+ var MAX_V3_OUTBOUND_FRAME_BYTES = 256 * 1024;
5425
5527
  var DbConnectionImpl = class {
5426
5528
  /**
5427
5529
  * Whether or not the connection is active.
@@ -5456,22 +5558,35 @@ var DbConnectionImpl = class {
5456
5558
  * The `ConnectionId` of the connection to to the database.
5457
5559
  */
5458
5560
  connectionId = ConnectionId.random();
5561
+ #connectionIdHex = this.connectionId.toHexString();
5459
5562
  // These fields are meant to be strictly private.
5460
5563
  #queryId = 0;
5461
5564
  #requestId = 0;
5462
5565
  #eventId = 0;
5463
5566
  #emitter;
5464
- #messageQueue = Promise.resolve();
5567
+ #inboundQueue = [];
5568
+ #inboundQueueOffset = 0;
5569
+ #isDrainingInboundQueue = false;
5465
5570
  #outboundQueue = [];
5571
+ #isOutboundFlushScheduled = false;
5572
+ #negotiatedWsProtocol = V2_WS_PROTOCOL;
5466
5573
  #subscriptionManager = new SubscriptionManager();
5467
5574
  #remoteModule;
5468
5575
  #reducerCallbacks = /* @__PURE__ */ new Map();
5469
5576
  #reducerCallInfo = /* @__PURE__ */ new Map();
5470
5577
  #procedureCallbacks = /* @__PURE__ */ new Map();
5471
5578
  #rowDeserializers;
5579
+ #rowIdMetadata;
5472
5580
  #reducerArgsSerializers;
5473
5581
  #procedureSerializers;
5582
+ #reducerNameBytes;
5583
+ #procedureNameBytes;
5474
5584
  #sourceNameToTableDef;
5585
+ #messageReader = new BinaryReader(new Uint8Array());
5586
+ #rowListReader = new BinaryReader(new Uint8Array());
5587
+ #clientFrameEncoder = new BinaryWriter(1024);
5588
+ #boundSubscriptionBuilder;
5589
+ #boundDisconnect;
5475
5590
  // These fields are not part of the public API, but in a pinch you
5476
5591
  // could use JavaScript to access them by bypassing TypeScript's
5477
5592
  // private fields.
@@ -5500,22 +5615,35 @@ var DbConnectionImpl = class {
5500
5615
  this.token = token;
5501
5616
  this.#remoteModule = remoteModule;
5502
5617
  this.#emitter = emitter;
5618
+ this.#boundSubscriptionBuilder = this.subscriptionBuilder.bind(this);
5619
+ this.#boundDisconnect = this.disconnect.bind(this);
5503
5620
  this.#rowDeserializers = /* @__PURE__ */ Object.create(null);
5621
+ this.#rowIdMetadata = /* @__PURE__ */ Object.create(null);
5504
5622
  this.#sourceNameToTableDef = /* @__PURE__ */ Object.create(null);
5505
5623
  for (const table2 of Object.values(remoteModule.tables)) {
5506
5624
  this.#rowDeserializers[table2.sourceName] = ProductType.makeDeserializer(
5507
5625
  table2.rowType
5508
5626
  );
5509
5627
  this.#sourceNameToTableDef[table2.sourceName] = table2;
5628
+ const primaryKeyColumn = Object.entries(table2.columns).find(
5629
+ ([, column]) => column.columnMetadata.isPrimaryKey
5630
+ );
5631
+ this.#rowIdMetadata[table2.sourceName] = primaryKeyColumn ? {
5632
+ primaryKeyColName: primaryKeyColumn[0],
5633
+ primaryKeyColType: primaryKeyColumn[1].typeBuilder.algebraicType
5634
+ } : {};
5510
5635
  }
5511
5636
  this.#reducerArgsSerializers = /* @__PURE__ */ Object.create(null);
5637
+ this.#reducerNameBytes = /* @__PURE__ */ Object.create(null);
5512
5638
  for (const reducer of remoteModule.reducers) {
5513
5639
  this.#reducerArgsSerializers[reducer.name] = {
5514
5640
  serialize: ProductType.makeSerializer(reducer.paramsType),
5515
5641
  deserialize: ProductType.makeDeserializer(reducer.paramsType)
5516
5642
  };
5643
+ this.#reducerNameBytes[reducer.name] = TEXT_ENCODER.encode(reducer.name);
5517
5644
  }
5518
5645
  this.#procedureSerializers = /* @__PURE__ */ Object.create(null);
5646
+ this.#procedureNameBytes = /* @__PURE__ */ Object.create(null);
5519
5647
  for (const procedure of remoteModule.procedures) {
5520
5648
  this.#procedureSerializers[procedure.name] = {
5521
5649
  serializeArgs: ProductType.makeSerializer(
@@ -5525,9 +5653,11 @@ var DbConnectionImpl = class {
5525
5653
  procedure.returnType.algebraicType
5526
5654
  )
5527
5655
  };
5656
+ this.#procedureNameBytes[procedure.name] = TEXT_ENCODER.encode(
5657
+ procedure.name
5658
+ );
5528
5659
  }
5529
- const connectionId = this.connectionId.toHexString();
5530
- url.searchParams.set("connection_id", connectionId);
5660
+ url.searchParams.set("connection_id", this.#connectionIdHex);
5531
5661
  this.clientCache = new ClientCache();
5532
5662
  this.db = this.#makeDbView();
5533
5663
  this.reducers = this.#makeReducers(remoteModule);
@@ -5535,7 +5665,7 @@ var DbConnectionImpl = class {
5535
5665
  this.wsPromise = createWSFn({
5536
5666
  url,
5537
5667
  nameOrAddress,
5538
- wsProtocol: "v2.bsatn.spacetimedb",
5668
+ wsProtocol: [...PREFERRED_WS_PROTOCOLS],
5539
5669
  authToken: token,
5540
5670
  compression,
5541
5671
  lightMode,
@@ -5579,16 +5709,22 @@ var DbConnectionImpl = class {
5579
5709
  }
5580
5710
  #makeReducers(def) {
5581
5711
  const out = {};
5582
- const writer = new BinaryWriter(1024);
5583
5712
  for (const reducer of def.reducers) {
5584
5713
  const reducerName = reducer.name;
5714
+ const encodedReducerName = this.#reducerNameBytes[reducerName];
5585
5715
  const key = reducer.accessorName;
5586
5716
  const { serialize: serializeArgs } = this.#reducerArgsSerializers[reducerName];
5587
5717
  out[key] = (params) => {
5718
+ const writer = this.#reducerArgsEncoder;
5588
5719
  writer.clear();
5589
5720
  serializeArgs(writer, params);
5590
5721
  const argsBuffer = writer.getBuffer();
5591
- return this.callReducer(reducerName, argsBuffer, params);
5722
+ return this.#callReducerWithEncodedName(
5723
+ reducerName,
5724
+ encodedReducerName,
5725
+ argsBuffer,
5726
+ params
5727
+ );
5592
5728
  };
5593
5729
  }
5594
5730
  return out;
@@ -5598,13 +5734,18 @@ var DbConnectionImpl = class {
5598
5734
  const writer = new BinaryWriter(1024);
5599
5735
  for (const procedure of def.procedures) {
5600
5736
  const procedureName = procedure.name;
5737
+ const encodedProcedureName = this.#procedureNameBytes[procedureName];
5601
5738
  const key = procedure.accessorName;
5602
5739
  const { serializeArgs, deserializeReturn } = this.#procedureSerializers[procedureName];
5603
5740
  out[key] = (params) => {
5604
5741
  writer.clear();
5605
5742
  serializeArgs(writer, params);
5606
5743
  const argsBuffer = writer.getBuffer();
5607
- return this.callProcedure(procedureName, argsBuffer).then((returnBuf) => {
5744
+ return this.#callProcedureWithEncodedName(
5745
+ procedureName,
5746
+ encodedProcedureName,
5747
+ argsBuffer
5748
+ ).then((returnBuf) => {
5608
5749
  return deserializeReturn(new BinaryReader(returnBuf));
5609
5750
  });
5610
5751
  };
@@ -5616,8 +5757,8 @@ var DbConnectionImpl = class {
5616
5757
  db: this.db,
5617
5758
  reducers: this.reducers,
5618
5759
  isActive: this.isActive,
5619
- subscriptionBuilder: this.subscriptionBuilder.bind(this),
5620
- disconnect: this.disconnect.bind(this),
5760
+ subscriptionBuilder: this.#boundSubscriptionBuilder,
5761
+ disconnect: this.#boundDisconnect,
5621
5762
  event
5622
5763
  };
5623
5764
  }
@@ -5662,21 +5803,16 @@ var DbConnectionImpl = class {
5662
5803
  }
5663
5804
  #parseRowList(type, tableName, rowList) {
5664
5805
  const buffer = rowList.rowsData;
5665
- const reader = new BinaryReader(buffer);
5806
+ const reader = this.#rowListReader;
5807
+ reader.reset(buffer);
5666
5808
  const rows = [];
5667
5809
  const deserializeRow = this.#rowDeserializers[tableName];
5668
- const table2 = this.#sourceNameToTableDef[tableName];
5669
- const columnsArray = Object.entries(table2.columns);
5670
- const primaryKeyColumnEntry = columnsArray.find(
5671
- (col) => col[1].columnMetadata.isPrimaryKey
5672
- );
5810
+ const { primaryKeyColName, primaryKeyColType } = this.#rowIdMetadata[tableName];
5673
5811
  let previousOffset = 0;
5674
5812
  while (reader.remaining > 0) {
5675
5813
  const row = deserializeRow(reader);
5676
5814
  let rowId = void 0;
5677
- if (primaryKeyColumnEntry !== void 0) {
5678
- const primaryKeyColName = primaryKeyColumnEntry[0];
5679
- const primaryKeyColType = primaryKeyColumnEntry[1].typeBuilder.algebraicType;
5815
+ if (primaryKeyColName !== void 0 && primaryKeyColType !== void 0) {
5680
5816
  rowId = AlgebraicType.intoMapKey(
5681
5817
  primaryKeyColType,
5682
5818
  row[primaryKeyColName]
@@ -5757,40 +5893,136 @@ var DbConnectionImpl = class {
5757
5893
  return this.#mergeTableUpdates(updates);
5758
5894
  }
5759
5895
  #flushOutboundQueue(wsResolved) {
5896
+ if (this.#negotiatedWsProtocol === V3_WS_PROTOCOL) {
5897
+ this.#flushOutboundQueueV3(wsResolved);
5898
+ return;
5899
+ }
5900
+ this.#flushOutboundQueueV2(wsResolved);
5901
+ }
5902
+ #flushOutboundQueueV2(wsResolved) {
5760
5903
  const pending = this.#outboundQueue.splice(0);
5761
5904
  for (const message of pending) {
5762
5905
  wsResolved.send(message);
5763
5906
  }
5764
5907
  }
5908
+ #flushOutboundQueueV3(wsResolved) {
5909
+ if (this.#outboundQueue.length === 0) {
5910
+ return;
5911
+ }
5912
+ const batchSize = countClientMessagesForV3Frame(
5913
+ this.#outboundQueue,
5914
+ MAX_V3_OUTBOUND_FRAME_BYTES
5915
+ );
5916
+ wsResolved.send(
5917
+ encodeClientMessagesV3(
5918
+ this.#clientFrameEncoder,
5919
+ this.#outboundQueue,
5920
+ batchSize
5921
+ )
5922
+ );
5923
+ if (batchSize === this.#outboundQueue.length) {
5924
+ this.#outboundQueue.length = 0;
5925
+ return;
5926
+ }
5927
+ this.#outboundQueue.copyWithin(0, batchSize);
5928
+ this.#outboundQueue.length -= batchSize;
5929
+ if (this.#outboundQueue.length > 0) {
5930
+ this.#scheduleDeferredOutboundFlush();
5931
+ }
5932
+ }
5933
+ #scheduleOutboundFlush() {
5934
+ this.#scheduleOutboundFlushWith("microtask");
5935
+ }
5936
+ #scheduleDeferredOutboundFlush() {
5937
+ this.#scheduleOutboundFlushWith("next-task");
5938
+ }
5939
+ #scheduleOutboundFlushWith(schedule) {
5940
+ if (this.#isOutboundFlushScheduled) {
5941
+ return;
5942
+ }
5943
+ this.#isOutboundFlushScheduled = true;
5944
+ const flush = () => {
5945
+ this.#isOutboundFlushScheduled = false;
5946
+ if (this.ws && this.isActive) {
5947
+ this.#flushOutboundQueue(this.ws);
5948
+ }
5949
+ };
5950
+ if (schedule === "next-task") {
5951
+ setTimeout(flush, 0);
5952
+ } else {
5953
+ queueMicrotask(flush);
5954
+ }
5955
+ }
5956
+ #reducerArgsEncoder = new BinaryWriter(1024);
5765
5957
  #clientMessageEncoder = new BinaryWriter(1024);
5958
+ #sendEncodedMessage(encoded, describe) {
5959
+ stdbLogger("trace", describe);
5960
+ if (this.ws && this.isActive) {
5961
+ if (this.#negotiatedWsProtocol === V2_WS_PROTOCOL) {
5962
+ if (this.#outboundQueue.length) this.#flushOutboundQueue(this.ws);
5963
+ this.ws.send(encoded);
5964
+ return;
5965
+ }
5966
+ this.#outboundQueue.push(encoded.slice());
5967
+ this.#scheduleOutboundFlush();
5968
+ } else {
5969
+ this.#outboundQueue.push(encoded.slice());
5970
+ }
5971
+ }
5766
5972
  #sendMessage(message) {
5767
5973
  const writer = this.#clientMessageEncoder;
5768
5974
  writer.clear();
5769
5975
  ClientMessage.serialize(writer, message);
5770
5976
  const encoded = writer.getBuffer();
5771
- if (this.ws && this.isActive) {
5772
- if (this.#outboundQueue.length) this.#flushOutboundQueue(this.ws);
5773
- stdbLogger(
5774
- "trace",
5775
- () => `Sending message to server: ${stringify(message)}`
5776
- );
5777
- this.ws.send(encoded);
5778
- } else {
5779
- stdbLogger(
5780
- "trace",
5781
- () => `Queuing message to server: ${stringify(message)}`
5782
- );
5783
- this.#outboundQueue.push(encoded.slice());
5784
- }
5977
+ const isLive = !!(this.ws && this.isActive);
5978
+ this.#sendEncodedMessage(
5979
+ encoded,
5980
+ () => isLive ? `Sending message to server: ${stringify(message)}` : `Queuing message to server: ${stringify(message)}`
5981
+ );
5982
+ }
5983
+ #sendCallReducerMessage(requestId, reducerNameBytes, argsBuffer) {
5984
+ const writer = this.#clientMessageEncoder;
5985
+ writer.clear();
5986
+ writer.writeByte(CLIENT_MESSAGE_CALL_REDUCER_TAG);
5987
+ writer.writeU32(requestId);
5988
+ writer.writeU8(0);
5989
+ writer.writeUInt8Array(reducerNameBytes);
5990
+ writer.writeUInt8Array(argsBuffer);
5991
+ const encoded = writer.getBuffer();
5992
+ this.#sendEncodedMessage(
5993
+ encoded,
5994
+ () => `Sending reducer call message to server: requestId=${requestId}`
5995
+ );
5996
+ }
5997
+ #sendCallProcedureMessage(requestId, procedureNameBytes, argsBuffer) {
5998
+ const writer = this.#clientMessageEncoder;
5999
+ writer.clear();
6000
+ writer.writeByte(CLIENT_MESSAGE_CALL_PROCEDURE_TAG);
6001
+ writer.writeU32(requestId);
6002
+ writer.writeU8(0);
6003
+ writer.writeUInt8Array(procedureNameBytes);
6004
+ writer.writeUInt8Array(argsBuffer);
6005
+ const encoded = writer.getBuffer();
6006
+ this.#sendEncodedMessage(
6007
+ encoded,
6008
+ () => `Sending procedure call message to server: requestId=${requestId}`
6009
+ );
6010
+ }
6011
+ #setConnectionId(connectionId) {
6012
+ this.connectionId = connectionId;
6013
+ this.#connectionIdHex = connectionId.toHexString();
5785
6014
  }
5786
6015
  #nextEventId() {
5787
6016
  this.#eventId += 1;
5788
- return `${this.connectionId.toHexString()}:${this.#eventId}`;
6017
+ return `${this.#connectionIdHex}:${this.#eventId}`;
5789
6018
  }
5790
6019
  /**
5791
6020
  * Handles WebSocket onOpen event.
5792
6021
  */
5793
6022
  #handleOnOpen() {
6023
+ if (this.ws) {
6024
+ this.#negotiatedWsProtocol = normalizeWsProtocol(this.ws.protocol);
6025
+ }
5794
6026
  this.isActive = true;
5795
6027
  if (this.ws) {
5796
6028
  this.#flushOutboundQueue(this.ws);
@@ -5825,8 +6057,16 @@ var DbConnectionImpl = class {
5825
6057
  eventContext
5826
6058
  );
5827
6059
  }
5828
- async #processMessage(data) {
5829
- const serverMessage = ServerMessage.deserialize(new BinaryReader(data));
6060
+ #dispatchPendingCallbacks(callbacks) {
6061
+ stdbLogger(
6062
+ "trace",
6063
+ () => `Calling ${callbacks.length} triggered row callbacks`
6064
+ );
6065
+ for (const callback of callbacks) {
6066
+ callback.cb();
6067
+ }
6068
+ }
6069
+ #processServerMessage(serverMessage) {
5830
6070
  stdbLogger(
5831
6071
  "trace",
5832
6072
  () => `Processing server message: ${stringify(serverMessage)}`
@@ -5837,7 +6077,7 @@ var DbConnectionImpl = class {
5837
6077
  if (!this.token && serverMessage.value.token) {
5838
6078
  this.token = serverMessage.value.token;
5839
6079
  }
5840
- this.connectionId = serverMessage.value.connectionId;
6080
+ this.#setConnectionId(serverMessage.value.connectionId);
5841
6081
  this.#emitter.emit("connect", this, this.identity, this.token);
5842
6082
  break;
5843
6083
  }
@@ -5863,13 +6103,7 @@ var DbConnectionImpl = class {
5863
6103
  const callbacks = this.#applyTableUpdates(tableUpdates, eventContext);
5864
6104
  const { event: _, ...subscriptionEventContext } = eventContext;
5865
6105
  subscription.emitter.emit("applied", subscriptionEventContext);
5866
- stdbLogger(
5867
- "trace",
5868
- () => `Calling ${callbacks.length} triggered row callbacks`
5869
- );
5870
- for (const callback of callbacks) {
5871
- callback.cb();
5872
- }
6106
+ this.#dispatchPendingCallbacks(callbacks);
5873
6107
  break;
5874
6108
  }
5875
6109
  case "UnsubscribeApplied": {
@@ -5892,13 +6126,7 @@ var DbConnectionImpl = class {
5892
6126
  const { event: _, ...subscriptionEventContext } = eventContext;
5893
6127
  subscription.emitter.emit("end", subscriptionEventContext);
5894
6128
  this.#subscriptionManager.subscriptions.delete(querySetId);
5895
- stdbLogger(
5896
- "trace",
5897
- () => `Calling ${callbacks.length} triggered row callbacks`
5898
- );
5899
- for (const callback of callbacks) {
5900
- callback.cb();
5901
- }
6129
+ this.#dispatchPendingCallbacks(callbacks);
5902
6130
  break;
5903
6131
  }
5904
6132
  case "SubscriptionError": {
@@ -5946,13 +6174,7 @@ var DbConnectionImpl = class {
5946
6174
  eventContext,
5947
6175
  serverMessage.value
5948
6176
  );
5949
- stdbLogger(
5950
- "trace",
5951
- () => `Calling ${callbacks.length} triggered row callbacks`
5952
- );
5953
- for (const callback of callbacks) {
5954
- callback.cb();
5955
- }
6177
+ this.#dispatchPendingCallbacks(callbacks);
5956
6178
  break;
5957
6179
  }
5958
6180
  case "ReducerResult": {
@@ -5980,13 +6202,7 @@ var DbConnectionImpl = class {
5980
6202
  eventContext,
5981
6203
  result.value.transactionUpdate
5982
6204
  );
5983
- stdbLogger(
5984
- "trace",
5985
- () => `Calling ${callbacks.length} triggered row callbacks`
5986
- );
5987
- for (const callback of callbacks) {
5988
- callback.cb();
5989
- }
6205
+ this.#dispatchPendingCallbacks(callbacks);
5990
6206
  }
5991
6207
  this.#reducerCallInfo.delete(requestId);
5992
6208
  const cb = this.#reducerCallbacks.get(requestId);
@@ -6011,14 +6227,55 @@ var DbConnectionImpl = class {
6011
6227
  }
6012
6228
  }
6013
6229
  }
6230
+ #processV2Message(data) {
6231
+ const reader = this.#messageReader;
6232
+ reader.reset(data);
6233
+ this.#processServerMessage(ServerMessage.deserialize(reader));
6234
+ }
6235
+ #processMessage(data) {
6236
+ if (this.#negotiatedWsProtocol !== V3_WS_PROTOCOL) {
6237
+ this.#processV2Message(data);
6238
+ return;
6239
+ }
6240
+ const messageCount = forEachServerMessageV3(
6241
+ this.#messageReader,
6242
+ data,
6243
+ (serverMessage) => {
6244
+ this.#processServerMessage(serverMessage);
6245
+ }
6246
+ );
6247
+ stdbLogger(
6248
+ "trace",
6249
+ () => `Processing server v3 payload with ${messageCount} message(s)`
6250
+ );
6251
+ }
6014
6252
  /**
6015
6253
  * Handles WebSocket onMessage event.
6016
6254
  * @param wsMessage MessageEvent object.
6017
6255
  */
6018
6256
  #handleOnMessage(wsMessage) {
6019
- this.#messageQueue = this.#messageQueue.then(() => {
6020
- return this.#processMessage(wsMessage.data);
6021
- });
6257
+ this.#inboundQueue.push(wsMessage.data);
6258
+ if (this.#isDrainingInboundQueue) {
6259
+ return;
6260
+ }
6261
+ this.#isDrainingInboundQueue = true;
6262
+ try {
6263
+ while (this.#inboundQueueOffset < this.#inboundQueue.length) {
6264
+ const data = this.#inboundQueue[this.#inboundQueueOffset];
6265
+ this.#inboundQueueOffset += 1;
6266
+ if (data) {
6267
+ this.#processMessage(data);
6268
+ }
6269
+ }
6270
+ } finally {
6271
+ if (this.#inboundQueueOffset >= this.#inboundQueue.length) {
6272
+ this.#inboundQueue.length = 0;
6273
+ } else if (this.#inboundQueueOffset > 0) {
6274
+ this.#inboundQueue = this.#inboundQueue.slice(this.#inboundQueueOffset);
6275
+ }
6276
+ this.#inboundQueueOffset = 0;
6277
+ this.#isDrainingInboundQueue = false;
6278
+ }
6022
6279
  }
6023
6280
  /**
6024
6281
  * Call a reducer on your SpacetimeDB module.
@@ -6027,6 +6284,45 @@ var DbConnectionImpl = class {
6027
6284
  * @param argsSerializer The arguments to pass to the reducer
6028
6285
  */
6029
6286
  callReducer(reducerName, argsBuffer, reducerArgs) {
6287
+ const encodedReducerName = this.#reducerNameBytes[reducerName];
6288
+ if (encodedReducerName) {
6289
+ return this.#callReducerWithEncodedName(
6290
+ reducerName,
6291
+ encodedReducerName,
6292
+ argsBuffer,
6293
+ reducerArgs
6294
+ );
6295
+ }
6296
+ return this.#callReducerGeneric(reducerName, argsBuffer, reducerArgs);
6297
+ }
6298
+ #callReducerWithEncodedName(reducerName, encodedReducerName, argsBuffer, reducerArgs) {
6299
+ const { promise, resolve, reject } = Promise.withResolvers();
6300
+ const requestId = this.#getNextRequestId();
6301
+ this.#sendCallReducerMessage(requestId, encodedReducerName, argsBuffer);
6302
+ if (reducerArgs) {
6303
+ this.#reducerCallInfo.set(requestId, {
6304
+ name: reducerName,
6305
+ args: reducerArgs
6306
+ });
6307
+ }
6308
+ this.#reducerCallbacks.set(requestId, (result) => {
6309
+ if (result.tag === "Ok" || result.tag === "OkEmpty") {
6310
+ resolve();
6311
+ } else {
6312
+ if (result.tag === "Err") {
6313
+ const reader = new BinaryReader(result.value);
6314
+ const errorString = reader.readString();
6315
+ reject(new SenderError(errorString));
6316
+ } else if (result.tag === "InternalError") {
6317
+ reject(new InternalError(result.value));
6318
+ } else {
6319
+ reject(new Error("Unexpected reducer result"));
6320
+ }
6321
+ }
6322
+ });
6323
+ return promise;
6324
+ }
6325
+ #callReducerGeneric(reducerName, argsBuffer, reducerArgs) {
6030
6326
  const { promise, resolve, reject } = Promise.withResolvers();
6031
6327
  const requestId = this.#getNextRequestId();
6032
6328
  const message = ClientMessage.CallReducer({
@@ -6066,7 +6362,8 @@ var DbConnectionImpl = class {
6066
6362
  * @param params The arguments to pass to the reducer
6067
6363
  */
6068
6364
  callReducerWithParams(reducerName, _paramsType, params) {
6069
- const writer = new BinaryWriter(1024);
6365
+ const writer = this.#reducerArgsEncoder;
6366
+ writer.clear();
6070
6367
  this.#reducerArgsSerializers[reducerName].serialize(writer, params);
6071
6368
  const argsBuffer = writer.getBuffer();
6072
6369
  return this.callReducer(reducerName, argsBuffer, params);
@@ -6078,6 +6375,30 @@ var DbConnectionImpl = class {
6078
6375
  * @param argsBuffer The arguments to pass to the reducer
6079
6376
  */
6080
6377
  callProcedure(procedureName, argsBuffer) {
6378
+ const encodedProcedureName = this.#procedureNameBytes[procedureName];
6379
+ if (encodedProcedureName) {
6380
+ return this.#callProcedureWithEncodedName(
6381
+ procedureName,
6382
+ encodedProcedureName,
6383
+ argsBuffer
6384
+ );
6385
+ }
6386
+ return this.#callProcedureGeneric(procedureName, argsBuffer);
6387
+ }
6388
+ #callProcedureWithEncodedName(procedureName, encodedProcedureName, argsBuffer) {
6389
+ const { promise, resolve, reject } = Promise.withResolvers();
6390
+ const requestId = this.#getNextRequestId();
6391
+ this.#sendCallProcedureMessage(requestId, encodedProcedureName, argsBuffer);
6392
+ this.#procedureCallbacks.set(requestId, (result) => {
6393
+ if (result.tag === "Ok") {
6394
+ resolve(result.value);
6395
+ } else {
6396
+ reject(result.value);
6397
+ }
6398
+ });
6399
+ return promise;
6400
+ }
6401
+ #callProcedureGeneric(procedureName, argsBuffer) {
6081
6402
  const { promise, resolve, reject } = Promise.withResolvers();
6082
6403
  const requestId = this.#getNextRequestId();
6083
6404
  const message = ClientMessage.CallProcedure({
@@ -7018,7 +7339,7 @@ function table(opts, row, ..._) {
7018
7339
  increment: 1n
7019
7340
  });
7020
7341
  }
7021
- if (meta.defaultValue) {
7342
+ if (Object.prototype.hasOwnProperty.call(meta, "defaultValue")) {
7022
7343
  const writer = new BinaryWriter(16);
7023
7344
  builder.serialize(writer, meta.defaultValue);
7024
7345
  defaultValues.push({