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