@livestore/sync-cf 0.5.0-dev.0 → 0.5.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 (101) hide show
  1. package/README.md +5 -5
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cf-worker/do/durable-object.d.ts.map +1 -1
  4. package/dist/cf-worker/do/durable-object.js +34 -30
  5. package/dist/cf-worker/do/durable-object.js.map +1 -1
  6. package/dist/cf-worker/do/layer.d.ts.map +1 -1
  7. package/dist/cf-worker/do/layer.js +2 -2
  8. package/dist/cf-worker/do/observability.d.ts +23 -0
  9. package/dist/cf-worker/do/observability.d.ts.map +1 -0
  10. package/dist/cf-worker/do/observability.js +52 -0
  11. package/dist/cf-worker/do/observability.js.map +1 -0
  12. package/dist/cf-worker/do/observability.test.d.ts +2 -0
  13. package/dist/cf-worker/do/observability.test.d.ts.map +1 -0
  14. package/dist/cf-worker/do/observability.test.js +204 -0
  15. package/dist/cf-worker/do/observability.test.js.map +1 -0
  16. package/dist/cf-worker/do/pull.d.ts.map +1 -1
  17. package/dist/cf-worker/do/pull.js +3 -3
  18. package/dist/cf-worker/do/push.d.ts +2 -2
  19. package/dist/cf-worker/do/push.d.ts.map +1 -1
  20. package/dist/cf-worker/do/push.js +7 -6
  21. package/dist/cf-worker/do/push.js.map +1 -1
  22. package/dist/cf-worker/do/push.test.js +1 -1
  23. package/dist/cf-worker/do/sqlite.js +1 -1
  24. package/dist/cf-worker/do/sync-storage.d.ts.map +1 -1
  25. package/dist/cf-worker/do/sync-storage.js +3 -3
  26. package/dist/cf-worker/do/transport/do-rpc-server.d.ts +15 -0
  27. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -1
  28. package/dist/cf-worker/do/transport/do-rpc-server.js +23 -7
  29. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -1
  30. package/dist/cf-worker/do/transport/do-rpc-server.test.d.ts +2 -0
  31. package/dist/cf-worker/do/transport/do-rpc-server.test.d.ts.map +1 -0
  32. package/dist/cf-worker/do/transport/do-rpc-server.test.js +28 -0
  33. package/dist/cf-worker/do/transport/do-rpc-server.test.js.map +1 -0
  34. package/dist/cf-worker/do/transport/http-rpc-server.js +6 -6
  35. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +8 -1
  36. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -1
  37. package/dist/cf-worker/do/transport/ws-rpc-server.js +32 -24
  38. package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -1
  39. package/dist/cf-worker/mod.js +3 -3
  40. package/dist/cf-worker/shared.d.ts +10 -5
  41. package/dist/cf-worker/shared.d.ts.map +1 -1
  42. package/dist/cf-worker/shared.js +2 -2
  43. package/dist/cf-worker/shared.js.map +1 -1
  44. package/dist/cf-worker/worker.d.ts +3 -3
  45. package/dist/cf-worker/worker.d.ts.map +1 -1
  46. package/dist/cf-worker/worker.js +2 -2
  47. package/dist/cf-worker/worker.js.map +1 -1
  48. package/dist/client/mod.js +3 -3
  49. package/dist/client/transport/do-rpc-client.d.ts +6 -3
  50. package/dist/client/transport/do-rpc-client.d.ts.map +1 -1
  51. package/dist/client/transport/do-rpc-client.js +65 -20
  52. package/dist/client/transport/do-rpc-client.js.map +1 -1
  53. package/dist/client/transport/do-rpc-client.test.d.ts +2 -0
  54. package/dist/client/transport/do-rpc-client.test.d.ts.map +1 -0
  55. package/dist/client/transport/do-rpc-client.test.js +268 -0
  56. package/dist/client/transport/do-rpc-client.test.js.map +1 -0
  57. package/dist/client/transport/http-rpc-client.d.ts.map +1 -1
  58. package/dist/client/transport/http-rpc-client.js +20 -10
  59. package/dist/client/transport/http-rpc-client.js.map +1 -1
  60. package/dist/client/transport/rpc-client-error.d.ts +5 -0
  61. package/dist/client/transport/rpc-client-error.d.ts.map +1 -0
  62. package/dist/client/transport/rpc-client-error.js +8 -0
  63. package/dist/client/transport/rpc-client-error.js.map +1 -0
  64. package/dist/client/transport/rpc-client-error.test.d.ts +2 -0
  65. package/dist/client/transport/rpc-client-error.test.d.ts.map +1 -0
  66. package/dist/client/transport/rpc-client-error.test.js +33 -0
  67. package/dist/client/transport/rpc-client-error.test.js.map +1 -0
  68. package/dist/client/transport/ws-rpc-client.d.ts.map +1 -1
  69. package/dist/client/transport/ws-rpc-client.js +18 -12
  70. package/dist/client/transport/ws-rpc-client.js.map +1 -1
  71. package/dist/common/do-rpc-schema.d.ts +24 -11
  72. package/dist/common/do-rpc-schema.d.ts.map +1 -1
  73. package/dist/common/do-rpc-schema.js +10 -1
  74. package/dist/common/do-rpc-schema.js.map +1 -1
  75. package/dist/common/http-rpc-schema.d.ts +8 -8
  76. package/dist/common/http-rpc-schema.js +1 -1
  77. package/dist/common/mod.d.ts +1 -0
  78. package/dist/common/mod.d.ts.map +1 -1
  79. package/dist/common/mod.js +4 -3
  80. package/dist/common/mod.js.map +1 -1
  81. package/dist/common/sync-message-types.d.ts.map +1 -1
  82. package/dist/common/ws-rpc-schema.d.ts +1 -1
  83. package/dist/common/ws-rpc-schema.js +1 -1
  84. package/package.json +41 -41
  85. package/src/cf-worker/do/durable-object.ts +42 -45
  86. package/src/cf-worker/do/observability.test.ts +337 -0
  87. package/src/cf-worker/do/observability.ts +121 -0
  88. package/src/cf-worker/do/push.ts +3 -2
  89. package/src/cf-worker/do/transport/do-rpc-server.test.ts +32 -0
  90. package/src/cf-worker/do/transport/do-rpc-server.ts +23 -1
  91. package/src/cf-worker/do/transport/ws-rpc-server.ts +45 -21
  92. package/src/cf-worker/shared.ts +13 -6
  93. package/src/cf-worker/worker.ts +1 -1
  94. package/src/client/transport/do-rpc-client.test.ts +411 -0
  95. package/src/client/transport/do-rpc-client.ts +100 -18
  96. package/src/client/transport/http-rpc-client.ts +20 -9
  97. package/src/client/transport/rpc-client-error.test.ts +41 -0
  98. package/src/client/transport/rpc-client-error.ts +9 -0
  99. package/src/client/transport/ws-rpc-client.ts +15 -10
  100. package/src/common/do-rpc-schema.ts +10 -0
  101. package/src/common/mod.ts +1 -0
