@livestore/sync-cf 0.4.0-dev.1 → 0.4.0-dev.10

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 (136) hide show
  1. package/README.md +60 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/cf-worker/do/durable-object.d.ts +45 -0
  4. package/dist/cf-worker/do/durable-object.d.ts.map +1 -0
  5. package/dist/cf-worker/do/durable-object.js +150 -0
  6. package/dist/cf-worker/do/durable-object.js.map +1 -0
  7. package/dist/cf-worker/do/layer.d.ts +34 -0
  8. package/dist/cf-worker/do/layer.d.ts.map +1 -0
  9. package/dist/cf-worker/do/layer.js +91 -0
  10. package/dist/cf-worker/do/layer.js.map +1 -0
  11. package/dist/cf-worker/do/pull.d.ts +6 -0
  12. package/dist/cf-worker/do/pull.d.ts.map +1 -0
  13. package/dist/cf-worker/do/pull.js +47 -0
  14. package/dist/cf-worker/do/pull.js.map +1 -0
  15. package/dist/cf-worker/do/push.d.ts +14 -0
  16. package/dist/cf-worker/do/push.d.ts.map +1 -0
  17. package/dist/cf-worker/do/push.js +131 -0
  18. package/dist/cf-worker/do/push.js.map +1 -0
  19. package/dist/cf-worker/{durable-object.d.ts → do/sqlite.d.ts} +77 -70
  20. package/dist/cf-worker/do/sqlite.d.ts.map +1 -0
  21. package/dist/cf-worker/do/sqlite.js +27 -0
  22. package/dist/cf-worker/do/sqlite.js.map +1 -0
  23. package/dist/cf-worker/do/sync-storage.d.ts +25 -0
  24. package/dist/cf-worker/do/sync-storage.d.ts.map +1 -0
  25. package/dist/cf-worker/do/sync-storage.js +190 -0
  26. package/dist/cf-worker/do/sync-storage.js.map +1 -0
  27. package/dist/cf-worker/do/transport/do-rpc-server.d.ts +9 -0
  28. package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -0
  29. package/dist/cf-worker/do/transport/do-rpc-server.js +45 -0
  30. package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -0
  31. package/dist/cf-worker/do/transport/http-rpc-server.d.ts +7 -0
  32. package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -0
  33. package/dist/cf-worker/do/transport/http-rpc-server.js +24 -0
  34. package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -0
  35. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +4 -0
  36. package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -0
  37. package/dist/cf-worker/do/transport/ws-rpc-server.js +21 -0
  38. package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -0
  39. package/dist/cf-worker/mod.d.ts +4 -2
  40. package/dist/cf-worker/mod.d.ts.map +1 -1
  41. package/dist/cf-worker/mod.js +3 -2
  42. package/dist/cf-worker/mod.js.map +1 -1
  43. package/dist/cf-worker/shared.d.ts +147 -0
  44. package/dist/cf-worker/shared.d.ts.map +1 -0
  45. package/dist/cf-worker/shared.js +32 -0
  46. package/dist/cf-worker/shared.js.map +1 -0
  47. package/dist/cf-worker/worker.d.ts +45 -45
  48. package/dist/cf-worker/worker.d.ts.map +1 -1
  49. package/dist/cf-worker/worker.js +51 -39
  50. package/dist/cf-worker/worker.js.map +1 -1
  51. package/dist/client/mod.d.ts +4 -0
  52. package/dist/client/mod.d.ts.map +1 -0
  53. package/dist/client/mod.js +4 -0
  54. package/dist/client/mod.js.map +1 -0
  55. package/dist/client/transport/do-rpc-client.d.ts +40 -0
  56. package/dist/client/transport/do-rpc-client.d.ts.map +1 -0
  57. package/dist/client/transport/do-rpc-client.js +117 -0
  58. package/dist/client/transport/do-rpc-client.js.map +1 -0
  59. package/dist/client/transport/http-rpc-client.d.ts +43 -0
  60. package/dist/client/transport/http-rpc-client.d.ts.map +1 -0
  61. package/dist/client/transport/http-rpc-client.js +103 -0
  62. package/dist/client/transport/http-rpc-client.js.map +1 -0
  63. package/dist/client/transport/ws-rpc-client.d.ts +45 -0
  64. package/dist/client/transport/ws-rpc-client.d.ts.map +1 -0
  65. package/dist/client/transport/ws-rpc-client.js +108 -0
  66. package/dist/client/transport/ws-rpc-client.js.map +1 -0
  67. package/dist/common/constants.d.ts +7 -0
  68. package/dist/common/constants.d.ts.map +1 -0
  69. package/dist/common/constants.js +17 -0
  70. package/dist/common/constants.js.map +1 -0
  71. package/dist/common/do-rpc-schema.d.ts +76 -0
  72. package/dist/common/do-rpc-schema.d.ts.map +1 -0
  73. package/dist/common/do-rpc-schema.js +48 -0
  74. package/dist/common/do-rpc-schema.js.map +1 -0
  75. package/dist/common/http-rpc-schema.d.ts +58 -0
  76. package/dist/common/http-rpc-schema.d.ts.map +1 -0
  77. package/dist/common/http-rpc-schema.js +37 -0
  78. package/dist/common/http-rpc-schema.js.map +1 -0
  79. package/dist/common/mod.d.ts +8 -1
  80. package/dist/common/mod.d.ts.map +1 -1
  81. package/dist/common/mod.js +7 -1
  82. package/dist/common/mod.js.map +1 -1
  83. package/dist/common/{ws-message-types.d.ts → sync-message-types.d.ts} +119 -153
  84. package/dist/common/sync-message-types.d.ts.map +1 -0
  85. package/dist/common/sync-message-types.js +60 -0
  86. package/dist/common/sync-message-types.js.map +1 -0
  87. package/dist/common/ws-rpc-schema.d.ts +55 -0
  88. package/dist/common/ws-rpc-schema.d.ts.map +1 -0
  89. package/dist/common/ws-rpc-schema.js +32 -0
  90. package/dist/common/ws-rpc-schema.js.map +1 -0
  91. package/package.json +7 -8
  92. package/src/cf-worker/do/durable-object.ts +237 -0
  93. package/src/cf-worker/do/layer.ts +128 -0
  94. package/src/cf-worker/do/pull.ts +77 -0
  95. package/src/cf-worker/do/push.ts +205 -0
  96. package/src/cf-worker/do/sqlite.ts +28 -0
  97. package/src/cf-worker/do/sync-storage.ts +321 -0
  98. package/src/cf-worker/do/transport/do-rpc-server.ts +84 -0
  99. package/src/cf-worker/do/transport/http-rpc-server.ts +37 -0
  100. package/src/cf-worker/do/transport/ws-rpc-server.ts +34 -0
  101. package/src/cf-worker/mod.ts +4 -2
  102. package/src/cf-worker/shared.ts +112 -0
  103. package/src/cf-worker/worker.ts +91 -105
  104. package/src/client/mod.ts +3 -0
  105. package/src/client/transport/do-rpc-client.ts +191 -0
  106. package/src/client/transport/http-rpc-client.ts +225 -0
  107. package/src/client/transport/ws-rpc-client.ts +202 -0
  108. package/src/common/constants.ts +18 -0
  109. package/src/common/do-rpc-schema.ts +54 -0
  110. package/src/common/http-rpc-schema.ts +40 -0
  111. package/src/common/mod.ts +10 -1
  112. package/src/common/sync-message-types.ts +117 -0
  113. package/src/common/ws-rpc-schema.ts +36 -0
  114. package/dist/cf-worker/cf-types.d.ts +0 -2
  115. package/dist/cf-worker/cf-types.d.ts.map +0 -1
  116. package/dist/cf-worker/cf-types.js +0 -2
  117. package/dist/cf-worker/cf-types.js.map +0 -1
  118. package/dist/cf-worker/durable-object.d.ts.map +0 -1
  119. package/dist/cf-worker/durable-object.js +0 -317
  120. package/dist/cf-worker/durable-object.js.map +0 -1
  121. package/dist/common/ws-message-types.d.ts.map +0 -1
  122. package/dist/common/ws-message-types.js +0 -57
  123. package/dist/common/ws-message-types.js.map +0 -1
  124. package/dist/sync-impl/mod.d.ts +0 -2
  125. package/dist/sync-impl/mod.d.ts.map +0 -1
  126. package/dist/sync-impl/mod.js +0 -2
  127. package/dist/sync-impl/mod.js.map +0 -1
  128. package/dist/sync-impl/ws-impl.d.ts +0 -7
  129. package/dist/sync-impl/ws-impl.d.ts.map +0 -1
  130. package/dist/sync-impl/ws-impl.js +0 -175
  131. package/dist/sync-impl/ws-impl.js.map +0 -1
  132. package/src/cf-worker/cf-types.ts +0 -12
  133. package/src/cf-worker/durable-object.ts +0 -478
  134. package/src/common/ws-message-types.ts +0 -114
  135. package/src/sync-impl/mod.ts +0 -1
  136. package/src/sync-impl/ws-impl.ts +0 -274
