@livestore/sync-cf 0.3.0-dev.11 → 0.3.0-dev.13
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/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/durable-object.d.ts +14 -5
- package/dist/cf-worker/durable-object.d.ts.map +1 -1
- package/dist/cf-worker/durable-object.js +96 -71
- package/dist/cf-worker/durable-object.js.map +1 -1
- package/dist/common/ws-message-types.d.ts +9 -44
- package/dist/common/ws-message-types.d.ts.map +1 -1
- package/dist/common/ws-message-types.js +3 -9
- 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 +19 -25
- package/dist/sync-impl/ws-impl.js.map +1 -1
- package/package.json +3 -3
- package/src/cf-worker/durable-object.ts +139 -102
- package/src/common/ws-message-types.ts +2 -20
- package/src/sync-impl/ws-impl.ts +21 -32
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
import { InvalidPullError, InvalidPushError } from '@livestore/common';
|
|
3
|
-
import {
|
|
3
|
+
import { LS_DEV } from '@livestore/utils';
|
|
4
4
|
import { Deferred, Effect, Option, PubSub, Queue, Schedule, Schema, Stream, SubscriptionRef, WebSocket, } from '@livestore/utils/effect';
|
|
5
5
|
import { nanoid } from '@livestore/utils/nanoid';
|
|
6
6
|
import { WSMessage } from '../common/mod.js';
|
|
@@ -14,27 +14,18 @@ export const makeWsSync = (options) => Effect.gen(function* () {
|
|
|
14
14
|
const requestId = nanoid();
|
|
15
15
|
const cursor = Option.getOrUndefined(args)?.cursor.global;
|
|
16
16
|
yield* send(WSMessage.PullReq.make({ cursor, requestId }));
|
|
17
|
-
return Stream.fromPubSub(incomingMessages).pipe(Stream.
|
|
17
|
+
return Stream.fromPubSub(incomingMessages).pipe(Stream.tap((_) => _._tag === 'WSMessage.Error' && _.requestId === requestId
|
|
18
18
|
? new InvalidPullError({ message: _.message })
|
|
19
|
-
: Effect.void),
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
batch: msg.events.map(({ mutationEventEncoded, metadata }) => ({
|
|
23
|
-
mutationEventEncoded,
|
|
24
|
-
metadata,
|
|
25
|
-
})),
|
|
26
|
-
remaining: msg.remaining,
|
|
27
|
-
}));
|
|
19
|
+
: Effect.void),
|
|
20
|
+
// This call is mostly here to for type narrowing
|
|
21
|
+
Stream.filter(Schema.is(WSMessage.PullRes)));
|
|
28
22
|
}).pipe(Stream.unwrap),
|
|
29
23
|
push: (batch) => Effect.gen(function* () {
|
|
30
24
|
const ready = yield* Deferred.make();
|
|
31
25
|
const requestId = nanoid();
|
|
32
|
-
yield* Stream.fromPubSub(incomingMessages).pipe(Stream.
|
|
26
|
+
yield* Stream.fromPubSub(incomingMessages).pipe(Stream.tap((_) => _._tag === 'WSMessage.Error' && _.requestId === requestId
|
|
33
27
|
? Deferred.fail(ready, new InvalidPushError({ reason: { _tag: 'Unexpected', message: _.message } }))
|
|
34
|
-
: Effect.void), Stream.filter(
|
|
35
|
-
// TODO bring back filterting of "own events"
|
|
36
|
-
// Stream.filter((_) => _.mutationId === mutationEventEncoded.id.global),
|
|
37
|
-
Stream.take(1), Stream.tap(() => Deferred.succeed(ready, void 0)), Stream.runDrain, Effect.tapCauseLogPretty, Effect.fork);
|
|
28
|
+
: Effect.void), Stream.filter((_) => _._tag === 'WSMessage.PushAck' && _.requestId === requestId), Stream.take(1), Stream.tap(() => Deferred.succeed(ready, void 0)), Stream.runDrain, Effect.tapCauseLogPretty, Effect.fork);
|
|
38
29
|
yield* send(WSMessage.PushReq.make({ batch, requestId }));
|
|
39
30
|
yield* ready;
|
|
40
31
|
const createdAt = new Date().toISOString();
|
|
@@ -51,17 +42,19 @@ const connect = (wsUrl) => Effect.gen(function* () {
|
|
|
51
42
|
const send = (message) => Effect.gen(function* () {
|
|
52
43
|
// Wait first until we're online
|
|
53
44
|
yield* waitUntilOnline;
|
|
54
|
-
yield* Effect.spanEvent(`Sending message: ${message._tag}`, message._tag === 'WSMessage.PushReq'
|
|
55
|
-
? {
|
|
56
|
-
id: message.batch[0].id,
|
|
57
|
-
parentId: message.batch[0].parentId,
|
|
58
|
-
batchLength: message.batch.length,
|
|
59
|
-
}
|
|
60
|
-
: message._tag === 'WSMessage.PullReq'
|
|
61
|
-
? { cursor: message.cursor ?? '-' }
|
|
62
|
-
: {});
|
|
63
45
|
// TODO use MsgPack instead of JSON to speed up the serialization / reduce the size of the messages
|
|
64
46
|
socketRef.current.send(Schema.encodeSync(Schema.parseJson(WSMessage.Message))(message));
|
|
47
|
+
if (LS_DEV) {
|
|
48
|
+
yield* Effect.spanEvent(`Sent message: ${message._tag}`, message._tag === 'WSMessage.PushReq'
|
|
49
|
+
? {
|
|
50
|
+
id: message.batch[0].id,
|
|
51
|
+
parentId: message.batch[0].parentId,
|
|
52
|
+
batchLength: message.batch.length,
|
|
53
|
+
}
|
|
54
|
+
: message._tag === 'WSMessage.PullReq'
|
|
55
|
+
? { cursor: message.cursor ?? '-' }
|
|
56
|
+
: {});
|
|
57
|
+
}
|
|
65
58
|
});
|
|
66
59
|
const innerConnect = Effect.gen(function* () {
|
|
67
60
|
// If the browser already tells us we're offline, then we'll at least wait until the browser
|
|
@@ -69,6 +62,7 @@ const connect = (wsUrl) => Effect.gen(function* () {
|
|
|
69
62
|
while (typeof navigator !== 'undefined' && navigator.onLine === false) {
|
|
70
63
|
yield* Effect.sleep(1000);
|
|
71
64
|
}
|
|
65
|
+
// TODO bring this back in a cross-platform way
|
|
72
66
|
// if (navigator.onLine === false) {
|
|
73
67
|
// yield* Effect.async((cb) => self.addEventListener('online', () => cb(Effect.void)))
|
|
74
68
|
// }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ws-impl.js","sourceRoot":"","sources":["../../src/sync-impl/ws-impl.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAG3B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ws-impl.js","sourceRoot":"","sources":["../../src/sync-impl/ws-impl.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAG3B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAEzC,OAAO,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,MAAM,EACN,KAAK,EACL,QAAQ,EACR,MAAM,EACN,MAAM,EACN,eAAe,EACf,SAAS,GACV,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAQ5C,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAsB,EAAgE,EAAE,CACjH,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,qCAAqC;IACrC,MAAM,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,sBAAsB,OAAO,CAAC,OAAO,EAAE,CAAA;IAEnE,MAAM,EAAE,WAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IAErE,MAAM,GAAG,GAAG;QACV,WAAW;QACX,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CACb,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,SAAS,GAAG,MAAM,EAAE,CAAA;YAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,CAAA;YAEzD,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAE1D,OAAO,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACf,CAAC,CAAC,IAAI,KAAK,iBAAiB,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS;gBACvD,CAAC,CAAC,IAAI,gBAAgB,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC9C,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB;YACD,iDAAiD;YACjD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAC5C,CAAA;QACH,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;QAExB,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACd,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAA0B,CAAA;YAC5D,MAAM,SAAS,GAAG,MAAM,EAAE,CAAA;YAE1B,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACf,CAAC,CAAC,IAAI,KAAK,iBAAiB,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS;gBACvD,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;gBACpG,CAAC,CAAC,MAAM,CAAC,IAAI,CAChB,EACD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,EACjF,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EACd,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EACjD,MAAM,CAAC,QAAQ,EACf,MAAM,CAAC,iBAAiB,EACxB,MAAM,CAAC,IAAI,CACZ,CAAA;YAED,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;YAEzD,KAAK,CAAC,CAAC,KAAK,CAAA;YAEZ,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAE1C,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAA;QAC7F,CAAC,CAAC;KAC+B,CAAA;IAErC,OAAO,GAAG,CAAA;AACZ,CAAC,CAAC,CAAA;AAEJ,MAAM,OAAO,GAAG,CAAC,KAAa,EAAE,EAAE,CAChC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACtD,MAAM,SAAS,GAAkD,EAAE,OAAO,EAAE,SAAS,EAAE,CAAA;IAEvF,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,EAA6D,CAAC,IAAI,CAChH,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CACvC,CAAA;IAED,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAA;IAEzG,MAAM,IAAI,GAAG,CAAC,OAA0B,EAAE,EAAE,CAC1C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClB,gCAAgC;QAChC,KAAK,CAAC,CAAC,eAAe,CAAA;QAEtB,mGAAmG;QACnG,SAAS,CAAC,OAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;QAExF,IAAI,MAAM,EAAE,CAAC;YACX,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CACrB,iBAAiB,OAAO,CAAC,IAAI,EAAE,EAC/B,OAAO,CAAC,IAAI,KAAK,mBAAmB;gBAClC,CAAC,CAAC;oBACE,EAAE,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,EAAE;oBACxB,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,QAAQ;oBACpC,WAAW,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM;iBAClC;gBACH,CAAC,CAAC,OAAO,CAAC,IAAI,KAAK,mBAAmB;oBACpC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,GAAG,EAAE;oBACnC,CAAC,CAAC,EAAE,CACT,CAAA;QACH,CAAC;IACH,CAAC,CAAC,CAAA;IAEJ,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACvC,4FAA4F;QAC5F,+FAA+F;QAC/F,OAAO,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YACtE,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC3B,CAAC;QACD,+CAA+C;QAC/C,oCAAoC;QACpC,wFAAwF;QACxF,IAAI;QAEJ,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAEnG,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,CAAA;QAC7C,SAAS,CAAC,OAAO,GAAG,MAAM,CAAA;QAE1B,MAAM,gBAAgB,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAQ,CAAA;QAErD,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAA;QAEzG,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,KAAmB,EAAE,EAAE,CACrE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,eAAe,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,CACpG,KAAK,CAAC,IAAI,CACX,CAAA;YAED,IAAI,eAAe,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpC,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,eAAe,CAAC,IAAI,CAAC,CAAA;gBACrE,OAAM;YACR,CAAC;iBAAM,CAAC;gBACN,IAAI,eAAe,CAAC,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;oBACpD,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;gBACzD,CAAC;qBAAM,CAAC;oBACN,2EAA2E;oBAC3E,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,eAAe,CAAC,KAAK,CAAC,CAAA;gBAChE,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CACH,CAAA;QAED,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;QAE9F,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,CAChD,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAA;YAC3C,KAAK,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAA;QACnD,CAAC,CAAC,CACH,CAAA;QAED,4GAA4G;QAC5G,wGAAwG;QACxG,sDAAsD;QACtD,IAAI,OAAO,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,sDAAsD;YACtD,KAAK,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;QAChG,CAAC;QAED,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAC9B,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAClB,SAAS,CAAC,OAAO,GAAG,SAAS,CAAA;YAC7B,KAAK,CAAC,CAAC,eAAe,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;QAChD,CAAC,CAAC,CACH,CAAA;QAED,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YACxC,yDAAyD;YACzD,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;YAE1D,qFAAqF;YACrF,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAA;YAE1D,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAC7B,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,0CAA0C,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,CAAA;QAEnF,KAAK,CAAC,CAAC,eAAe,CAAC,IAAI,CACzB,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAClD,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC,CAAC,EACtE,MAAM,CAAC,UAAU,CAClB,CAAA;QAED,KAAK,CAAC,CAAC,gBAAgB,CAAA;IACzB,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,4BAA4B,CAAC,CAAC,CAAA;IAErE,KAAK,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,UAAU,CAAC,CAAA;IAE3G,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAA;AAChD,CAAC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/sync-cf",
|
|
3
|
-
"version": "0.3.0-dev.
|
|
3
|
+
"version": "0.3.0-dev.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@livestore/common": "0.3.0-dev.
|
|
18
|
-
"@livestore/utils": "0.3.0-dev.
|
|
17
|
+
"@livestore/common": "0.3.0-dev.13",
|
|
18
|
+
"@livestore/utils": "0.3.0-dev.13"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@cloudflare/workers-types": "^4.20241022.0"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { makeColumnSpec } from '@livestore/common'
|
|
1
|
+
import { makeColumnSpec, UnexpectedError } from '@livestore/common'
|
|
2
2
|
import { DbSchema, EventId, type MutationEvent } from '@livestore/common/schema'
|
|
3
3
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
4
4
|
import { Effect, Logger, LogLevel, Option, Schema } from '@livestore/utils/effect'
|
|
@@ -19,14 +19,15 @@ const encodeOutgoingMessage = Schema.encodeSync(Schema.parseJson(WSMessage.Backe
|
|
|
19
19
|
const encodeIncomingMessage = Schema.encodeSync(Schema.parseJson(WSMessage.ClientToBackendMessage))
|
|
20
20
|
const decodeIncomingMessage = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.ClientToBackendMessage))
|
|
21
21
|
|
|
22
|
-
// NOTE actual table name is determined at runtime
|
|
23
|
-
export const mutationLogTable = DbSchema.table('
|
|
22
|
+
// NOTE actual table name is determined at runtime
|
|
23
|
+
export const mutationLogTable = DbSchema.table('mutation_log_${PERSISTENCE_FORMAT_VERSION}_${storeId}', {
|
|
24
24
|
id: DbSchema.integer({ primaryKey: true, schema: EventId.GlobalEventId }),
|
|
25
25
|
parentId: DbSchema.integer({ schema: EventId.GlobalEventId }),
|
|
26
26
|
mutation: DbSchema.text({}),
|
|
27
27
|
args: DbSchema.text({ schema: Schema.parseJson(Schema.Any) }),
|
|
28
28
|
/** ISO date format. Currently only used for debugging purposes. */
|
|
29
29
|
createdAt: DbSchema.text({}),
|
|
30
|
+
clientId: DbSchema.text({}),
|
|
30
31
|
})
|
|
31
32
|
|
|
32
33
|
const WebSocketAttachmentSchema = Schema.parseJson(
|
|
@@ -40,7 +41,7 @@ const WebSocketAttachmentSchema = Schema.parseJson(
|
|
|
40
41
|
*
|
|
41
42
|
* Changing this version number will lead to a "soft reset".
|
|
42
43
|
*/
|
|
43
|
-
export const PERSISTENCE_FORMAT_VERSION =
|
|
44
|
+
export const PERSISTENCE_FORMAT_VERSION = 3
|
|
44
45
|
|
|
45
46
|
export type MakeDurableObjectClassOptions = {
|
|
46
47
|
onPush?: (message: WSMessage.PushReq) => Effect.Effect<void> | Promise<void>
|
|
@@ -104,6 +105,7 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
|
|
|
104
105
|
|
|
105
106
|
try {
|
|
106
107
|
switch (decodedMessage._tag) {
|
|
108
|
+
// TODO allow pulling concurrently to not block incoming push requests
|
|
107
109
|
case 'WSMessage.PullReq': {
|
|
108
110
|
if (options?.onPull) {
|
|
109
111
|
yield* Effect.tryAll(() => options.onPull!(decodedMessage))
|
|
@@ -113,21 +115,19 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
|
|
|
113
115
|
const CHUNK_SIZE = 100
|
|
114
116
|
|
|
115
117
|
// TODO use streaming
|
|
116
|
-
const remainingEvents =
|
|
117
|
-
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
break
|
|
130
|
-
}
|
|
118
|
+
const remainingEvents = yield* storage.getEvents(cursor)
|
|
119
|
+
|
|
120
|
+
// Send at least one response, even if there are no events
|
|
121
|
+
const batches =
|
|
122
|
+
remainingEvents.length === 0
|
|
123
|
+
? [[]]
|
|
124
|
+
: Array.from({ length: Math.ceil(remainingEvents.length / CHUNK_SIZE) }, (_, i) =>
|
|
125
|
+
remainingEvents.slice(i * CHUNK_SIZE, (i + 1) * CHUNK_SIZE),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
for (const [index, batch] of batches.entries()) {
|
|
129
|
+
const remaining = Math.max(0, remainingEvents.length - (index + 1) * CHUNK_SIZE)
|
|
130
|
+
ws.send(encodeOutgoingMessage(WSMessage.PullRes.make({ batch, remaining })))
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
break
|
|
@@ -137,61 +137,63 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
|
|
|
137
137
|
yield* Effect.tryAll(() => options.onPush!(decodedMessage))
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
if (decodedMessage.batch.length === 0) {
|
|
141
|
+
ws.send(encodeOutgoingMessage(WSMessage.PushAck.make({ requestId })))
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
140
145
|
// TODO check whether we could use the Durable Object storage for this to speed up the lookup
|
|
141
|
-
const
|
|
142
|
-
const expectedParentId = latestEvent?.id ?? EventId.ROOT.global
|
|
146
|
+
const expectedParentId = yield* storage.getHead
|
|
143
147
|
|
|
144
|
-
|
|
145
|
-
for (const mutationEventEncoded of decodedMessage.batch) {
|
|
146
|
-
if (mutationEventEncoded.parentId !== expectedParentId + i) {
|
|
147
|
-
const err = WSMessage.Error.make({
|
|
148
|
-
message: `Invalid parent id. Received ${mutationEventEncoded.parentId} but expected ${expectedParentId}`,
|
|
149
|
-
requestId,
|
|
150
|
-
})
|
|
148
|
+
// TODO handle clientId unique conflict
|
|
151
149
|
|
|
152
|
-
|
|
150
|
+
// Validate the batch
|
|
151
|
+
const firstEvent = decodedMessage.batch[0]!
|
|
152
|
+
if (firstEvent.parentId !== expectedParentId) {
|
|
153
|
+
const err = WSMessage.Error.make({
|
|
154
|
+
message: `Invalid parent id. Received ${firstEvent.parentId} but expected ${expectedParentId}`,
|
|
155
|
+
requestId,
|
|
156
|
+
})
|
|
153
157
|
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
}
|
|
158
|
+
yield* Effect.logError(err)
|
|
157
159
|
|
|
158
|
-
|
|
160
|
+
ws.send(encodeOutgoingMessage(err))
|
|
161
|
+
return
|
|
162
|
+
}
|
|
159
163
|
|
|
160
|
-
|
|
164
|
+
ws.send(encodeOutgoingMessage(WSMessage.PushAck.make({ requestId })))
|
|
161
165
|
|
|
162
|
-
|
|
163
|
-
const storePromise = storage.appendEvent(mutationEventEncoded, createdAt)
|
|
166
|
+
const createdAt = new Date().toISOString()
|
|
164
167
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
// NOTE we're not waiting for this to complete yet to allow the broadcast to happen right away
|
|
169
|
+
// while letting the async storage write happen in the background
|
|
170
|
+
const storeFiber = yield* storage.appendEvents(decodedMessage.batch, createdAt).pipe(Effect.fork)
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
const connectedClients = this.ctx.getWebSockets()
|
|
170
173
|
|
|
171
|
-
|
|
174
|
+
// console.debug(`Broadcasting push batch to ${this.subscribedWebSockets.size} clients`)
|
|
172
175
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
if (connectedClients.length > 0) {
|
|
177
|
+
const pullRes = encodeOutgoingMessage(
|
|
178
|
+
// TODO refactor to batch api
|
|
179
|
+
WSMessage.PullRes.make({
|
|
180
|
+
batch: decodedMessage.batch.map((mutationEventEncoded) => ({
|
|
177
181
|
mutationEventEncoded,
|
|
178
182
|
metadata: Option.some({ createdAt }),
|
|
179
|
-
}),
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
console.log('Broadcasting to client', conn === ws ? 'self' : 'other')
|
|
184
|
-
// if (conn !== ws) {
|
|
185
|
-
conn.send(broadcastMessage)
|
|
186
|
-
// }
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
yield* Effect.promise(() => storePromise)
|
|
183
|
+
})),
|
|
184
|
+
remaining: 0,
|
|
185
|
+
}),
|
|
186
|
+
)
|
|
191
187
|
|
|
192
|
-
|
|
188
|
+
// NOTE we're also sending the pullRes to the pushing ws client as a confirmation
|
|
189
|
+
for (const conn of connectedClients) {
|
|
190
|
+
conn.send(pullRes)
|
|
191
|
+
}
|
|
193
192
|
}
|
|
194
193
|
|
|
194
|
+
// Wait for the storage write to complete before finishing this request
|
|
195
|
+
yield* storeFiber
|
|
196
|
+
|
|
195
197
|
break
|
|
196
198
|
}
|
|
197
199
|
case 'WSMessage.AdminResetRoomReq': {
|
|
@@ -200,7 +202,7 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
|
|
|
200
202
|
return
|
|
201
203
|
}
|
|
202
204
|
|
|
203
|
-
yield*
|
|
205
|
+
yield* storage.resetStore
|
|
204
206
|
ws.send(encodeOutgoingMessage(WSMessage.AdminResetRoomRes.make({ requestId })))
|
|
205
207
|
|
|
206
208
|
break
|
|
@@ -244,62 +246,97 @@ export const makeDurableObject: MakeDurableObjectClass = (options) => {
|
|
|
244
246
|
|
|
245
247
|
type SyncStorage = {
|
|
246
248
|
dbName: string
|
|
247
|
-
|
|
249
|
+
getHead: Effect.Effect<EventId.GlobalEventId, UnexpectedError>
|
|
248
250
|
getEvents: (
|
|
249
251
|
cursor: number | undefined,
|
|
250
|
-
) =>
|
|
251
|
-
ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncodedGlobal; metadata: Option.Option<SyncMetadata> }
|
|
252
|
+
) => Effect.Effect<
|
|
253
|
+
ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncodedGlobal; metadata: Option.Option<SyncMetadata> }>,
|
|
254
|
+
UnexpectedError
|
|
252
255
|
>
|
|
253
|
-
|
|
254
|
-
|
|
256
|
+
appendEvents: (
|
|
257
|
+
batch: ReadonlyArray<MutationEvent.AnyEncodedGlobal>,
|
|
258
|
+
createdAt: string,
|
|
259
|
+
) => Effect.Effect<void, UnexpectedError>
|
|
260
|
+
resetStore: Effect.Effect<void, UnexpectedError>
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
const makeStorage = (ctx: DurableObjectState, env: Env, storeId: string): SyncStorage => {
|
|
258
264
|
const dbName = `mutation_log_${PERSISTENCE_FORMAT_VERSION}_${toValidTableName(storeId)}`
|
|
259
265
|
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results)
|
|
266
|
+
const execDb = <T>(cb: (db: D1Database) => Promise<D1Result<T>>) =>
|
|
267
|
+
Effect.tryPromise({
|
|
268
|
+
try: () => cb(env.DB),
|
|
269
|
+
catch: (error) => new UnexpectedError({ cause: error, payload: { dbName } }),
|
|
270
|
+
}).pipe(Effect.map((_) => _.results))
|
|
266
271
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const getEvents = async (
|
|
271
|
-
cursor: number | undefined,
|
|
272
|
-
): Promise<
|
|
273
|
-
ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncodedGlobal; metadata: Option.Option<SyncMetadata> }>
|
|
274
|
-
> => {
|
|
275
|
-
const whereClause = cursor === undefined ? '' : `WHERE id > ${cursor}`
|
|
276
|
-
const sql = `SELECT * FROM ${dbName} ${whereClause} ORDER BY id ASC`
|
|
277
|
-
// TODO handle case where `cursor` was not found
|
|
278
|
-
const rawEvents = await env.DB.prepare(sql).all()
|
|
279
|
-
if (rawEvents.error) {
|
|
280
|
-
throw new Error(rawEvents.error)
|
|
281
|
-
}
|
|
282
|
-
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results).map(
|
|
283
|
-
({ createdAt, ...mutationEventEncoded }) => ({
|
|
284
|
-
mutationEventEncoded,
|
|
285
|
-
metadata: Option.some({ createdAt }),
|
|
286
|
-
}),
|
|
272
|
+
const getHead: Effect.Effect<EventId.GlobalEventId, UnexpectedError> = Effect.gen(function* () {
|
|
273
|
+
const result = yield* execDb<{ id: EventId.GlobalEventId }>((db) =>
|
|
274
|
+
db.prepare(`SELECT id FROM ${dbName} ORDER BY id DESC LIMIT 1`).all(),
|
|
287
275
|
)
|
|
288
|
-
return events
|
|
289
|
-
}
|
|
290
276
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
await env.DB.prepare(sql)
|
|
294
|
-
.bind(event.id, event.parentId, JSON.stringify(event.args), event.mutation, createdAt)
|
|
295
|
-
.run()
|
|
296
|
-
}
|
|
277
|
+
return result[0]?.id ?? EventId.ROOT.global
|
|
278
|
+
}).pipe(UnexpectedError.mapToUnexpectedError)
|
|
297
279
|
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
280
|
+
const getEvents = (
|
|
281
|
+
cursor: number | undefined,
|
|
282
|
+
): Effect.Effect<
|
|
283
|
+
ReadonlyArray<{ mutationEventEncoded: MutationEvent.AnyEncodedGlobal; metadata: Option.Option<SyncMetadata> }>,
|
|
284
|
+
UnexpectedError
|
|
285
|
+
> =>
|
|
286
|
+
Effect.gen(function* () {
|
|
287
|
+
const whereClause = cursor === undefined ? '' : `WHERE id > ${cursor}`
|
|
288
|
+
const sql = `SELECT * FROM ${dbName} ${whereClause} ORDER BY id ASC`
|
|
289
|
+
// TODO handle case where `cursor` was not found
|
|
290
|
+
const rawEvents = yield* execDb((db) => db.prepare(sql).all())
|
|
291
|
+
const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents).map(
|
|
292
|
+
({ createdAt, ...mutationEventEncoded }) => ({
|
|
293
|
+
mutationEventEncoded,
|
|
294
|
+
metadata: Option.some({ createdAt }),
|
|
295
|
+
}),
|
|
296
|
+
)
|
|
297
|
+
return events
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
const appendEvents: SyncStorage['appendEvents'] = (batch, createdAt) =>
|
|
301
|
+
Effect.gen(function* () {
|
|
302
|
+
// If there are no events, do nothing.
|
|
303
|
+
if (batch.length === 0) return
|
|
304
|
+
|
|
305
|
+
// CF D1 limits:
|
|
306
|
+
// Maximum bound parameters per query 100, Maximum arguments per SQL function 32
|
|
307
|
+
// Thus we need to split the batch into chunks of max (100/6=)16 events each.
|
|
308
|
+
const CHUNK_SIZE = 16
|
|
309
|
+
|
|
310
|
+
for (let i = 0; i < batch.length; i += CHUNK_SIZE) {
|
|
311
|
+
const chunk = batch.slice(i, i + CHUNK_SIZE)
|
|
312
|
+
|
|
313
|
+
// Create a list of placeholders ("(?, ?, ?, ?, ?), …") corresponding to each event.
|
|
314
|
+
const valuesPlaceholders = chunk.map(() => '(?, ?, ?, ?, ?, ?)').join(', ')
|
|
315
|
+
const sql = `INSERT INTO ${dbName} (id, parentId, args, mutation, createdAt, clientId) VALUES ${valuesPlaceholders}`
|
|
316
|
+
// Flatten the event properties into a parameters array.
|
|
317
|
+
const params = chunk.flatMap((event) => [
|
|
318
|
+
event.id,
|
|
319
|
+
event.parentId,
|
|
320
|
+
JSON.stringify(event.args),
|
|
321
|
+
event.mutation,
|
|
322
|
+
createdAt,
|
|
323
|
+
event.clientId,
|
|
324
|
+
])
|
|
325
|
+
|
|
326
|
+
yield* execDb((db) =>
|
|
327
|
+
db
|
|
328
|
+
.prepare(sql)
|
|
329
|
+
.bind(...params)
|
|
330
|
+
.run(),
|
|
331
|
+
)
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
const resetStore = Effect.gen(function* () {
|
|
336
|
+
yield* Effect.promise(() => ctx.storage.deleteAll())
|
|
337
|
+
}).pipe(UnexpectedError.mapToUnexpectedError)
|
|
301
338
|
|
|
302
|
-
return { dbName,
|
|
339
|
+
return { dbName, getHead, getEvents, appendEvents, resetStore }
|
|
303
340
|
}
|
|
304
341
|
|
|
305
342
|
const getStoreId = (request: Request) => {
|
|
@@ -17,8 +17,7 @@ export const SyncMetadata = Schema.Struct({
|
|
|
17
17
|
export type SyncMetadata = typeof SyncMetadata.Type
|
|
18
18
|
|
|
19
19
|
export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
|
|
20
|
-
|
|
21
|
-
events: Schema.Array(
|
|
20
|
+
batch: Schema.Array(
|
|
22
21
|
Schema.Struct({
|
|
23
22
|
mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
|
|
24
23
|
metadata: Schema.Option(SyncMetadata),
|
|
@@ -29,13 +28,6 @@ export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
|
|
|
29
28
|
|
|
30
29
|
export type PullRes = typeof PullRes.Type
|
|
31
30
|
|
|
32
|
-
export const PushBroadcast = Schema.TaggedStruct('WSMessage.PushBroadcast', {
|
|
33
|
-
mutationEventEncoded: MutationEvent.AnyEncodedGlobal,
|
|
34
|
-
metadata: Schema.Option(SyncMetadata),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
export type PushBroadcast = typeof PushBroadcast.Type
|
|
38
|
-
|
|
39
31
|
export const PushReq = Schema.TaggedStruct('WSMessage.PushReq', {
|
|
40
32
|
requestId: Schema.String,
|
|
41
33
|
batch: Schema.Array(MutationEvent.AnyEncodedGlobal),
|
|
@@ -45,7 +37,6 @@ export type PushReq = typeof PushReq.Type
|
|
|
45
37
|
|
|
46
38
|
export const PushAck = Schema.TaggedStruct('WSMessage.PushAck', {
|
|
47
39
|
requestId: Schema.String,
|
|
48
|
-
mutationId: Schema.Number,
|
|
49
40
|
})
|
|
50
41
|
|
|
51
42
|
export type PushAck = typeof PushAck.Type
|
|
@@ -99,7 +90,6 @@ export type AdminInfoRes = typeof AdminInfoRes.Type
|
|
|
99
90
|
export const Message = Schema.Union(
|
|
100
91
|
PullReq,
|
|
101
92
|
PullRes,
|
|
102
|
-
PushBroadcast,
|
|
103
93
|
PushReq,
|
|
104
94
|
PushAck,
|
|
105
95
|
Error,
|
|
@@ -113,15 +103,7 @@ export const Message = Schema.Union(
|
|
|
113
103
|
export type Message = typeof Message.Type
|
|
114
104
|
export type MessageEncoded = typeof Message.Encoded
|
|
115
105
|
|
|
116
|
-
export const BackendToClientMessage = Schema.Union(
|
|
117
|
-
PullRes,
|
|
118
|
-
PushBroadcast,
|
|
119
|
-
PushAck,
|
|
120
|
-
AdminResetRoomRes,
|
|
121
|
-
AdminInfoRes,
|
|
122
|
-
Error,
|
|
123
|
-
Pong,
|
|
124
|
-
)
|
|
106
|
+
export const BackendToClientMessage = Schema.Union(PullRes, PushAck, AdminResetRoomRes, AdminInfoRes, Error, Pong)
|
|
125
107
|
export type BackendToClientMessage = typeof BackendToClientMessage.Type
|
|
126
108
|
|
|
127
109
|
export const ClientToBackendMessage = Schema.Union(PullReq, PushReq, AdminResetRoomReq, AdminInfoReq, Ping)
|
package/src/sync-impl/ws-impl.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { SyncBackend } from '@livestore/common'
|
|
4
4
|
import { InvalidPullError, InvalidPushError } from '@livestore/common'
|
|
5
|
-
import {
|
|
5
|
+
import { LS_DEV } from '@livestore/utils'
|
|
6
6
|
import type { Scope } from '@livestore/utils/effect'
|
|
7
7
|
import {
|
|
8
8
|
Deferred,
|
|
@@ -43,24 +43,13 @@ export const makeWsSync = (options: WsSyncOptions): Effect.Effect<SyncBackend<Sy
|
|
|
43
43
|
yield* send(WSMessage.PullReq.make({ cursor, requestId }))
|
|
44
44
|
|
|
45
45
|
return Stream.fromPubSub(incomingMessages).pipe(
|
|
46
|
-
Stream.filter((_) => (_._tag === 'WSMessage.PullRes' ? _.requestId === requestId : true)),
|
|
47
46
|
Stream.tap((_) =>
|
|
48
47
|
_._tag === 'WSMessage.Error' && _.requestId === requestId
|
|
49
48
|
? new InvalidPullError({ message: _.message })
|
|
50
49
|
: Effect.void,
|
|
51
50
|
),
|
|
52
|
-
|
|
53
|
-
Stream.
|
|
54
|
-
msg._tag === 'WSMessage.PushBroadcast'
|
|
55
|
-
? { batch: [pick(msg, ['mutationEventEncoded', 'metadata'])], remaining: 0 }
|
|
56
|
-
: {
|
|
57
|
-
batch: msg.events.map(({ mutationEventEncoded, metadata }) => ({
|
|
58
|
-
mutationEventEncoded,
|
|
59
|
-
metadata,
|
|
60
|
-
})),
|
|
61
|
-
remaining: msg.remaining,
|
|
62
|
-
},
|
|
63
|
-
),
|
|
51
|
+
// This call is mostly here to for type narrowing
|
|
52
|
+
Stream.filter(Schema.is(WSMessage.PullRes)),
|
|
64
53
|
)
|
|
65
54
|
}).pipe(Stream.unwrap),
|
|
66
55
|
|
|
@@ -70,15 +59,12 @@ export const makeWsSync = (options: WsSyncOptions): Effect.Effect<SyncBackend<Sy
|
|
|
70
59
|
const requestId = nanoid()
|
|
71
60
|
|
|
72
61
|
yield* Stream.fromPubSub(incomingMessages).pipe(
|
|
73
|
-
Stream.filter((_) => _._tag !== 'WSMessage.PushBroadcast' && _.requestId === requestId),
|
|
74
62
|
Stream.tap((_) =>
|
|
75
|
-
_._tag === 'WSMessage.Error'
|
|
63
|
+
_._tag === 'WSMessage.Error' && _.requestId === requestId
|
|
76
64
|
? Deferred.fail(ready, new InvalidPushError({ reason: { _tag: 'Unexpected', message: _.message } }))
|
|
77
65
|
: Effect.void,
|
|
78
66
|
),
|
|
79
|
-
Stream.filter(
|
|
80
|
-
// TODO bring back filterting of "own events"
|
|
81
|
-
// Stream.filter((_) => _.mutationId === mutationEventEncoded.id.global),
|
|
67
|
+
Stream.filter((_) => _._tag === 'WSMessage.PushAck' && _.requestId === requestId),
|
|
82
68
|
Stream.take(1),
|
|
83
69
|
Stream.tap(() => Deferred.succeed(ready, void 0)),
|
|
84
70
|
Stream.runDrain,
|
|
@@ -115,21 +101,23 @@ const connect = (wsUrl: string) =>
|
|
|
115
101
|
// Wait first until we're online
|
|
116
102
|
yield* waitUntilOnline
|
|
117
103
|
|
|
118
|
-
yield* Effect.spanEvent(
|
|
119
|
-
`Sending message: ${message._tag}`,
|
|
120
|
-
message._tag === 'WSMessage.PushReq'
|
|
121
|
-
? {
|
|
122
|
-
id: message.batch[0]!.id,
|
|
123
|
-
parentId: message.batch[0]!.parentId,
|
|
124
|
-
batchLength: message.batch.length,
|
|
125
|
-
}
|
|
126
|
-
: message._tag === 'WSMessage.PullReq'
|
|
127
|
-
? { cursor: message.cursor ?? '-' }
|
|
128
|
-
: {},
|
|
129
|
-
)
|
|
130
|
-
|
|
131
104
|
// TODO use MsgPack instead of JSON to speed up the serialization / reduce the size of the messages
|
|
132
105
|
socketRef.current!.send(Schema.encodeSync(Schema.parseJson(WSMessage.Message))(message))
|
|
106
|
+
|
|
107
|
+
if (LS_DEV) {
|
|
108
|
+
yield* Effect.spanEvent(
|
|
109
|
+
`Sent message: ${message._tag}`,
|
|
110
|
+
message._tag === 'WSMessage.PushReq'
|
|
111
|
+
? {
|
|
112
|
+
id: message.batch[0]!.id,
|
|
113
|
+
parentId: message.batch[0]!.parentId,
|
|
114
|
+
batchLength: message.batch.length,
|
|
115
|
+
}
|
|
116
|
+
: message._tag === 'WSMessage.PullReq'
|
|
117
|
+
? { cursor: message.cursor ?? '-' }
|
|
118
|
+
: {},
|
|
119
|
+
)
|
|
120
|
+
}
|
|
133
121
|
})
|
|
134
122
|
|
|
135
123
|
const innerConnect = Effect.gen(function* () {
|
|
@@ -138,6 +126,7 @@ const connect = (wsUrl: string) =>
|
|
|
138
126
|
while (typeof navigator !== 'undefined' && navigator.onLine === false) {
|
|
139
127
|
yield* Effect.sleep(1000)
|
|
140
128
|
}
|
|
129
|
+
// TODO bring this back in a cross-platform way
|
|
141
130
|
// if (navigator.onLine === false) {
|
|
142
131
|
// yield* Effect.async((cb) => self.addEventListener('online', () => cb(Effect.void)))
|
|
143
132
|
// }
|