@livestore/sync-cf 0.0.58-dev.6 → 0.0.58-dev.7
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/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/8ee300993f9a2c909aede59646369fa0cc28d25304b9b9af94f54d4543ad60f4.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/037646d8e1e6ad005a1f4ff1c53fd716bec6a458a8b35ebffd9d5b78ec4ea8a3.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/1471dd2fd02c2d9eb229fcf3cad5fd330cd295bfdee44481714d91b883756b11.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/23a5a193673cd4aa1f412953ff8ee7cf6169b146127a42e9b84ce79993672b0a.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/31569057f8ec82f1d479956671fe58b9f0f4e32a64a68c3ba9c3cb6744f74b3d.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/354c1d4fbf3d44a1e359b6595b4e7f2e0c320e607cad72290b0f842246dea4c3.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/472d06f3610154b855a10d98212ec0ae8aa4022def5c932134cafe07ddca6cc4.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/472d06f3610154b855a10d98212ec0ae8aa4022def5c932134cafe07ddca6cc4.sqlite-shm +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/472d06f3610154b855a10d98212ec0ae8aa4022def5c932134cafe07ddca6cc4.sqlite-wal +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/56fc052bba96d75e557bb09c4826387d0d3500ab0eb8de9e9b7379b795277338.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/70a851401fda75ce3a534dacc3f979fceaff8234430fc6333f22112519388493.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/82afe32c9f3dfe706e088859636f9137e1824b12206e1c0870eef31abcb77d2e.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/83b7880fabbc105109963ee7d1b9b8560b31a560b6b06cf2c326f4f1eca6e80f.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/985f28c526c3fe0133b72007b09233c1a827ae9a588ca5cff0e740f2f5da7dd3.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/a0f452412c79c524292ab9f6895e09605492f5980d8ad6cfc34c0fb88ee6d1cd.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/a58eeee7c3798f238d9446895cd2d0f140b06589dcdf9a777ed79131eb83ea5b.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/baf2225931d572afb47e60d5e189ae58b9764ea0a8a501e0ac202a35c2b35084.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/c27c5b00d9c92102951df7d882fb0b9c9f1a9466e0752265b255041c6c3dc739.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/c4aa0e61dcd784604bee52282348e0538ab69ec76e1652322bbccd539d9ff3ee.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/d2245ee36dd16e1f76180d89bd2503f0d4808a5986a58b8677f9a365a793acc6.sqlite +0 -0
- package/.wrangler/state/v3/do/websocket-server-WebSocketServer/d87433156774c933da83e962eba16107cf02ade2f133d7205963558fe47db3ff.sqlite +0 -0
- package/.wrangler/tmp/dev-33iU4b/index.js +42167 -0
- package/.wrangler/tmp/dev-33iU4b/index.js.map +8 -0
- package/.wrangler/tmp/dev-rI63Kk/index.js +42165 -0
- package/.wrangler/tmp/dev-rI63Kk/index.js.map +8 -0
- package/.wrangler/tmp/dev-txPodK/index.js +42118 -0
- package/.wrangler/tmp/dev-txPodK/index.js.map +8 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/durable-object.d.ts +15 -5
- package/dist/cf-worker/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/durable-object.js +100 -68
- package/dist/cf-worker/durable-object.js.map +1 -1
- package/dist/common/ws-message-types.d.ts +238 -50
- package/dist/common/ws-message-types.d.ts.map +1 -1
- package/dist/common/ws-message-types.js +4 -4
- package/dist/common/ws-message-types.js.map +1 -1
- package/dist/sync-impl/ws-impl.d.ts.map +1 -1
- package/dist/sync-impl/ws-impl.js +16 -13
- package/dist/sync-impl/ws-impl.js.map +1 -1
- package/package.json +4 -4
- package/src/cf-worker/durable-object.ts +123 -80
- package/src/common/ws-message-types.ts +15 -5
- package/src/sync-impl/ws-impl.ts +31 -20
- package/tsconfig.json +1 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { makeColumnSpec } from '@livestore/common'
|
|
2
|
-
import { DbSchema, type MutationEvent } from '@livestore/common/schema'
|
|
2
|
+
import { DbSchema, type MutationEvent, mutationEventSchemaAny } from '@livestore/common/schema'
|
|
3
3
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
4
4
|
import { Effect, Schema } from '@livestore/utils/effect'
|
|
5
5
|
import { DurableObject } from 'cloudflare:workers'
|
|
@@ -14,14 +14,15 @@ export interface Env {
|
|
|
14
14
|
|
|
15
15
|
type WebSocketClient = WebSocket
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const encodeOutgoingMessage = Schema.encodeSync(Schema.parseJson(WSMessage.BackendToClientMessage))
|
|
18
|
+
const encodeIncomingMessage = Schema.encodeSync(Schema.parseJson(WSMessage.ClientToBackendMessage))
|
|
19
|
+
const decodeIncomingMessage = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.ClientToBackendMessage))
|
|
19
20
|
|
|
20
21
|
export const mutationLogTable = DbSchema.table('__unused', {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
mutation: DbSchema.text({
|
|
24
|
-
args: DbSchema.text({
|
|
22
|
+
id: DbSchema.integer({ primaryKey: true }),
|
|
23
|
+
parentId: DbSchema.integer({}),
|
|
24
|
+
mutation: DbSchema.text({}),
|
|
25
|
+
args: DbSchema.text({ schema: Schema.parseJson(Schema.Any) }),
|
|
25
26
|
})
|
|
26
27
|
|
|
27
28
|
// Durable Object
|
|
@@ -43,8 +44,8 @@ export class WebSocketServer extends DurableObject<Env> {
|
|
|
43
44
|
|
|
44
45
|
this.ctx.setWebSocketAutoResponse(
|
|
45
46
|
new WebSocketRequestResponsePair(
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
encodeIncomingMessage(WSMessage.Ping.make({ requestId: 'ping' })),
|
|
48
|
+
encodeOutgoingMessage(WSMessage.Pong.make({ requestId: 'ping' })),
|
|
48
49
|
),
|
|
49
50
|
)
|
|
50
51
|
|
|
@@ -58,7 +59,7 @@ export class WebSocketServer extends DurableObject<Env> {
|
|
|
58
59
|
}).pipe(Effect.tapCauseLogPretty, Effect.runPromise)
|
|
59
60
|
|
|
60
61
|
webSocketMessage = async (ws: WebSocketClient, message: ArrayBuffer | string) => {
|
|
61
|
-
const decodedMessageRes =
|
|
62
|
+
const decodedMessageRes = decodeIncomingMessage(message)
|
|
62
63
|
|
|
63
64
|
if (decodedMessageRes._tag === 'Left') {
|
|
64
65
|
console.error('Invalid message received', decodedMessageRes.left)
|
|
@@ -68,91 +69,116 @@ export class WebSocketServer extends DurableObject<Env> {
|
|
|
68
69
|
const decodedMessage = decodedMessageRes.right
|
|
69
70
|
const requestId = decodedMessage.requestId
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
try {
|
|
73
|
+
switch (decodedMessage._tag) {
|
|
74
|
+
case 'WSMessage.PullReq': {
|
|
75
|
+
const cursor = decodedMessage.cursor
|
|
76
|
+
const CHUNK_SIZE = 100
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
// TODO use streaming
|
|
79
|
+
const remainingEvents = [...(await this.storage.getEvents(cursor))]
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// NOTE we want to make sure the WS server responds at least once with `InitRes` even if `events` is empty
|
|
82
|
+
while (true) {
|
|
83
|
+
const events = remainingEvents.splice(0, CHUNK_SIZE)
|
|
84
|
+
const encodedEvents = Schema.encodeSync(Schema.Array(mutationEventSchemaAny))(events)
|
|
85
|
+
const hasMore = remainingEvents.length > 0
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
ws.send(encodeOutgoingMessage(WSMessage.PullRes.make({ events: encodedEvents, hasMore, requestId })))
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
if (hasMore === false) {
|
|
90
|
+
break
|
|
91
|
+
}
|
|
88
92
|
}
|
|
93
|
+
|
|
94
|
+
break
|
|
89
95
|
}
|
|
96
|
+
case 'WSMessage.PushReq': {
|
|
97
|
+
// TODO check whether we could use the Durable Object storage for this to speed up the lookup
|
|
98
|
+
const latestEvent = await this.storage.getLatestEvent()
|
|
99
|
+
const expectedParentId = latestEvent?.id ?? { global: 0, local: 0 }
|
|
100
|
+
|
|
101
|
+
if (decodedMessage.mutationEventEncoded.parentId !== expectedParentId) {
|
|
102
|
+
ws.send(
|
|
103
|
+
encodeOutgoingMessage(
|
|
104
|
+
WSMessage.Error.make({
|
|
105
|
+
message: `Invalid parent id. Received ${decodedMessage.mutationEventEncoded.parentId} but expected ${expectedParentId}`,
|
|
106
|
+
requestId,
|
|
107
|
+
}),
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
return
|
|
111
|
+
}
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
// console.debug(`Broadcasting mutation event to ${this.subscribedWebSockets.size} clients`)
|
|
105
|
-
|
|
106
|
-
const connectedClients = this.ctx.getWebSockets()
|
|
107
|
-
|
|
108
|
-
if (connectedClients.length > 0) {
|
|
109
|
-
const broadcastMessage = encodeMessage(
|
|
110
|
-
WSMessage.PushBroadcast.make({
|
|
111
|
-
mutationEventEncoded: decodedMessage.mutationEventEncoded,
|
|
112
|
-
requestId,
|
|
113
|
-
persisted: decodedMessage.persisted,
|
|
114
|
-
}),
|
|
113
|
+
// TODO handle clientId unique conflict
|
|
114
|
+
|
|
115
|
+
// NOTE we're currently not blocking on this to allow broadcasting right away
|
|
116
|
+
const storePromise = decodedMessage.persisted
|
|
117
|
+
? this.storage.appendEvent(decodedMessage.mutationEventEncoded)
|
|
118
|
+
: Promise.resolve()
|
|
119
|
+
|
|
120
|
+
ws.send(
|
|
121
|
+
encodeOutgoingMessage(
|
|
122
|
+
WSMessage.PushAck.make({ mutationId: decodedMessage.mutationEventEncoded.id.global, requestId }),
|
|
123
|
+
),
|
|
115
124
|
)
|
|
116
125
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
// console.debug(`Broadcasting mutation event to ${this.subscribedWebSockets.size} clients`)
|
|
127
|
+
|
|
128
|
+
const connectedClients = this.ctx.getWebSockets()
|
|
129
|
+
|
|
130
|
+
if (connectedClients.length > 0) {
|
|
131
|
+
const broadcastMessage = encodeOutgoingMessage(
|
|
132
|
+
WSMessage.PushBroadcast.make({
|
|
133
|
+
mutationEventEncoded: decodedMessage.mutationEventEncoded,
|
|
134
|
+
persisted: decodedMessage.persisted,
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
for (const conn of connectedClients) {
|
|
139
|
+
console.log('Broadcasting to client', conn === ws ? 'self' : 'other')
|
|
140
|
+
// if (conn !== ws) {
|
|
120
141
|
conn.send(broadcastMessage)
|
|
142
|
+
// }
|
|
121
143
|
}
|
|
122
144
|
}
|
|
123
|
-
}
|
|
124
145
|
|
|
125
|
-
|
|
146
|
+
await storePromise
|
|
126
147
|
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
case 'WSMessage.AdminResetRoomReq': {
|
|
130
|
-
if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
|
|
131
|
-
ws.send(encodeMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })))
|
|
132
|
-
return
|
|
148
|
+
break
|
|
133
149
|
}
|
|
150
|
+
case 'WSMessage.AdminResetRoomReq': {
|
|
151
|
+
if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
|
|
152
|
+
ws.send(encodeOutgoingMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })))
|
|
153
|
+
return
|
|
154
|
+
}
|
|
134
155
|
|
|
135
|
-
|
|
136
|
-
|
|
156
|
+
await this.storage.resetRoom()
|
|
157
|
+
ws.send(encodeOutgoingMessage(WSMessage.AdminResetRoomRes.make({ requestId })))
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
case 'WSMessage.AdminInfoReq': {
|
|
141
|
-
if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
|
|
142
|
-
ws.send(encodeMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })))
|
|
143
|
-
return
|
|
159
|
+
break
|
|
144
160
|
}
|
|
161
|
+
case 'WSMessage.AdminInfoReq': {
|
|
162
|
+
if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
|
|
163
|
+
ws.send(encodeOutgoingMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })))
|
|
164
|
+
return
|
|
165
|
+
}
|
|
145
166
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
167
|
+
ws.send(
|
|
168
|
+
encodeOutgoingMessage(
|
|
169
|
+
WSMessage.AdminInfoRes.make({ requestId, info: { durableObjectId: this.ctx.id.toString() } }),
|
|
170
|
+
),
|
|
171
|
+
)
|
|
149
172
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
break
|
|
174
|
+
}
|
|
175
|
+
default: {
|
|
176
|
+
console.error('unsupported message', decodedMessage)
|
|
177
|
+
return shouldNeverHappen()
|
|
178
|
+
}
|
|
155
179
|
}
|
|
180
|
+
} catch (error: any) {
|
|
181
|
+
ws.send(encodeOutgoingMessage(WSMessage.Error.make({ message: error.message, requestId })))
|
|
156
182
|
}
|
|
157
183
|
}
|
|
158
184
|
|
|
@@ -163,25 +189,42 @@ export class WebSocketServer extends DurableObject<Env> {
|
|
|
163
189
|
}
|
|
164
190
|
|
|
165
191
|
const makeStorage = (ctx: DurableObjectState, env: Env, dbName: string) => {
|
|
166
|
-
const
|
|
167
|
-
const
|
|
192
|
+
const getLatestEvent = async (): Promise<MutationEvent.Any | undefined> => {
|
|
193
|
+
const rawEvents = await env.DB.prepare(`SELECT * FROM ${dbName} ORDER BY id DESC LIMIT 1`).all()
|
|
194
|
+
if (rawEvents.error) {
|
|
195
|
+
throw new Error(rawEvents.error)
|
|
196
|
+
}
|
|
197
|
+
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results).map((e) => ({
|
|
198
|
+
...e,
|
|
199
|
+
id: { global: e.id, local: 0 },
|
|
200
|
+
parentId: { global: e.parentId, local: 0 },
|
|
201
|
+
}))
|
|
202
|
+
return events[0]
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const getEvents = async (cursor: number | undefined): Promise<ReadonlyArray<MutationEvent.Any>> => {
|
|
206
|
+
const whereClause = cursor ? `WHERE id > ${cursor}` : ''
|
|
168
207
|
// TODO handle case where `cursor` was not found
|
|
169
208
|
const rawEvents = await env.DB.prepare(`SELECT * FROM ${dbName} ${whereClause} ORDER BY id ASC`).all()
|
|
170
209
|
if (rawEvents.error) {
|
|
171
210
|
throw new Error(rawEvents.error)
|
|
172
211
|
}
|
|
173
|
-
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results)
|
|
212
|
+
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results).map((e) => ({
|
|
213
|
+
...e,
|
|
214
|
+
id: { global: e.id, local: 0 },
|
|
215
|
+
parentId: { global: e.parentId, local: 0 },
|
|
216
|
+
}))
|
|
174
217
|
return events
|
|
175
218
|
}
|
|
176
219
|
|
|
177
220
|
const appendEvent = async (event: MutationEvent.Any) => {
|
|
178
|
-
const sql = `INSERT INTO ${dbName} (id, args, mutation) VALUES (?, ?, ?)`
|
|
179
|
-
await env.DB.prepare(sql).bind(event.id, JSON.stringify(event.args), event.mutation).run()
|
|
221
|
+
const sql = `INSERT INTO ${dbName} (id, parentId, args, mutation) VALUES (?, ?, ?, ?)`
|
|
222
|
+
await env.DB.prepare(sql).bind(event.id, event.parentId, JSON.stringify(event.args), event.mutation).run()
|
|
180
223
|
}
|
|
181
224
|
|
|
182
225
|
const resetRoom = async () => {
|
|
183
226
|
await ctx.storage.deleteAll()
|
|
184
227
|
}
|
|
185
228
|
|
|
186
|
-
return { getEvents, appendEvent, resetRoom }
|
|
229
|
+
return { getLatestEvent, getEvents, appendEvent, resetRoom }
|
|
187
230
|
}
|
|
@@ -4,7 +4,7 @@ import { Schema } from '@livestore/utils/effect'
|
|
|
4
4
|
export const PullReq = Schema.TaggedStruct('WSMessage.PullReq', {
|
|
5
5
|
requestId: Schema.String,
|
|
6
6
|
/** Omitting the cursor will start from the beginning */
|
|
7
|
-
cursor: Schema.optional(Schema.
|
|
7
|
+
cursor: Schema.optional(Schema.Number),
|
|
8
8
|
})
|
|
9
9
|
|
|
10
10
|
export type PullReq = typeof PullReq.Type
|
|
@@ -20,7 +20,6 @@ export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
|
|
|
20
20
|
export type PullRes = typeof PullRes.Type
|
|
21
21
|
|
|
22
22
|
export const PushBroadcast = Schema.TaggedStruct('WSMessage.PushBroadcast', {
|
|
23
|
-
requestId: Schema.String,
|
|
24
23
|
mutationEventEncoded: mutationEventSchemaEncodedAny,
|
|
25
24
|
persisted: Schema.Boolean,
|
|
26
25
|
})
|
|
@@ -37,7 +36,7 @@ export type PushReq = typeof PushReq.Type
|
|
|
37
36
|
|
|
38
37
|
export const PushAck = Schema.TaggedStruct('WSMessage.PushAck', {
|
|
39
38
|
requestId: Schema.String,
|
|
40
|
-
mutationId: Schema.
|
|
39
|
+
mutationId: Schema.Number,
|
|
41
40
|
})
|
|
42
41
|
|
|
43
42
|
export type PushAck = typeof PushAck.Type
|
|
@@ -105,5 +104,16 @@ export const Message = Schema.Union(
|
|
|
105
104
|
export type Message = typeof Message.Type
|
|
106
105
|
export type MessageEncoded = typeof Message.Encoded
|
|
107
106
|
|
|
108
|
-
export const
|
|
109
|
-
|
|
107
|
+
export const BackendToClientMessage = Schema.Union(
|
|
108
|
+
PullRes,
|
|
109
|
+
PushBroadcast,
|
|
110
|
+
PushAck,
|
|
111
|
+
AdminResetRoomRes,
|
|
112
|
+
AdminInfoRes,
|
|
113
|
+
Error,
|
|
114
|
+
Pong,
|
|
115
|
+
)
|
|
116
|
+
export type BackendToClientMessage = typeof BackendToClientMessage.Type
|
|
117
|
+
|
|
118
|
+
export const ClientToBackendMessage = Schema.Union(PullReq, PushReq, AdminResetRoomReq, AdminInfoReq, Ping)
|
|
119
|
+
export type ClientToBackendMessage = typeof ClientToBackendMessage.Type
|
package/src/sync-impl/ws-impl.ts
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import type { SyncBackend, SyncBackendOptionsBase } from '@livestore/common'
|
|
4
4
|
import { InvalidPullError, InvalidPushError } from '@livestore/common'
|
|
5
|
-
import { cuid } from '@livestore/utils/cuid'
|
|
6
5
|
import type { Scope } from '@livestore/utils/effect'
|
|
7
6
|
import { Deferred, Effect, Option, PubSub, Queue, Schema, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
|
7
|
+
import { nanoid } from '@livestore/utils/nanoid'
|
|
8
8
|
|
|
9
9
|
import { WSMessage } from '../common/index.js'
|
|
10
10
|
|
|
@@ -34,25 +34,34 @@ export const makeWsSync = (options: WsSyncOptions): Effect.Effect<SyncBackend<nu
|
|
|
34
34
|
isConnected,
|
|
35
35
|
pull: (args, { listenForNew }) =>
|
|
36
36
|
listenForNew
|
|
37
|
-
?
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
37
|
+
? Effect.gen(function* () {
|
|
38
|
+
const requestId = nanoid()
|
|
39
|
+
const cursor = Option.getOrUndefined(args)?.cursor.global
|
|
40
|
+
|
|
41
|
+
yield* send(WSMessage.PullReq.make({ cursor, requestId }))
|
|
42
|
+
|
|
43
|
+
return Stream.fromPubSub(incomingMessages).pipe(
|
|
44
|
+
Stream.filter((_) => (_._tag === 'WSMessage.PullRes' ? _.requestId === requestId : true)),
|
|
45
|
+
Stream.tap((_) =>
|
|
46
|
+
_._tag === 'WSMessage.Error' ? new InvalidPullError({ message: _.message }) : Effect.void,
|
|
47
|
+
),
|
|
48
|
+
Stream.filter(Schema.is(Schema.Union(WSMessage.PushBroadcast, WSMessage.PullRes))),
|
|
49
|
+
Stream.map((msg) =>
|
|
50
|
+
msg._tag === 'WSMessage.PushBroadcast'
|
|
51
|
+
? [{ mutationEventEncoded: msg.mutationEventEncoded, persisted: msg.persisted, metadata }]
|
|
52
|
+
: msg.events.map((_) => ({ mutationEventEncoded: _, metadata, persisted: true })),
|
|
53
|
+
),
|
|
54
|
+
Stream.flattenIterables,
|
|
55
|
+
)
|
|
56
|
+
}).pipe(Stream.unwrap)
|
|
48
57
|
: Effect.gen(function* () {
|
|
49
|
-
const requestId =
|
|
50
|
-
const cursor = Option.getOrUndefined(args)?.cursor
|
|
58
|
+
const requestId = nanoid()
|
|
59
|
+
const cursor = Option.getOrUndefined(args)?.cursor.global
|
|
51
60
|
|
|
52
61
|
yield* send(WSMessage.PullReq.make({ cursor, requestId }))
|
|
53
62
|
|
|
54
63
|
return Stream.fromPubSub(incomingMessages).pipe(
|
|
55
|
-
Stream.filter((_) => _.requestId === requestId),
|
|
64
|
+
Stream.filter((_) => _._tag !== 'WSMessage.PushBroadcast' && _.requestId === requestId),
|
|
56
65
|
Stream.tap((_) =>
|
|
57
66
|
_._tag === 'WSMessage.Error' ? new InvalidPullError({ message: _.message }) : Effect.void,
|
|
58
67
|
),
|
|
@@ -70,17 +79,17 @@ export const makeWsSync = (options: WsSyncOptions): Effect.Effect<SyncBackend<nu
|
|
|
70
79
|
push: (mutationEventEncoded, persisted) =>
|
|
71
80
|
Effect.gen(function* () {
|
|
72
81
|
const ready = yield* Deferred.make<void, InvalidPushError>()
|
|
73
|
-
const requestId =
|
|
82
|
+
const requestId = nanoid()
|
|
74
83
|
|
|
75
84
|
yield* Stream.fromPubSub(incomingMessages).pipe(
|
|
76
|
-
Stream.filter((_) => _.requestId === requestId),
|
|
85
|
+
Stream.filter((_) => _._tag !== 'WSMessage.PushBroadcast' && _.requestId === requestId),
|
|
77
86
|
Stream.tap((_) =>
|
|
78
87
|
_._tag === 'WSMessage.Error'
|
|
79
88
|
? Deferred.fail(ready, new InvalidPushError({ message: _.message }))
|
|
80
89
|
: Effect.void,
|
|
81
90
|
),
|
|
82
91
|
Stream.filter(Schema.is(WSMessage.PushAck)),
|
|
83
|
-
Stream.filter((_) => _.mutationId === mutationEventEncoded.id),
|
|
92
|
+
Stream.filter((_) => _.mutationId === mutationEventEncoded.id.global),
|
|
84
93
|
Stream.take(1),
|
|
85
94
|
Stream.tap(() => Deferred.succeed(ready, void 0)),
|
|
86
95
|
Stream.runDrain,
|
|
@@ -104,7 +113,7 @@ const connect = (wsUrl: string) =>
|
|
|
104
113
|
const isConnected = yield* SubscriptionRef.make(false)
|
|
105
114
|
const wsRef: { current: WebSocket | undefined } = { current: undefined }
|
|
106
115
|
|
|
107
|
-
const incomingMessages = yield* PubSub.unbounded<Exclude<WSMessage.
|
|
116
|
+
const incomingMessages = yield* PubSub.unbounded<Exclude<WSMessage.BackendToClientMessage, WSMessage.Pong>>()
|
|
108
117
|
|
|
109
118
|
const waitUntilOnline = isConnected.changes.pipe(Stream.filter(Boolean), Stream.take(1), Stream.runDrain)
|
|
110
119
|
|
|
@@ -132,7 +141,9 @@ const connect = (wsUrl: string) =>
|
|
|
132
141
|
const pongMessages = yield* Queue.unbounded<WSMessage.Pong>()
|
|
133
142
|
|
|
134
143
|
const messageHandler = (event: MessageEvent<any>): void => {
|
|
135
|
-
const decodedEventRes = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.
|
|
144
|
+
const decodedEventRes = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.BackendToClientMessage))(
|
|
145
|
+
event.data,
|
|
146
|
+
)
|
|
136
147
|
|
|
137
148
|
if (decodedEventRes._tag === 'Left') {
|
|
138
149
|
console.error('Sync: Invalid message received', decodedEventRes.left)
|