@@ -25,6 +25,7 @@ import { MAX_HTTP_REQUEST_BYTES, MAX_PUSH_EVENTS_PER_REQUEST } from '../../commo
25
25
  import { SyncHttpRpc } from '../../common/http-rpc-schema.ts'
26
26
  import { SearchParamsSchema } from '../../common/mod.ts'
27
27
  import type { SyncMetadata } from '../../common/sync-message-types.ts'
28
+ import { mapRpcClientError } from './rpc-client-error.ts'
28
29
 
29
30
  type PushBatchItem = LiveStoreEvent.Global.Encoded
30
31
 
@@ -108,9 +109,13 @@ export const makeHttpSync =
108
109
 
109
110
  yield* SubscriptionRef.set(isConnected, true)
110
111
  }).pipe(
111
- UnknownError.mapToUnknownError,
112
- Effect.timeout(pingTimeout),
113
- Effect.catchTag('TimeoutError', () => SubscriptionRef.set(isConnected, false)),
112
+ Effect.mapError((cause) =>
113
+ cause._tag === 'RpcClientError' ? mapRpcClientError(cause) : new UnknownError({ cause }),
114
+ ),
115
+ Effect.timeoutOrElse({
116
+ duration: pingTimeout,
117
+ orElse: () => SubscriptionRef.set(isConnected, false),
118
+ }),
114
119
  )
115
120
 
116
121
  const pingInterval = options.ping?.requestInterval ?? 10_000
@@ -178,9 +183,11 @@ export const makeHttpSync =
178
183
  Stream.tap((res) => backendIdHelper.lazySet(res.backendId)),
179
184
  Stream.map((res) => Struct.omit(res, ['backendId'])),
