@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
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
+
import { Hash, Schema } from '@livestore/utils/effect'
|
|
3
|
+
import * as ApiSchema from './api-schema.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This function should be called in a trusted environment (e.g. a proxy server) as it
|
|
7
|
+
* requires access to senstive information (e.g. `apiSecret` / `sourceSecret`).
|
|
8
|
+
*/
|
|
9
|
+
export const makeElectricUrl = ({
|
|
10
|
+
electricHost,
|
|
11
|
+
searchParams: providedSearchParams,
|
|
12
|
+
sourceId,
|
|
13
|
+
sourceSecret,
|
|
14
|
+
apiSecret,
|
|
15
|
+
}: {
|
|
16
|
+
electricHost: string
|
|
17
|
+
/**
|
|
18
|
+
* Needed to extract information from the search params which the `@livestore/sync-electric`
|
|
19
|
+
* client implementation automatically adds:
|
|
20
|
+
* - `handle`: the ElectricSQL handle
|
|
21
|
+
* - `storeId`: the Livestore storeId
|
|
22
|
+
*/
|
|
23
|
+
searchParams: URLSearchParams
|
|
24
|
+
/** Needed for Electric Cloud */
|
|
25
|
+
sourceId?: string
|
|
26
|
+
/** Needed for Electric Cloud */
|
|
27
|
+
sourceSecret?: string
|
|
28
|
+
/** For self-hosted ElectricSQL */
|
|
29
|
+
apiSecret?: string
|
|
30
|
+
}): {
|
|
31
|
+
/**
|
|
32
|
+
* The URL to the ElectricSQL API endpoint with needed search params.
|
|
33
|
+
*/
|
|
34
|
+
url: string
|
|
35
|
+
/** The Livestore storeId */
|
|
36
|
+
storeId: string
|
|
37
|
+
/**
|
|
38
|
+
* Whether the Postgres table needs to be created.
|
|
39
|
+
*/
|
|
40
|
+
needsInit: boolean
|
|
41
|
+
/** Sync payload provided by the client */
|
|
42
|
+
payload: Schema.JsonValue | undefined
|
|
43
|
+
} => {
|
|
44
|
+
const endpointUrl = `${electricHost}/v1/shape`
|
|
45
|
+
const UrlParamsSchema = Schema.Struct({ args: ApiSchema.ArgsSchema })
|
|
46
|
+
const argsResult = Schema.decodeUnknownEither(UrlParamsSchema)(Object.fromEntries(providedSearchParams.entries()))
|
|
47
|
+
|
|
48
|
+
if (argsResult._tag === 'Left') {
|
|
49
|
+
return shouldNeverHappen(
|
|
50
|
+
'Invalid search params provided to makeElectricUrl',
|
|
51
|
+
providedSearchParams,
|
|
52
|
+
Object.fromEntries(providedSearchParams.entries()),
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const args = argsResult.right.args
|
|
57
|
+
const tableName = toTableName(args.storeId)
|
|
58
|
+
// TODO refactor with Effect URLSearchParams schema
|
|
59
|
+
// https://electric-sql.com/openapi.html
|
|
60
|
+
const searchParams = new URLSearchParams()
|
|
61
|
+
// Electric requires table names with capital letters to be quoted
|
|
62
|
+
// Since our table names include the storeId which may have capitals, we always quote
|
|
63
|
+
searchParams.set('table', `"${tableName}"`)
|
|
64
|
+
if (sourceId !== undefined) {
|
|
65
|
+
searchParams.set('source_id', sourceId)
|
|
66
|
+
}
|
|
67
|
+
if (sourceSecret !== undefined) {
|
|
68
|
+
searchParams.set('source_secret', sourceSecret)
|
|
69
|
+
}
|
|
70
|
+
if (apiSecret !== undefined) {
|
|
71
|
+
searchParams.set('api_secret', apiSecret)
|
|
72
|
+
}
|
|
73
|
+
if (args.handle._tag === 'None') {
|
|
74
|
+
searchParams.set('offset', '-1')
|
|
75
|
+
} else {
|
|
76
|
+
searchParams.set('offset', args.handle.value.offset)
|
|
77
|
+
searchParams.set('handle', args.handle.value.handle)
|
|
78
|
+
searchParams.set('live', args.live ? 'true' : 'false')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const payload = args.payload
|
|
82
|
+
|
|
83
|
+
const url = `${endpointUrl}?${searchParams.toString()}`
|
|
84
|
+
|
|
85
|
+
return { url, storeId: args.storeId, needsInit: args.handle._tag === 'None', payload }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const toTableName = (storeId: string) => {
|
|
89
|
+
const escapedStoreId = storeId.replaceAll(/[^a-zA-Z0-9_]/g, '_')
|
|
90
|
+
const tableName = `eventlog_${PERSISTENCE_FORMAT_VERSION}_${escapedStoreId}`
|
|
91
|
+
|
|
92
|
+
if (tableName.length > 63) {
|
|
93
|
+
const hashedStoreId = Hash.string(storeId)
|
|
94
|
+
|
|
95
|
+
console.warn(
|
|
96
|
+
`Table name is too long: "${tableName}". Postgres table names are limited to 63 characters. Using hashed storeId instead: "${hashedStoreId}".`,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return `eventlog_${PERSISTENCE_FORMAT_VERSION}_hash_${hashedStoreId}`
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return tableName
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* CRITICAL: Increment this version whenever you modify the Postgres table schema structure.
|
|
107
|
+
*
|
|
108
|
+
* Bump required when:
|
|
109
|
+
* - Adding/removing/renaming columns in the eventlog table (see examples/web-todomvc-sync-electric/src/server/db.ts)
|
|
110
|
+
* - Changing column types or constraints
|
|
111
|
+
* - Modifying primary keys or indexes
|
|
112
|
+
*
|
|
113
|
+
* Bump NOT required when:
|
|
114
|
+
* - Changing query patterns or fetch logic
|
|
115
|
+
* - Adding new tables (as long as existing table schema remains unchanged)
|
|
116
|
+
* - Updating client-side implementation details
|
|
117
|
+
*
|
|
118
|
+
* Impact: Changing this version triggers a "soft reset" - new table names are created
|
|
119
|
+
* and old data becomes inaccessible (but remains in the database).
|
|
120
|
+
*
|
|
121
|
+
* Current schema (PostgreSQL):
|
|
122
|
+
* - seqNum (INTEGER PRIMARY KEY)
|
|
123
|
+
* - parentSeqNum (INTEGER)
|
|
124
|
+
* - name (TEXT NOT NULL)
|
|
125
|
+
* - args (JSONB NOT NULL)
|
|
126
|
+
* - clientId (TEXT NOT NULL)
|
|
127
|
+
* - sessionId (TEXT NOT NULL)
|
|
128
|
+
*/
|
|
129
|
+
export const PERSISTENCE_FORMAT_VERSION = 6
|