@@ -0,0 +1,117 @@
1
+ import { InvalidPullError, InvalidPushError, SyncBackend, UnexpectedError } from '@livestore/common';
2
+ import { splitChunkBySize } from '@livestore/common/sync';
3
+ import { layerProtocolDurableObject } from '@livestore/common-cf';
4
+ import { omit, shouldNeverHappen } from '@livestore/utils';
5
+ import { Chunk, Effect, identity, Layer, Mailbox, Option, RpcClient, RpcSerialization, Schema, Stream, SubscriptionRef, } from '@livestore/utils/effect';
6
+ import { MAX_DO_RPC_REQUEST_BYTES, MAX_PUSH_EVENTS_PER_REQUEST } from "../../common/constants.js";
7
+ import { SyncDoRpc } from "../../common/do-rpc-schema.js";
8
+ import { SyncMessage } from "../../common/mod.js";
9
+ const requestIdMailboxMap = new Map();
10
+ /**
11
+ * Creates a sync backend that uses Durable Object RPC to communicate with the sync backend.
12
+ *
13
+ * Used internally by `@livestore/adapter-cf` to connect to the sync backend.
14
+ */
15
+ export const makeDoRpcSync = ({ syncBackendStub, durableObjectContext }) => ({ storeId, payload }) => Effect.gen(function* () {
16
+ const isConnected = yield* SubscriptionRef.make(true);
17
+ const ProtocolLive = layerProtocolDurableObject({
18
+ callRpc: (payload) => syncBackendStub.rpc(payload),
19
+ callerContext: durableObjectContext,
20
+ }).pipe(Layer.provide(RpcSerialization.layerJson));
21
+ const context = yield* Layer.build(ProtocolLive);
22
+ const rpcClient = yield* RpcClient.make(SyncDoRpc).pipe(Effect.provide(context));
23
+ // Nothing to do here
24
+ const connect = Effect.void;
25
+ const backendIdHelper = yield* SyncBackend.makeBackendIdHelper;
26
+ const pull = (cursor, options) => rpcClient.SyncDoRpc.Pull({
27
+ cursor: cursor.pipe(Option.map((a) => ({
28
+ eventSequenceNumber: a.eventSequenceNumber,
29
+ backendId: backendIdHelper.get().pipe(Option.getOrThrow),
30
+ }))),
31
+ storeId,
32
+ rpcContext: options?.live ? { callerContext: durableObjectContext } : undefined,
33
+ }).pipe(options?.live
34
+ ? Stream.concatWithLastElement((res) => Effect.gen(function* () {
35
+ if (res._tag === 'None')
36
+ return shouldNeverHappen('There should at least be a no-more page info response');
37
+ const mailbox = yield* Mailbox.make().pipe(Effect.acquireRelease((mailbox) => mailbox.shutdown));
38
+ requestIdMailboxMap.set(res.value.rpcRequestId, mailbox);
39
+ return Mailbox.toStream(mailbox);
40
+ }).pipe(Stream.unwrapScoped))
41
+ : identity, Stream.tap((res) => backendIdHelper.lazySet(res.backendId)), Stream.map((res) => omit(res, ['backendId'])), Stream.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))), Stream.withSpan('rpc-sync-client:pull'));
42
+ const push = (batch) => Effect.gen(function* () {
43
+ if (batch.length === 0) {
44
+ return;
45
+ }
46
+ const backendId = backendIdHelper.get();
47
+ const batchChunks = yield* Chunk.fromIterable(batch).pipe(splitChunkBySize({
48
+ maxItems: MAX_PUSH_EVENTS_PER_REQUEST,
49
+ maxBytes: MAX_DO_RPC_REQUEST_BYTES,
50
+ encode: (items) => ({
51
+ batch: items,
52
+ storeId,
53
+ backendId,
54
+ }),
55
+ }), Effect.mapError((cause) => new InvalidPushError({ cause: new UnexpectedError({ cause }) })));
56
+ for (const chunk of Chunk.toReadonlyArray(batchChunks)) {
57
+ const chunkArray = Chunk.toReadonlyArray(chunk);
58
+ yield* rpcClient.SyncDoRpc.Push({ batch: chunkArray, storeId, backendId });
59
+ }
60
+ }).pipe(Effect.mapError((cause) => cause._tag === 'InvalidPushError'
61
+ ? cause
62
+ : InvalidPushError.make({ cause: new UnexpectedError({ cause }) })), Effect.withSpan('rpc-sync-client:push'));
63
+ const ping = rpcClient.SyncDoRpc.Ping({
64
+ storeId,
65
+ payload,
66
+ }).pipe(UnexpectedError.mapToUnexpectedError, Effect.withSpan('rpc-sync-client:ping'));
67
+ return SyncBackend.of({
68
+ connect,
69
+ isConnected,
70
+ pull,
71
+ push,
72
+ ping,
73
+ metadata: {
74
+ name: 'rpc-sync-client',
75
+ description: 'Cloudflare Durable Object RPC Sync Client',
76
+ protocol: 'rpc',
77
+ storeId,
78
+ },
79
+ supports: {
80
+ pullPageInfoKnown: true,
81
+ pullLive: true,
82
+ },
83
+ });
84
+ }).pipe(Effect.withSpan('rpc-sync-client:makeDoRpcSync'));
85
+ /**
86
+ *
87
+ * ```ts
88
+ * import { DurableObject } from 'cloudflare:workers'
89
+ * import { ClientDoWithRpcCallback } from '@livestore/common-cf'
90
+ *
91
+ * export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback {
92
+ * // ...
93
+ *
94
+ * async syncUpdateRpc(payload: RpcMessage.ResponseChunkEncoded) {
95
+ * return handleSyncUpdateRpc(payload)
96
+ * }
97
+ * }
98
+ * ```
99
+ */
100
+ export const handleSyncUpdateRpc = (payload) => Effect.gen(function* () {
101
+ const decodedPayload = yield* Schema.decodeUnknown(ResponseChunkEncoded)(payload);
102
+ const decoded = yield* Schema.decodeUnknown(SyncMessage.PullResponse)(decodedPayload.values[0]);
103
+ const pullStreamMailbox = requestIdMailboxMap.get(decodedPayload.requestId);
104
+ if (pullStreamMailbox === undefined) {
105
+ // Case: DO was hibernated, so we need to manually update the store
106
+ yield* Effect.log(`No mailbox found for ${decodedPayload.requestId}`);
107
+ }
108
+ else {
109
+ // Case: DO was still alive, so the existing `pull` will pick up the new events
110
+ yield* pullStreamMailbox.offer(decoded);
111
+ }
112
+ }).pipe(Effect.withSpan('rpc-sync-client:rpcCallback'), Effect.tapCauseLogPretty, Effect.runPromise);
113
+ const ResponseChunkEncoded = Schema.Struct({
114
+ requestId: Schema.String,
115
+ values: Schema.Array(Schema.Any),
116
+ });
117
+ //# sourceMappingURL=do-rpc-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"do-rpc-client.js","sourceRoot":"","sources":["../../../src/client/transport/do-rpc-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { SyncBackend } from '@livestore/common';
2
+ import { type Duration } from '@livestore/utils/effect';
3
+ import type { SyncMetadata } from '../../common/sync-message-types.ts';
4
+ export interface HttpSyncOptions {
5
+ /**
6
+ * URL of the sync backend
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const syncBackend = makeHttpSync({ url: 'https://sync.example.com' })
11
+ * ```
12
+ */
13
+ url: string;
14
+ headers?: Record<string, string>;
15
+ livePull?: {
16
+ /**
17
+ * How often to poll for new events
18
+ * @default 5 seconds
19
+ */
20
+ pollInterval?: Duration.DurationInput;
21
+ };
22
+ ping?: {
23
+ /**
24
+ * @default true
25
+ */
26
+ enabled?: boolean;
27
+ /**
28
+ * How long to wait for a ping response before timing out
29
+ * @default 10 seconds
30
+ */
31
+ requestTimeout?: Duration.DurationInput;
32
+ /**
33
+ * How often to send ping requests
34
+ * @default 10 seconds
35
+ */
36
+ requestInterval?: Duration.DurationInput;
37
+ };
38
+ }
39
+ /**
40
+ * Note: This implementation requires the `enable_request_signal` compatibility flag to properly support `pull` streaming responses
41
+ */
42
+ export declare const makeHttpSync: (options: HttpSyncOptions) => SyncBackend.SyncBackendConstructor<SyncMetadata>;
43
+ //# sourceMappingURL=http-rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-rpc-client.d.ts","sourceRoot":"","sources":["../../../src/client/transport/http-rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,WAAW,EAAmB,MAAM,mBAAmB,CAAA;AAIpG,OAAO,EAEL,KAAK,QAAQ,EAcd,MAAM,yBAAyB,CAAA;AAIhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,QAAQ,CAAC,EAAE;QACT;;;WAGG;QACH,YAAY,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;KACtC,CAAA;IACD,IAAI,CAAC,EAAE;QACL;;WAEG;QACH,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;WAGG;QACH,cAAc,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;QACvC;;;WAGG;QACH,eAAe,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;KACzC,CAAA;CACF;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,GACtB,SAAS,eAAe,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CA8JvE,CAAA"}
@@ -0,0 +1,103 @@
1
+ import { InvalidPullError, InvalidPushError, SyncBackend, UnexpectedError } from '@livestore/common';
2
+ import { splitChunkBySize } from '@livestore/common/sync';
3
+ import { omit } from '@livestore/utils';
4
+ import { Chunk, Effect, HttpClient, HttpClientRequest, identity, Layer, Option, RpcClient, RpcSerialization, Schedule, Schema, Stream, SubscriptionRef, UrlParams, } from '@livestore/utils/effect';
5
+ import { MAX_HTTP_REQUEST_BYTES, MAX_PUSH_EVENTS_PER_REQUEST } from "../../common/constants.js";
6
+ import { SyncHttpRpc } from "../../common/http-rpc-schema.js";
7
+ import { SearchParamsSchema } from "../../common/mod.js";
8
+ /**
9
+ * Note: This implementation requires the `enable_request_signal` compatibility flag to properly support `pull` streaming responses
10
+ */
11
+ export const makeHttpSync = (options) => ({ storeId, payload }) => Effect.gen(function* () {
12
+ // Based on ping responses
13
+ const isConnected = yield* SubscriptionRef.make(false);
14
+ const livePullInterval = options.livePull?.pollInterval ?? 5_000;
15
+ const urlParamsData = yield* Schema.encode(SearchParamsSchema)({
16
+ storeId,
17
+ payload,
18
+ transport: 'http',
19
+ }).pipe(UnexpectedError.mapToUnexpectedError);
20
+ const urlParams = UrlParams.fromInput(urlParamsData);
21
+ // Setup HTTP RPC Protocol
22
+ const HttpProtocolLive = RpcClient.layerProtocolHttp({
23
+ url: `${options.url}?${UrlParams.toString(urlParams)}`,
24
+ transformClient: HttpClient.mapRequest((request) => request.pipe(HttpClientRequest.setHeaders({
25
+ ...options.headers,
26
+ // Used in CF Worker to identify the store (additionally to storeId embedded in the RPC requests)
27
+ 'x-livestore-store-id': storeId,
28
+ }))),
29
+ }).pipe(Layer.provide(RpcSerialization.layerJson));
30
+ const rpcClient = yield* RpcClient.make(SyncHttpRpc).pipe(Effect.provide(HttpProtocolLive));
31
+ const pingTimeout = options.ping?.requestTimeout ?? 10_000;
32
+ const ping = Effect.gen(function* () {
33
+ yield* rpcClient.SyncHttpRpc.Ping({ storeId, payload });
34
+ yield* SubscriptionRef.set(isConnected, true);
35
+ }).pipe(UnexpectedError.mapToUnexpectedError, Effect.timeout(pingTimeout), Effect.catchTag('TimeoutException', () => SubscriptionRef.set(isConnected, false)));
36
+ const pingInterval = options.ping?.requestInterval ?? 10_000;
37
+ if (options.ping?.enabled !== false) {
38
+ // Automatically ping the server to keep the connection alive
39
+ yield* ping.pipe(Effect.repeat(Schedule.spaced(pingInterval)), Effect.tapCauseLogPretty, Effect.forkScoped);
40
+ }
41
+ // Helps already establish a TCP connection to the server
42
+ const connect = ping.pipe(UnexpectedError.mapToUnexpectedError);
43
+ const backendIdHelper = yield* SyncBackend.makeBackendIdHelper;
44
+ const mapCursor = (cursor) => cursor.pipe(Option.map((a) => ({
45
+ eventSequenceNumber: a.eventSequenceNumber,
46
+ backendId: backendIdHelper.get().pipe(Option.getOrThrow),
47
+ })));
48
+ const pull = (cursor, options) => rpcClient.SyncHttpRpc.Pull({
49
+ storeId,
50
+ payload,
51
+ cursor: mapCursor(cursor),
52
+ }).pipe(options?.live
53
+ ? // Phase 2: Simulate `live` pull by polling for new events
54
+ Stream.concatWithLastElement((lastElement) => {
55
+ const initialPhase2Cursor = lastElement.pipe(Option.flatMap((_) => Option.fromNullable(_.batch.at(-1)?.eventEncoded.seqNum)), Option.map((eventSequenceNumber) => ({ eventSequenceNumber })), Option.orElse(() => cursor), mapCursor);
56
+ return Stream.unfoldChunkEffect(initialPhase2Cursor, (currentCursor) => Effect.gen(function* () {
57
+ yield* Effect.sleep(livePullInterval);
58
+ const items = yield* rpcClient.SyncHttpRpc.Pull({ storeId, payload, cursor: currentCursor }).pipe(Stream.runCollect);
59
+ const nextCursor = Chunk.last(items).pipe(Option.flatMap((item) => Option.fromNullable(item.batch.at(-1)?.eventEncoded.seqNum)), Option.map((eventSequenceNumber) => ({ eventSequenceNumber })), Option.orElse(() => currentCursor), mapCursor);
60
+ return Option.some([items, nextCursor]);
61
+ }));
62
+ })
63
+ : identity, Stream.tap((res) => backendIdHelper.lazySet(res.backendId)), Stream.map((res) => omit(res, ['backendId'])), Stream.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))), Stream.withSpan('http-sync-client:pull'));
64
+ const pushSemaphore = yield* Effect.makeSemaphore(1);
65
+ const push = (batch) => Effect.gen(function* () {
66
+ if (batch.length === 0) {
67
+ return;
68
+ }
69
+ const backendId = backendIdHelper.get();
70
+ const batchChunks = yield* Chunk.fromIterable(batch).pipe(splitChunkBySize({
71
+ maxItems: MAX_PUSH_EVENTS_PER_REQUEST,
72
+ maxBytes: MAX_HTTP_REQUEST_BYTES,
73
+ encode: (items) => ({
74
+ batch: items,
75
+ storeId,
76
+ payload,
77
+ backendId,
78
+ }),
79
+ }), Effect.mapError((cause) => new InvalidPushError({ cause: new UnexpectedError({ cause }) })));
80
+ for (const chunk of Chunk.toReadonlyArray(batchChunks)) {
81
+ const chunkArray = Chunk.toReadonlyArray(chunk);
82
+ yield* rpcClient.SyncHttpRpc.Push({ storeId, payload, batch: chunkArray, backendId });
83
+ }
84
+ }).pipe(pushSemaphore.withPermits(1), Effect.mapError((cause) => cause._tag === 'InvalidPushError' ? cause : new InvalidPushError({ cause: new UnexpectedError({ cause }) })), Effect.withSpan('http-sync-client:push'));
85
+ return SyncBackend.of({
86
+ connect,
87
+ isConnected,
88
+ pull,
89
+ push,
90
+ ping,
91
+ metadata: {
92
+ name: '@livestore/cf-sync-http',
93
+ description: 'LiveStore sync backend implementation using HTTP RPC',
94
+ protocol: 'http',
95
+ url: options.url,
96
+ },
97
+ supports: {
98
+ pullPageInfoKnown: true,
99
+ pullLive: true,
100
+ },
101
+ });
102
+ });
103
+ //# sourceMappingURL=http-rpc-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-rpc-client.js","sourceRoot":"","sources":["../../../src/client/transport/http-rpc-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { SyncBackend } from '@livestore/common';
2
+ import { type Duration, Effect, type Scope, type WebSocket } from '@livestore/utils/effect';
3
+ import type { SyncMetadata } from '../../common/sync-message-types.ts';
4
+ export interface WsSyncOptions {
5
+ /**
6
+ * URL of the sync backend
7
+ *
8
+ * The protocol can either `http`/`https` or `ws`/`wss`
9
+ *
10
+ * @example 'https://sync.example.com'
11
+ */
12
+ url: string;
13
+ /**
14
+ * Optional WebSocket factory for custom WebSocket implementations (e.g., Cloudflare Durable Objects)
15
+ * If not provided, uses standard WebSocket from @livestore/utils/effect
16
+ */
17
+ webSocketFactory?: (wsUrl: string) => Effect.Effect<globalThis.WebSocket, WebSocket.WebSocketError, Scope.Scope>;
18
+ ping?: {
19
+ /**
20
+ * @default true
21
+ */
22
+ enabled?: boolean;
23
+ /**
24
+ * How long to wait for a ping response before timing out
25
+ * @default 10 seconds
26
+ */
27
+ requestTimeout?: Duration.DurationInput;
28
+ /**
29
+ * How often to send ping requests
30
+ * @default 10 seconds
31
+ */
32
+ requestInterval?: Duration.DurationInput;
33
+ };
34
+ }
35
+ /**
36
+ * Creates a sync backend that uses WebSocket to communicate with the sync backend.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * import { makeWsSync } from '@livestore/sync-cf/client'
41
+ *
42
+ * const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })
43
+ */
44
+ export declare const makeWsSync: (options: WsSyncOptions) => SyncBackend.SyncBackendConstructor<SyncMetadata>;
45
+ //# sourceMappingURL=ws-rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-rpc-client.d.ts","sourceRoot":"","sources":["../../../src/client/transport/ws-rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsD,WAAW,EAAmB,MAAM,mBAAmB,CAAA;AAIpH,OAAO,EAEL,KAAK,QAAQ,EACb,MAAM,EAON,KAAK,KAAK,EAKV,KAAK,SAAS,EACf,MAAM,yBAAyB,CAAA;AAGhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAGtE,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,GAAG,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;IAChH,IAAI,CAAC,EAAE;QACL;;WAEG;QACH,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB;;;WAGG;QACH,cAAc,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;QACvC;;;WAGG;QACH,eAAe,CAAC,EAAE,QAAQ,CAAC,aAAa,CAAA;KACzC,CAAA;CACF;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,GACpB,SAAS,aAAa,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CAqIrE,CAAA"}
@@ -0,0 +1,108 @@
1
+ import { InvalidPullError, InvalidPushError, IsOfflineError, SyncBackend, UnexpectedError } from '@livestore/common';
2
+ import { splitChunkBySize } from '@livestore/common/sync';
3
+ import { omit } from '@livestore/utils';
4
+ import { Chunk, Effect, Layer, Option, RpcClient, RpcSerialization, Schedule, Schema, Socket, Stream, SubscriptionRef, UrlParams, } from '@livestore/utils/effect';
5
+ import { MAX_PUSH_EVENTS_PER_REQUEST, MAX_WS_MESSAGE_BYTES } from "../../common/constants.js";
6
+ import { SearchParamsSchema } from "../../common/mod.js";
7
+ import { SyncWsRpc } from "../../common/ws-rpc-schema.js";
8
+ /**
9
+ * Creates a sync backend that uses WebSocket to communicate with the sync backend.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { makeWsSync } from '@livestore/sync-cf/client'
14
+ *
15
+ * const syncBackend = makeWsSync({ url: 'wss://sync.example.com' })
16
+ */
17
+ export const makeWsSync = (options) => ({ storeId, payload }) => Effect.gen(function* () {
18
+ const urlParamsData = yield* Schema.encode(SearchParamsSchema)({
19
+ storeId,
20
+ payload,
21
+ transport: 'ws',
22
+ }).pipe(UnexpectedError.mapToUnexpectedError);
23
+ const urlParams = UrlParams.fromInput(urlParamsData);
24
+ const wsUrl = `${options.url}?${UrlParams.toString(urlParams)}`;
25
+ const isConnected = yield* SubscriptionRef.make(false);
26
+ // TODO bring this back in a cross-platform way
27
+ // If the browser already tells us we're offline, then we'll at least wait until the browser
28
+ // thinks we're online again. (We'll only know for sure once the WS conneciton is established.)
29
+ // while (typeof navigator !== 'undefined' && navigator.onLine === false) {
30
+ // yield* Effect.sleep(1000)
31
+ // }
32
+ // TODO bring this back in a cross-platform way
33
+ // if (navigator.onLine === false) {
34
+ // yield* Effect.async((cb) => self.addEventListener('online', () => cb(Effect.void)))
35
+ // }
36
+ const pingInterval = options.ping?.requestInterval ?? 10_000;
37
+ const ProtocolLive = RpcClient.layerProtocolSocketWithIsConnected({
38
+ isConnected,
39
+ retryTransientErrors: Schedule.fixed(1000),
40
+ pingSchedule: Schedule.once.pipe(Schedule.andThen(Schedule.fixed(pingInterval))),
41
+ url: wsUrl,
42
+ }).pipe(Layer.provide(Socket.layerWebSocket(wsUrl)), Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(RpcSerialization.layerJson));
43
+ // Warning: we need to build the layer here eagerly to tie it to the scope
44
+ // instead of using `Effect.provide(ProtocolLive)` which would close the layer scope too early
45
+ const ctx = yield* Layer.build(ProtocolLive);
46
+ const rpcClient = yield* RpcClient.make(SyncWsRpc).pipe(Effect.provide(ctx));
47
+ const pingTimeout = options.ping?.requestTimeout ?? 10_000;
48
+ const ping = Effect.gen(function* () {
49
+ const pinger = yield* RpcClient.SocketPinger.pipe(Effect.provide(ctx));
50
+ yield* pinger.ping;
51
+ yield* SubscriptionRef.set(isConnected, true);
52
+ }).pipe(Effect.timeout(pingTimeout), Effect.catchTag('TimeoutException', () => SubscriptionRef.set(isConnected, false)), UnexpectedError.mapToUnexpectedError, Effect.withSpan('ping'));
53
+ const backendIdHelper = yield* SyncBackend.makeBackendIdHelper;
54
+ return SyncBackend.of({
55
+ isConnected,
56
+ connect: ping,
57
+ pull: (cursor, options) => rpcClient.SyncWsRpc.Pull({
58
+ storeId,
59
+ payload,
60
+ cursor: cursor.pipe(Option.map((a) => ({
61
+ eventSequenceNumber: a.eventSequenceNumber,
62
+ backendId: backendIdHelper.get().pipe(Option.getOrThrow),
63
+ }))),
64
+ live: options?.live ?? false,
65
+ }).pipe(Stream.tap((res) => backendIdHelper.lazySet(res.backendId)), Stream.map((res) => omit(res, ['backendId'])), Stream.mapError((cause) => cause._tag === 'RpcClientError' && Socket.isSocketError(cause.cause)
66
+ ? new IsOfflineError({ cause: cause.cause })
67
+ : cause._tag === 'InvalidPullError'
68
+ ? cause
69
+ : InvalidPullError.make({ cause })), Stream.withSpan('pull')),
70
+ push: (batch) => Effect.gen(function* () {
71
+ if (batch.length === 0)
72
+ return;
73
+ const encodePayload = (batch) => ({
74
+ storeId,
75
+ payload,
76
+ batch,
77
+ backendId: backendIdHelper.get(),
78
+ });
79
+ const chunksChunk = yield* Chunk.fromIterable(batch).pipe(splitChunkBySize({
80
+ maxItems: MAX_PUSH_EVENTS_PER_REQUEST,
81
+ maxBytes: MAX_WS_MESSAGE_BYTES,
82
+ encode: encodePayload,
83
+ }), Effect.mapError((cause) => new InvalidPushError({ cause: new UnexpectedError({ cause }) })));
84
+ for (const sub of chunksChunk) {
85
+ yield* rpcClient.SyncWsRpc.Push({
86
+ storeId,
87
+ payload,
88
+ batch: Chunk.toReadonlyArray(sub),
89
+ backendId: backendIdHelper.get(),
90
+ }).pipe(Effect.mapError((cause) => cause._tag === 'InvalidPushError'
91
+ ? cause
92
+ : new InvalidPushError({ cause: new UnexpectedError({ cause }) })));
93
+ }
94
+ }).pipe(Effect.withSpan('push')),
95
+ ping,
96
+ metadata: {
97
+ name: '@livestore/cf-sync',
98
+ description: 'LiveStore sync backend implementation using Cloudflare Workers & Durable Objects',
99
+ protocol: 'ws',
100
+ url: options.url,
101
+ },
102
+ supports: {
103
+ pullPageInfoKnown: true,
104
+ pullLive: true,
105
+ },
106
+ });
107
+ });
108
+ //# sourceMappingURL=ws-rpc-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-rpc-client.js","sourceRoot":"","sources":["../../../src/client/transport/ws-rpc-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare const MAX_TRANSPORT_PAYLOAD_BYTES = 900000;
2
+ export declare const MAX_WS_MESSAGE_BYTES = 900000;
3
+ export declare const MAX_DO_RPC_REQUEST_BYTES = 900000;
4
+ export declare const MAX_HTTP_REQUEST_BYTES = 900000;
5
+ export declare const MAX_PULL_EVENTS_PER_MESSAGE = 100;
6
+ export declare const MAX_PUSH_EVENTS_PER_REQUEST = 100;
7
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/common/constants.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,2BAA2B,SAAU,CAAA;AAElD,eAAO,MAAM,oBAAoB,SAA8B,CAAA;AAC/D,eAAO,MAAM,wBAAwB,SAA8B,CAAA;AACnE,eAAO,MAAM,sBAAsB,SAA8B,CAAA;AAIjE,eAAO,MAAM,2BAA2B,MAAM,CAAA;AAC9C,eAAO,MAAM,2BAA2B,MAAM,CAAA"}
@@ -0,0 +1,17 @@
1
+ // Shared transport limits for Cloudflare sync provider
2
+ // Keep payloads comfortably below ~1MB frame caps across Cloudflare transports.
3
+ // References:
4
+ // - Durable Objects WebSockets + hibernation best practices:
5
+ // https://developers.cloudflare.com/durable-objects/best-practices/websockets/
6
+ // - Workers platform limits (general context):
7
+ // https://developers.cloudflare.com/workers/platform/limits/
8
+ // Empirically, frames just below 1MB can fail on hibernated DO WebSockets; we use 900_000 bytes to keep a safety margin.
9
+ export const MAX_TRANSPORT_PAYLOAD_BYTES = 900_000;
10
+ export const MAX_WS_MESSAGE_BYTES = MAX_TRANSPORT_PAYLOAD_BYTES;
11
+ export const MAX_DO_RPC_REQUEST_BYTES = MAX_TRANSPORT_PAYLOAD_BYTES;
12
+ export const MAX_HTTP_REQUEST_BYTES = MAX_TRANSPORT_PAYLOAD_BYTES;
13
+ // Upper bound for items per message/request. Mirrors server broadcast chunking.
14
+ // Not Cloudflare-enforced; chosen to balance payload size and latency.
15
+ export const MAX_PULL_EVENTS_PER_MESSAGE = 100;
16
+ export const MAX_PUSH_EVENTS_PER_REQUEST = 100;
17
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/common/constants.ts"],"names":[],"mappings":"AAAA,uDAAuD;AACvD,gFAAgF;AAChF,cAAc;AACd,6DAA6D;AAC7D,iFAAiF;AACjF,+CAA+C;AAC/C,+DAA+D;AAC/D,yHAAyH;AACzH,MAAM,CAAC,MAAM,2BAA2B,GAAG,OAAO,CAAA;AAElD,MAAM,CAAC,MAAM,oBAAoB,GAAG,2BAA2B,CAAA;AAC/D,MAAM,CAAC,MAAM,wBAAwB,GAAG,2BAA2B,CAAA;AACnE,MAAM,CAAC,MAAM,sBAAsB,GAAG,2BAA2B,CAAA;AAEjE,gFAAgF;AAChF,uEAAuE;AACvE,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA;AAC9C,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAA"}
@@ -0,0 +1,76 @@
1
+ import { InvalidPullError, InvalidPushError } from '@livestore/common';
2
+ import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
3
+ declare const SyncDoRpc_base: RpcGroup.RpcGroup<Rpc.Rpc<"SyncDoRpc.Pull", Schema.Struct<{
4
+ /**
5
+ * While the storeId is already implied by the durable object, we still need the explicit storeId
6
+ * since a DO doesn't know its own id.name value. 🫠
7
+ * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
8
+ */
9
+ storeId: typeof Schema.String;
10
+ /** Needed for various reasons (e.g. auth) */
11
+ payload: Schema.optional<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
12
+ /** Omitting the cursor will start from the beginning */
13
+ cursor: Schema.Option<Schema.Struct<{
14
+ backendId: Schema.SchemaClass<string, string, never>;
15
+ eventSequenceNumber: Schema.BrandSchema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
16
+ }>>;
17
+ /** Whether to keep the pull stream alive and wait for more events */
18
+ rpcContext: Schema.optional<Schema.Struct<{
19
+ callerContext: Schema.Struct<{
20
+ bindingName: typeof Schema.String;
21
+ durableObjectId: typeof Schema.String;
22
+ }>;
23
+ }>>;
24
+ }>, import("@effect/rpc/RpcSchema").Stream<Schema.Struct<{
25
+ batch: Schema.Array$<Schema.Struct<{
26
+ eventEncoded: Schema.Struct<{
27
+ name: typeof Schema.String;
28
+ args: typeof Schema.Any;
29
+ seqNum: Schema.BrandSchema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
30
+ parentSeqNum: Schema.BrandSchema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
31
+ clientId: typeof Schema.String;
32
+ sessionId: typeof Schema.String;
33
+ }>;
34
+ metadata: Schema.Option<Schema.Struct<{
35
+ _tag: Schema.tag<"SyncMessage.SyncMetadata">;
36
+ } & {
37
+ createdAt: typeof Schema.String;
38
+ }>>;
39
+ }>>;
40
+ pageInfo: Schema.Union<[Schema.TaggedStruct<"MoreUnknown", {}>, Schema.TaggedStruct<"MoreKnown", {
41
+ remaining: typeof Schema.Number;
42
+ }>, Schema.TaggedStruct<"NoMore", {}>]>;
43
+ backendId: Schema.SchemaClass<string, string, never>;
44
+ rpcRequestId: typeof Schema.String;
45
+ }>, typeof InvalidPullError>, typeof Schema.Never, never> | Rpc.Rpc<"SyncDoRpc.Push", Schema.Struct<{
46
+ /**
47
+ * While the storeId is already implied by the durable object, we still need the explicit storeId
48
+ * since a DO doesn't know its own id.name value. 🫠
49
+ * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
50
+ */
51
+ storeId: typeof Schema.String;
52
+ /** Needed for various reasons (e.g. auth) */
53
+ payload: Schema.optional<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
54
+ batch: Schema.Array$<Schema.Struct<{
55
+ name: typeof Schema.String;
56
+ args: typeof Schema.Any;
57
+ seqNum: Schema.BrandSchema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
58
+ parentSeqNum: Schema.BrandSchema<number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">, number, never>;
59
+ clientId: typeof Schema.String;
60
+ sessionId: typeof Schema.String;
61
+ }>>;
62
+ backendId: Schema.Option<Schema.SchemaClass<string, string, never>>;
63
+ }>, Schema.Struct<{}>, typeof InvalidPushError, never> | Rpc.Rpc<"SyncDoRpc.Ping", Schema.Struct<{
64
+ /**
65
+ * While the storeId is already implied by the durable object, we still need the explicit storeId
66
+ * since a DO doesn't know its own id.name value. 🫠
67
+ * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
68
+ */
69
+ storeId: typeof Schema.String;
70
+ /** Needed for various reasons (e.g. auth) */
71
+ payload: Schema.optional<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
72
+ }>, typeof Schema.Void, typeof Schema.Never, never>>;
73
+ export declare class SyncDoRpc extends SyncDoRpc_base {
74
+ }
75
+ export {};
76
+ //# sourceMappingURL=do-rpc-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"do-rpc-schema.d.ts","sourceRoot":"","sources":["../../src/common/do-rpc-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;;IAI7D;;;;OAIG;;IAEH,6CAA6C;;IAOzC,wDAAwD;;;;;IAGxD,qEAAqE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAhBzE;;;;OAIG;;IAEH,6CAA6C;;;;;;;;;;;;IAN7C;;;;OAIG;;IAEH,6CAA6C;;;AAI/C,qBAAa,SAAU,SAAQ,cAsC9B;CAAG"}
@@ -0,0 +1,48 @@
1
+ import { InvalidPullError, InvalidPushError } from '@livestore/common';
2
+ import { Rpc, RpcGroup, Schema } from '@livestore/utils/effect';
3
+ import * as SyncMessage from "./sync-message-types.js";
4
+ const commonPayloadFields = {
5
+ /**
6
+ * While the storeId is already implied by the durable object, we still need the explicit storeId
7
+ * since a DO doesn't know its own id.name value. 🫠
8
+ * https://community.cloudflare.com/t/how-can-i-get-the-name-of-a-durable-object-from-itself/505961/8
9
+ */
10
+ storeId: Schema.String,
11
+ /** Needed for various reasons (e.g. auth) */
12
+ payload: Schema.optional(Schema.JsonValue),
13
+ };
14
+ export class SyncDoRpc extends RpcGroup.make(Rpc.make('SyncDoRpc.Pull', {
15
+ payload: {
16
+ /** Omitting the cursor will start from the beginning */
17
+ cursor: SyncMessage.PullRequest.fields.cursor,
18
+ // TODO rename
19
+ /** Whether to keep the pull stream alive and wait for more events */
20
+ rpcContext: Schema.optional(Schema.Struct({
21
+ callerContext: Schema.Struct({
22
+ bindingName: Schema.String,
23
+ durableObjectId: Schema.String,
24
+ }),
25
+ })),
26
+ ...commonPayloadFields,
27
+ },
28
+ success: Schema.Struct({
29
+ rpcRequestId: Schema.String,
30
+ ...SyncMessage.PullResponse.fields,
31
+ }),
32
+ error: InvalidPullError,
33
+ stream: true,
34
+ }), Rpc.make('SyncDoRpc.Push', {
35
+ payload: {
36
+ ...SyncMessage.PushRequest.fields,
37
+ ...commonPayloadFields,
38
+ },
39
+ success: SyncMessage.PushAck,
40
+ error: InvalidPushError,
41
+ }), Rpc.make('SyncDoRpc.Ping', {
42
+ payload: {
43
+ ...commonPayloadFields,
44
+ },
45
+ success: Schema.Void,
46
+ })) {
47
+ }
48
+ //# sourceMappingURL=do-rpc-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"do-rpc-schema.js","sourceRoot":"","sources":["../../src/common/do-rpc-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACtE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,WAAW,MAAM,yBAAyB,CAAA;AAEtD,MAAM,mBAAmB,GAAG;IAC1B;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;CAC3C,CAAA;AAED,MAAM,OAAO,SAAU,SAAQ,QAAQ,CAAC,IAAI,CAC1C,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE;IACzB,OAAO,EAAE;QACP,wDAAwD;QACxD,MAAM,EAAE,WAAW,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM;QAC7C,cAAc;QACd,qEAAqE;QACrE,UAAU,EAAE,MAAM,CAAC,QAAQ,CACzB,MAAM,CAAC,MAAM,CAAC;YACZ,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC;gBAC3B,WAAW,EAAE,MAAM,CAAC,MAAM;gBAC1B,eAAe,EAAE,MAAM,CAAC,MAAM;aAC/B,CAAC;SACH,CAAC,CACH;QACD,GAAG,mBAAmB;KACvB;IACD,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC,MAAM;QAC3B,GAAG,WAAW,CAAC,YAAY,CAAC,MAAM;KACnC,CAAC;IACF,KAAK,EAAE,gBAAgB;IACvB,MAAM,EAAE,IAAI;CACb,CAAC,EACF,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE;IACzB,OAAO,EAAE;QACP,GAAG,WAAW,CAAC,WAAW,CAAC,MAAM;QACjC,GAAG,mBAAmB;KACvB;IACD,OAAO,EAAE,WAAW,CAAC,OAAO;IAC5B,KAAK,EAAE,gBAAgB;CACxB,CAAC,EACF,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE;IACzB,OAAO,EAAE;QACP,GAAG,mBAAmB;KACvB;IACD,OAAO,EAAE,MAAM,CAAC,IAAI;CACrB,CAAC,CACH;CAAG"}