180
185
  Stream.mapError((cause) =>
181
- cause._tag === 'UnknownError' || cause._tag === 'BackendIdMismatchError'
182
- ? cause
183
- : new UnknownError({ cause }),
186
+ cause._tag === 'RpcClientError'
187
+ ? mapRpcClientError(cause)
188
+ : cause._tag === 'UnknownError' || cause._tag === 'BackendIdMismatchError'
189
+ ? cause
190
+ : new UnknownError({ cause }),
184
191
  ),
185
192
  Stream.withSpan('http-sync-client:pull'),
186
193
  )
@@ -215,9 +222,13 @@ export const makeHttpSync =
215
222
  },
216
223
  pushSemaphore.withPermits(1),
217
224
  Effect.mapError((cause) =>
218
- cause._tag === 'UnknownError' || cause._tag === 'ServerAheadError' || cause._tag === 'BackendIdMismatchError'
219
- ? cause
220
- : new UnknownError({ cause }),
225
+ cause._tag === 'RpcClientError'
226
+ ? mapRpcClientError(cause)
227
+ : cause._tag === 'UnknownError' ||
228
+ cause._tag === 'ServerAheadError' ||
229
+ cause._tag === 'BackendIdMismatchError'
230
+ ? cause
231
+ : new UnknownError({ cause }),
221
232
  ),
222
233
  )
223
234
 
@@ -0,0 +1,41 @@
1
+ import { expect, it } from 'vitest'
2
+
3
+ import { IsOfflineError, UnknownError } from '@livestore/common'
4
+ import { HttpClientError, RpcClientError, Socket } from '@livestore/utils/effect'
5
+
6
+ import { mapRpcClientError } from './rpc-client-error.ts'
7
+
8
+ const socketReasons = [
9
+ new Socket.SocketCloseError({ code: 1012, closeReason: 'Restart' }),
10
+ new Socket.SocketReadError({ cause: new Error('Disconnected') }),
11
+ new Socket.SocketWriteError({ cause: new Error('Disconnected') }),
12
+ new Socket.SocketOpenError({ kind: 'Timeout', cause: new Error('Unavailable') }),
13
+ new Socket.SocketUpgradeError({ cause: new Error('Upgrade failed') }),
14
+ ]
15
+
16
+ it.each(socketReasons)('maps $\u005ftag to offline', (reason) => {
17
+ const result = mapRpcClientError(new RpcClientError.RpcClientError({ reason }))
18
+ expect(result).toBeInstanceOf(IsOfflineError)
19
+ expect(result.cause).toBe(reason)
20
+ })
21
+
22
+ it('maps an HTTP transport failure to offline', () => {
23
+ const reason = new HttpClientError.HttpClientErrorSchema({ kind: 'TransportError' })
24
+ const result = mapRpcClientError(new RpcClientError.RpcClientError({ reason }))
25
+ expect(result).toBeInstanceOf(IsOfflineError)
26
+ expect(result.cause).toBe(reason)
27
+ })
28
+
29
+ const protocolReasons = [
30
+ new RpcClientError.RpcClientDefect({ message: 'Malformed RPC response', cause: new Error('Invalid message') }),
31
+ ...(['EncodeError', 'DecodeError', 'InvalidUrlError', 'StatusCodeError', 'EmptyBodyError'] as const).map(
32
+ (kind) => new HttpClientError.HttpClientErrorSchema({ kind }),
33
+ ),
34
+ ]
35
+
36
+ it.each(protocolReasons)('keeps protocol failure $\u005ftag terminal', (reason) => {
37
+ const error = new RpcClientError.RpcClientError({ reason })
38
+ const result = mapRpcClientError(error)
39
+ expect(result).toBeInstanceOf(UnknownError)
40
+ expect(result.cause).toBe(error)
41
+ })
@@ -0,0 +1,9 @@
1
+ import { IsOfflineError, UnknownError } from '@livestore/common'
2
+ import { type RpcClientError, Schema, Socket } from '@livestore/utils/effect'
3
+
4
+ /** Only transport loss is retryable. RPC defects and HTTP protocol failures stay terminal. */
5
+ export const mapRpcClientError = (error: RpcClientError.RpcClientError): IsOfflineError | UnknownError =>
6
+ Schema.is(Socket.SocketErrorReason)(error.reason) === true ||
7
+ (error.reason._tag === 'HttpError' && error.reason.kind === 'TransportError')
8
+ ? new IsOfflineError({ cause: error.reason })
9
+ : new UnknownError({ cause: error })
@@ -1,4 +1,4 @@
1
- import { IsOfflineError, SyncBackend, UnknownError } from '@livestore/common'
1
+ import { SyncBackend, UnknownError } from '@livestore/common'
2
2
  import type { LiveStoreEvent } from '@livestore/common/schema'
