@electric-sql/client 1.5.4 → 1.5.5

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@electric-sql/client",
3
3
  "description": "Postgres everywhere - your data, in sync, wherever you need it.",
4
- "version": "1.5.4",
4
+ "version": "1.5.5",
5
5
  "author": "ElectricSQL team and contributors.",
6
6
  "bugs": {
7
7
  "url": "https://github.com/electric-sql/electric/issues"
package/src/client.ts CHANGED
@@ -865,11 +865,15 @@ export class ShapeStream<T extends Row<unknown> = Row>
865
865
  this.#reset(newShapeHandle)
866
866
 
867
867
  // must refetch control message might be in a list or not depending
868
- // on whether it came from an SSE request or long poll - handle both
869
- // cases for safety here but worth revisiting 409 handling
870
- await this.#publish(
871
- (Array.isArray(e.json) ? e.json : [e.json]) as Message<T>[]
872
- )
868
+ // on whether it came from an SSE request or long poll. The body may
869
+ // also be null/undefined if a proxy returned an unexpected response.
870
+ // Handle all cases defensively here.
871
+ const messages409 = Array.isArray(e.json)
872
+ ? e.json
873
+ : e.json != null
874
+ ? [e.json]
875
+ : []
876
+ await this.#publish(messages409 as Message<T>[])
873
877
  return this.#requestShape()
874
878
  } else {
875
879
  // errors that have reached this point are not actionable without
@@ -1142,6 +1146,13 @@ export class ShapeStream<T extends Row<unknown> = Row>
1142
1146
  }
1143
1147
 
1144
1148
  async #onMessages(batch: Array<Message<T>>, isSseMessage = false) {
1149
+ if (!Array.isArray(batch)) {
1150
+ console.warn(
1151
+ `[Electric] #onMessages called with non-array argument (${typeof batch}). ` +
1152
+ `This is a client bug — please report it.`
1153
+ )
1154
+ return
1155
+ }
1145
1156
  if (batch.length === 0) return
1146
1157
 
1147
1158
  const lastMessage = batch[batch.length - 1]
@@ -1249,6 +1260,19 @@ export class ShapeStream<T extends Row<unknown> = Row>
1249
1260
  const messages = res || `[]`
1250
1261
  const batch = this.#messageParser.parse<Array<Message<T>>>(messages, schema)
1251
1262
 
1263
+ if (!Array.isArray(batch)) {
1264
+ const preview = JSON.stringify(batch)?.slice(0, 200)
1265
+ throw new FetchError(
1266
+ response.status,
1267
+ `Received non-array response body from shape endpoint. ` +
1268
+ `This may indicate a proxy or CDN is returning an unexpected response. ` +
1269
+ `Expected a JSON array, got ${typeof batch}: ${preview}`,
1270
+ undefined,
1271
+ Object.fromEntries(response.headers.entries()),
1272
+ fetchUrl.toString()
1273
+ )
1274
+ }
1275
+
1252
1276
  await this.#onMessages(batch)
1253
1277
  }
1254
1278
 
package/src/helpers.ts CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  export function isChangeMessage<T extends Row<unknown> = Row>(
29
29
  message: Message<T>
30
30
  ): message is ChangeMessage<T> {
31
- return `key` in message
31
+ return message != null && `key` in message
32
32
  }
33
33
 
34
34
  /**
@@ -51,7 +51,7 @@ export function isChangeMessage<T extends Row<unknown> = Row>(
51
51
  export function isControlMessage<T extends Row<unknown> = Row>(
52
52
  message: Message<T>
53
53
  ): message is ControlMessage {
54
- return !isChangeMessage(message)
54
+ return message != null && !isChangeMessage(message)
55
55
  }
56
56
 
57
57
  export function isUpToDateMessage<T extends Row<unknown> = Row>(