@livestore/sync-cf 0.0.58-dev.6

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 (37) hide show
  1. package/.wrangler/state/v3/do/websocket-server-WebSocketServer/33d6e9d204e8c535d55150b9258a9388ddf594a89f30fc1d557e64546a59e731.sqlite +0 -0
  2. package/.wrangler/tmp/dev-9rcIR8/index.js +18887 -0
  3. package/.wrangler/tmp/dev-9rcIR8/index.js.map +8 -0
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/cf-worker/durable-object.d.ts +59 -0
  6. package/dist/cf-worker/durable-object.d.ts.map +1 -0
  7. package/dist/cf-worker/durable-object.js +132 -0
  8. package/dist/cf-worker/durable-object.js.map +1 -0
  9. package/dist/cf-worker/index.d.ts +8 -0
  10. package/dist/cf-worker/index.d.ts.map +1 -0
  11. package/dist/cf-worker/index.js +67 -0
  12. package/dist/cf-worker/index.js.map +1 -0
  13. package/dist/common/index.d.ts +2 -0
  14. package/dist/common/index.d.ts.map +1 -0
  15. package/dist/common/index.js +2 -0
  16. package/dist/common/index.js.map +1 -0
  17. package/dist/common/ws-message-types.d.ts +156 -0
  18. package/dist/common/ws-message-types.d.ts.map +1 -0
  19. package/dist/common/ws-message-types.js +58 -0
  20. package/dist/common/ws-message-types.js.map +1 -0
  21. package/dist/sync-impl/index.d.ts +2 -0
  22. package/dist/sync-impl/index.d.ts.map +1 -0
  23. package/dist/sync-impl/index.js +2 -0
  24. package/dist/sync-impl/index.js.map +1 -0
  25. package/dist/sync-impl/ws-impl.d.ts +18 -0
  26. package/dist/sync-impl/ws-impl.d.ts.map +1 -0
  27. package/dist/sync-impl/ws-impl.js +122 -0
  28. package/dist/sync-impl/ws-impl.js.map +1 -0
  29. package/package.json +26 -0
  30. package/src/cf-worker/durable-object.ts +187 -0
  31. package/src/cf-worker/index.ts +84 -0
  32. package/src/common/index.ts +1 -0
  33. package/src/common/ws-message-types.ts +109 -0
  34. package/src/sync-impl/index.ts +1 -0
  35. package/src/sync-impl/ws-impl.ts +210 -0
  36. package/tsconfig.json +11 -0
  37. package/wrangler.toml +21 -0
