@livestore/sync-electric 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/dist/.tsbuildinfo +1 -1
- package/dist/api-schema.d.ts +13 -0
- package/dist/api-schema.d.ts.map +1 -1
- package/dist/api-schema.js +3 -0
- package/dist/api-schema.js.map +1 -1
- package/dist/index.d.ts +21 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -74
- package/dist/index.js.map +1 -1
- package/dist/make-electric-url.d.ts +42 -0
- package/dist/make-electric-url.d.ts.map +1 -0
- package/dist/make-electric-url.js +60 -0
- package/dist/make-electric-url.js.map +1 -0
- package/package.json +3 -3
- package/src/api-schema.ts +4 -0
- package/src/index.ts +128 -129
- package/src/make-electric-url.ts +110 -0
package/dist/api-schema.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export declare const PullPayload: Schema.Struct<{
|
|
|
21
21
|
offset: typeof Schema.String;
|
|
22
22
|
handle: typeof Schema.String;
|
|
23
23
|
}>>;
|
|
24
|
+
live: typeof Schema.Boolean;
|
|
24
25
|
}>;
|
|
25
26
|
export declare const ApiPayload: Schema.Union<[Schema.Struct<{
|
|
26
27
|
_tag: Schema.tag<"@livestore/sync-electric.Pull">;
|
|
@@ -31,6 +32,7 @@ export declare const ApiPayload: Schema.Union<[Schema.Struct<{
|
|
|
31
32
|
offset: typeof Schema.String;
|
|
32
33
|
handle: typeof Schema.String;
|
|
33
34
|
}>>;
|
|
35
|
+
live: typeof Schema.Boolean;
|
|
34
36
|
}>, Schema.Struct<{
|
|
35
37
|
_tag: Schema.tag<"@livestore/sync-electric.Push">;
|
|
36
38
|
} & {
|
|
@@ -44,4 +46,15 @@ export declare const ApiPayload: Schema.Union<[Schema.Struct<{
|
|
|
44
46
|
sessionId: typeof Schema.String;
|
|
45
47
|
}>>;
|
|
46
48
|
}>]>;
|
|
49
|
+
export declare const ArgsSchema: Schema.transform<Schema.transformOrFail<Schema.SchemaClass<string, string, never>, typeof Schema.String, never>, Schema.transform<Schema.SchemaClass<unknown, string, never>, Schema.Struct<{
|
|
50
|
+
_tag: Schema.tag<"@livestore/sync-electric.Pull">;
|
|
51
|
+
} & {
|
|
52
|
+
storeId: typeof Schema.String;
|
|
53
|
+
payload: Schema.UndefinedOr<Schema.Schema<Schema.JsonValue, Schema.JsonValue, never>>;
|
|
54
|
+
handle: Schema.Option<Schema.Struct<{
|
|
55
|
+
offset: typeof Schema.String;
|
|
56
|
+
handle: typeof Schema.String;
|
|
57
|
+
}>>;
|
|
58
|
+
live: typeof Schema.Boolean;
|
|
59
|
+
}>>>;
|
|
47
60
|
//# sourceMappingURL=api-schema.d.ts.map
|
package/dist/api-schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-schema.d.ts","sourceRoot":"","sources":["../src/api-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,eAAO,MAAM,WAAW;;;;;;;;;;;;EAGyC,CAAA;AAEjE,eAAO,MAAM,WAAW
|
|
1
|
+
{"version":3,"file":"api-schema.d.ts","sourceRoot":"","sources":["../src/api-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,eAAO,MAAM,WAAW;;;;;;;;;;;;EAGyC,CAAA;AAEjE,eAAO,MAAM,WAAW;;;;;;;;;;EAUyC,CAAA;AAEjE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;IAAyC,CAAA;AAGhE,eAAO,MAAM,UAAU;;;;;;;;;;IAA+E,CAAA"}
|
package/dist/api-schema.js
CHANGED
|
@@ -11,6 +11,9 @@ export const PullPayload = Schema.TaggedStruct('@livestore/sync-electric.Pull',
|
|
|
11
11
|
offset: Schema.String,
|
|
12
12
|
handle: Schema.String,
|
|
13
13
|
})),
|
|
14
|
+
live: Schema.Boolean,
|
|
14
15
|
}).annotations({ title: '@livestore/sync-electric.PullPayload' });
|
|
15
16
|
export const ApiPayload = Schema.Union(PullPayload, PushPayload);
|
|
17
|
+
// Format for the query params
|
|
18
|
+
export const ArgsSchema = Schema.compose(Schema.StringFromUriComponent, Schema.parseJson(PullPayload));
|
|
16
19
|
//# sourceMappingURL=api-schema.js.map
|
package/dist/api-schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-schema.js","sourceRoot":"","sources":["../src/api-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,+BAA+B,EAAE;IAC9E,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC;CACrD,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC,CAAA;AAEjE,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,+BAA+B,EAAE;IAC9E,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CACH;
|
|
1
|
+
{"version":3,"file":"api-schema.js","sourceRoot":"","sources":["../src/api-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAEhD,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,+BAA+B,EAAE;IAC9E,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC;CACrD,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC,CAAA;AAEjE,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,+BAA+B,EAAE;IAC9E,OAAO,EAAE,MAAM,CAAC,MAAM;IACtB,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,MAAM,CAAC;QACZ,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CACH;IACD,IAAI,EAAE,MAAM,CAAC,OAAO;CACrB,CAAC,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC,CAAA;AAEjE,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAA;AAEhE,8BAA8B;AAC9B,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,33 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Schema } from '@livestore/utils/effect';
|
|
1
|
+
import { SyncBackend } from '@livestore/common';
|
|
2
|
+
import { type Duration, Schema } from '@livestore/utils/effect';
|
|
3
3
|
export * as ApiSchema from './api-schema.ts';
|
|
4
|
+
export * from './make-electric-url.ts';
|
|
4
5
|
export declare const syncBackend: any;
|
|
5
6
|
export declare const syncBackendOptions: <TOptions extends SyncBackendOptions>(options: TOptions) => TOptions;
|
|
6
|
-
/**
|
|
7
|
-
* This function should be called in a trusted environment (e.g. a proxy server) as it
|
|
8
|
-
* requires access to senstive information (e.g. `apiSecret` / `sourceSecret`).
|
|
9
|
-
*/
|
|
10
|
-
export declare const makeElectricUrl: ({ electricHost, searchParams: providedSearchParams, sourceId, sourceSecret, apiSecret, }: {
|
|
11
|
-
electricHost: string;
|
|
12
|
-
/**
|
|
13
|
-
* Needed to extract information from the search params which the `@livestore/sync-electric`
|
|
14
|
-
* client implementation automatically adds:
|
|
15
|
-
* - `handle`: the ElectricSQL handle
|
|
16
|
-
* - `storeId`: the Livestore storeId
|
|
17
|
-
*/
|
|
18
|
-
searchParams: URLSearchParams;
|
|
19
|
-
/** Needed for Electric Cloud */
|
|
20
|
-
sourceId?: string;
|
|
21
|
-
/** Needed for Electric Cloud */
|
|
22
|
-
sourceSecret?: string;
|
|
23
|
-
/** For self-hosted ElectricSQL */
|
|
24
|
-
apiSecret?: string;
|
|
25
|
-
}) => {
|
|
26
|
-
url: string;
|
|
27
|
-
storeId: string;
|
|
28
|
-
needsInit: boolean;
|
|
29
|
-
payload: Schema.JsonValue | undefined;
|
|
30
|
-
};
|
|
31
7
|
export interface SyncBackendOptions {
|
|
32
8
|
/**
|
|
33
9
|
* The endpoint to pull/push events. Pull is a `GET` request, push is a `POST` request.
|
|
@@ -40,6 +16,23 @@ export interface SyncBackendOptions {
|
|
|
40
16
|
endpoint: string | {
|
|
41
17
|
push: string;
|
|
42
18
|
pull: string;
|
|
19
|
+
ping: string;
|
|
20
|
+
};
|
|
21
|
+
ping?: {
|
|
22
|
+
/**
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* How long to wait for a ping response before timing out
|
|
28
|
+
* @default 10 seconds
|
|
29
|
+
*/
|
|
30
|
+
requestTimeout?: Duration.DurationInput;
|
|
31
|
+
/**
|
|
32
|
+
* How often to send ping requests
|
|
33
|
+
* @default 10 seconds
|
|
34
|
+
*/
|
|
35
|
+
requestInterval?: Duration.DurationInput;
|
|
43
36
|
};
|
|
44
37
|
}
|
|
45
38
|
export declare const SyncMetadata: Schema.Struct<{
|
|
@@ -50,12 +43,5 @@ type SyncMetadata = {
|
|
|
50
43
|
offset: string;
|
|
51
44
|
handle: string;
|
|
52
45
|
};
|
|
53
|
-
export declare const makeSyncBackend: ({ endpoint }: SyncBackendOptions) => SyncBackendConstructor<SyncMetadata>;
|
|
54
|
-
/**
|
|
55
|
-
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
56
|
-
*
|
|
57
|
-
* Changing this version number will lead to a "soft reset".
|
|
58
|
-
*/
|
|
59
|
-
export declare const PERSISTENCE_FORMAT_VERSION = 6;
|
|
60
|
-
export declare const toTableName: (storeId: string) => string;
|
|
46
|
+
export declare const makeSyncBackend: ({ endpoint, ...options }: SyncBackendOptions) => SyncBackend.SyncBackendConstructor<SyncMetadata>;
|
|
61
47
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,WAAW,EAEZ,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EACL,KAAK,QAAQ,EAOb,MAAM,EAGP,MAAM,yBAAyB,CAAA;AAIhC,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AA8EtC,eAAO,MAAM,WAAW,EAAS,GAAG,CAAA;AAEpC,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,kBAAkB,EAAE,SAAS,QAAQ,aAAY,CAAA;AAErG,MAAM,WAAW,kBAAkB;IACjC;;;;;;;OAOG;IACH,QAAQ,EACJ,MAAM,GACN;QACE,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;QACZ,IAAI,EAAE,MAAM,CAAA;KACb,CAAA;IAEL,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,eAAO,MAAM,YAAY;;;EAIvB,CAAA;AAEF,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,MAAM,CAAA;IAEd,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAED,eAAO,MAAM,eAAe,GACzB,0BAA0B,kBAAkB,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CAgM3F,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { InvalidPullError, InvalidPushError, UnexpectedError } from '@livestore/common';
|
|
1
|
+
import { InvalidPullError, InvalidPushError, SyncBackend, UnexpectedError, } from '@livestore/common';
|
|
2
2
|
import { LiveStoreEvent } from '@livestore/common/schema';
|
|
3
|
-
import { notYetImplemented
|
|
4
|
-
import {
|
|
3
|
+
import { notYetImplemented } from '@livestore/utils';
|
|
4
|
+
import { Effect, HttpClient, HttpClientRequest, HttpClientResponse, Option, Schedule, Schema, Stream, SubscriptionRef, } from '@livestore/utils/effect';
|
|
5
5
|
import * as ApiSchema from "./api-schema.js";
|
|
6
6
|
export * as ApiSchema from "./api-schema.js";
|
|
7
|
+
export * from "./make-electric-url.js";
|
|
7
8
|
/*
|
|
8
9
|
Example data:
|
|
9
10
|
|
|
@@ -73,60 +74,32 @@ const ResponseHeaders = Schema.Struct({
|
|
|
73
74
|
});
|
|
74
75
|
export const syncBackend = {};
|
|
75
76
|
export const syncBackendOptions = (options) => options;
|
|
76
|
-
/**
|
|
77
|
-
* This function should be called in a trusted environment (e.g. a proxy server) as it
|
|
78
|
-
* requires access to senstive information (e.g. `apiSecret` / `sourceSecret`).
|
|
79
|
-
*/
|
|
80
|
-
export const makeElectricUrl = ({ electricHost, searchParams: providedSearchParams, sourceId, sourceSecret, apiSecret, }) => {
|
|
81
|
-
const endpointUrl = `${electricHost}/v1/shape`;
|
|
82
|
-
const argsResult = Schema.decodeUnknownEither(Schema.Struct({ args: Schema.parseJson(ApiSchema.PullPayload) }))(Object.fromEntries(providedSearchParams.entries()));
|
|
83
|
-
if (argsResult._tag === 'Left') {
|
|
84
|
-
return shouldNeverHappen('Invalid search params provided to makeElectricUrl', providedSearchParams, Object.fromEntries(providedSearchParams.entries()));
|
|
85
|
-
}
|
|
86
|
-
const args = argsResult.right.args;
|
|
87
|
-
const tableName = toTableName(args.storeId);
|
|
88
|
-
const searchParams = new URLSearchParams();
|
|
89
|
-
searchParams.set('table', tableName);
|
|
90
|
-
if (sourceId !== undefined) {
|
|
91
|
-
searchParams.set('source_id', sourceId);
|
|
92
|
-
}
|
|
93
|
-
if (sourceSecret !== undefined) {
|
|
94
|
-
searchParams.set('source_secret', sourceSecret);
|
|
95
|
-
}
|
|
96
|
-
if (apiSecret !== undefined) {
|
|
97
|
-
searchParams.set('api_secret', apiSecret);
|
|
98
|
-
}
|
|
99
|
-
if (args.handle._tag === 'None') {
|
|
100
|
-
searchParams.set('offset', '-1');
|
|
101
|
-
}
|
|
102
|
-
else {
|
|
103
|
-
searchParams.set('offset', args.handle.value.offset);
|
|
104
|
-
searchParams.set('handle', args.handle.value.handle);
|
|
105
|
-
searchParams.set('live', 'true');
|
|
106
|
-
}
|
|
107
|
-
const payload = args.payload;
|
|
108
|
-
const url = `${endpointUrl}?${searchParams.toString()}`;
|
|
109
|
-
return { url, storeId: args.storeId, needsInit: args.handle._tag === 'None', payload };
|
|
110
|
-
};
|
|
111
77
|
export const SyncMetadata = Schema.Struct({
|
|
112
78
|
offset: Schema.String,
|
|
113
79
|
// TODO move this into some kind of "global" sync metadata as it's the same for each event
|
|
114
80
|
handle: Schema.String,
|
|
115
81
|
});
|
|
116
|
-
export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effect.gen(function* () {
|
|
117
|
-
const isConnected = yield* SubscriptionRef.make(
|
|
82
|
+
export const makeSyncBackend = ({ endpoint, ...options }) => ({ storeId, payload }) => Effect.gen(function* () {
|
|
83
|
+
const isConnected = yield* SubscriptionRef.make(false);
|
|
118
84
|
const pullEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.pull;
|
|
119
85
|
const pushEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.push;
|
|
120
|
-
const
|
|
121
|
-
|
|
86
|
+
const pingEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.ping;
|
|
87
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
88
|
+
const runPull = (handle, { live }) => Effect.gen(function* () {
|
|
89
|
+
const argsJson = yield* Schema.encode(ApiSchema.ArgsSchema)(ApiSchema.PullPayload.make({ storeId, handle, payload, live }));
|
|
122
90
|
const url = `${pullEndpoint}?args=${argsJson}`;
|
|
123
|
-
const resp = yield*
|
|
91
|
+
const resp = yield* httpClient.get(url);
|
|
124
92
|
if (resp.status === 401) {
|
|
125
93
|
const body = yield* resp.text.pipe(Effect.catchAll(() => Effect.succeed('-')));
|
|
126
94
|
return yield* InvalidPullError.make({
|
|
127
|
-
|
|
95
|
+
cause: new Error(`Unauthorized (401): Couldn't connect to ElectricSQL: ${body}`),
|
|
128
96
|
});
|
|
129
97
|
}
|
|
98
|
+
else if (resp.status === 400) {
|
|
99
|
+
// Electric returns 400 when table doesn't exist
|
|
100
|
+
// Return empty result for non-existent tables
|
|
101
|
+
return Option.some([[], Option.none()]);
|
|
102
|
+
}
|
|
130
103
|
else if (resp.status === 409) {
|
|
131
104
|
// https://electric-sql.com/openapi.html#/paths/~1v1~1shape/get
|
|
132
105
|
// {
|
|
@@ -140,8 +113,9 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
140
113
|
return notYetImplemented(`Electric shape not found`);
|
|
141
114
|
}
|
|
142
115
|
else if (resp.status < 200 || resp.status >= 300) {
|
|
116
|
+
const body = yield* resp.text;
|
|
143
117
|
return yield* InvalidPullError.make({
|
|
144
|
-
|
|
118
|
+
cause: new Error(`Unexpected status code: ${resp.status}: ${body}`),
|
|
145
119
|
});
|
|
146
120
|
}
|
|
147
121
|
const headers = yield* HttpClientResponse.schemaHeaders(ResponseHeaders)(resp);
|
|
@@ -152,7 +126,7 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
152
126
|
// Electric completes the long-poll request after ~20 seconds with a 204 status
|
|
153
127
|
// In this case we just retry where we left off
|
|
154
128
|
if (resp.status === 204) {
|
|
155
|
-
return Option.some([
|
|
129
|
+
return Option.some([[], Option.some(nextHandle)]);
|
|
156
130
|
}
|
|
157
131
|
const body = yield* HttpClientResponse.schemaBodyJson(Schema.Array(ResponseItem), {
|
|
158
132
|
onExcessProperty: 'preserve',
|
|
@@ -163,28 +137,59 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
163
137
|
metadata: Option.some({ offset: nextHandle.offset, handle: nextHandle.handle }),
|
|
164
138
|
eventEncoded: item.value,
|
|
165
139
|
}));
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
// return Option.none()
|
|
170
|
-
// }
|
|
171
|
-
return Option.some([Chunk.fromIterable(items), Option.some(nextHandle)]);
|
|
172
|
-
}).pipe(Effect.scoped, Effect.mapError((cause) => cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ message: cause.toString() })));
|
|
140
|
+
yield* Effect.annotateCurrentSpan({ itemsCount: items.length, nextHandle });
|
|
141
|
+
return Option.some([items, Option.some(nextHandle)]);
|
|
142
|
+
}).pipe(Effect.scoped, Effect.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))), Effect.withSpan('electric-provider:runPull', { attributes: { handle, live } }));
|
|
173
143
|
const pullEndpointHasSameOrigin = pullEndpoint.startsWith('/') ||
|
|
174
144
|
(globalThis.location !== undefined && globalThis.location.origin === new URL(pullEndpoint).origin);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
145
|
+
const pingTimeout = options.ping?.requestTimeout ?? 10_000;
|
|
146
|
+
const ping = Effect.gen(function* () {
|
|
147
|
+
yield* httpClient.pipe(HttpClient.filterStatusOk).head(pingEndpoint);
|
|
148
|
+
yield* SubscriptionRef.set(isConnected, true);
|
|
149
|
+
}).pipe(UnexpectedError.mapToUnexpectedError, Effect.timeout(pingTimeout), Effect.catchTag('TimeoutException', () => SubscriptionRef.set(isConnected, false)), Effect.withSpan('electric-provider:ping'));
|
|
150
|
+
const pingInterval = options.ping?.requestInterval ?? 10_000;
|
|
151
|
+
if (options.ping?.enabled !== false) {
|
|
152
|
+
// Automatically ping the server to keep the connection alive
|
|
153
|
+
yield* ping.pipe(Effect.repeat(Schedule.spaced(pingInterval)), Effect.tapCauseLogPretty, Effect.forkScoped);
|
|
154
|
+
}
|
|
155
|
+
// If the pull endpoint has the same origin as the current page, we can assume that we already have a connection
|
|
156
|
+
// otherwise we send a HEAD request to speed up the connection process
|
|
157
|
+
const connect = pullEndpointHasSameOrigin
|
|
158
|
+
? Effect.void
|
|
159
|
+
: ping.pipe(UnexpectedError.mapToUnexpectedError);
|
|
160
|
+
return SyncBackend.of({
|
|
161
|
+
connect,
|
|
162
|
+
pull: (cursor, options) => {
|
|
163
|
+
let hasEmittedAtLeastOnce = false;
|
|
164
|
+
return Stream.unfoldEffect(cursor.pipe(Option.flatMap((_) => _.metadata)), (metadataOption) => Effect.gen(function* () {
|
|
165
|
+
const result = yield* runPull(metadataOption, { live: options?.live ?? false });
|
|
166
|
+
if (Option.isNone(result))
|
|
167
|
+
return Option.none();
|
|
168
|
+
const [batch, nextMetadataOption] = result.value;
|
|
169
|
+
// Continue pagination if we have data
|
|
170
|
+
if (batch.length > 0) {
|
|
171
|
+
hasEmittedAtLeastOnce = true;
|
|
172
|
+
return Option.some([{ batch, hasMore: true }, nextMetadataOption]);
|
|
173
|
+
}
|
|
174
|
+
// Make sure we emit at least once even if there's no data or we're live-pulling
|
|
175
|
+
if (hasEmittedAtLeastOnce === false || options?.live) {
|
|
176
|
+
hasEmittedAtLeastOnce = true;
|
|
177
|
+
return Option.some([{ batch, hasMore: false }, nextMetadataOption]);
|
|
178
|
+
}
|
|
179
|
+
// Stop on empty batch (when not live)
|
|
180
|
+
return Option.none();
|
|
181
|
+
})).pipe(Stream.map(({ batch, hasMore }) => ({
|
|
182
|
+
batch,
|
|
183
|
+
pageInfo: hasMore ? SyncBackend.pageInfoMoreUnknown : SyncBackend.pageInfoNoMore,
|
|
184
|
+
})), Stream.withSpan('electric-provider:pull'));
|
|
185
|
+
},
|
|
182
186
|
push: (batch) => Effect.gen(function* () {
|
|
183
|
-
const resp = yield* HttpClientRequest.schemaBodyJson(ApiSchema.PushPayload)(HttpClientRequest.post(pushEndpoint), ApiSchema.PushPayload.make({ storeId, batch })).pipe(Effect.andThen(HttpClient.execute), Effect.andThen(HttpClientResponse.schemaBodyJson(Schema.Struct({ success: Schema.Boolean }))), Effect.scoped, Effect.mapError((cause) => InvalidPushError.make({
|
|
187
|
+
const resp = yield* HttpClientRequest.schemaBodyJson(ApiSchema.PushPayload)(HttpClientRequest.post(pushEndpoint), ApiSchema.PushPayload.make({ storeId, batch })).pipe(Effect.andThen(httpClient.pipe(HttpClient.filterStatusOk).execute), Effect.andThen(HttpClientResponse.schemaBodyJson(Schema.Struct({ success: Schema.Boolean }))), Effect.scoped, Effect.mapError((cause) => InvalidPushError.make({ cause: UnexpectedError.make({ cause }) })));
|
|
184
188
|
if (!resp.success) {
|
|
185
|
-
yield* InvalidPushError.make({
|
|
189
|
+
return yield* InvalidPushError.make({ cause: new UnexpectedError({ cause: new Error('Push failed') }) });
|
|
186
190
|
}
|
|
187
|
-
}),
|
|
191
|
+
}).pipe(Effect.withSpan('electric-provider:push')),
|
|
192
|
+
ping,
|
|
188
193
|
isConnected,
|
|
189
194
|
metadata: {
|
|
190
195
|
name: '@livestore/sync-electric',
|
|
@@ -192,16 +197,12 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
192
197
|
protocol: 'http',
|
|
193
198
|
endpoint,
|
|
194
199
|
},
|
|
195
|
-
|
|
200
|
+
supports: {
|
|
201
|
+
// Given Electric is heavily optimized for immutable caching, we can't know the remaining count
|
|
202
|
+
// until we've reached the end of the stream
|
|
203
|
+
pullPageInfoKnown: false,
|
|
204
|
+
pullLive: true,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
196
207
|
});
|
|
197
|
-
/**
|
|
198
|
-
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
199
|
-
*
|
|
200
|
-
* Changing this version number will lead to a "soft reset".
|
|
201
|
-
*/
|
|
202
|
-
export const PERSISTENCE_FORMAT_VERSION = 6;
|
|
203
|
-
export const toTableName = (storeId) => {
|
|
204
|
-
const escapedStoreId = storeId.replaceAll(/[^a-zA-Z0-9_]/g, '_');
|
|
205
|
-
return `eventlog_${PERSISTENCE_FORMAT_VERSION}_${escapedStoreId}`;
|
|
206
|
-
};
|
|
207
208
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Schema } from '@livestore/utils/effect';
|
|
2
|
+
/**
|
|
3
|
+
* This function should be called in a trusted environment (e.g. a proxy server) as it
|
|
4
|
+
* requires access to senstive information (e.g. `apiSecret` / `sourceSecret`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const makeElectricUrl: ({ electricHost, searchParams: providedSearchParams, sourceId, sourceSecret, apiSecret, }: {
|
|
7
|
+
electricHost: string;
|
|
8
|
+
/**
|
|
9
|
+
* Needed to extract information from the search params which the `@livestore/sync-electric`
|
|
10
|
+
* client implementation automatically adds:
|
|
11
|
+
* - `handle`: the ElectricSQL handle
|
|
12
|
+
* - `storeId`: the Livestore storeId
|
|
13
|
+
*/
|
|
14
|
+
searchParams: URLSearchParams;
|
|
15
|
+
/** Needed for Electric Cloud */
|
|
16
|
+
sourceId?: string;
|
|
17
|
+
/** Needed for Electric Cloud */
|
|
18
|
+
sourceSecret?: string;
|
|
19
|
+
/** For self-hosted ElectricSQL */
|
|
20
|
+
apiSecret?: string;
|
|
21
|
+
}) => {
|
|
22
|
+
/**
|
|
23
|
+
* The URL to the ElectricSQL API endpoint with needed search params.
|
|
24
|
+
*/
|
|
25
|
+
url: string;
|
|
26
|
+
/** The Livestore storeId */
|
|
27
|
+
storeId: string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether the Postgres table needs to be created.
|
|
30
|
+
*/
|
|
31
|
+
needsInit: boolean;
|
|
32
|
+
/** Sync payload provided by the client */
|
|
33
|
+
payload: Schema.JsonValue | undefined;
|
|
34
|
+
};
|
|
35
|
+
export declare const toTableName: (storeId: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
38
|
+
*
|
|
39
|
+
* Changing this version number will lead to a "soft reset".
|
|
40
|
+
*/
|
|
41
|
+
export declare const PERSISTENCE_FORMAT_VERSION = 6;
|
|
42
|
+
//# sourceMappingURL=make-electric-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"make-electric-url.d.ts","sourceRoot":"","sources":["../src/make-electric-url.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,MAAM,EAAE,MAAM,yBAAyB,CAAA;AAGtD;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,0FAM7B;IACD,YAAY,EAAE,MAAM,CAAA;IACpB;;;;;OAKG;IACH,YAAY,EAAE,eAAe,CAAA;IAC7B,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,kCAAkC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,KAAG;IACF;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IACX,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,SAAS,EAAE,OAAO,CAAA;IAClB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC,SAAS,GAAG,SAAS,CAAA;CA4CtC,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,WAe1C,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { shouldNeverHappen } from '@livestore/utils';
|
|
2
|
+
import { Hash, Schema } from '@livestore/utils/effect';
|
|
3
|
+
import * as ApiSchema from "./api-schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* This function should be called in a trusted environment (e.g. a proxy server) as it
|
|
6
|
+
* requires access to senstive information (e.g. `apiSecret` / `sourceSecret`).
|
|
7
|
+
*/
|
|
8
|
+
export const makeElectricUrl = ({ electricHost, searchParams: providedSearchParams, sourceId, sourceSecret, apiSecret, }) => {
|
|
9
|
+
const endpointUrl = `${electricHost}/v1/shape`;
|
|
10
|
+
const UrlParamsSchema = Schema.Struct({ args: ApiSchema.ArgsSchema });
|
|
11
|
+
const argsResult = Schema.decodeUnknownEither(UrlParamsSchema)(Object.fromEntries(providedSearchParams.entries()));
|
|
12
|
+
if (argsResult._tag === 'Left') {
|
|
13
|
+
return shouldNeverHappen('Invalid search params provided to makeElectricUrl', providedSearchParams, Object.fromEntries(providedSearchParams.entries()));
|
|
14
|
+
}
|
|
15
|
+
const args = argsResult.right.args;
|
|
16
|
+
const tableName = toTableName(args.storeId);
|
|
17
|
+
// TODO refactor with Effect URLSearchParams schema
|
|
18
|
+
// https://electric-sql.com/openapi.html
|
|
19
|
+
const searchParams = new URLSearchParams();
|
|
20
|
+
// Electric requires table names with capital letters to be quoted
|
|
21
|
+
// Since our table names include the storeId which may have capitals, we always quote
|
|
22
|
+
searchParams.set('table', `"${tableName}"`);
|
|
23
|
+
if (sourceId !== undefined) {
|
|
24
|
+
searchParams.set('source_id', sourceId);
|
|
25
|
+
}
|
|
26
|
+
if (sourceSecret !== undefined) {
|
|
27
|
+
searchParams.set('source_secret', sourceSecret);
|
|
28
|
+
}
|
|
29
|
+
if (apiSecret !== undefined) {
|
|
30
|
+
searchParams.set('api_secret', apiSecret);
|
|
31
|
+
}
|
|
32
|
+
if (args.handle._tag === 'None') {
|
|
33
|
+
searchParams.set('offset', '-1');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
searchParams.set('offset', args.handle.value.offset);
|
|
37
|
+
searchParams.set('handle', args.handle.value.handle);
|
|
38
|
+
searchParams.set('live', args.live ? 'true' : 'false');
|
|
39
|
+
}
|
|
40
|
+
const payload = args.payload;
|
|
41
|
+
const url = `${endpointUrl}?${searchParams.toString()}`;
|
|
42
|
+
return { url, storeId: args.storeId, needsInit: args.handle._tag === 'None', payload };
|
|
43
|
+
};
|
|
44
|
+
export const toTableName = (storeId) => {
|
|
45
|
+
const escapedStoreId = storeId.replaceAll(/[^a-zA-Z0-9_]/g, '_');
|
|
46
|
+
const tableName = `eventlog_${PERSISTENCE_FORMAT_VERSION}_${escapedStoreId}`;
|
|
47
|
+
if (tableName.length > 63) {
|
|
48
|
+
const hashedStoreId = Hash.string(storeId);
|
|
49
|
+
console.warn(`Table name is too long: "${tableName}". Postgres table names are limited to 63 characters. Using hashed storeId instead: "${hashedStoreId}".`);
|
|
50
|
+
return `eventlog_${PERSISTENCE_FORMAT_VERSION}_hash_${hashedStoreId}`;
|
|
51
|
+
}
|
|
52
|
+
return tableName;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Needs to be bumped when the storage format changes (e.g. eventlogTable schema changes)
|
|
56
|
+
*
|
|
57
|
+
* Changing this version number will lead to a "soft reset".
|
|
58
|
+
*/
|
|
59
|
+
export const PERSISTENCE_FORMAT_VERSION = 6;
|
|
60
|
+
//# sourceMappingURL=make-electric-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"make-electric-url.js","sourceRoot":"","sources":["../src/make-electric-url.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/sync-electric",
|
|
3
|
-
"version": "0.4.0-dev.
|
|
3
|
+
"version": "0.4.0-dev.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@livestore/common": "0.4.0-dev.
|
|
11
|
-
"@livestore/utils": "0.4.0-dev.
|
|
10
|
+
"@livestore/common": "0.4.0-dev.5",
|
|
11
|
+
"@livestore/utils": "0.4.0-dev.5"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {},
|
|
14
14
|
"files": [
|
package/src/api-schema.ts
CHANGED
|
@@ -15,6 +15,10 @@ export const PullPayload = Schema.TaggedStruct('@livestore/sync-electric.Pull',
|
|
|
15
15
|
handle: Schema.String,
|
|
16
16
|
}),
|
|
17
17
|
),
|
|
18
|
+
live: Schema.Boolean,
|
|
18
19
|
}).annotations({ title: '@livestore/sync-electric.PullPayload' })
|
|
19
20
|
|
|
20
21
|
export const ApiPayload = Schema.Union(PullPayload, PushPayload)
|
|
22
|
+
|
|
23
|
+
// Format for the query params
|
|
24
|
+
export const ArgsSchema = Schema.compose(Schema.StringFromUriComponent, Schema.parseJson(PullPayload))
|