@livestore/sync-cf 0.4.0-dev.2 → 0.4.0-dev.5
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/README.md +60 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/cf-worker/do/durable-object.d.ts +45 -0
- package/dist/cf-worker/do/durable-object.d.ts.map +1 -0
- package/dist/cf-worker/do/durable-object.js +154 -0
- package/dist/cf-worker/do/durable-object.js.map +1 -0
- package/dist/cf-worker/do/layer.d.ts +34 -0
- package/dist/cf-worker/do/layer.d.ts.map +1 -0
- package/dist/cf-worker/do/layer.js +68 -0
- package/dist/cf-worker/do/layer.js.map +1 -0
- package/dist/cf-worker/do/pull.d.ts +6 -0
- package/dist/cf-worker/do/pull.d.ts.map +1 -0
- package/dist/cf-worker/do/pull.js +39 -0
- package/dist/cf-worker/do/pull.js.map +1 -0
- package/dist/cf-worker/do/push.d.ts +14 -0
- package/dist/cf-worker/do/push.d.ts.map +1 -0
- package/dist/cf-worker/do/push.js +99 -0
- package/dist/cf-worker/do/push.js.map +1 -0
- package/dist/cf-worker/do/sqlite.d.ts +196 -0
- package/dist/cf-worker/do/sqlite.d.ts.map +1 -0
- package/dist/cf-worker/do/sqlite.js +27 -0
- package/dist/cf-worker/do/sqlite.js.map +1 -0
- package/dist/cf-worker/do/sync-storage.d.ts +17 -0
- package/dist/cf-worker/do/sync-storage.d.ts.map +1 -0
- package/dist/cf-worker/do/sync-storage.js +73 -0
- package/dist/cf-worker/do/sync-storage.js.map +1 -0
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts +8 -0
- package/dist/cf-worker/do/transport/do-rpc-server.d.ts.map +1 -0
- package/dist/cf-worker/do/transport/do-rpc-server.js +45 -0
- package/dist/cf-worker/do/transport/do-rpc-server.js.map +1 -0
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts +7 -0
- package/dist/cf-worker/do/transport/http-rpc-server.d.ts.map +1 -0
- package/dist/cf-worker/do/transport/http-rpc-server.js +24 -0
- package/dist/cf-worker/do/transport/http-rpc-server.js.map +1 -0
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts +4 -0
- package/dist/cf-worker/do/transport/ws-rpc-server.d.ts.map +1 -0
- package/dist/cf-worker/do/transport/ws-rpc-server.js +21 -0
- package/dist/cf-worker/do/transport/ws-rpc-server.js.map +1 -0
- package/dist/cf-worker/mod.d.ts +4 -2
- package/dist/cf-worker/mod.d.ts.map +1 -1
- package/dist/cf-worker/mod.js +3 -2
- package/dist/cf-worker/mod.js.map +1 -1
- package/dist/cf-worker/shared.d.ts +127 -0
- package/dist/cf-worker/shared.d.ts.map +1 -0
- package/dist/cf-worker/shared.js +26 -0
- package/dist/cf-worker/shared.js.map +1 -0
- package/dist/cf-worker/worker.d.ts +36 -21
- package/dist/cf-worker/worker.d.ts.map +1 -1
- package/dist/cf-worker/worker.js +39 -32
- package/dist/cf-worker/worker.js.map +1 -1
- package/dist/client/mod.d.ts +4 -0
- package/dist/client/mod.d.ts.map +1 -0
- package/dist/client/mod.js +4 -0
- package/dist/client/mod.js.map +1 -0
- package/dist/client/transport/do-rpc-client.d.ts +40 -0
- package/dist/client/transport/do-rpc-client.d.ts.map +1 -0
- package/dist/client/transport/do-rpc-client.js +102 -0
- package/dist/client/transport/do-rpc-client.js.map +1 -0
- package/dist/client/transport/http-rpc-client.d.ts +43 -0
- package/dist/client/transport/http-rpc-client.d.ts.map +1 -0
- package/dist/client/transport/http-rpc-client.js +87 -0
- package/dist/client/transport/http-rpc-client.js.map +1 -0
- package/dist/client/transport/ws-rpc-client.d.ts +45 -0
- package/dist/client/transport/ws-rpc-client.d.ts.map +1 -0
- package/dist/client/transport/ws-rpc-client.js +94 -0
- package/dist/client/transport/ws-rpc-client.js.map +1 -0
- package/dist/common/do-rpc-schema.d.ts +76 -0
- package/dist/common/do-rpc-schema.d.ts.map +1 -0
- package/dist/common/do-rpc-schema.js +48 -0
- package/dist/common/do-rpc-schema.js.map +1 -0
- package/dist/common/http-rpc-schema.d.ts +58 -0
- package/dist/common/http-rpc-schema.d.ts.map +1 -0
- package/dist/common/http-rpc-schema.js +37 -0
- package/dist/common/http-rpc-schema.js.map +1 -0
- package/dist/common/mod.d.ts +5 -1
- package/dist/common/mod.d.ts.map +1 -1
- package/dist/common/mod.js +4 -1
- package/dist/common/mod.js.map +1 -1
- package/dist/common/sync-message-types.d.ts +236 -0
- package/dist/common/sync-message-types.d.ts.map +1 -0
- package/dist/common/sync-message-types.js +60 -0
- package/dist/common/sync-message-types.js.map +1 -0
- package/dist/common/ws-rpc-schema.d.ts +55 -0
- package/dist/common/ws-rpc-schema.d.ts.map +1 -0
- package/dist/common/ws-rpc-schema.js +32 -0
- package/dist/common/ws-rpc-schema.js.map +1 -0
- package/package.json +7 -8
- package/src/cf-worker/do/durable-object.ts +241 -0
- package/src/cf-worker/do/layer.ts +107 -0
- package/src/cf-worker/do/pull.ts +64 -0
- package/src/cf-worker/do/push.ts +162 -0
- package/src/cf-worker/do/sqlite.ts +28 -0
- package/src/cf-worker/do/sync-storage.ts +126 -0
- package/src/cf-worker/do/transport/do-rpc-server.ts +82 -0
- package/src/cf-worker/do/transport/http-rpc-server.ts +37 -0
- package/src/cf-worker/do/transport/ws-rpc-server.ts +34 -0
- package/src/cf-worker/mod.ts +4 -2
- package/src/cf-worker/shared.ts +95 -0
- package/src/cf-worker/worker.ts +72 -63
- package/src/client/mod.ts +3 -0
- package/src/client/transport/do-rpc-client.ts +171 -0
- package/src/client/transport/http-rpc-client.ts +205 -0
- package/src/client/transport/ws-rpc-client.ts +182 -0
- package/src/common/do-rpc-schema.ts +54 -0
- package/src/common/http-rpc-schema.ts +40 -0
- package/src/common/mod.ts +8 -1
- package/src/common/sync-message-types.ts +117 -0
- package/src/common/ws-rpc-schema.ts +36 -0
- package/src/cf-worker/cf-types.ts +0 -12
- package/src/cf-worker/durable-object.ts +0 -478
- package/src/common/ws-message-types.ts +0 -114
- package/src/sync-impl/mod.ts +0 -1
- package/src/sync-impl/ws-impl.ts +0 -274
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { InvalidPullError, InvalidPushError } from '@livestore/common';
|
|
2
|
+
import type { CfTypes } from '@livestore/common-cf';
|
|
3
|
+
import { Effect, type Option, Schema } from '@livestore/utils/effect';
|
|
4
|
+
import { SearchParamsSchema, SyncMessage } from '../common/mod.ts';
|
|
5
|
+
export interface Env {
|
|
6
|
+
/** Eventlog database */
|
|
7
|
+
DB: CfTypes.D1Database;
|
|
8
|
+
ADMIN_SECRET: string;
|
|
9
|
+
}
|
|
10
|
+
export type MakeDurableObjectClassOptions = {
|
|
11
|
+
onPush?: (message: SyncMessage.PushRequest, context: {
|
|
12
|
+
storeId: StoreId;
|
|
13
|
+
payload?: Schema.JsonValue;
|
|
14
|
+
}) => Effect.SyncOrPromiseOrEffect<void>;
|
|
15
|
+
onPushRes?: (message: SyncMessage.PushAck | InvalidPushError) => Effect.SyncOrPromiseOrEffect<void>;
|
|
16
|
+
onPull?: (message: SyncMessage.PullRequest, context: {
|
|
17
|
+
storeId: StoreId;
|
|
18
|
+
payload?: Schema.JsonValue;
|
|
19
|
+
}) => Effect.SyncOrPromiseOrEffect<void>;
|
|
20
|
+
onPullRes?: (message: SyncMessage.PullResponse | InvalidPullError) => Effect.SyncOrPromiseOrEffect<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Enabled transports for sync backend
|
|
23
|
+
* - `http`: HTTP JSON-RPC
|
|
24
|
+
* - `ws`: WebSocket
|
|
25
|
+
* - `do-rpc`: Durable Object RPC calls (only works in combination with `@livestore/adapter-cf`)
|
|
26
|
+
*
|
|
27
|
+
* @default Set(['http', 'ws', 'do-rpc'])
|
|
28
|
+
*/
|
|
29
|
+
enabledTransports?: Set<'http' | 'ws' | 'do-rpc'>;
|
|
30
|
+
otel?: {
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
serviceName?: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export type StoreId = string;
|
|
36
|
+
export type DurableObjectId = string;
|
|
37
|
+
/**
|
|
38
|
+
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
39
|
+
*
|
|
40
|
+
* Changing this version number will lead to a "soft reset".
|
|
41
|
+
*/
|
|
42
|
+
export declare const PERSISTENCE_FORMAT_VERSION = 7;
|
|
43
|
+
export declare const DEFAULT_SYNC_DURABLE_OBJECT_NAME = "SYNC_BACKEND_DO";
|
|
44
|
+
export declare const encodeOutgoingMessage: (a: {
|
|
45
|
+
readonly backendId: string;
|
|
46
|
+
readonly batch: readonly {
|
|
47
|
+
readonly metadata: Option.Option<{
|
|
48
|
+
readonly createdAt: string;
|
|
49
|
+
readonly _tag: "SyncMessage.SyncMetadata";
|
|
50
|
+
}>;
|
|
51
|
+
readonly eventEncoded: {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly args: any;
|
|
54
|
+
readonly seqNum: any;
|
|
55
|
+
readonly parentSeqNum: any;
|
|
56
|
+
readonly clientId: string;
|
|
57
|
+
readonly sessionId: string;
|
|
58
|
+
};
|
|
59
|
+
}[];
|
|
60
|
+
readonly pageInfo: {
|
|
61
|
+
readonly _tag: "MoreUnknown";
|
|
62
|
+
} | {
|
|
63
|
+
readonly _tag: "MoreKnown";
|
|
64
|
+
readonly remaining: number;
|
|
65
|
+
} | {
|
|
66
|
+
readonly _tag: "NoMore";
|
|
67
|
+
};
|
|
68
|
+
} | {} | {
|
|
69
|
+
readonly _tag: "SyncMessage.Pong";
|
|
70
|
+
} | {
|
|
71
|
+
readonly _tag: "SyncMessage.AdminResetRoomResponse";
|
|
72
|
+
} | {
|
|
73
|
+
readonly info: {
|
|
74
|
+
readonly durableObjectId: string;
|
|
75
|
+
};
|
|
76
|
+
readonly _tag: "SyncMessage.AdminInfoResponse";
|
|
77
|
+
}, overrideOptions?: import("effect/SchemaAST").ParseOptions) => string;
|
|
78
|
+
export declare const encodeIncomingMessage: (a: {
|
|
79
|
+
readonly cursor: Option.Option<{
|
|
80
|
+
readonly backendId: string;
|
|
81
|
+
readonly eventSequenceNumber: number & import("effect/Brand").Brand<"GlobalEventSequenceNumber">;
|
|
82
|
+
}>;
|
|
83
|
+
} | {
|
|
84
|
+
readonly backendId: Option.Option<string>;
|
|
85
|
+
readonly batch: readonly {
|
|
86
|
+
readonly name: string;
|
|
87
|
+
readonly args: any;
|
|
88
|
+
readonly seqNum: any;
|
|
89
|
+
readonly parentSeqNum: any;
|
|
90
|
+
readonly clientId: string;
|
|
91
|
+
readonly sessionId: string;
|
|
92
|
+
}[];
|
|
93
|
+
} | {
|
|
94
|
+
readonly _tag: "SyncMessage.Ping";
|
|
95
|
+
} | {
|
|
96
|
+
readonly _tag: "SyncMessage.AdminResetRoomRequest";
|
|
97
|
+
readonly adminSecret: string;
|
|
98
|
+
} | {
|
|
99
|
+
readonly _tag: "SyncMessage.AdminInfoRequest";
|
|
100
|
+
readonly adminSecret: string;
|
|
101
|
+
}, overrideOptions?: import("effect/SchemaAST").ParseOptions) => string;
|
|
102
|
+
export declare const getSyncRequestSearchParams: (request: CfTypes.Request) => Option.Option<typeof SearchParamsSchema.Type>;
|
|
103
|
+
export declare const PULL_CHUNK_SIZE = 100;
|
|
104
|
+
export type RpcSubscription = {
|
|
105
|
+
storeId: StoreId;
|
|
106
|
+
payload?: Schema.JsonValue;
|
|
107
|
+
subscribedAt: number;
|
|
108
|
+
/** Effect RPC request ID */
|
|
109
|
+
requestId: string;
|
|
110
|
+
callerContext: {
|
|
111
|
+
bindingName: string;
|
|
112
|
+
durableObjectId: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Durable Object interface supporting the DO RPC protocol for DO <> DO syncing.
|
|
117
|
+
*/
|
|
118
|
+
export interface SyncBackendRpcInterface {
|
|
119
|
+
__DURABLE_OBJECT_BRAND: never;
|
|
120
|
+
rpc(payload: Uint8Array): Promise<Uint8Array | CfTypes.ReadableStream>;
|
|
121
|
+
}
|
|
122
|
+
export declare const WebSocketAttachmentSchema: Schema.transform<Schema.SchemaClass<unknown, string, never>, Schema.Struct<{
|
|
123
|
+
storeId: typeof Schema.String;
|
|
124
|
+
payload: Schema.optional<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
|
|
125
|
+
pullRequestIds: Schema.Array$<typeof Schema.String>;
|
|
126
|
+
}>>;
|
|
127
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC3E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAA;AACnD,OAAO,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,MAAM,EAAa,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAElE,MAAM,WAAW,GAAG;IAClB,wBAAwB;IACxB,EAAE,EAAE,OAAO,CAAC,UAAU,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,OAAO,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACnG,MAAM,CAAC,EAAE,CACP,OAAO,EAAE,WAAW,CAAC,WAAW,EAChC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;KAAE,KACtD,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IACvC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,YAAY,GAAG,gBAAgB,KAAK,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;IAGxG;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAA;IAEjD,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;KACrB,CAAA;CACF,CAAA;AAED,MAAM,MAAM,OAAO,GAAG,MAAM,CAAA;AAC5B,MAAM,MAAM,eAAe,GAAG,MAAM,CAAA;AAEpC;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA;AAE3C,eAAO,MAAM,gCAAgC,oBAAoB,CAAA;AAEjE,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAC5G,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;uEAA0E,CAAA;AAE5G,eAAO,MAAM,0BAA0B,GAAI,SAAS,OAAO,CAAC,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAMjH,CAAA;AAED,eAAO,MAAM,eAAe,MAAM,CAAA;AAGlC,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,SAAS,CAAA;IAC1B,YAAY,EAAE,MAAM,CAAA;IACpB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE;QACb,WAAW,EAAE,MAAM,CAAA;QACnB,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF,CAAA;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,EAAE,KAAK,CAAA;IAC7B,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC,CAAA;CACvE;AAED,eAAO,MAAM,yBAAyB;;;;GAQrC,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Effect, Schema, UrlParams } from '@livestore/utils/effect';
|
|
2
|
+
import { SearchParamsSchema, SyncMessage } from "../common/mod.js";
|
|
3
|
+
/**
|
|
4
|
+
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
5
|
+
*
|
|
6
|
+
* Changing this version number will lead to a "soft reset".
|
|
7
|
+
*/
|
|
8
|
+
export const PERSISTENCE_FORMAT_VERSION = 7;
|
|
9
|
+
export const DEFAULT_SYNC_DURABLE_OBJECT_NAME = 'SYNC_BACKEND_DO';
|
|
10
|
+
export const encodeOutgoingMessage = Schema.encodeSync(Schema.parseJson(SyncMessage.BackendToClientMessage));
|
|
11
|
+
export const encodeIncomingMessage = Schema.encodeSync(Schema.parseJson(SyncMessage.ClientToBackendMessage));
|
|
12
|
+
export const getSyncRequestSearchParams = (request) => {
|
|
13
|
+
const url = new URL(request.url);
|
|
14
|
+
const urlParams = UrlParams.fromInput(url.searchParams);
|
|
15
|
+
const paramsResult = UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.option, Effect.runSync);
|
|
16
|
+
return paramsResult;
|
|
17
|
+
};
|
|
18
|
+
export const PULL_CHUNK_SIZE = 100;
|
|
19
|
+
export const WebSocketAttachmentSchema = Schema.parseJson(Schema.Struct({
|
|
20
|
+
// Same across all websocket connections
|
|
21
|
+
storeId: Schema.String,
|
|
22
|
+
// Different for each websocket connection
|
|
23
|
+
payload: Schema.optional(Schema.JsonValue),
|
|
24
|
+
pullRequestIds: Schema.Array(Schema.String),
|
|
25
|
+
}));
|
|
26
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/cf-worker/shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAe,MAAM,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAChF,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAwClE;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAA;AAE3C,MAAM,CAAC,MAAM,gCAAgC,GAAG,iBAAiB,CAAA;AAEjE,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAC5G,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,sBAAsB,CAAC,CAAC,CAAA;AAE5G,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,OAAwB,EAAiD,EAAE;IACpH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAChC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAA;IACvD,MAAM,YAAY,GAAG,SAAS,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAA;IAE9G,OAAO,YAAY,CAAA;AACrB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAA;AAuBlC,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC,SAAS,CACvD,MAAM,CAAC,MAAM,CAAC;IACZ,wCAAwC;IACxC,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC;IAC1C,cAAc,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;CAC5C,CAAC,CACH,CAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type * as CfWorker from '@cloudflare/workers-types';
|
|
2
1
|
import type { Schema } from '@livestore/utils/effect';
|
|
3
|
-
import type {
|
|
2
|
+
import type { CfTypes, SearchParams } from '../common/mod.ts';
|
|
3
|
+
import { type Env } from './shared.ts';
|
|
4
4
|
export declare namespace HelperTypes {
|
|
5
|
-
type AnyDON =
|
|
5
|
+
type AnyDON = CfTypes.DurableObjectNamespace<undefined>;
|
|
6
6
|
type DOKeys<T> = {
|
|
7
7
|
[K in keyof T]-?: T[K] extends AnyDON ? K : never;
|
|
8
8
|
}[keyof T];
|
|
@@ -15,18 +15,18 @@ export declare namespace HelperTypes {
|
|
|
15
15
|
* type PlatformEnv = {
|
|
16
16
|
* DB: D1Database
|
|
17
17
|
* ADMIN_TOKEN: string
|
|
18
|
-
*
|
|
18
|
+
* SYNC_BACKEND_DO: DurableObjectNamespace<SyncBackendDO>
|
|
19
19
|
* }
|
|
20
20
|
* export default makeWorker<PlatformEnv>({
|
|
21
|
-
* durableObject: { name: "
|
|
22
|
-
* // ^ (property) name?: "
|
|
21
|
+
* durableObject: { name: "SYNC_BACKEND_DO" },
|
|
22
|
+
* // ^ (property) name?: "SYNC_BACKEND_DO" | undefined
|
|
23
23
|
* });
|
|
24
24
|
*/
|
|
25
25
|
export type ExtractDurableObjectKeys<TEnv = Env> = DOKeys<NonBuiltins<TEnv>> extends never ? string : DOKeys<NonBuiltins<TEnv>>;
|
|
26
26
|
export {};
|
|
27
27
|
}
|
|
28
|
-
export type CFWorker<TEnv extends Env = Env, _T extends
|
|
29
|
-
fetch: <CFHostMetada = unknown>(request:
|
|
28
|
+
export type CFWorker<TEnv extends Env = Env, _T extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined> = {
|
|
29
|
+
fetch: <CFHostMetada = unknown>(request: CfTypes.Request<CFHostMetada>, env: TEnv, ctx: CfTypes.ExecutionContext) => Promise<CfTypes.Response>;
|
|
30
30
|
};
|
|
31
31
|
export type MakeWorkerOptions<TEnv extends Env = Env> = {
|
|
32
32
|
/**
|
|
@@ -43,14 +43,14 @@ export type MakeWorkerOptions<TEnv extends Env = Env> = {
|
|
|
43
43
|
/**
|
|
44
44
|
* Needs to match the binding name from the wrangler config
|
|
45
45
|
*
|
|
46
|
-
* @default '
|
|
46
|
+
* @default 'SYNC_BACKEND_DO'
|
|
47
47
|
*/
|
|
48
48
|
name?: HelperTypes.ExtractDurableObjectKeys<TEnv>;
|
|
49
49
|
};
|
|
50
50
|
};
|
|
51
|
-
export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends
|
|
51
|
+
export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined>(options?: MakeWorkerOptions<TEnv>) => CFWorker<TEnv, TDurableObjectRpc>;
|
|
52
52
|
/**
|
|
53
|
-
* Handles `/
|
|
53
|
+
* Handles `/sync` endpoint.
|
|
54
54
|
*
|
|
55
55
|
* @example
|
|
56
56
|
* ```ts
|
|
@@ -63,23 +63,38 @@ export declare const makeWorker: <TEnv extends Env = Env, TDurableObjectRpc exte
|
|
|
63
63
|
*
|
|
64
64
|
* export default {
|
|
65
65
|
* fetch: async (request, env, ctx) => {
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* const requestParamsResult = getSyncRequestSearchParams(request)
|
|
67
|
+
*
|
|
68
|
+
* // Is LiveStore sync request
|
|
69
|
+
* if (requestParamsResult._tag === 'Some') {
|
|
70
|
+
* return handleSyncRequest({
|
|
71
|
+
* request,
|
|
72
|
+
* searchParams: requestParamsResult.value,
|
|
73
|
+
* env,
|
|
74
|
+
* ctx,
|
|
75
|
+
* options: { headers: {}, validatePayload }
|
|
76
|
+
* })
|
|
68
77
|
* }
|
|
69
78
|
*
|
|
70
79
|
* return new Response('Invalid path', { status: 400 })
|
|
71
|
-
* return new Response('Invalid path', { status: 400 })
|
|
72
80
|
* }
|
|
73
81
|
* }
|
|
74
82
|
* ```
|
|
75
83
|
*
|
|
76
84
|
* @throws {UnexpectedError} If the payload is invalid
|
|
77
85
|
*/
|
|
78
|
-
export declare const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
86
|
+
export declare const handleSyncRequest: <TEnv extends Env = Env, TDurableObjectRpc extends CfTypes.Rpc.DurableObjectBranded | undefined = undefined, CFHostMetada = unknown>({ request, searchParams, env, options, }: {
|
|
87
|
+
request: CfTypes.Request<CFHostMetada>;
|
|
88
|
+
searchParams: SearchParams;
|
|
89
|
+
env: TEnv;
|
|
90
|
+
/** Only there for type-level reasons */
|
|
91
|
+
ctx: CfTypes.ExecutionContext;
|
|
92
|
+
options?: {
|
|
93
|
+
headers?: CfTypes.HeadersInit;
|
|
94
|
+
durableObject?: MakeWorkerOptions<TEnv>["durableObject"];
|
|
95
|
+
validatePayload?: (payload: Schema.JsonValue | undefined, context: {
|
|
96
|
+
storeId: string;
|
|
97
|
+
}) => void | Promise<void>;
|
|
98
|
+
};
|
|
99
|
+
}) => Promise<CfTypes.Response>;
|
|
85
100
|
//# sourceMappingURL=worker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
package/dist/cf-worker/worker.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { UnexpectedError } from '@livestore/common';
|
|
2
|
-
import { Effect
|
|
3
|
-
import {
|
|
2
|
+
import { Effect } from '@livestore/utils/effect';
|
|
3
|
+
import { DEFAULT_SYNC_DURABLE_OBJECT_NAME, getSyncRequestSearchParams } from "./shared.js";
|
|
4
4
|
export const makeWorker = (options = {}) => {
|
|
5
5
|
return {
|
|
6
6
|
fetch: async (request, env, _ctx) => {
|
|
7
7
|
const url = new URL(request.url);
|
|
8
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
9
|
-
if (request.method === 'GET' && url.pathname === '/') {
|
|
10
|
-
return new Response('Info: WebSocket sync backend endpoint for @livestore/sync-cf.', {
|
|
11
|
-
status: 200,
|
|
12
|
-
headers: { 'Content-Type': 'text/plain' },
|
|
13
|
-
});
|
|
14
|
-
}
|
|
15
8
|
const corsHeaders = options.enableCORS
|
|
16
9
|
? {
|
|
17
10
|
'Access-Control-Allow-Origin': '*',
|
|
@@ -25,11 +18,26 @@ export const makeWorker = (options = {}) => {
|
|
|
25
18
|
headers: corsHeaders,
|
|
26
19
|
});
|
|
27
20
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
const requestParamsResult = getSyncRequestSearchParams(request);
|
|
22
|
+
// Check if this is a sync request first, before showing info message
|
|
23
|
+
if (requestParamsResult._tag === 'Some') {
|
|
24
|
+
return handleSyncRequest({
|
|
25
|
+
request,
|
|
26
|
+
searchParams: requestParamsResult.value,
|
|
27
|
+
env,
|
|
28
|
+
ctx: _ctx,
|
|
29
|
+
options: {
|
|
30
|
+
headers: corsHeaders,
|
|
31
|
+
validatePayload: options.validatePayload,
|
|
32
|
+
durableObject: options.durableObject,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
// Only show info message for GET requests to / without sync parameters
|
|
37
|
+
if (request.method === 'GET' && url.pathname === '/') {
|
|
38
|
+
return new Response('Info: Sync backend endpoint for @livestore/sync-cf.', {
|
|
39
|
+
status: 200,
|
|
40
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
33
41
|
});
|
|
34
42
|
}
|
|
35
43
|
console.error('Invalid path', url.pathname);
|
|
@@ -45,7 +53,7 @@ export const makeWorker = (options = {}) => {
|
|
|
45
53
|
};
|
|
46
54
|
};
|
|
47
55
|
/**
|
|
48
|
-
* Handles `/
|
|
56
|
+
* Handles `/sync` endpoint.
|
|
49
57
|
*
|
|
50
58
|
* @example
|
|
51
59
|
* ```ts
|
|
@@ -58,29 +66,28 @@ export const makeWorker = (options = {}) => {
|
|
|
58
66
|
*
|
|
59
67
|
* export default {
|
|
60
68
|
* fetch: async (request, env, ctx) => {
|
|
61
|
-
*
|
|
62
|
-
*
|
|
69
|
+
* const requestParamsResult = getSyncRequestSearchParams(request)
|
|
70
|
+
*
|
|
71
|
+
* // Is LiveStore sync request
|
|
72
|
+
* if (requestParamsResult._tag === 'Some') {
|
|
73
|
+
* return handleSyncRequest({
|
|
74
|
+
* request,
|
|
75
|
+
* searchParams: requestParamsResult.value,
|
|
76
|
+
* env,
|
|
77
|
+
* ctx,
|
|
78
|
+
* options: { headers: {}, validatePayload }
|
|
79
|
+
* })
|
|
63
80
|
* }
|
|
64
81
|
*
|
|
65
82
|
* return new Response('Invalid path', { status: 400 })
|
|
66
|
-
* return new Response('Invalid path', { status: 400 })
|
|
67
83
|
* }
|
|
68
84
|
* }
|
|
69
85
|
* ```
|
|
70
86
|
*
|
|
71
87
|
* @throws {UnexpectedError} If the payload is invalid
|
|
72
88
|
*/
|
|
73
|
-
export const
|
|
74
|
-
const
|
|
75
|
-
const urlParams = UrlParams.fromInput(url.searchParams);
|
|
76
|
-
const paramsResult = yield* UrlParams.schemaStruct(SearchParamsSchema)(urlParams).pipe(Effect.either);
|
|
77
|
-
if (paramsResult._tag === 'Left') {
|
|
78
|
-
return new Response(`Invalid search params: ${paramsResult.left.toString()}`, {
|
|
79
|
-
status: 500,
|
|
80
|
-
headers: options?.headers,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
const { storeId, payload } = paramsResult.right;
|
|
89
|
+
export const handleSyncRequest = ({ request, searchParams, env, options = {}, }) => Effect.gen(function* () {
|
|
90
|
+
const { storeId, payload, transport } = searchParams;
|
|
84
91
|
if (options.validatePayload !== undefined) {
|
|
85
92
|
const result = yield* Effect.promise(async () => options.validatePayload(payload, { storeId })).pipe(UnexpectedError.mapToUnexpectedError, Effect.either);
|
|
86
93
|
if (result._tag === 'Left') {
|
|
@@ -88,7 +95,7 @@ export const handleWebSocket = (request, env, _ctx, options = {}) => Effect.gen(
|
|
|
88
95
|
return new Response(result.left.toString(), { status: 400, headers: options.headers });
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
|
-
const durableObjectName = options.durableObject?.name ??
|
|
98
|
+
const durableObjectName = options.durableObject?.name ?? DEFAULT_SYNC_DURABLE_OBJECT_NAME;
|
|
92
99
|
if (!(durableObjectName in env)) {
|
|
93
100
|
return new Response(`Failed dependency: Required Durable Object binding '${durableObjectName}' not available`, {
|
|
94
101
|
status: 424,
|
|
@@ -98,14 +105,14 @@ export const handleWebSocket = (request, env, _ctx, options = {}) => Effect.gen(
|
|
|
98
105
|
const durableObjectNamespace = env[durableObjectName];
|
|
99
106
|
const id = durableObjectNamespace.idFromName(storeId);
|
|
100
107
|
const durableObject = durableObjectNamespace.get(id);
|
|
108
|
+
// Handle WebSocket upgrade request
|
|
101
109
|
const upgradeHeader = request.headers.get('Upgrade');
|
|
102
|
-
if (
|
|
110
|
+
if (transport === 'ws' && (upgradeHeader === null || upgradeHeader !== 'websocket')) {
|
|
103
111
|
return new Response('Durable Object expected Upgrade: websocket', {
|
|
104
112
|
status: 426,
|
|
105
113
|
headers: options?.headers,
|
|
106
114
|
});
|
|
107
115
|
}
|
|
108
|
-
// Cloudflare Durable Object type clashing with lib.dom Response type, which is why we need the casts here.
|
|
109
116
|
return yield* Effect.promise(() => durableObject.fetch(request));
|
|
110
117
|
}).pipe(Effect.tapCauseLogPretty, Effect.runPromise);
|
|
111
118
|
//# sourceMappingURL=worker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/cf-worker/worker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.d.ts","sourceRoot":"","sources":["../../src/client/mod.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,8BAA8B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","sourceRoot":"","sources":["../../src/client/mod.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAA;AAC5C,cAAc,gCAAgC,CAAA;AAC9C,cAAc,8BAA8B,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SyncBackend } from '@livestore/common';
|
|
2
|
+
import { type CfTypes } from '@livestore/common-cf';
|
|
3
|
+
import type { SyncBackendRpcInterface } from '../../cf-worker/shared.ts';
|
|
4
|
+
import type { SyncMetadata } from '../../common/sync-message-types.ts';
|
|
5
|
+
export interface SyncBackendRpcStub extends CfTypes.DurableObjectStub, SyncBackendRpcInterface {
|
|
6
|
+
}
|
|
7
|
+
export interface DoRpcSyncOptions {
|
|
8
|
+
/** Durable Object stub that implements the SyncDoRpc interface */
|
|
9
|
+
syncBackendStub: SyncBackendRpcStub;
|
|
10
|
+
/** Information about this DurableObject instance so the Sync DO instance can call back to this instance */
|
|
11
|
+
durableObjectContext: {
|
|
12
|
+
/** See `wrangler.toml` for the binding name */
|
|
13
|
+
bindingName: string;
|
|
14
|
+
/** `state.id.toString()` in the DO */
|
|
15
|
+
durableObjectId: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a sync backend that uses Durable Object RPC to communicate with the sync backend.
|
|
20
|
+
*
|
|
21
|
+
* Used internally by `@livestore/adapter-cf` to connect to the sync backend.
|
|
22
|
+
*/
|
|
23
|
+
export declare const makeDoRpcSync: ({ syncBackendStub, durableObjectContext }: DoRpcSyncOptions) => SyncBackend.SyncBackendConstructor<SyncMetadata>;
|
|
24
|
+
/**
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import { DurableObject } from 'cloudflare:workers'
|
|
28
|
+
* import { ClientDoWithRpcCallback } from '@livestore/common-cf'
|
|
29
|
+
*
|
|
30
|
+
* export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback {
|
|
31
|
+
* // ...
|
|
32
|
+
*
|
|
33
|
+
* async syncUpdateRpc(payload: RpcMessage.ResponseChunkEncoded) {
|
|
34
|
+
* return handleSyncUpdateRpc(payload)
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare const handleSyncUpdateRpc: (payload: unknown) => Promise<void>;
|
|
40
|
+
//# sourceMappingURL=do-rpc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"do-rpc-client.d.ts","sourceRoot":"","sources":["../../../src/client/transport/do-rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,WAAW,EAAmB,MAAM,mBAAmB,CAAA;AACpG,OAAO,EAAE,KAAK,OAAO,EAA8B,MAAM,sBAAsB,CAAA;AAc/E,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,MAAM,WAAW,kBAAmB,SAAQ,OAAO,CAAC,iBAAiB,EAAE,uBAAuB;CAAG;AAMjG,MAAM,WAAW,gBAAgB;IAC/B,kEAAkE;IAClE,eAAe,EAAE,kBAAkB,CAAA;IACnC,2GAA2G;IAC3G,oBAAoB,EAAE;QACpB,+CAA+C;QAC/C,WAAW,EAAE,MAAM,CAAA;QACnB,sCAAsC;QACtC,eAAe,EAAE,MAAM,CAAA;KACxB,CAAA;CACF;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACvB,2CAA2C,gBAAgB,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CA0FnD,CAAA;AAE7D;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,mBAAmB,GAAI,SAAS,OAAO,kBAckD,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { InvalidPullError, InvalidPushError, SyncBackend, UnexpectedError } from '@livestore/common';
|
|
2
|
+
import { layerProtocolDurableObject } from '@livestore/common-cf';
|
|
3
|
+
import { omit, shouldNeverHappen } from '@livestore/utils';
|
|
4
|
+
import { Effect, identity, Layer, Mailbox, Option, RpcClient, RpcSerialization, Schema, Stream, SubscriptionRef, } from '@livestore/utils/effect';
|
|
5
|
+
import { SyncDoRpc } from "../../common/do-rpc-schema.js";
|
|
6
|
+
import { SyncMessage } from "../../common/mod.js";
|
|
7
|
+
const requestIdMailboxMap = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Creates a sync backend that uses Durable Object RPC to communicate with the sync backend.
|
|
10
|
+
*
|
|
11
|
+
* Used internally by `@livestore/adapter-cf` to connect to the sync backend.
|
|
12
|
+
*/
|
|
13
|
+
export const makeDoRpcSync = ({ syncBackendStub, durableObjectContext }) => ({ storeId, payload }) => Effect.gen(function* () {
|
|
14
|
+
const isConnected = yield* SubscriptionRef.make(true);
|
|
15
|
+
const ProtocolLive = layerProtocolDurableObject({
|
|
16
|
+
callRpc: (payload) => syncBackendStub.rpc(payload),
|
|
17
|
+
callerContext: durableObjectContext,
|
|
18
|
+
}).pipe(Layer.provide(RpcSerialization.layerJson));
|
|
19
|
+
const context = yield* Layer.build(ProtocolLive);
|
|
20
|
+
const rpcClient = yield* RpcClient.make(SyncDoRpc).pipe(Effect.provide(context));
|
|
21
|
+
// Nothing to do here
|
|
22
|
+
const connect = Effect.void;
|
|
23
|
+
const backendIdHelper = yield* SyncBackend.makeBackendIdHelper;
|
|
24
|
+
const pull = (cursor, options) => rpcClient.SyncDoRpc.Pull({
|
|
25
|
+
cursor: cursor.pipe(Option.map((a) => ({
|
|
26
|
+
eventSequenceNumber: a.eventSequenceNumber,
|
|
27
|
+
backendId: backendIdHelper.get().pipe(Option.getOrThrow),
|
|
28
|
+
}))),
|
|
29
|
+
storeId,
|
|
30
|
+
rpcContext: options?.live ? { callerContext: durableObjectContext } : undefined,
|
|
31
|
+
}).pipe(options?.live
|
|
32
|
+
? Stream.concatWithLastElement((res) => Effect.gen(function* () {
|
|
33
|
+
if (res._tag === 'None')
|
|
34
|
+
return shouldNeverHappen('There should at least be a no-more page info response');
|
|
35
|
+
const mailbox = yield* Mailbox.make().pipe(Effect.acquireRelease((mailbox) => mailbox.shutdown));
|
|
36
|
+
requestIdMailboxMap.set(res.value.rpcRequestId, mailbox);
|
|
37
|
+
return Mailbox.toStream(mailbox);
|
|
38
|
+
}).pipe(Stream.unwrapScoped))
|
|
39
|
+
: 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'));
|
|
40
|
+
const push = (batch) => Effect.gen(function* () {
|
|
41
|
+
if (batch.length === 0) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
yield* rpcClient.SyncDoRpc.Push({ batch, storeId, backendId: backendIdHelper.get() });
|
|
45
|
+
}).pipe(Effect.mapError((cause) => cause._tag === 'InvalidPushError'
|
|
46
|
+
? cause
|
|
47
|
+
: InvalidPushError.make({ cause: new UnexpectedError({ cause }) })), Effect.withSpan('rpc-sync-client:push'));
|
|
48
|
+
const ping = rpcClient.SyncDoRpc.Ping({
|
|
49
|
+
storeId,
|
|
50
|
+
payload,
|
|
51
|
+
}).pipe(UnexpectedError.mapToUnexpectedError, Effect.withSpan('rpc-sync-client:ping'));
|
|
52
|
+
return SyncBackend.of({
|
|
53
|
+
connect,
|
|
54
|
+
isConnected,
|
|
55
|
+
pull,
|
|
56
|
+
push,
|
|
57
|
+
ping,
|
|
58
|
+
metadata: {
|
|
59
|
+
name: 'rpc-sync-client',
|
|
60
|
+
description: 'Cloudflare Durable Object RPC Sync Client',
|
|
61
|
+
protocol: 'rpc',
|
|
62
|
+
storeId,
|
|
63
|
+
},
|
|
64
|
+
supports: {
|
|
65
|
+
pullPageInfoKnown: true,
|
|
66
|
+
pullLive: true,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}).pipe(Effect.withSpan('rpc-sync-client:makeDoRpcSync'));
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import { DurableObject } from 'cloudflare:workers'
|
|
74
|
+
* import { ClientDoWithRpcCallback } from '@livestore/common-cf'
|
|
75
|
+
*
|
|
76
|
+
* export class MyDurableObject extends DurableObject implements ClientDoWithRpcCallback {
|
|
77
|
+
* // ...
|
|
78
|
+
*
|
|
79
|
+
* async syncUpdateRpc(payload: RpcMessage.ResponseChunkEncoded) {
|
|
80
|
+
* return handleSyncUpdateRpc(payload)
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export const handleSyncUpdateRpc = (payload) => Effect.gen(function* () {
|
|
86
|
+
const decodedPayload = yield* Schema.decodeUnknown(ResponseChunkEncoded)(payload);
|
|
87
|
+
const decoded = yield* Schema.decodeUnknown(SyncMessage.PullResponse)(decodedPayload.values[0]);
|
|
88
|
+
const pullStreamMailbox = requestIdMailboxMap.get(decodedPayload.requestId);
|
|
89
|
+
if (pullStreamMailbox === undefined) {
|
|
90
|
+
// Case: DO was hibernated, so we need to manually update the store
|
|
91
|
+
yield* Effect.log(`No mailbox found for ${decodedPayload.requestId}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// Case: DO was still alive, so the existing `pull` will pick up the new events
|
|
95
|
+
yield* pullStreamMailbox.offer(decoded);
|
|
96
|
+
}
|
|
97
|
+
}).pipe(Effect.withSpan('rpc-sync-client:rpcCallback'), Effect.tapCauseLogPretty, Effect.runPromise);
|
|
98
|
+
const ResponseChunkEncoded = Schema.Struct({
|
|
99
|
+
requestId: Schema.String,
|
|
100
|
+
values: Schema.Array(Schema.Any),
|
|
101
|
+
});
|
|
102
|
+
//# 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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"}
|