@@ -0,0 +1,132 @@
1
+ import { makeColumnSpec } from '@livestore/common';
2
+ import { DbSchema } from '@livestore/common/schema';
3
+ import { shouldNeverHappen } from '@livestore/utils';
4
+ import { Effect, Schema } from '@livestore/utils/effect';
5
+ import { DurableObject } from 'cloudflare:workers';
6
+ import { WSMessage } from '../common/index.js';
7
+ const encodeMessage = Schema.encodeSync(Schema.parseJson(WSMessage.Message));
8
+ const decodeMessage = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.Message));
9
+ export const mutationLogTable = DbSchema.table('__unused', {
10
+ // TODO add parent ids (see https://vlcn.io/blog/crdt-substrate)
11
+ id: DbSchema.text({ primaryKey: true }),
12
+ mutation: DbSchema.text({ nullable: false }),
13
+ args: DbSchema.text({ nullable: false, schema: Schema.parseJson(Schema.Any) }),
14
+ });
15
+ // Durable Object
16
+ export class WebSocketServer extends DurableObject {
17
+ dbName = `mutation_log_${this.ctx.id.toString()}`;
18
+ storage = makeStorage(this.ctx, this.env, this.dbName);
19
+ constructor(ctx, env) {
20
+ super(ctx, env);
21
+ }
22
+ fetch = async (_request) => Effect.gen(this, function* () {
23
+ const { 0: client, 1: server } = new WebSocketPair();
24
+ // See https://developers.cloudflare.com/durable-objects/examples/websocket-hibernation-server
25
+ this.ctx.acceptWebSocket(server);
26
+ this.ctx.setWebSocketAutoResponse(new WebSocketRequestResponsePair(encodeMessage(WSMessage.Ping.make({ requestId: 'ping' })), encodeMessage(WSMessage.Pong.make({ requestId: 'ping' }))));
27
+ const colSpec = makeColumnSpec(mutationLogTable.sqliteDef.ast);
28
+ this.env.DB.exec(`CREATE TABLE IF NOT EXISTS ${this.dbName} (${colSpec}) strict`);
29
+ return new Response(null, {
30
+ status: 101,
31
+ webSocket: client,
32
+ });
33
+ }).pipe(Effect.tapCauseLogPretty, Effect.runPromise);
34
+ webSocketMessage = async (ws, message) => {
35
+ const decodedMessageRes = decodeMessage(message);
36
+ if (decodedMessageRes._tag === 'Left') {
37
+ console.error('Invalid message received', decodedMessageRes.left);
38
+ return;
39
+ }
40
+ const decodedMessage = decodedMessageRes.right;
41
+ const requestId = decodedMessage.requestId;
42
+ switch (decodedMessage._tag) {
43
+ case 'WSMessage.PullReq': {
44
+ const cursor = decodedMessage.cursor;
45
+ const CHUNK_SIZE = 100;
46
+ // TODO use streaming
47
+ const remainingEvents = [...(await this.storage.getEvents(cursor))];
48
+ // NOTE we want to make sure the WS server responds at least once with `InitRes` even if `events` is empty
49
+ while (true) {
50
+ const events = remainingEvents.splice(0, CHUNK_SIZE);
51
+ const hasMore = remainingEvents.length > 0;
52
+ ws.send(encodeMessage(WSMessage.PullRes.make({ events, hasMore, requestId })));
53
+ if (hasMore === false) {
54
+ break;
55
+ }
56
+ }
57
+ break;
58
+ }
59
+ case 'WSMessage.PushReq': {
60
+ // NOTE we're currently not blocking on this to allow broadcasting right away
61
+ // however we should do some mutation validation first (e.g. checking parent event id)
62
+ const storePromise = decodedMessage.persisted
63
+ ? this.storage.appendEvent(decodedMessage.mutationEventEncoded)
64
+ : Promise.resolve();
65
+ ws.send(encodeMessage(WSMessage.PushAck.make({ mutationId: decodedMessage.mutationEventEncoded.id, requestId })));
66
+ // console.debug(`Broadcasting mutation event to ${this.subscribedWebSockets.size} clients`)
67
+ const connectedClients = this.ctx.getWebSockets();
68
+ if (connectedClients.length > 0) {
69
+ const broadcastMessage = encodeMessage(WSMessage.PushBroadcast.make({
70
+ mutationEventEncoded: decodedMessage.mutationEventEncoded,
71
+ requestId,
72
+ persisted: decodedMessage.persisted,
73
+ }));
74
+ for (const conn of connectedClients) {
75
+ console.log('Broadcasting to client', conn === ws ? 'self' : 'other');
76
+ if (conn !== ws) {
77
+ conn.send(broadcastMessage);
78
+ }
79
+ }
80
+ }
81
+ await storePromise;
82
+ break;
83
+ }
84
+ case 'WSMessage.AdminResetRoomReq': {
85
+ if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
86
+ ws.send(encodeMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })));
87
+ return;
88
+ }
89
+ await this.storage.resetRoom();
90
+ ws.send(encodeMessage(WSMessage.AdminResetRoomRes.make({ requestId })));
91
+ break;
92
+ }
93
+ case 'WSMessage.AdminInfoReq': {
94
+ if (decodedMessage.adminSecret !== this.env.ADMIN_SECRET) {
95
+ ws.send(encodeMessage(WSMessage.Error.make({ message: 'Invalid admin secret', requestId })));
96
+ return;
97
+ }
98
+ ws.send(encodeMessage(WSMessage.AdminInfoRes.make({ requestId, info: { durableObjectId: this.ctx.id.toString() } })));
99
+ break;
100
+ }
101
+ default: {
102
+ console.error('unsupported message', decodedMessage);
103
+ return shouldNeverHappen();
104
+ }
105
+ }
106
+ };
107
+ webSocketClose = async (ws, code, _reason, _wasClean) => {
108
+ // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
109
+ ws.close(code, 'Durable Object is closing WebSocket');
110
+ };
111
+ }
112
+ const makeStorage = (ctx, env, dbName) => {
113
+ const getEvents = async (cursor) => {
114
+ const whereClause = cursor ? `WHERE id > '${cursor}'` : '';
115
+ // TODO handle case where `cursor` was not found
116
+ const rawEvents = await env.DB.prepare(`SELECT * FROM ${dbName} ${whereClause} ORDER BY id ASC`).all();
117
+ if (rawEvents.error) {
118
+ throw new Error(rawEvents.error);
119
+ }
120
+ const events = Schema.decodeUnknownSync(Schema.Array(mutationLogTable.schema))(rawEvents.results);
121
+ return events;
122
+ };
123
+ const appendEvent = async (event) => {
124
+ const sql = `INSERT INTO ${dbName} (id, args, mutation) VALUES (?, ?, ?)`;
125
+ await env.DB.prepare(sql).bind(event.id, JSON.stringify(event.args), event.mutation).run();
126
+ };
127
+ const resetRoom = async () => {
128
+ await ctx.storage.deleteAll();
129
+ };
130
+ return { getEvents, appendEvent, resetRoom };
131
+ };
132
+ //# sourceMappingURL=durable-object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"durable-object.js","sourceRoot":"","sources":["../../src/cf-worker/durable-object.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /// <reference no-default-lib="true"/>
2
+ import type { Env } from './durable-object.js';
3
+ export * from './durable-object.js';
4
+ declare const _default: {
5
+ fetch: (request: Request, env: Env, _ctx: ExecutionContext) => Promise<Response>;
6
+ };
7
+ export default _default;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cf-worker/index.ts"],"names":[],"mappings":";AAMA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAA;AAE9C,cAAc,qBAAqB,CAAA;;qBA4CV,OAAO,OAAO,GAAG,QAAQ,gBAAgB,KAAG,OAAO,CAAC,QAAQ,CAAC;;AADtF,wBAgCC"}
@@ -0,0 +1,67 @@
1
+ /// <reference no-default-lib="true"/>
2
+ /// <reference lib="esnext" />
3
+ export * from './durable-object.js';
4
+ // const handleRequest = (request: Request, env: Env) =>
5
+ // HttpServer.router.empty.pipe(
6
+ // HttpServer.router.get(
7
+ // '/websocket',
8
+ // Effect.gen(function* () {
9
+ // // This example will refer to the same Durable Object instance,
10
+ // // since the name "foo" is hardcoded.
11
+ // const id = env.WEBSOCKET_SERVER.idFromName('foo')
12
+ // const durableObject = env.WEBSOCKET_SERVER.get(id)
13
+ // HttpServer.
14
+ // // Expect to receive a WebSocket Upgrade request.
15
+ // // If there is one, accept the request and return a WebSocket Response.
16
+ // const headerRes = yield* HttpServer.request
17
+ // .schemaHeaders(
18
+ // Schema.Struct({
19
+ // Upgrade: Schema.Literal('websocket'),
20
+ // }),
21
+ // )
22
+ // .pipe(Effect.either)
23
+ // if (headerRes._tag === 'Left') {
24
+ // // return new Response('Durable Object expected Upgrade: websocket', { status: 426 })
25
+ // return yield* HttpServer.response.text('Durable Object expected Upgrade: websocket', { status: 426 })
26
+ // }
27
+ // HttpServer.response.empty
28
+ // return yield* Effect.promise(() => durableObject.fetch(request))
29
+ // }),
30
+ // ),
31
+ // HttpServer.router.catchAll((e) => {
32
+ // console.log(e)
33
+ // return HttpServer.response.empty({ status: 400 })
34
+ // }),
35
+ // (_) => HttpServer.app.toWebHandler(_)(request),
36
+ // // request
37
+ // )
38
+ // Worker
39
+ export default {
40
+ fetch: async (request, env, _ctx) => {
41
+ const url = new URL(request.url);
42
+ const searchParams = url.searchParams;
43
+ const roomId = searchParams.get('room');
44
+ if (roomId === null) {
45
+ return new Response('Room ID is required', { status: 400 });
46
+ }
47
+ // This example will refer to the same Durable Object instance,
48
+ // since the name "foo" is hardcoded.
49
+ const id = env.WEBSOCKET_SERVER.idFromName(roomId);
50
+ const durableObject = env.WEBSOCKET_SERVER.get(id);
51
+ if (url.pathname.endsWith('/websocket')) {
52
+ const upgradeHeader = request.headers.get('Upgrade');
53
+ if (!upgradeHeader || upgradeHeader !== 'websocket') {
54
+ return new Response('Durable Object expected Upgrade: websocket', { status: 426 });
55
+ }
56
+ return durableObject.fetch(request);
57
+ }
58
+ return new Response(null, {
59
+ status: 400,
60
+ statusText: 'Bad Request',
61
+ headers: {
62
+ 'Content-Type': 'text/plain',
63
+ },
64
+ });
65
+ },
66
+ };
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cf-worker/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,8BAA8B;AAO9B,cAAc,qBAAqB,CAAA;AAEnC,wDAAwD;AACxD,kCAAkC;AAClC,6BAA6B;AAC7B,sBAAsB;AACtB,kCAAkC;AAClC,0EAA0E;AAC1E,gDAAgD;AAChD,4DAA4D;AAC5D,6DAA6D;AAE7D,sBAAsB;AAEtB,4DAA4D;AAC5D,kFAAkF;AAClF,sDAAsD;AACtD,4BAA4B;AAC5B,8BAA8B;AAC9B,sDAAsD;AACtD,kBAAkB;AAClB,cAAc;AACd,iCAAiC;AAEjC,2CAA2C;AAC3C,kGAAkG;AAClG,kHAAkH;AAClH,YAAY;AAEZ,oCAAoC;AAEpC,2EAA2E;AAC3E,YAAY;AACZ,SAAS;AACT,0CAA0C;AAC1C,uBAAuB;AACvB,0DAA0D;AAC1D,UAAU;AACV,sDAAsD;AACtD,iBAAiB;AACjB,MAAM;AAEN,SAAS;AACT,eAAe;IACb,KAAK,EAAE,KAAK,EAAE,OAAgB,EAAE,GAAQ,EAAE,IAAsB,EAAqB,EAAE;QACrF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAChC,MAAM,YAAY,GAAG,GAAG,CAAC,YAAY,CAAA;QACrC,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QAEvC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,IAAI,QAAQ,CAAC,qBAAqB,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;QAC7D,CAAC;QAED,+DAA+D;QAC/D,qCAAqC;QACrC,MAAM,EAAE,GAAG,GAAG,CAAC,gBAAgB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAA;QAClD,MAAM,aAAa,GAAG,GAAG,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QAElD,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACxC,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YACpD,IAAI,CAAC,aAAa,IAAI,aAAa,KAAK,WAAW,EAAE,CAAC;gBACpD,OAAO,IAAI,QAAQ,CAAC,4CAA4C,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAA;YACpF,CAAC;YAED,OAAO,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrC,CAAC;QAED,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG;YACX,UAAU,EAAE,aAAa;YACzB,OAAO,EAAE;gBACP,cAAc,EAAE,YAAY;aAC7B;SACF,CAAC,CAAA;IACJ,CAAC;CACF,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * as WSMessage from './ws-message-types.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * as WSMessage from './ws-message-types.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/common/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,uBAAuB,CAAA"}
@@ -0,0 +1,156 @@
1
+ import { Schema } from '@livestore/utils/effect';
2
+ export declare const PullReq: Schema.TaggedStruct<"WSMessage.PullReq", {
3
+ requestId: typeof Schema.String;
4
+ /** Omitting the cursor will start from the beginning */
5
+ cursor: Schema.optional<typeof Schema.String>;
6
+ }>;
7
+ export type PullReq = typeof PullReq.Type;
8
+ export declare const PullRes: Schema.TaggedStruct<"WSMessage.PullRes", {
9
+ requestId: typeof Schema.String;
10
+ events: Schema.Array$<Schema.Struct<{
11
+ mutation: typeof Schema.String;
12
+ args: typeof Schema.Any;
13
+ id: typeof Schema.String;
14
+ }>>;
15
+ hasMore: typeof Schema.Boolean;
16
+ }>;
17
+ export type PullRes = typeof PullRes.Type;
18
+ export declare const PushBroadcast: Schema.TaggedStruct<"WSMessage.PushBroadcast", {
19
+ requestId: typeof Schema.String;
20
+ mutationEventEncoded: Schema.Struct<{
21
+ mutation: typeof Schema.String;
22
+ args: typeof Schema.Any;
23
+ id: typeof Schema.String;
24
+ }>;
25
+ persisted: typeof Schema.Boolean;
26
+ }>;
27
+ export type PushBroadcast = typeof PushBroadcast.Type;
28
+ export declare const PushReq: Schema.TaggedStruct<"WSMessage.PushReq", {
29
+ requestId: typeof Schema.String;
30
+ mutationEventEncoded: Schema.Struct<{
31
+ mutation: typeof Schema.String;
32
+ args: typeof Schema.Any;
33
+ id: typeof Schema.String;
34
+ }>;
35
+ persisted: typeof Schema.Boolean;
36
+ }>;
37
+ export type PushReq = typeof PushReq.Type;
38
+ export declare const PushAck: Schema.TaggedStruct<"WSMessage.PushAck", {
39
+ requestId: typeof Schema.String;
40
+ mutationId: typeof Schema.String;
41
+ }>;
42
+ export type PushAck = typeof PushAck.Type;
43
+ export declare const Error: Schema.TaggedStruct<"WSMessage.Error", {
44
+ requestId: typeof Schema.String;
45
+ message: typeof Schema.String;
46
+ }>;
47
+ export declare const Ping: Schema.TaggedStruct<"WSMessage.Ping", {
48
+ requestId: Schema.Literal<["ping"]>;
49
+ }>;
50
+ export type Ping = typeof Ping.Type;
51
+ export declare const Pong: Schema.TaggedStruct<"WSMessage.Pong", {
52
+ requestId: Schema.Literal<["ping"]>;
53
+ }>;
54
+ export type Pong = typeof Pong.Type;
55
+ export declare const AdminResetRoomReq: Schema.TaggedStruct<"WSMessage.AdminResetRoomReq", {
56
+ requestId: typeof Schema.String;
57
+ adminSecret: typeof Schema.String;
58
+ }>;
59
+ export type AdminResetRoomReq = typeof AdminResetRoomReq.Type;
60
+ export declare const AdminResetRoomRes: Schema.TaggedStruct<"WSMessage.AdminResetRoomRes", {
61
+ requestId: typeof Schema.String;
62
+ }>;
63
+ export type AdminResetRoomRes = typeof AdminResetRoomRes.Type;
64
+ export declare const AdminInfoReq: Schema.TaggedStruct<"WSMessage.AdminInfoReq", {
65
+ requestId: typeof Schema.String;
66
+ adminSecret: typeof Schema.String;
67
+ }>;
68
+ export type AdminInfoReq = typeof AdminInfoReq.Type;
69
+ export declare const AdminInfoRes: Schema.TaggedStruct<"WSMessage.AdminInfoRes", {
70
+ requestId: typeof Schema.String;
71
+ info: Schema.Struct<{
72
+ durableObjectId: typeof Schema.String;
73
+ }>;
74
+ }>;
75
+ export type AdminInfoRes = typeof AdminInfoRes.Type;
76
+ export declare const Message: Schema.Union<[Schema.TaggedStruct<"WSMessage.PullReq", {
77
+ requestId: typeof Schema.String;
78
+ /** Omitting the cursor will start from the beginning */
79
+ cursor: Schema.optional<typeof Schema.String>;
80
+ }>, Schema.TaggedStruct<"WSMessage.PullRes", {
81
+ requestId: typeof Schema.String;
82
+ events: Schema.Array$<Schema.Struct<{
83
+ mutation: typeof Schema.String;
84
+ args: typeof Schema.Any;
85
+ id: typeof Schema.String;
86
+ }>>;
87
+ hasMore: typeof Schema.Boolean;
88
+ }>, Schema.TaggedStruct<"WSMessage.PushBroadcast", {
89
+ requestId: typeof Schema.String;
90
+ mutationEventEncoded: Schema.Struct<{
91
+ mutation: typeof Schema.String;
92
+ args: typeof Schema.Any;
93
+ id: typeof Schema.String;
94
+ }>;
95
+ persisted: typeof Schema.Boolean;
96
+ }>, Schema.TaggedStruct<"WSMessage.PushReq", {
97
+ requestId: typeof Schema.String;
98
+ mutationEventEncoded: Schema.Struct<{
99
+ mutation: typeof Schema.String;
100
+ args: typeof Schema.Any;
101
+ id: typeof Schema.String;
102
+ }>;
103
+ persisted: typeof Schema.Boolean;
104
+ }>, Schema.TaggedStruct<"WSMessage.PushAck", {
105
+ requestId: typeof Schema.String;
106
+ mutationId: typeof Schema.String;
107
+ }>, Schema.TaggedStruct<"WSMessage.Error", {
108
+ requestId: typeof Schema.String;
109
+ message: typeof Schema.String;
110
+ }>, Schema.TaggedStruct<"WSMessage.Ping", {
111
+ requestId: Schema.Literal<["ping"]>;
112
+ }>, Schema.TaggedStruct<"WSMessage.Pong", {
113
+ requestId: Schema.Literal<["ping"]>;
114
+ }>, Schema.TaggedStruct<"WSMessage.AdminResetRoomReq", {
115
+ requestId: typeof Schema.String;
116
+ adminSecret: typeof Schema.String;
117
+ }>, Schema.TaggedStruct<"WSMessage.AdminResetRoomRes", {
118
+ requestId: typeof Schema.String;
119
+ }>, Schema.TaggedStruct<"WSMessage.AdminInfoReq", {
120
+ requestId: typeof Schema.String;
121
+ adminSecret: typeof Schema.String;
122
+ }>, Schema.TaggedStruct<"WSMessage.AdminInfoRes", {
123
+ requestId: typeof Schema.String;
124
+ info: Schema.Struct<{
125
+ durableObjectId: typeof Schema.String;
126
+ }>;
127
+ }>]>;
128
+ export type Message = typeof Message.Type;
129
+ export type MessageEncoded = typeof Message.Encoded;
130
+ export declare const IncomingMessage: Schema.Union<[Schema.TaggedStruct<"WSMessage.PullRes", {
131
+ requestId: typeof Schema.String;
132
+ events: Schema.Array$<Schema.Struct<{
133
+ mutation: typeof Schema.String;
134
+ args: typeof Schema.Any;
135
+ id: typeof Schema.String;
136
+ }>>;
137
+ hasMore: typeof Schema.Boolean;
138
+ }>, Schema.TaggedStruct<"WSMessage.PushBroadcast", {
139
+ requestId: typeof Schema.String;
140
+ mutationEventEncoded: Schema.Struct<{
141
+ mutation: typeof Schema.String;
142
+ args: typeof Schema.Any;
143
+ id: typeof Schema.String;
144
+ }>;
145
+ persisted: typeof Schema.Boolean;
146
+ }>, Schema.TaggedStruct<"WSMessage.PushAck", {
147
+ requestId: typeof Schema.String;
148
+ mutationId: typeof Schema.String;
149
+ }>, Schema.TaggedStruct<"WSMessage.Error", {
150
+ requestId: typeof Schema.String;
151
+ message: typeof Schema.String;
152
+ }>, Schema.TaggedStruct<"WSMessage.Pong", {
153
+ requestId: Schema.Literal<["ping"]>;
154
+ }>]>;
155
+ export type IncomingMessage = typeof IncomingMessage.Type;
156
+ //# sourceMappingURL=ws-message-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-message-types.d.ts","sourceRoot":"","sources":["../../src/common/ws-message-types.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,eAAO,MAAM,OAAO;;IAElB,wDAAwD;;EAExD,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,OAAO;;;;;;;;EAMlB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,aAAa;;;;;;;;EAIxB,CAAA;AAEF,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,IAAI,CAAA;AAErD,eAAO,MAAM,OAAO;;;;;;;;EAIlB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,OAAO;;;EAGlB,CAAA;AAEF,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AAEzC,eAAO,MAAM,KAAK;;;EAGhB,CAAA;AAEF,eAAO,MAAM,IAAI;;EAEf,CAAA;AAEF,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAA;AAEnC,eAAO,MAAM,IAAI;;EAEf,CAAA;AAEF,MAAM,MAAM,IAAI,GAAG,OAAO,IAAI,CAAC,IAAI,CAAA;AAEnC,eAAO,MAAM,iBAAiB;;;EAG5B,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAA;AAE7D,eAAO,MAAM,iBAAiB;;EAE5B,CAAA;AAEF,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAA;AAE7D,eAAO,MAAM,YAAY;;;EAGvB,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,YAAY;;;;;EAKvB,CAAA;AAEF,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD,eAAO,MAAM,OAAO;;IArFlB,wDAAwD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAkGzD,CAAA;AACD,MAAM,MAAM,OAAO,GAAG,OAAO,OAAO,CAAC,IAAI,CAAA;AACzC,MAAM,MAAM,cAAc,GAAG,OAAO,OAAO,CAAC,OAAO,CAAA;AAEnD,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;IAA6D,CAAA;AACzF,MAAM,MAAM,eAAe,GAAG,OAAO,eAAe,CAAC,IAAI,CAAA"}
@@ -0,0 +1,58 @@
1
+ import { mutationEventSchemaEncodedAny } from '@livestore/common/schema';
2
+ import { Schema } from '@livestore/utils/effect';
3
+ export const PullReq = Schema.TaggedStruct('WSMessage.PullReq', {
4
+ requestId: Schema.String,
5
+ /** Omitting the cursor will start from the beginning */
6
+ cursor: Schema.optional(Schema.String),
7
+ });
8
+ export const PullRes = Schema.TaggedStruct('WSMessage.PullRes', {
9
+ requestId: Schema.String,
10
+ // /** The */
11
+ // cursor: Schema.String,
12
+ events: Schema.Array(mutationEventSchemaEncodedAny),
13
+ hasMore: Schema.Boolean,
14
+ });
15
+ export const PushBroadcast = Schema.TaggedStruct('WSMessage.PushBroadcast', {
16
+ requestId: Schema.String,
17
+ mutationEventEncoded: mutationEventSchemaEncodedAny,
18
+ persisted: Schema.Boolean,
19
+ });
20
+ export const PushReq = Schema.TaggedStruct('WSMessage.PushReq', {
21
+ requestId: Schema.String,
22
+ mutationEventEncoded: mutationEventSchemaEncodedAny,
23
+ persisted: Schema.Boolean,
24
+ });
25
+ export const PushAck = Schema.TaggedStruct('WSMessage.PushAck', {
26
+ requestId: Schema.String,
27
+ mutationId: Schema.String,
28
+ });
29
+ export const Error = Schema.TaggedStruct('WSMessage.Error', {
30
+ requestId: Schema.String,
31
+ message: Schema.String,
32
+ });
33
+ export const Ping = Schema.TaggedStruct('WSMessage.Ping', {
34
+ requestId: Schema.Literal('ping'),
35
+ });
36
+ export const Pong = Schema.TaggedStruct('WSMessage.Pong', {
37
+ requestId: Schema.Literal('ping'),
38
+ });
39
+ export const AdminResetRoomReq = Schema.TaggedStruct('WSMessage.AdminResetRoomReq', {
40
+ requestId: Schema.String,
41
+ adminSecret: Schema.String,
42
+ });
43
+ export const AdminResetRoomRes = Schema.TaggedStruct('WSMessage.AdminResetRoomRes', {
44
+ requestId: Schema.String,
45
+ });
46
+ export const AdminInfoReq = Schema.TaggedStruct('WSMessage.AdminInfoReq', {
47
+ requestId: Schema.String,
48
+ adminSecret: Schema.String,
49
+ });
50
+ export const AdminInfoRes = Schema.TaggedStruct('WSMessage.AdminInfoRes', {
51
+ requestId: Schema.String,
52
+ info: Schema.Struct({
53
+ durableObjectId: Schema.String,
54
+ }),
55
+ });
56
+ export const Message = Schema.Union(PullReq, PullRes, PushBroadcast, PushReq, PushAck, Error, Ping, Pong, AdminResetRoomReq, AdminResetRoomRes, AdminInfoReq, AdminInfoRes);
57
+ export const IncomingMessage = Schema.Union(PullRes, PushBroadcast, PushAck, Error, Pong);
58
+ //# sourceMappingURL=ws-message-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-message-types.js","sourceRoot":"","sources":["../../src/common/ws-message-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export * from './ws-impl.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync-impl/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from './ws-impl.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sync-impl/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA"}
@@ -0,0 +1,18 @@
1
+ import type { SyncBackend, SyncBackendOptionsBase } from '@livestore/common';
2
+ import type { Scope } from '@livestore/utils/effect';
3
+ import { Effect } from '@livestore/utils/effect';
4
+ export interface WsSyncOptions extends SyncBackendOptionsBase {
5
+ type: 'cf';
6
+ url: string;
7
+ roomId: string;
8
+ }
9
+ interface LiveStoreGlobalCf {
10
+ syncBackend: WsSyncOptions;
11
+ }
12
+ declare global {
13
+ interface LiveStoreGlobal extends LiveStoreGlobalCf {
14
+ }
15
+ }
16
+ export declare const makeWsSync: (options: WsSyncOptions) => Effect.Effect<SyncBackend<null>, never, Scope.Scope>;
17
+ export {};
18
+ //# sourceMappingURL=ws-impl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-impl.d.ts","sourceRoot":"","sources":["../../src/sync-impl/ws-impl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAG5E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAA;AACpD,OAAO,EAAY,MAAM,EAA0D,MAAM,yBAAyB,CAAA;AAIlH,MAAM,WAAW,aAAc,SAAQ,sBAAsB;IAC3D,IAAI,EAAE,IAAI,CAAA;IACV,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AAED,UAAU,iBAAiB;IACzB,WAAW,EAAE,aAAa,CAAA;CAC3B;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,eAAgB,SAAQ,iBAAiB;KAAG;CACvD;AAED,eAAO,MAAM,UAAU,YAAa,aAAa,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CA2EnG,CAAA"}
@@ -0,0 +1,122 @@
1
+ /// <reference lib="dom" />
2
+ import { InvalidPullError, InvalidPushError } from '@livestore/common';
3
+ import { cuid } from '@livestore/utils/cuid';
4
+ import { Deferred, Effect, Option, PubSub, Queue, Schema, Stream, SubscriptionRef } from '@livestore/utils/effect';
5
+ import { WSMessage } from '../common/index.js';
6
+ export const makeWsSync = (options) => Effect.gen(function* () {
7
+ const wsUrl = `${options.url}/websocket?room=${options.roomId}`;
8
+ const { isConnected, incomingMessages, send } = yield* connect(wsUrl);
9
+ const metadata = Option.none();
10
+ const api = {
11
+ isConnected,
12
+ pull: (args, { listenForNew }) => listenForNew
13
+ ? Stream.fromPubSub(incomingMessages).pipe(Stream.tap((_) => _._tag === 'WSMessage.Error' ? new InvalidPullError({ message: _.message }) : Effect.void), Stream.filter(Schema.is(WSMessage.PushBroadcast)), Stream.map((_) => ({
14
+ mutationEventEncoded: _.mutationEventEncoded,
15
+ persisted: _.persisted,
16
+ metadata,
17
+ })))
18
+ : Effect.gen(function* () {
19
+ const requestId = cuid();
20
+ const cursor = Option.getOrUndefined(args)?.cursor;
21
+ yield* send(WSMessage.PullReq.make({ cursor, requestId }));
22
+ return Stream.fromPubSub(incomingMessages).pipe(Stream.filter((_) => _.requestId === requestId), Stream.tap((_) => _._tag === 'WSMessage.Error' ? new InvalidPullError({ message: _.message }) : Effect.void), Stream.filter(Schema.is(WSMessage.PullRes)), Stream.takeUntil((_) => _.hasMore === false), Stream.map((_) => _.events), Stream.flattenIterables, Stream.map((mutationEventEncoded) => ({
23
+ mutationEventEncoded,
24
+ metadata,
25
+ persisted: true,
26
+ })));
27
+ }).pipe(Stream.unwrap),
28
+ push: (mutationEventEncoded, persisted) => Effect.gen(function* () {
29
+ const ready = yield* Deferred.make();
30
+ const requestId = cuid();
31
+ yield* Stream.fromPubSub(incomingMessages).pipe(Stream.filter((_) => _.requestId === requestId), Stream.tap((_) => _._tag === 'WSMessage.Error'
32
+ ? Deferred.fail(ready, new InvalidPushError({ message: _.message }))
33
+ : Effect.void), Stream.filter(Schema.is(WSMessage.PushAck)), Stream.filter((_) => _.mutationId === mutationEventEncoded.id), Stream.take(1), Stream.tap(() => Deferred.succeed(ready, void 0)), Stream.runDrain, Effect.tapCauseLogPretty, Effect.fork);
34
+ yield* send(WSMessage.PushReq.make({ mutationEventEncoded, requestId, persisted }));
35
+ yield* Deferred.await(ready);
36
+ return { metadata };
37
+ }),
38
+ };
39
+ return api;
40
+ });
41
+ const connect = (wsUrl) => Effect.gen(function* () {
42
+ const isConnected = yield* SubscriptionRef.make(false);
43
+ const wsRef = { current: undefined };
44
+ const incomingMessages = yield* PubSub.unbounded();
45
+ const waitUntilOnline = isConnected.changes.pipe(Stream.filter(Boolean), Stream.take(1), Stream.runDrain);
46
+ const send = (message) => Effect.gen(function* () {
47
+ // Wait first until we're online
48
+ yield* waitUntilOnline;
49
+ wsRef.current.send(Schema.encodeSync(Schema.parseJson(WSMessage.Message))(message));
50
+ });
51
+ const innerConnect = Effect.gen(function* () {
52
+ // If the browser already tells us we're offline, then we'll at least wait until the browser
53
+ // thinks we're online again. (We'll only know for sure once the WS conneciton is established.)
54
+ while (navigator.onLine === false) {
55
+ yield* Effect.sleep(1000);
56
+ }
57
+ // if (navigator.onLine === false) {
58
+ // yield* Effect.async((cb) => self.addEventListener('online', () => cb(Effect.void)))
59
+ // }
60
+ const ws = new WebSocket(wsUrl);
61
+ const connectionClosed = yield* Deferred.make();
62
+ const pongMessages = yield* Queue.unbounded();
63
+ const messageHandler = (event) => {
64
+ const decodedEventRes = Schema.decodeUnknownEither(Schema.parseJson(WSMessage.IncomingMessage))(event.data);
65
+ if (decodedEventRes._tag === 'Left') {
66
+ console.error('Sync: Invalid message received', decodedEventRes.left);
67
+ return;
68
+ }
69
+ else {
70
+ if (decodedEventRes.right._tag === 'WSMessage.Pong') {
71
+ Queue.offer(pongMessages, decodedEventRes.right).pipe(Effect.runSync);
72
+ }
73
+ else {
74
+ PubSub.publish(incomingMessages, decodedEventRes.right).pipe(Effect.runSync);
75
+ }
76
+ }
77
+ };
78
+ const offlineHandler = () => {
79
+ Deferred.succeed(connectionClosed, void 0).pipe(Effect.runSync);
80
+ };
81
+ // NOTE it seems that this callback doesn't work reliably on a worker but only via `window.addEventListener`
82
+ // We might need to proxy the event from the main thread to the worker if we want this to work reliably.
83
+ self.addEventListener('offline', offlineHandler);
84
+ yield* Effect.addFinalizer(() => Effect.gen(function* () {
85
+ ws.removeEventListener('message', messageHandler);
86
+ self.removeEventListener('offline', offlineHandler);
87
+ wsRef.current?.close();
88
+ wsRef.current = undefined;
89
+ yield* SubscriptionRef.set(isConnected, false);
90
+ }));
91
+ ws.addEventListener('message', messageHandler);
92
+ if (ws.readyState === WebSocket.OPEN) {
93
+ wsRef.current = ws;
94
+ SubscriptionRef.set(isConnected, true).pipe(Effect.runSync);
95
+ }
96
+ else {
97
+ ws.addEventListener('open', () => {
98
+ wsRef.current = ws;
99
+ SubscriptionRef.set(isConnected, true).pipe(Effect.runSync);
100
+ });
101
+ }
102
+ ws.addEventListener('close', () => {
103
+ Deferred.succeed(connectionClosed, void 0).pipe(Effect.runSync);
104
+ });
105
+ ws.addEventListener('error', () => {
106
+ ws.close();
107
+ Deferred.succeed(connectionClosed, void 0).pipe(Effect.runSync);
108
+ });
109
+ const checkPingPong = Effect.gen(function* () {
110
+ // TODO include pong latency infomation in network status
111
+ yield* send({ _tag: 'WSMessage.Ping', requestId: 'ping' });
112
+ // NOTE those numbers might need more fine-tuning to allow for bad network conditions
113
+ yield* Queue.take(pongMessages).pipe(Effect.timeout(5000));
114
+ yield* Effect.sleep(25_000);
115
+ }).pipe(Effect.withSpan('@livestore/sync-cf:connect:checkPingPong'));
116
+ yield* waitUntilOnline.pipe(Effect.andThen(checkPingPong.pipe(Effect.forever)), Effect.tapErrorCause(() => Deferred.succeed(connectionClosed, void 0)), Effect.forkScoped);
117
+ yield* Deferred.await(connectionClosed);
118
+ }).pipe(Effect.scoped);
119
+ yield* innerConnect.pipe(Effect.forever, Effect.tapCauseLogPretty, Effect.forkScoped);
120
+ return { isConnected, incomingMessages, send };
121
+ });
122
+ //# sourceMappingURL=ws-impl.js.map