@livestore/sync-s2 0.4.0 → 0.5.0-dev.0
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 +19 -19
- package/dist/api-schema.d.ts.map +1 -1
- package/dist/api-schema.js +3 -3
- package/dist/api-schema.js.map +1 -1
- package/dist/api-schema.test.js +15 -10
- package/dist/api-schema.test.js.map +1 -1
- package/dist/decode.js +3 -3
- package/dist/decode.js.map +1 -1
- package/dist/http-client-generated.d.ts +337 -1051
- package/dist/http-client-generated.d.ts.map +1 -1
- package/dist/http-client-generated.js +223 -220
- package/dist/http-client-generated.js.map +1 -1
- package/dist/limits.d.ts +6 -8
- package/dist/limits.d.ts.map +1 -1
- package/dist/limits.js +8 -9
- package/dist/limits.js.map +1 -1
- package/dist/limits.test.js.map +1 -1
- package/dist/make-s2-url.js +4 -4
- package/dist/make-s2-url.js.map +1 -1
- package/dist/sync-provider.d.ts +3 -3
- package/dist/sync-provider.d.ts.map +1 -1
- package/dist/sync-provider.js +8 -7
- package/dist/sync-provider.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +29 -48
- package/src/api-schema.test.ts +15 -11
- package/src/api-schema.ts +3 -3
- package/src/decode.ts +3 -3
- package/src/http-client-generated.ts +166 -258
- package/src/limits.test.ts +1 -2
- package/src/limits.ts +17 -19
- package/src/make-s2-url.ts +4 -4
- package/src/sync-provider.ts +13 -12
- package/src/types.ts +1 -1
package/src/limits.test.ts
CHANGED
package/src/limits.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LiveStoreEvent } from '@livestore/common/schema'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { splitArrayBySize } from '@livestore/common/sync'
|
|
3
|
+
import { Effect, ReadonlyArray as EffectArray, Schema } from '@livestore/utils/effect'
|
|
4
4
|
|
|
5
5
|
const textEncoder = new TextEncoder()
|
|
6
6
|
|
|
@@ -19,9 +19,11 @@ export const MAX_BATCH_METERED_BYTES = 1_048_576 // 1 MiB
|
|
|
19
19
|
*/
|
|
20
20
|
export const MAX_RECORDS_PER_BATCH = 1_000
|
|
21
21
|
|
|
22
|
-
const LimitType = Schema.
|
|
22
|
+
const LimitType = Schema.Literals(['record-metered-bytes', 'batch-metered-bytes', 'batch-count'])
|
|
23
23
|
|
|
24
|
-
export class S2LimitExceededError extends Schema.TaggedError<S2LimitExceededError>(
|
|
24
|
+
export class S2LimitExceededError extends Schema.TaggedError<S2LimitExceededError>(
|
|
25
|
+
'~@livestore/sync-s2/S2LimitExceededError',
|
|
26
|
+
)('S2LimitExceededError', {
|
|
25
27
|
limitType: LimitType,
|
|
26
28
|
max: Schema.Number,
|
|
27
29
|
actual: Schema.Number,
|
|
@@ -89,9 +91,8 @@ const convertEventsToPrepared = (events: ReadonlyArray<LiveStoreEvent.Global.Enc
|
|
|
89
91
|
const makeChunkMeasure = (items: ReadonlyArray<PreparedEvent>): number =>
|
|
90
92
|
items.reduce((acc, item) => acc + item.meteredBytes, 0)
|
|
91
93
|
|
|
92
|
-
const mapPreparedChunks = (chunks:
|
|
93
|
-
|
|
94
|
-
const chunkItems = Chunk.toReadonlyArray(chunk)
|
|
94
|
+
const mapPreparedChunks = (chunks: ReadonlyArray<ReadonlyArray<PreparedEvent>>): ReadonlyArray<S2Chunk> =>
|
|
95
|
+
chunks.map((chunkItems) => {
|
|
95
96
|
const events = chunkItems.map((item) => item.event)
|
|
96
97
|
const records = chunkItems.map((item) => item.record)
|
|
97
98
|
return {
|
|
@@ -102,22 +103,19 @@ const mapPreparedChunks = (chunks: Chunk.Chunk<Chunk.Chunk<PreparedEvent>>): Rea
|
|
|
102
103
|
})
|
|
103
104
|
|
|
104
105
|
export const chunkEventsForS2 = (events: ReadonlyArray<LiveStoreEvent.Global.Encoded>): ReadonlyArray<S2Chunk> => {
|
|
105
|
-
|
|
106
|
+
const prepared = convertEventsToPrepared(events)
|
|
107
|
+
|
|
108
|
+
if (EffectArray.isReadonlyArrayNonEmpty(prepared) === false) {
|
|
106
109
|
return []
|
|
107
110
|
}
|
|
108
111
|
|
|
109
|
-
const prepared = convertEventsToPrepared(events)
|
|
110
|
-
|
|
111
112
|
try {
|
|
112
|
-
const chunks =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}),
|
|
119
|
-
Effect.runSync,
|
|
120
|
-
)
|
|
113
|
+
const chunks = splitArrayBySize<PreparedEvent>({
|
|
114
|
+
maxItems: MAX_RECORDS_PER_BATCH,
|
|
115
|
+
maxBytes: MAX_BATCH_METERED_BYTES,
|
|
116
|
+
encode: (items) => ({ records: items.map((item) => item.record) }),
|
|
117
|
+
measure: makeChunkMeasure,
|
|
118
|
+
})(prepared).pipe(Effect.runSync)
|
|
121
119
|
|
|
122
120
|
return mapPreparedChunks(chunks)
|
|
123
121
|
} catch (error) {
|
package/src/make-s2-url.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { shouldNeverHappen } from '@livestore/utils'
|
|
2
|
-
import { Schema } from '@livestore/utils/effect'
|
|
2
|
+
import { Result, Schema } from '@livestore/utils/effect'
|
|
3
3
|
|
|
4
4
|
import * as ApiSchema from './api-schema.ts'
|
|
5
5
|
|
|
@@ -10,9 +10,9 @@ export const makeS2StreamName = (storeId: string) => storeId.replace(/[^a-zA-Z0-
|
|
|
10
10
|
*/
|
|
11
11
|
export const decodePullArgsFromSearchParams = (searchParams: URLSearchParams): typeof ApiSchema.PullArgs.Type => {
|
|
12
12
|
const UrlParamsSchema = Schema.Struct({ args: ApiSchema.ArgsSchema })
|
|
13
|
-
const argsResult = Schema.
|
|
13
|
+
const argsResult = Schema.decodeUnknownResult(UrlParamsSchema)(Object.fromEntries(searchParams.entries()))
|
|
14
14
|
|
|
15
|
-
if (argsResult
|
|
15
|
+
if (Result.isFailure(argsResult) === true) {
|
|
16
16
|
return shouldNeverHappen(
|
|
17
17
|
'Invalid search params provided to decodePullArgsFromSearchParams',
|
|
18
18
|
searchParams,
|
|
@@ -20,5 +20,5 @@ export const decodePullArgsFromSearchParams = (searchParams: URLSearchParams): t
|
|
|
20
20
|
)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
return argsResult.
|
|
23
|
+
return argsResult.success.args
|
|
24
24
|
}
|
package/src/sync-provider.ts
CHANGED
|
@@ -37,16 +37,17 @@ import { shouldNeverHappen } from '@livestore/utils'
|
|
|
37
37
|
import {
|
|
38
38
|
type Duration,
|
|
39
39
|
Effect,
|
|
40
|
+
Filter,
|
|
40
41
|
HttpClient,
|
|
41
42
|
HttpClientRequest,
|
|
42
43
|
HttpClientResponse,
|
|
43
44
|
Option,
|
|
44
45
|
Schedule,
|
|
45
46
|
Schema,
|
|
46
|
-
Sse,
|
|
47
47
|
Stream,
|
|
48
48
|
SubscriptionRef,
|
|
49
49
|
} from '@livestore/utils/effect'
|
|
50
|
+
import { Sse } from 'effect/unstable/encoding'
|
|
50
51
|
|
|
51
52
|
import * as ApiSchema from './api-schema.ts'
|
|
52
53
|
import { decodeReadBatch } from './decode.ts'
|
|
@@ -66,9 +67,9 @@ export interface SyncS2Options {
|
|
|
66
67
|
/** Enable periodic ping; default true */
|
|
67
68
|
enabled?: boolean
|
|
68
69
|
/** Timeout for individual ping request; default 10s */
|
|
69
|
-
requestTimeout?: Duration.
|
|
70
|
+
requestTimeout?: Duration.Input
|
|
70
71
|
/** Interval between ping requests; default 10s */
|
|
71
|
-
requestInterval?: Duration.
|
|
72
|
+
requestInterval?: Duration.Input
|
|
72
73
|
}
|
|
73
74
|
retry?: {
|
|
74
75
|
/** Custom retry schedule for non-live pulls (default: 2 recurs, 100ms spaced) */
|
|
@@ -78,7 +79,7 @@ export interface SyncS2Options {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
export const defaultRetry = Schedule.
|
|
82
|
+
export const defaultRetry = Schedule.recurs(2).pipe(Schedule.addDelay(() => Effect.succeed(100)))
|
|
82
83
|
|
|
83
84
|
const getBrowserOrigin = () => {
|
|
84
85
|
if (typeof globalThis !== 'object' || globalThis === null || !('location' in globalThis)) {
|
|
@@ -115,7 +116,7 @@ export const makeSyncBackend =
|
|
|
115
116
|
}).pipe(
|
|
116
117
|
UnknownError.mapToUnknownError,
|
|
117
118
|
Effect.timeout(pingTimeout),
|
|
118
|
-
Effect.catchTag('
|
|
119
|
+
Effect.catchTag('TimeoutError', () => SubscriptionRef.set(isConnected, false)),
|
|
119
120
|
)
|
|
120
121
|
|
|
121
122
|
const pingInterval = pingOptions?.requestInterval ?? 10_000
|
|
@@ -149,8 +150,8 @@ export const makeSyncBackend =
|
|
|
149
150
|
.pipe(
|
|
150
151
|
HttpClientResponse.stream,
|
|
151
152
|
// decode text and split into lines
|
|
152
|
-
Stream.decodeText('utf8'),
|
|
153
|
-
Stream.pipeThroughChannel(Sse.
|
|
153
|
+
Stream.decodeText({ encoding: 'utf8' }),
|
|
154
|
+
Stream.pipeThroughChannel(Sse.decode()),
|
|
154
155
|
// Filter out pings, map errors to stream failures
|
|
155
156
|
Stream.mapEffect(
|
|
156
157
|
Effect.fnUntraced(function* (msg) {
|
|
@@ -160,7 +161,9 @@ export const makeSyncBackend =
|
|
|
160
161
|
return yield* new UnknownError({ cause: new Error(`SSE error: ${msg.data}`) })
|
|
161
162
|
}
|
|
162
163
|
if (evt === 'batch') {
|
|
163
|
-
const readBatch = yield* Schema.
|
|
164
|
+
const readBatch = yield* Schema.decodeEffect(Schema.fromJsonString(HttpClientGenerated.ReadBatch))(
|
|
165
|
+
msg.data,
|
|
166
|
+
)
|
|
164
167
|
const batch = decodeReadBatch(readBatch)
|
|
165
168
|
|
|
166
169
|
const lastS2SeqNum = batch.at(-1)?.metadata.pipe(
|
|
@@ -188,10 +191,8 @@ export const makeSyncBackend =
|
|
|
188
191
|
return shouldNeverHappen(`Unexpected SSE event: ${evt}`, msg)
|
|
189
192
|
}),
|
|
190
193
|
),
|
|
191
|
-
Stream.filterMap((_) => _), // filter out Option.none()
|
|
192
|
-
Stream.mapError((cause) =>
|
|
193
|
-
cause._tag === 'UnknownError' ? cause : new UnknownError({ cause }),
|
|
194
|
-
),
|
|
194
|
+
Stream.filterMap(Filter.fromPredicateOption((_) => _)), // filter out Option.none()
|
|
195
|
+
Stream.mapError((cause) => (cause._tag === 'UnknownError' ? cause : new UnknownError({ cause }))),
|
|
195
196
|
Stream.retry(retry?.pull ?? defaultRetry),
|
|
196
197
|
)
|
|
197
198
|
}
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { Brand, Schema } from '@livestore/utils/effect'
|
|
|
12
12
|
*/
|
|
13
13
|
export type S2SeqNum = Brand.Branded<number, 'S2SeqNum'>
|
|
14
14
|
export const s2SeqNum = Brand.nominal<S2SeqNum>()
|
|
15
|
-
export const S2SeqNum = Schema.fromBrand(s2SeqNum)(Schema.Int.
|
|
15
|
+
export const S2SeqNum = Schema.fromBrand('S2SeqNum', s2SeqNum)(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)))
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Metadata for tracking S2-specific cursor information.
|