@livestore/sync-electric 0.4.0-dev.2 → 0.4.0-dev.21
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 +4 -1
- package/dist/api-schema.js.map +1 -1
- package/dist/index.d.ts +68 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +151 -92
- package/dist/index.js.map +1 -1
- package/dist/make-electric-url.d.ts +61 -0
- package/dist/make-electric-url.d.ts.map +1 -0
- package/dist/make-electric-url.js +79 -0
- package/dist/make-electric-url.js.map +1 -0
- package/package.json +3 -3
- package/src/api-schema.ts +5 -1
- package/src/index.ts +210 -161
- package/src/make-electric-url.ts +129 -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
|
@@ -2,7 +2,7 @@ import { LiveStoreEvent } from '@livestore/common/schema';
|
|
|
2
2
|
import { Schema } from '@livestore/utils/effect';
|
|
3
3
|
export const PushPayload = Schema.TaggedStruct('@livestore/sync-electric.Push', {
|
|
4
4
|
storeId: Schema.String,
|
|
5
|
-
batch: Schema.Array(LiveStoreEvent.
|
|
5
|
+
batch: Schema.Array(LiveStoreEvent.Global.Encoded),
|
|
6
6
|
}).annotations({ title: '@livestore/sync-electric.PushPayload' });
|
|
7
7
|
export const PullPayload = Schema.TaggedStruct('@livestore/sync-electric.Pull', {
|
|
8
8
|
storeId: Schema.String,
|
|
@@ -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,
|
|
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,MAAM,CAAC,OAAO,CAAC;CACnD,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,16 @@
|
|
|
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
|
+
declare const InvalidOperationError_base: Schema.TaggedErrorClass<InvalidOperationError, "InvalidOperationError", {
|
|
4
|
+
readonly _tag: Schema.tag<"InvalidOperationError">;
|
|
5
|
+
} & {
|
|
6
|
+
operation: Schema.Literal<["delete", "update"]>;
|
|
7
|
+
message: typeof Schema.String;
|
|
8
|
+
}>;
|
|
9
|
+
export declare class InvalidOperationError extends InvalidOperationError_base {
|
|
10
|
+
}
|
|
3
11
|
export * as ApiSchema from './api-schema.ts';
|
|
4
|
-
export
|
|
12
|
+
export * from './make-electric-url.ts';
|
|
5
13
|
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
14
|
export interface SyncBackendOptions {
|
|
32
15
|
/**
|
|
33
16
|
* The endpoint to pull/push events. Pull is a `GET` request, push is a `POST` request.
|
|
@@ -40,22 +23,70 @@ export interface SyncBackendOptions {
|
|
|
40
23
|
endpoint: string | {
|
|
41
24
|
push: string;
|
|
42
25
|
pull: string;
|
|
26
|
+
ping: string;
|
|
27
|
+
};
|
|
28
|
+
ping?: {
|
|
29
|
+
/**
|
|
30
|
+
* @default true
|
|
31
|
+
*/
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* How long to wait for a ping response before timing out
|
|
35
|
+
* @default 10 seconds
|
|
36
|
+
*/
|
|
37
|
+
requestTimeout?: Duration.DurationInput;
|
|
38
|
+
/**
|
|
39
|
+
* How often to send ping requests
|
|
40
|
+
* @default 10 seconds
|
|
41
|
+
*/
|
|
42
|
+
requestInterval?: Duration.DurationInput;
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
export declare const SyncMetadata: Schema.Struct<{
|
|
46
46
|
offset: typeof Schema.String;
|
|
47
47
|
handle: typeof Schema.String;
|
|
48
48
|
}>;
|
|
49
|
-
type SyncMetadata =
|
|
50
|
-
offset: string;
|
|
51
|
-
handle: string;
|
|
52
|
-
};
|
|
53
|
-
export declare const makeSyncBackend: ({ endpoint }: SyncBackendOptions) => SyncBackendConstructor<SyncMetadata>;
|
|
49
|
+
export type SyncMetadata = typeof SyncMetadata.Type;
|
|
54
50
|
/**
|
|
55
|
-
*
|
|
51
|
+
* Creates a sync backend that uses ElectricSQL for real-time event synchronization.
|
|
52
|
+
*
|
|
53
|
+
* ElectricSQL enables real-time sync by streaming PostgreSQL changes to clients.
|
|
54
|
+
* This backend handles push (inserting events) and pull (streaming events via Electric's
|
|
55
|
+
* shape-based sync protocol).
|
|
56
|
+
*
|
|
57
|
+
* The endpoint should typically be part of your API layer to handle authentication,
|
|
58
|
+
* rate limiting, and proxying requests to the Electric server.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { makeSyncBackend } from '@livestore/sync-electric'
|
|
63
|
+
*
|
|
64
|
+
* const adapter = makePersistedAdapter({
|
|
65
|
+
* sync: {
|
|
66
|
+
* backend: makeSyncBackend({
|
|
67
|
+
* endpoint: '/api/electric',
|
|
68
|
+
* }),
|
|
69
|
+
* },
|
|
70
|
+
* })
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* // With separate endpoints for push/pull/ping
|
|
76
|
+
* const backend = makeSyncBackend({
|
|
77
|
+
* endpoint: {
|
|
78
|
+
* push: '/api/push-event',
|
|
79
|
+
* pull: '/api/pull-events',
|
|
80
|
+
* ping: '/api/ping',
|
|
81
|
+
* },
|
|
82
|
+
* ping: {
|
|
83
|
+
* enabled: true,
|
|
84
|
+
* requestInterval: 15_000, // 15 seconds
|
|
85
|
+
* },
|
|
86
|
+
* })
|
|
87
|
+
* ```
|
|
56
88
|
*
|
|
57
|
-
*
|
|
89
|
+
* @see https://livestore.dev/docs/sync/electric for setup guide
|
|
58
90
|
*/
|
|
59
|
-
export declare const
|
|
60
|
-
export declare const toTableName: (storeId: string) => string;
|
|
91
|
+
export declare const makeSyncBackend: ({ endpoint, ...options }: SyncBackendOptions) => SyncBackend.SyncBackendConstructor<SyncMetadata>;
|
|
61
92
|
//# 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,EAA2D,WAAW,EAAgB,MAAM,mBAAmB,CAAA;AAGtH,OAAO,EACL,KAAK,QAAQ,EAQb,MAAM,EAGP,MAAM,yBAAyB,CAAA;;;;;;;AAIhC,qBAAa,qBAAsB,SAAQ,0BAGzC;CAAG;AAEL,OAAO,KAAK,SAAS,MAAM,iBAAiB,CAAA;AAC5C,cAAc,wBAAwB,CAAA;AAkFtC,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,MAAM,MAAM,YAAY,GAAG,OAAO,YAAY,CAAC,IAAI,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,eAAO,MAAM,eAAe,GACzB,0BAA0B,kBAAkB,KAAG,WAAW,CAAC,sBAAsB,CAAC,YAAY,CA2M3F,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { InvalidPullError, InvalidPushError,
|
|
1
|
+
import { InvalidPullError, InvalidPushError, SyncBackend, UnknownError } 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, ReadonlyArray, Schedule, Schema, Stream, SubscriptionRef, } from '@livestore/utils/effect';
|
|
5
5
|
import * as ApiSchema from "./api-schema.js";
|
|
6
|
+
export class InvalidOperationError extends Schema.TaggedError()('InvalidOperationError', {
|
|
7
|
+
operation: Schema.Literal('delete', 'update'),
|
|
8
|
+
message: Schema.String,
|
|
9
|
+
}) {
|
|
10
|
+
}
|
|
6
11
|
export * as ApiSchema from "./api-schema.js";
|
|
12
|
+
export * from "./make-electric-url.js";
|
|
7
13
|
/*
|
|
8
14
|
Example data:
|
|
9
15
|
|
|
@@ -50,83 +56,101 @@ const LiveStoreEventGlobalFromStringRecord = Schema.Struct({
|
|
|
50
56
|
args: Schema.parseJson(Schema.Any),
|
|
51
57
|
clientId: Schema.String,
|
|
52
58
|
sessionId: Schema.String,
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const ResponseItem = Schema.Struct({
|
|
59
|
+
})
|
|
60
|
+
.pipe(Schema.compose(LiveStoreEvent.Global.Encoded))
|
|
61
|
+
.annotations({ title: '@livestore/sync-electric:LiveStoreEventGlobalFromStringRecord' });
|
|
62
|
+
const ResponseItemInsert = Schema.Struct({
|
|
58
63
|
/** Postgres path (e.g. `"public"."events_9069baf0_b3e6_42f7_980f_188416eab3fx3"/"0"`) */
|
|
59
64
|
key: Schema.optional(Schema.String),
|
|
60
|
-
value:
|
|
61
|
-
headers: Schema.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
65
|
+
value: LiveStoreEventGlobalFromStringRecord,
|
|
66
|
+
headers: Schema.Struct({ operation: Schema.Literal('insert'), relation: Schema.Array(Schema.String) }),
|
|
67
|
+
}).annotations({ title: '@livestore/sync-electric:ResponseItemInsert' });
|
|
68
|
+
const ResponseItemInvalid = Schema.Struct({
|
|
69
|
+
/** Postgres path (e.g. `"public"."events_9069baf0_b3e6_42f7_980f_188416eab3fx3"/"0"`) */
|
|
70
|
+
key: Schema.optional(Schema.String),
|
|
71
|
+
value: Schema.Any,
|
|
72
|
+
headers: Schema.Struct({ operation: Schema.Literal('update', 'delete'), relation: Schema.Array(Schema.String) }),
|
|
73
|
+
}).annotations({ title: '@livestore/sync-electric:ResponseItemInvalid' });
|
|
74
|
+
const ResponseItemControl = Schema.Struct({
|
|
75
|
+
key: Schema.optional(Schema.String),
|
|
76
|
+
value: Schema.optional(Schema.Any),
|
|
77
|
+
headers: Schema.Struct({ control: Schema.String }),
|
|
78
|
+
}).annotations({ title: '@livestore/sync-electric:ResponseItemControl' });
|
|
79
|
+
const ResponseItem = Schema.Union(ResponseItemInsert, ResponseItemInvalid, ResponseItemControl);
|
|
68
80
|
const ResponseHeaders = Schema.Struct({
|
|
69
81
|
'electric-handle': Schema.String,
|
|
70
82
|
// 'electric-schema': Schema.parseJson(Schema.Any),
|
|
71
83
|
/** e.g. 26799576_0 */
|
|
72
84
|
'electric-offset': Schema.String,
|
|
73
85
|
});
|
|
74
|
-
export const syncBackend = {};
|
|
75
86
|
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
87
|
export const SyncMetadata = Schema.Struct({
|
|
112
88
|
offset: Schema.String,
|
|
113
89
|
// TODO move this into some kind of "global" sync metadata as it's the same for each event
|
|
114
90
|
handle: Schema.String,
|
|
115
91
|
});
|
|
116
|
-
|
|
117
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Creates a sync backend that uses ElectricSQL for real-time event synchronization.
|
|
94
|
+
*
|
|
95
|
+
* ElectricSQL enables real-time sync by streaming PostgreSQL changes to clients.
|
|
96
|
+
* This backend handles push (inserting events) and pull (streaming events via Electric's
|
|
97
|
+
* shape-based sync protocol).
|
|
98
|
+
*
|
|
99
|
+
* The endpoint should typically be part of your API layer to handle authentication,
|
|
100
|
+
* rate limiting, and proxying requests to the Electric server.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* import { makeSyncBackend } from '@livestore/sync-electric'
|
|
105
|
+
*
|
|
106
|
+
* const adapter = makePersistedAdapter({
|
|
107
|
+
* sync: {
|
|
108
|
+
* backend: makeSyncBackend({
|
|
109
|
+
* endpoint: '/api/electric',
|
|
110
|
+
* }),
|
|
111
|
+
* },
|
|
112
|
+
* })
|
|
113
|
+
* ```
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* // With separate endpoints for push/pull/ping
|
|
118
|
+
* const backend = makeSyncBackend({
|
|
119
|
+
* endpoint: {
|
|
120
|
+
* push: '/api/push-event',
|
|
121
|
+
* pull: '/api/pull-events',
|
|
122
|
+
* ping: '/api/ping',
|
|
123
|
+
* },
|
|
124
|
+
* ping: {
|
|
125
|
+
* enabled: true,
|
|
126
|
+
* requestInterval: 15_000, // 15 seconds
|
|
127
|
+
* },
|
|
128
|
+
* })
|
|
129
|
+
* ```
|
|
130
|
+
*
|
|
131
|
+
* @see https://livestore.dev/docs/sync/electric for setup guide
|
|
132
|
+
*/
|
|
133
|
+
export const makeSyncBackend = ({ endpoint, ...options }) => ({ storeId, payload }) => Effect.gen(function* () {
|
|
134
|
+
const isConnected = yield* SubscriptionRef.make(false);
|
|
118
135
|
const pullEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.pull;
|
|
119
136
|
const pushEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.push;
|
|
120
|
-
const
|
|
121
|
-
|
|
137
|
+
const pingEndpoint = typeof endpoint === 'string' ? endpoint : endpoint.ping;
|
|
138
|
+
const httpClient = yield* HttpClient.HttpClient;
|
|
139
|
+
const runPull = (handle, { live }) => Effect.gen(function* () {
|
|
140
|
+
const argsJson = yield* Schema.encode(ApiSchema.ArgsSchema)(ApiSchema.PullPayload.make({ storeId, handle, payload, live }));
|
|
122
141
|
const url = `${pullEndpoint}?args=${argsJson}`;
|
|
123
|
-
const resp = yield*
|
|
142
|
+
const resp = yield* httpClient.get(url);
|
|
124
143
|
if (resp.status === 401) {
|
|
125
144
|
const body = yield* resp.text.pipe(Effect.catchAll(() => Effect.succeed('-')));
|
|
126
145
|
return yield* InvalidPullError.make({
|
|
127
|
-
|
|
146
|
+
cause: new Error(`Unauthorized (401): Couldn't connect to ElectricSQL: ${body}`),
|
|
128
147
|
});
|
|
129
148
|
}
|
|
149
|
+
else if (resp.status === 400) {
|
|
150
|
+
// Electric returns 400 when table doesn't exist
|
|
151
|
+
// Return empty result for non-existent tables
|
|
152
|
+
return Option.some([[], Option.none()]);
|
|
153
|
+
}
|
|
130
154
|
else if (resp.status === 409) {
|
|
131
155
|
// https://electric-sql.com/openapi.html#/paths/~1v1~1shape/get
|
|
132
156
|
// {
|
|
@@ -140,8 +164,9 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
140
164
|
return notYetImplemented(`Electric shape not found`);
|
|
141
165
|
}
|
|
142
166
|
else if (resp.status < 200 || resp.status >= 300) {
|
|
167
|
+
const body = yield* resp.text;
|
|
143
168
|
return yield* InvalidPullError.make({
|
|
144
|
-
|
|
169
|
+
cause: new Error(`Unexpected status code: ${resp.status}: ${body}`),
|
|
145
170
|
});
|
|
146
171
|
}
|
|
147
172
|
const headers = yield* HttpClientResponse.schemaHeaders(ResponseHeaders)(resp);
|
|
@@ -152,39 +177,77 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
152
177
|
// Electric completes the long-poll request after ~20 seconds with a 204 status
|
|
153
178
|
// In this case we just retry where we left off
|
|
154
179
|
if (resp.status === 204) {
|
|
155
|
-
return Option.some([
|
|
180
|
+
return Option.some([[], Option.some(nextHandle)]);
|
|
156
181
|
}
|
|
157
|
-
const
|
|
182
|
+
const allItems = yield* HttpClientResponse.schemaBodyJson(Schema.Array(ResponseItem), {
|
|
158
183
|
onExcessProperty: 'preserve',
|
|
159
184
|
})(resp);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
// Check for delete/update operations and throw descriptive error
|
|
186
|
+
const invalidOperations = ReadonlyArray.filterMap(allItems, (item) => Schema.is(ResponseItemInvalid)(item) ? Option.some(item.headers.operation) : Option.none());
|
|
187
|
+
if (invalidOperations.length > 0) {
|
|
188
|
+
const operation = invalidOperations[0];
|
|
189
|
+
return yield* new InvalidOperationError({
|
|
190
|
+
operation,
|
|
191
|
+
message: `ElectricSQL '${operation}' event received. This results from directly mutating the event log. Append a series of events that produce the desired state instead of mutating the event log.`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
const items = allItems.filter(Schema.is(ResponseItemInsert)).map((item) => ({
|
|
163
195
|
metadata: Option.some({ offset: nextHandle.offset, handle: nextHandle.handle }),
|
|
164
196
|
eventEncoded: item.value,
|
|
165
197
|
}));
|
|
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() })));
|
|
198
|
+
yield* Effect.annotateCurrentSpan({ itemsCount: items.length, nextHandle });
|
|
199
|
+
return Option.some([items, Option.some(nextHandle)]);
|
|
200
|
+
}).pipe(Effect.scoped, Effect.mapError((cause) => (cause._tag === 'InvalidPullError' ? cause : InvalidPullError.make({ cause }))), Effect.withSpan('electric-provider:runPull', { attributes: { handle, live } }));
|
|
173
201
|
const pullEndpointHasSameOrigin = pullEndpoint.startsWith('/') ||
|
|
174
202
|
(globalThis.location !== undefined && globalThis.location.origin === new URL(pullEndpoint).origin);
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
const pingTimeout = options.ping?.requestTimeout ?? 10_000;
|
|
204
|
+
const ping = Effect.gen(function* () {
|
|
205
|
+
yield* httpClient.pipe(HttpClient.filterStatusOk).head(pingEndpoint);
|
|
206
|
+
yield* SubscriptionRef.set(isConnected, true);
|
|
207
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.timeout(pingTimeout), Effect.catchTag('TimeoutException', () => SubscriptionRef.set(isConnected, false)), Effect.withSpan('electric-provider:ping'));
|
|
208
|
+
const pingInterval = options.ping?.requestInterval ?? 10_000;
|
|
209
|
+
if (options.ping?.enabled !== false) {
|
|
210
|
+
// Automatically ping the server to keep the connection alive
|
|
211
|
+
yield* ping.pipe(Effect.repeat(Schedule.spaced(pingInterval)), Effect.tapCauseLogPretty, Effect.forkScoped);
|
|
212
|
+
}
|
|
213
|
+
// If the pull endpoint has the same origin as the current page, we can assume that we already have a connection
|
|
214
|
+
// otherwise we send a HEAD request to speed up the connection process
|
|
215
|
+
const connect = pullEndpointHasSameOrigin
|
|
216
|
+
? Effect.void
|
|
217
|
+
: ping.pipe(UnknownError.mapToUnknownError);
|
|
218
|
+
return SyncBackend.of({
|
|
219
|
+
connect,
|
|
220
|
+
pull: (cursor, options) => {
|
|
221
|
+
let hasEmittedAtLeastOnce = false;
|
|
222
|
+
return Stream.unfoldEffect(cursor.pipe(Option.flatMap((_) => _.metadata)), (metadataOption) => Effect.gen(function* () {
|
|
223
|
+
const result = yield* runPull(metadataOption, { live: options?.live ?? false });
|
|
224
|
+
if (Option.isNone(result))
|
|
225
|
+
return Option.none();
|
|
226
|
+
const [batch, nextMetadataOption] = result.value;
|
|
227
|
+
// Continue pagination if we have data
|
|
228
|
+
if (batch.length > 0) {
|
|
229
|
+
hasEmittedAtLeastOnce = true;
|
|
230
|
+
return Option.some([{ batch, hasMore: true }, nextMetadataOption]);
|
|
231
|
+
}
|
|
232
|
+
// Make sure we emit at least once even if there's no data or we're live-pulling
|
|
233
|
+
if (hasEmittedAtLeastOnce === false || options?.live) {
|
|
234
|
+
hasEmittedAtLeastOnce = true;
|
|
235
|
+
return Option.some([{ batch, hasMore: false }, nextMetadataOption]);
|
|
236
|
+
}
|
|
237
|
+
// Stop on empty batch (when not live)
|
|
238
|
+
return Option.none();
|
|
239
|
+
})).pipe(Stream.map(({ batch, hasMore }) => ({
|
|
240
|
+
batch,
|
|
241
|
+
pageInfo: hasMore ? SyncBackend.pageInfoMoreUnknown : SyncBackend.pageInfoNoMore,
|
|
242
|
+
})), Stream.withSpan('electric-provider:pull'));
|
|
243
|
+
},
|
|
182
244
|
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({
|
|
245
|
+
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: UnknownError.make({ cause }) })));
|
|
184
246
|
if (!resp.success) {
|
|
185
|
-
yield* InvalidPushError.make({
|
|
247
|
+
return yield* InvalidPushError.make({ cause: new UnknownError({ cause: new Error('Push failed') }) });
|
|
186
248
|
}
|
|
187
|
-
}),
|
|
249
|
+
}).pipe(Effect.withSpan('electric-provider:push')),
|
|
250
|
+
ping,
|
|
188
251
|
isConnected,
|
|
189
252
|
metadata: {
|
|
190
253
|
name: '@livestore/sync-electric',
|
|
@@ -192,16 +255,12 @@ export const makeSyncBackend = ({ endpoint }) => ({ storeId, payload }) => Effec
|
|
|
192
255
|
protocol: 'http',
|
|
193
256
|
endpoint,
|
|
194
257
|
},
|
|
195
|
-
|
|
258
|
+
supports: {
|
|
259
|
+
// Given Electric is heavily optimized for immutable caching, we can't know the remaining count
|
|
260
|
+
// until we've reached the end of the stream
|
|
261
|
+
pullPageInfoKnown: false,
|
|
262
|
+
pullLive: true,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
196
265
|
});
|
|
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
266
|
//# 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,61 @@
|
|
|
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
|
+
* CRITICAL: Increment this version whenever you modify the Postgres table schema structure.
|
|
38
|
+
*
|
|
39
|
+
* Bump required when:
|
|
40
|
+
* - Adding/removing/renaming columns in the eventlog table (see examples/web-todomvc-sync-electric/src/server/db.ts)
|
|
41
|
+
* - Changing column types or constraints
|
|
42
|
+
* - Modifying primary keys or indexes
|
|
43
|
+
*
|
|
44
|
+
* Bump NOT required when:
|
|
45
|
+
* - Changing query patterns or fetch logic
|
|
46
|
+
* - Adding new tables (as long as existing table schema remains unchanged)
|
|
47
|
+
* - Updating client-side implementation details
|
|
48
|
+
*
|
|
49
|
+
* Impact: Changing this version triggers a "soft reset" - new table names are created
|
|
50
|
+
* and old data becomes inaccessible (but remains in the database).
|
|
51
|
+
*
|
|
52
|
+
* Current schema (PostgreSQL):
|
|
53
|
+
* - seqNum (INTEGER PRIMARY KEY)
|
|
54
|
+
* - parentSeqNum (INTEGER)
|
|
55
|
+
* - name (TEXT NOT NULL)
|
|
56
|
+
* - args (JSONB NOT NULL)
|
|
57
|
+
* - clientId (TEXT NOT NULL)
|
|
58
|
+
* - sessionId (TEXT NOT NULL)
|
|
59
|
+
*/
|
|
60
|
+
export declare const PERSISTENCE_FORMAT_VERSION = 6;
|
|
61
|
+
//# 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;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,0BAA0B,IAAI,CAAA"}
|