@livestore/adapter-node 0.4.0-dev.9 → 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/client-session/adapter.d.ts +69 -3
- package/dist/client-session/adapter.d.ts.map +1 -1
- package/dist/client-session/adapter.js +160 -62
- package/dist/client-session/adapter.js.map +1 -1
- package/dist/devtools/devtools-server.d.ts.map +1 -1
- package/dist/devtools/devtools-server.js +25 -19
- package/dist/devtools/devtools-server.js.map +1 -1
- package/dist/devtools/mod.d.ts +1 -1
- package/dist/devtools/mod.d.ts.map +1 -1
- package/dist/devtools/mod.js +1 -1
- package/dist/devtools/mod.js.map +1 -1
- package/dist/devtools/vite-dev-server.d.ts +22 -4
- package/dist/devtools/vite-dev-server.d.ts.map +1 -1
- package/dist/devtools/vite-dev-server.js +45 -11
- package/dist/devtools/vite-dev-server.js.map +1 -1
- package/dist/leader-thread-shared.d.ts +8 -7
- package/dist/leader-thread-shared.d.ts.map +1 -1
- package/dist/leader-thread-shared.js +19 -16
- package/dist/leader-thread-shared.js.map +1 -1
- package/dist/make-leader-worker.d.ts +5 -3
- package/dist/make-leader-worker.d.ts.map +1 -1
- package/dist/make-leader-worker.js +75 -59
- package/dist/make-leader-worker.js.map +1 -1
- package/dist/rpc-worker.d.ts +8 -0
- package/dist/rpc-worker.d.ts.map +1 -0
- package/dist/rpc-worker.js +5 -0
- package/dist/rpc-worker.js.map +1 -0
- package/dist/shutdown-channel.d.ts.map +1 -1
- package/dist/shutdown-channel.js.map +1 -1
- package/dist/webchannel.d.ts +1 -1
- package/dist/webchannel.d.ts.map +1 -1
- package/dist/webchannel.js +17 -18
- package/dist/webchannel.js.map +1 -1
- package/dist/worker-schema.d.ts +274 -138
- package/dist/worker-schema.d.ts.map +1 -1
- package/dist/worker-schema.js +79 -45
- package/dist/worker-schema.js.map +1 -1
- package/package.json +51 -20
- package/src/client-session/adapter.ts +228 -124
- package/src/devtools/devtools-server.ts +50 -35
- package/src/devtools/mod.ts +1 -1
- package/src/devtools/vite-dev-server.ts +65 -15
- package/src/leader-thread-shared.ts +37 -34
- package/src/make-leader-worker.ts +164 -115
- package/src/rpc-worker.ts +28 -0
- package/src/shutdown-channel.ts +1 -0
- package/src/webchannel.ts +21 -28
- package/src/worker-schema.ts +132 -117
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { hostname } from 'node:os'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import type { URL } from 'node:url'
|
|
3
4
|
import * as WT from 'node:worker_threads'
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
type Adapter,
|
|
6
8
|
type BootStatus,
|
|
@@ -9,11 +11,10 @@ import {
|
|
|
9
11
|
type LockStatus,
|
|
10
12
|
type MakeSqliteDb,
|
|
11
13
|
makeClientSession,
|
|
12
|
-
type SyncError,
|
|
13
14
|
type SyncOptions,
|
|
14
|
-
|
|
15
|
+
UnknownError,
|
|
15
16
|
} from '@livestore/common'
|
|
16
|
-
import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
17
|
+
import { Eventlog, LeaderThreadCtx, streamEventsWithSyncState } from '@livestore/common/leader-thread'
|
|
17
18
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
18
19
|
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
19
20
|
import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
@@ -27,20 +28,22 @@ import {
|
|
|
27
28
|
Fiber,
|
|
28
29
|
FileSystem,
|
|
29
30
|
Layer,
|
|
30
|
-
ParseResult,
|
|
31
31
|
Queue,
|
|
32
|
+
RpcClient,
|
|
33
|
+
RpcWorker,
|
|
32
34
|
Schedule,
|
|
33
35
|
Schema,
|
|
36
|
+
Scope,
|
|
34
37
|
Stream,
|
|
38
|
+
Subscribable,
|
|
35
39
|
SubscriptionRef,
|
|
36
|
-
Worker,
|
|
37
|
-
WorkerError,
|
|
38
40
|
} from '@livestore/utils/effect'
|
|
39
41
|
import { PlatformNode } from '@livestore/utils/node'
|
|
40
42
|
import * as Webmesh from '@livestore/webmesh'
|
|
41
43
|
|
|
42
44
|
import type { TestingOverrides } from '../leader-thread-shared.ts'
|
|
43
45
|
import { makeLeaderThread } from '../leader-thread-shared.ts'
|
|
46
|
+
import { type WithoutRpcClientError, dieOnRpcClientError, dieOnRpcClientErrorStream } from '../rpc-worker.ts'
|
|
44
47
|
import { makeShutdownChannel } from '../shutdown-channel.ts'
|
|
45
48
|
import * as WorkerSchema from '../worker-schema.ts'
|
|
46
49
|
|
|
@@ -87,7 +90,43 @@ export interface NodeAdapterOptions {
|
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
|
|
90
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* Creates a single-threaded LiveStore adapter for Node.js applications.
|
|
95
|
+
*
|
|
96
|
+
* This adapter runs the leader thread (persistence and sync) in the same thread as
|
|
97
|
+
* your application. Suitable for CLI tools, scripts, and applications where simplicity
|
|
98
|
+
* is preferred over maximum performance.
|
|
99
|
+
*
|
|
100
|
+
* For production servers or performance-critical applications, consider `makeWorkerAdapter`
|
|
101
|
+
* which runs persistence/sync in a separate worker thread.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* import { makeAdapter } from '@livestore/adapter-node'
|
|
106
|
+
* import { makeWsSync } from '@livestore/sync-cf/client'
|
|
107
|
+
*
|
|
108
|
+
* const adapter = makeAdapter({
|
|
109
|
+
* storage: { type: 'fs', baseDirectory: './data' },
|
|
110
|
+
* sync: {
|
|
111
|
+
* backend: makeWsSync({ url: 'wss://api.example.com/sync' }),
|
|
112
|
+
* },
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* // With DevTools support
|
|
119
|
+
* const adapter = makeAdapter({
|
|
120
|
+
* storage: { type: 'fs', baseDirectory: './data' },
|
|
121
|
+
* devtools: {
|
|
122
|
+
* schemaPath: new URL('./schema.ts', import.meta.url),
|
|
123
|
+
* port: 4242,
|
|
124
|
+
* },
|
|
125
|
+
* })
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* @see https://livestore.dev/docs/reference/adapters/node for setup guide
|
|
129
|
+
*/
|
|
91
130
|
export const makeAdapter = ({
|
|
92
131
|
sync,
|
|
93
132
|
...options
|
|
@@ -96,7 +135,36 @@ export const makeAdapter = ({
|
|
|
96
135
|
}): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'single-threaded', sync } })
|
|
97
136
|
|
|
98
137
|
/**
|
|
99
|
-
*
|
|
138
|
+
* Creates a multi-threaded LiveStore adapter for Node.js applications.
|
|
139
|
+
*
|
|
140
|
+
* This adapter runs the leader thread (persistence, sync, and heavy SQLite operations)
|
|
141
|
+
* in a separate worker thread, keeping your main thread responsive. Recommended for
|
|
142
|
+
* production servers and performance-critical applications.
|
|
143
|
+
*
|
|
144
|
+
* You must create a worker file that calls `makeLeaderWorker()` and pass its URL
|
|
145
|
+
* to this function.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* // In your main file:
|
|
150
|
+
* import { makeWorkerAdapter } from '@livestore/adapter-node'
|
|
151
|
+
*
|
|
152
|
+
* const adapter = makeWorkerAdapter({
|
|
153
|
+
* storage: { type: 'fs', baseDirectory: './data' },
|
|
154
|
+
* workerUrl: new URL('./livestore.worker.ts', import.meta.url),
|
|
155
|
+
* })
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* // In livestore.worker.ts:
|
|
161
|
+
* import { makeLeaderWorker } from '@livestore/adapter-node/worker'
|
|
162
|
+
* import { schema } from './schema'
|
|
163
|
+
*
|
|
164
|
+
* makeLeaderWorker({ schema })
|
|
165
|
+
* ```
|
|
166
|
+
*
|
|
167
|
+
* @see https://livestore.dev/docs/reference/adapters/node for setup guide
|
|
100
168
|
*/
|
|
101
169
|
export const makeWorkerAdapter = ({
|
|
102
170
|
workerUrl,
|
|
@@ -111,7 +179,7 @@ export const makeWorkerAdapter = ({
|
|
|
111
179
|
* Extra arguments to pass to the worker which can be accessed in the worker
|
|
112
180
|
* via `getWorkerArgs()`
|
|
113
181
|
*/
|
|
114
|
-
workerExtraArgs?: Schema.
|
|
182
|
+
workerExtraArgs?: Schema.Json
|
|
115
183
|
}): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'multi-threaded', workerUrl, workerExtraArgs } })
|
|
116
184
|
|
|
117
185
|
const makeAdapterImpl = ({
|
|
@@ -132,12 +200,13 @@ const makeAdapterImpl = ({
|
|
|
132
200
|
| {
|
|
133
201
|
_tag: 'multi-threaded'
|
|
134
202
|
workerUrl: URL
|
|
135
|
-
workerExtraArgs: Schema.
|
|
203
|
+
workerExtraArgs: Schema.Json | undefined
|
|
136
204
|
}
|
|
137
205
|
}): Adapter =>
|
|
138
206
|
((adapterArgs) =>
|
|
139
207
|
Effect.gen(function* () {
|
|
140
|
-
const { storeId, devtoolsEnabled, shutdown, bootStatusQueue,
|
|
208
|
+
const { storeId, devtoolsEnabled, shutdown, bootStatusQueue, syncPayloadEncoded, syncPayloadSchema, schema } =
|
|
209
|
+
adapterArgs
|
|
141
210
|
|
|
142
211
|
yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
|
|
143
212
|
|
|
@@ -157,15 +226,15 @@ const makeAdapterImpl = ({
|
|
|
157
226
|
if (resetPersistence === true) {
|
|
158
227
|
yield* shutdownChannel
|
|
159
228
|
.send(IntentionalShutdownCause.make({ reason: 'adapter-reset' }))
|
|
160
|
-
.pipe(
|
|
229
|
+
.pipe(UnknownError.mapToUnknownError)
|
|
161
230
|
|
|
162
231
|
yield* resetNodePersistence({ storage, storeId })
|
|
163
232
|
}
|
|
164
233
|
|
|
165
234
|
yield* shutdownChannel.listen.pipe(
|
|
166
|
-
Stream.
|
|
235
|
+
Stream.mapEffect(Effect.fromResult),
|
|
167
236
|
Stream.tap((cause) =>
|
|
168
|
-
shutdown(cause._tag === '
|
|
237
|
+
shutdown(cause._tag === 'IntentionalShutdownCause' ? Exit.succeed(cause) : Exit.fail(cause)),
|
|
169
238
|
),
|
|
170
239
|
Stream.runDrain,
|
|
171
240
|
Effect.interruptible,
|
|
@@ -178,8 +247,8 @@ const makeAdapterImpl = ({
|
|
|
178
247
|
// TODO actually implement this multi-session support
|
|
179
248
|
const lockStatus = yield* SubscriptionRef.make<LockStatus>('has-lock')
|
|
180
249
|
|
|
181
|
-
const devtoolsOptions: WorkerSchema.
|
|
182
|
-
devtoolsEnabled && devtoolsOptionsInput !== undefined
|
|
250
|
+
const devtoolsOptions: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools'] =
|
|
251
|
+
devtoolsEnabled === true && devtoolsOptionsInput !== undefined
|
|
183
252
|
? {
|
|
184
253
|
enabled: true,
|
|
185
254
|
schemaPath:
|
|
@@ -204,10 +273,11 @@ const makeAdapterImpl = ({
|
|
|
204
273
|
storage,
|
|
205
274
|
...omitUndefineds({
|
|
206
275
|
syncOptions: leaderThreadInput.sync,
|
|
207
|
-
|
|
276
|
+
syncPayloadEncoded,
|
|
277
|
+
syncPayloadSchema,
|
|
208
278
|
testing,
|
|
209
279
|
}),
|
|
210
|
-
}).pipe(
|
|
280
|
+
}).pipe(UnknownError.mapToUnknownError)
|
|
211
281
|
: yield* makeWorkerLeaderThread({
|
|
212
282
|
shutdown,
|
|
213
283
|
storeId,
|
|
@@ -218,8 +288,8 @@ const makeAdapterImpl = ({
|
|
|
218
288
|
storage,
|
|
219
289
|
devtools: devtoolsOptions,
|
|
220
290
|
bootStatusQueue,
|
|
221
|
-
|
|
222
|
-
})
|
|
291
|
+
syncPayloadEncoded,
|
|
292
|
+
}).pipe(UnknownError.mapToUnknownError)
|
|
223
293
|
|
|
224
294
|
syncInMemoryDb.import(initialSnapshot)
|
|
225
295
|
syncInMemoryDb.debug.head = leaderThread.initialState.leaderHead
|
|
@@ -229,7 +299,7 @@ const makeAdapterImpl = ({
|
|
|
229
299
|
sqliteDb: syncInMemoryDb,
|
|
230
300
|
webmeshMode: 'proxy',
|
|
231
301
|
connectWebmeshNode: Effect.fnUntraced(function* ({ webmeshNode }) {
|
|
232
|
-
if (devtoolsOptions.enabled) {
|
|
302
|
+
if (devtoolsOptions.enabled === true) {
|
|
233
303
|
yield* Webmesh.connectViaWebSocket({
|
|
234
304
|
node: webmeshNode,
|
|
235
305
|
url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
|
|
@@ -244,6 +314,7 @@ const makeAdapterImpl = ({
|
|
|
244
314
|
isLeader: true,
|
|
245
315
|
// Not really applicable for node as there is no "reload the app" concept
|
|
246
316
|
registerBeforeUnload: (_onBeforeUnload) => () => {},
|
|
317
|
+
origin: undefined,
|
|
247
318
|
})
|
|
248
319
|
|
|
249
320
|
return clientSession
|
|
@@ -258,7 +329,7 @@ const resetNodePersistence = ({
|
|
|
258
329
|
}: {
|
|
259
330
|
storage: WorkerSchema.StorageType
|
|
260
331
|
storeId: string
|
|
261
|
-
}): Effect.Effect<void,
|
|
332
|
+
}): Effect.Effect<void, UnknownError, FileSystem.FileSystem> => {
|
|
262
333
|
if (storage.type !== 'fs') {
|
|
263
334
|
return Effect.void
|
|
264
335
|
}
|
|
@@ -268,13 +339,13 @@ const resetNodePersistence = ({
|
|
|
268
339
|
return Effect.gen(function* () {
|
|
269
340
|
const fs = yield* FileSystem.FileSystem
|
|
270
341
|
|
|
271
|
-
const directoryExists = yield* fs.exists(directory).pipe(
|
|
342
|
+
const directoryExists = yield* fs.exists(directory).pipe(UnknownError.mapToUnknownError)
|
|
272
343
|
|
|
273
344
|
if (directoryExists === false) {
|
|
274
345
|
return
|
|
275
346
|
}
|
|
276
347
|
|
|
277
|
-
yield* fs.remove(directory, { recursive: true }).pipe(
|
|
348
|
+
yield* fs.remove(directory, { recursive: true }).pipe(UnknownError.mapToUnknownError)
|
|
278
349
|
}).pipe(
|
|
279
350
|
Effect.retry({ schedule: Schedule.exponentialBackoff10Sec }),
|
|
280
351
|
Effect.withSpan('@livestore/adapter-node:resetPersistence', { attributes: { directory } }),
|
|
@@ -287,7 +358,8 @@ const makeLocalLeaderThread = ({
|
|
|
287
358
|
schema,
|
|
288
359
|
makeSqliteDb,
|
|
289
360
|
syncOptions,
|
|
290
|
-
|
|
361
|
+
syncPayloadEncoded,
|
|
362
|
+
syncPayloadSchema,
|
|
291
363
|
storage,
|
|
292
364
|
devtools,
|
|
293
365
|
testing,
|
|
@@ -298,29 +370,46 @@ const makeLocalLeaderThread = ({
|
|
|
298
370
|
makeSqliteDb: MakeSqliteDb
|
|
299
371
|
syncOptions: SyncOptions | undefined
|
|
300
372
|
storage: WorkerSchema.StorageType
|
|
301
|
-
|
|
302
|
-
|
|
373
|
+
syncPayloadEncoded: Schema.Json | undefined
|
|
374
|
+
syncPayloadSchema: Schema.Schema<any>
|
|
375
|
+
devtools: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools']
|
|
303
376
|
testing?: {
|
|
304
377
|
overrides?: TestingOverrides
|
|
305
378
|
}
|
|
306
379
|
}) =>
|
|
307
380
|
Effect.gen(function* () {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)
|
|
381
|
+
// The single-threaded node adapter runs the leader thread co-located in the same runtime as the
|
|
382
|
+
// client session. Unlike the worker adapters (where the leader lives on its own event loop and drains
|
|
383
|
+
// pushes independently), a co-located leader only makes progress when the surrounding fibers yield.
|
|
384
|
+
//
|
|
385
|
+
// We give the leader thread its own dedicated scope whose teardown finalizer is registered *before*
|
|
386
|
+
// `makeClientSession`. Finalizers run in reverse registration order, so on store shutdown the client
|
|
387
|
+
// session tears down first and only then is the leader thread scope closed — keeping the leader (and
|
|
388
|
+
// its SQLite connections) alive while the client-session pusher's in-flight batch is still being
|
|
389
|
+
// persisted. Building the leader into the ambient store scope instead (via `Layer.build`) interrupts
|
|
390
|
+
// the leader's push drainer concurrently with the client session, dropping the in-flight batch and
|
|
391
|
+
// causing silent data loss on commit-then-shutdown.
|
|
392
|
+
const leaderThreadScope = yield* Scope.make()
|
|
393
|
+
yield* Effect.addFinalizer((exit) => Scope.close(leaderThreadScope, exit))
|
|
394
|
+
|
|
395
|
+
const leaderThreadLayer = yield* makeLeaderThread({
|
|
396
|
+
storeId,
|
|
397
|
+
clientId,
|
|
398
|
+
schema,
|
|
399
|
+
syncOptions,
|
|
400
|
+
storage,
|
|
401
|
+
syncPayloadEncoded,
|
|
402
|
+
syncPayloadSchema,
|
|
403
|
+
devtools,
|
|
404
|
+
makeSqliteDb,
|
|
405
|
+
...omitUndefineds({ testing: testing?.overrides }),
|
|
406
|
+
}).pipe(Scope.provide(leaderThreadScope))
|
|
407
|
+
|
|
408
|
+
const layer = yield* Layer.buildWithScope(leaderThreadLayer, leaderThreadScope)
|
|
321
409
|
|
|
322
410
|
return yield* Effect.gen(function* () {
|
|
323
|
-
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } =
|
|
411
|
+
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState, networkStatus } =
|
|
412
|
+
yield* LeaderThreadCtx
|
|
324
413
|
|
|
325
414
|
const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
|
|
326
415
|
|
|
@@ -329,16 +418,30 @@ const makeLocalLeaderThread = ({
|
|
|
329
418
|
events: {
|
|
330
419
|
pull: ({ cursor }) => syncProcessor.pull({ cursor }),
|
|
331
420
|
push: (batch) =>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
421
|
+
// Mirror the worker `PushToLeader` handler which runs the leader push uninterruptibly.
|
|
422
|
+
// Together with the dedicated `leaderThreadScope` above, this guarantees an in-flight batch is
|
|
423
|
+
// fully persisted by the (still-live) leader before the co-located pusher fiber is interrupted
|
|
424
|
+
// on store shutdown.
|
|
425
|
+
syncProcessor
|
|
426
|
+
.push(batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)))
|
|
427
|
+
.pipe(Effect.uninterruptible),
|
|
428
|
+
stream: (options) =>
|
|
429
|
+
streamEventsWithSyncState({
|
|
430
|
+
dbEventlog,
|
|
431
|
+
syncState: syncProcessor.syncState,
|
|
432
|
+
options,
|
|
433
|
+
}),
|
|
434
|
+
},
|
|
435
|
+
initialState: {
|
|
436
|
+
leaderHead: initialLeaderHead,
|
|
437
|
+
migrationsReport: initialState.migrationsReport,
|
|
438
|
+
storageMode: 'persisted',
|
|
336
439
|
},
|
|
337
|
-
initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
|
|
338
440
|
export: Effect.sync(() => dbState.export()),
|
|
339
441
|
getEventlogData: Effect.sync(() => dbEventlog.export()),
|
|
340
|
-
|
|
341
|
-
sendDevtoolsMessage: (message) =>
|
|
442
|
+
syncState: syncProcessor.syncState,
|
|
443
|
+
sendDevtoolsMessage: (message) => Queue.offer(extraIncomingMessagesQueue, message),
|
|
444
|
+
networkStatus,
|
|
342
445
|
},
|
|
343
446
|
{ ...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }) },
|
|
344
447
|
)
|
|
@@ -359,139 +462,140 @@ const makeWorkerLeaderThread = ({
|
|
|
359
462
|
storage,
|
|
360
463
|
devtools,
|
|
361
464
|
bootStatusQueue,
|
|
362
|
-
|
|
465
|
+
syncPayloadEncoded,
|
|
363
466
|
testing,
|
|
364
467
|
}: {
|
|
365
|
-
shutdown: (cause: Exit.Exit<IntentionalShutdownCause,
|
|
468
|
+
shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnknownError>) => Effect.Effect<void>
|
|
366
469
|
storeId: string
|
|
367
470
|
clientId: string
|
|
368
471
|
sessionId: string
|
|
369
472
|
workerUrl: URL
|
|
370
|
-
workerExtraArgs: Schema.
|
|
473
|
+
workerExtraArgs: Schema.Json | undefined
|
|
371
474
|
storage: WorkerSchema.StorageType
|
|
372
|
-
devtools: WorkerSchema.
|
|
475
|
+
devtools: WorkerSchema.LeaderWorkerInnerInitialMessagePayload['devtools']
|
|
373
476
|
bootStatusQueue: Queue.Queue<BootStatus>
|
|
374
|
-
|
|
477
|
+
syncPayloadEncoded: Schema.Json | undefined
|
|
375
478
|
testing?: {
|
|
376
479
|
overrides?: TestingOverrides
|
|
377
480
|
}
|
|
378
481
|
}) =>
|
|
379
482
|
Effect.gen(function* () {
|
|
380
483
|
const nodeWorker = new WT.Worker(workerUrl, {
|
|
381
|
-
execArgv: process.env.DEBUG_WORKER ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
|
|
382
|
-
argv: [
|
|
383
|
-
|
|
384
|
-
const nodeWorkerLayer = yield* Layer.build(PlatformNode.NodeWorker.layer(() => nodeWorker))
|
|
385
|
-
|
|
386
|
-
const worker = yield* Worker.makePoolSerialized<typeof WorkerSchema.LeaderWorkerInnerRequest.Type>({
|
|
387
|
-
size: 1,
|
|
388
|
-
concurrency: 100,
|
|
389
|
-
initialMessage: () =>
|
|
390
|
-
new WorkerSchema.LeaderWorkerInnerInitialMessage({
|
|
484
|
+
execArgv: process.env.DEBUG_WORKER !== undefined ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
|
|
485
|
+
argv: [
|
|
486
|
+
yield* Schema.encodeEffect(WorkerSchema.WorkerArgv)({
|
|
391
487
|
storeId,
|
|
392
488
|
clientId,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
})
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
489
|
+
sessionId,
|
|
490
|
+
extraArgs: workerExtraArgs,
|
|
491
|
+
}).pipe(Effect.orDie),
|
|
492
|
+
],
|
|
493
|
+
})
|
|
494
|
+
const innerWorkerContext = yield* Layer.build(
|
|
495
|
+
RpcClient.layerProtocolWorker({ size: 1, concurrency: 100 }).pipe(
|
|
496
|
+
Layer.provide(PlatformNode.NodeWorker.layer(() => nodeWorker)),
|
|
497
|
+
Layer.provide(
|
|
498
|
+
RpcWorker.layerInitialMessage(
|
|
499
|
+
WorkerSchema.LeaderWorkerInnerInitialMessage.payloadSchema,
|
|
500
|
+
Effect.succeed({ storeId, clientId, storage, devtools, syncPayloadEncoded }),
|
|
501
|
+
),
|
|
502
|
+
),
|
|
503
|
+
),
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
const worker = yield* RpcClient.make(WorkerSchema.LeaderWorkerInnerRpcs).pipe(
|
|
507
|
+
Effect.provide(innerWorkerContext),
|
|
508
|
+
UnknownError.mapToUnknownError,
|
|
509
|
+
Effect.tapCause((cause) => shutdown(Exit.failCause(cause))),
|
|
401
510
|
Effect.withSpan('@livestore/adapter-node:adapter:setupLeaderThread'),
|
|
402
511
|
)
|
|
403
512
|
|
|
404
|
-
const runInWorker = <
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
513
|
+
const runInWorker = <A, E, R>(
|
|
514
|
+
tag: string,
|
|
515
|
+
effect: Effect.Effect<A, E, R>,
|
|
516
|
+
): Effect.Effect<A, WithoutRpcClientError<E>, R> =>
|
|
517
|
+
effect.pipe(
|
|
518
|
+
dieOnRpcClientError,
|
|
410
519
|
Effect.logWarnIfTakesLongerThan({
|
|
411
|
-
label: `@livestore/adapter-node:client-session:runInWorker:${
|
|
520
|
+
label: `@livestore/adapter-node:client-session:runInWorker:${tag}`,
|
|
412
521
|
duration: 2000,
|
|
413
522
|
}),
|
|
414
|
-
Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${
|
|
415
|
-
|
|
416
|
-
Schema.is(UnexpectedError)(cause)
|
|
417
|
-
? cause
|
|
418
|
-
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
419
|
-
? new UnexpectedError({ cause })
|
|
420
|
-
: cause,
|
|
421
|
-
),
|
|
422
|
-
Effect.catchAllDefect((cause) => new UnexpectedError({ cause })),
|
|
423
|
-
) as any
|
|
424
|
-
|
|
425
|
-
const runInWorkerStream = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
|
|
426
|
-
req: TReq,
|
|
427
|
-
): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
|
|
428
|
-
? Stream.Stream<A, UnexpectedError, R>
|
|
429
|
-
: never =>
|
|
430
|
-
worker.execute(req as any).pipe(
|
|
431
|
-
Stream.mapError((cause) =>
|
|
432
|
-
Schema.is(UnexpectedError)(cause)
|
|
433
|
-
? cause
|
|
434
|
-
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
435
|
-
? new UnexpectedError({ cause })
|
|
436
|
-
: cause,
|
|
437
|
-
),
|
|
438
|
-
Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${req._tag}`),
|
|
439
|
-
) as any
|
|
523
|
+
Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${tag}`),
|
|
524
|
+
)
|
|
440
525
|
|
|
441
|
-
const
|
|
526
|
+
const runInWorkerStream = <A, E, R>(
|
|
527
|
+
tag: string,
|
|
528
|
+
stream: Stream.Stream<A, E, R>,
|
|
529
|
+
): Stream.Stream<A, WithoutRpcClientError<E>, R> =>
|
|
530
|
+
stream.pipe(
|
|
531
|
+
dieOnRpcClientErrorStream,
|
|
532
|
+
Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${tag}`),
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
const bootStatusFiber = yield* runInWorkerStream('BootStatusStream', worker.BootStatusStream({})).pipe(
|
|
442
536
|
Stream.tap((bootStatus) => Queue.offer(bootStatusQueue, bootStatus)),
|
|
443
537
|
Stream.runDrain,
|
|
444
|
-
Effect.
|
|
538
|
+
Effect.tapCause((cause) =>
|
|
539
|
+
Cause.hasInterruptsOnly(cause) === true ? Effect.void : shutdown(Exit.failCause(cause)),
|
|
540
|
+
),
|
|
445
541
|
Effect.interruptible,
|
|
446
542
|
Effect.tapCauseLogPretty,
|
|
447
543
|
Effect.forkScoped,
|
|
448
544
|
)
|
|
449
545
|
|
|
450
|
-
yield* Queue.
|
|
546
|
+
yield* Queue.await(bootStatusQueue).pipe(
|
|
451
547
|
Effect.andThen(Fiber.interrupt(bootStatusFiber)),
|
|
452
548
|
Effect.tapCauseLogPretty,
|
|
453
549
|
Effect.forkScoped,
|
|
454
550
|
)
|
|
455
551
|
|
|
456
|
-
const initialLeaderHead = yield* runInWorker(
|
|
552
|
+
const initialLeaderHead = yield* runInWorker('GetLeaderHead', worker.GetLeaderHead({}))
|
|
457
553
|
|
|
458
|
-
const bootResult = yield* runInWorker(
|
|
459
|
-
Effect.
|
|
460
|
-
UnexpectedError.mapToUnexpectedError,
|
|
554
|
+
const bootResult = yield* runInWorker('GetRecreateSnapshot', worker.GetRecreateSnapshot({})).pipe(
|
|
555
|
+
Effect.timeoutOrDie(10_000),
|
|
461
556
|
Effect.withSpan('@livestore/adapter-node:client-session:export'),
|
|
462
557
|
)
|
|
463
558
|
|
|
464
559
|
const leaderThread = ClientSessionLeaderThreadProxy.of(
|
|
465
560
|
{
|
|
466
561
|
events: {
|
|
467
|
-
pull: ({ cursor }) =>
|
|
468
|
-
runInWorkerStream(new WorkerSchema.LeaderWorkerInnerPullStream({ cursor })).pipe(Stream.orDie),
|
|
562
|
+
pull: ({ cursor }) => runInWorkerStream('PullStream', worker.PullStream({ cursor })).pipe(Stream.orDie),
|
|
469
563
|
push: (batch) =>
|
|
470
|
-
runInWorker(
|
|
564
|
+
runInWorker('PushToLeader', worker.PushToLeader({ batch })).pipe(
|
|
471
565
|
Effect.withSpan('@livestore/adapter-node:client-session:pushToLeader', {
|
|
472
566
|
attributes: { batchSize: batch.length },
|
|
473
567
|
}),
|
|
474
568
|
),
|
|
569
|
+
stream: (options) =>
|
|
570
|
+
runInWorkerStream('StreamEvents', worker.StreamEvents(options)).pipe(
|
|
571
|
+
Stream.withSpan('@livestore/adapter-node:client-session:streamEvents'),
|
|
572
|
+
Stream.orDie,
|
|
573
|
+
),
|
|
475
574
|
},
|
|
476
575
|
initialState: {
|
|
477
576
|
leaderHead: initialLeaderHead,
|
|
478
577
|
migrationsReport: bootResult.migrationsReport,
|
|
578
|
+
storageMode: 'persisted',
|
|
479
579
|
},
|
|
480
|
-
export: runInWorker(
|
|
481
|
-
Effect.
|
|
482
|
-
UnexpectedError.mapToUnexpectedError,
|
|
580
|
+
export: runInWorker('Export', worker.Export({})).pipe(
|
|
581
|
+
Effect.timeoutOrDie(10_000),
|
|
483
582
|
Effect.withSpan('@livestore/adapter-node:client-session:export'),
|
|
484
583
|
),
|
|
485
|
-
getEventlogData: Effect.
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
584
|
+
getEventlogData: Effect.die('Not implemented'),
|
|
585
|
+
syncState: Subscribable.make({
|
|
586
|
+
get: runInWorker('GetLeaderSyncState', worker.GetLeaderSyncState({})).pipe(
|
|
587
|
+
Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
|
|
588
|
+
),
|
|
589
|
+
changes: runInWorkerStream('SyncStateStream', worker.SyncStateStream({})).pipe(Stream.orDie),
|
|
590
|
+
}),
|
|
490
591
|
sendDevtoolsMessage: (message) =>
|
|
491
|
-
runInWorker(
|
|
492
|
-
UnexpectedError.mapToUnexpectedError,
|
|
592
|
+
runInWorker('ExtraDevtoolsMessage', worker.ExtraDevtoolsMessage({ message })).pipe(
|
|
493
593
|
Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
|
|
494
594
|
),
|
|
595
|
+
networkStatus: Subscribable.make({
|
|
596
|
+
get: runInWorker('GetNetworkStatus', worker.GetNetworkStatus({})).pipe(Effect.orDie),
|
|
597
|
+
changes: runInWorkerStream('NetworkStatusStream', worker.NetworkStatusStream({})).pipe(Stream.orDie),
|
|
598
|
+
}),
|
|
495
599
|
},
|
|
496
600
|
{
|
|
497
601
|
...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }),
|