3
3
  import { splitArrayBySize } from '@livestore/common/sync'
4
4
  import {
@@ -24,6 +24,7 @@ import { MAX_PUSH_EVENTS_PER_REQUEST, MAX_WS_MESSAGE_BYTES } from '../../common/
24
24
  import { SearchParamsSchema } from '../../common/mod.ts'
25
25
  import type { SyncMetadata } from '../../common/sync-message-types.ts'
26
26
  import { SyncWsRpc } from '../../common/ws-rpc-schema.ts'
27
+ import { mapRpcClientError } from './rpc-client-error.ts'
27
28
 
28
29
  export interface WsSyncOptions {
29
30
  /**
@@ -121,8 +122,10 @@ export const makeWsSync =
121
122
  yield* pinger.ping
122
123
  yield* SubscriptionRef.set(isConnected, true)
123
124
  }).pipe(
124
- Effect.timeout(pingTimeout),
125
- Effect.catchTag('TimeoutError', () => SubscriptionRef.set(isConnected, false)),
125
+ Effect.timeoutOrElse({
126
+ duration: pingTimeout,
127
+ orElse: () => SubscriptionRef.set(isConnected, false),
128
+ }),
126
129
  UnknownError.mapToUnknownError,
127
130
  Effect.withSpan('ping'),
128
131
  )
@@ -147,8 +150,8 @@ export const makeWsSync =
147
150
  Stream.tap((res) => backendIdHelper.lazySet(res.backendId)),
148
151
  Stream.map((res) => Struct.omit(res, ['backendId'])),
149
152
  Stream.mapError((cause) =>
150
- cause._tag === 'RpcClientError' && Socket.isSocketError(cause.cause) === true
151
- ? new IsOfflineError({ cause: cause.cause })
153
+ cause._tag === 'RpcClientError'
154
+ ? mapRpcClientError(cause)
152
155
  : cause._tag === 'UnknownError' || cause._tag === 'BackendIdMismatchError'
153
156
  ? cause
154
157
  : new UnknownError({ cause }),
@@ -184,11 +187,13 @@ export const makeWsSync =
184
187
  backendId: backendIdHelper.get(),
185
188
  }).pipe(
186
189
  Effect.mapError((cause) =>
187
- cause._tag === 'UnknownError' ||
188
- cause._tag === 'ServerAheadError' ||
189
- cause._tag === 'BackendIdMismatchError'
190
- ? cause
191
- : new UnknownError({ cause }),
190
+ cause._tag === 'RpcClientError'
191
+ ? mapRpcClientError(cause)
192
+ : cause._tag === 'UnknownError' ||
193
+ cause._tag === 'ServerAheadError' ||
194
+ cause._tag === 'BackendIdMismatchError'
195
+ ? cause
196
+ : new UnknownError({ cause }),
192
197
  ),
193
198
  )
194
199
  }
@@ -52,4 +52,14 @@ export class SyncDoRpc extends RpcGroup.make(
52
52
  },
53
53
  success: Schema.Void,
54
54
  }),
55
+ Rpc.make('SyncDoRpc.Unsubscribe', {
56
+ payload: {
57
+ /** Client DO id whose live-pull subscription row to drop. */
58
+ durableObjectId: Schema.String,
59
+ /** Request id of the live pull that registered the row; the row is only dropped if it still matches. */
60
+ requestId: Schema.String,
61
+ ...commonPayloadFields,
62
+ },
63
+ success: Schema.Void,
64
+ }),
55
65
  ) {}
package/src/common/mod.ts CHANGED
@@ -3,6 +3,7 @@ import { Schema } from '@livestore/utils/effect'
3
3
 
4
4
  export type { CfTypes } from '@livestore/common-cf'
5
5
  export * from './constants.ts'
6
+ export { SyncDoRpc } from './do-rpc-schema.ts'
6
7
  export { SyncHttpRpc } from './http-rpc-schema.ts'
7
8
  export * as SyncMessage from './sync-message-types.ts'
8
9