@livestore/adapter-node 0.4.0-dev.2 → 0.4.0-dev.20
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 +13 -1
- package/dist/client-session/adapter.d.ts.map +1 -1
- package/dist/client-session/adapter.js +68 -32
- package/dist/client-session/adapter.js.map +1 -1
- package/dist/devtools/vite-dev-server.d.ts +2 -2
- package/dist/devtools/vite-dev-server.d.ts.map +1 -1
- package/dist/devtools/vite-dev-server.js +3 -3
- package/dist/devtools/vite-dev-server.js.map +1 -1
- package/dist/leader-thread-shared.d.ts +5 -4
- package/dist/leader-thread-shared.d.ts.map +1 -1
- package/dist/leader-thread-shared.js +6 -4
- package/dist/leader-thread-shared.js.map +1 -1
- package/dist/make-leader-worker.d.ts +5 -2
- package/dist/make-leader-worker.d.ts.map +1 -1
- package/dist/make-leader-worker.js +32 -14
- package/dist/make-leader-worker.js.map +1 -1
- package/dist/worker-schema.d.ts +94 -43
- package/dist/worker-schema.d.ts.map +1 -1
- package/dist/worker-schema.js +37 -18
- package/dist/worker-schema.js.map +1 -1
- package/package.json +8 -8
- package/src/client-session/adapter.ts +116 -40
- package/src/devtools/vite-dev-server.ts +4 -6
- package/src/leader-thread-shared.ts +13 -9
- package/src/make-leader-worker.ts +43 -36
- package/src/worker-schema.ts +57 -18
|
@@ -1,33 +1,38 @@
|
|
|
1
1
|
import { hostname } from 'node:os'
|
|
2
|
+
import path from 'node:path'
|
|
2
3
|
import * as WT from 'node:worker_threads'
|
|
3
4
|
import {
|
|
4
5
|
type Adapter,
|
|
5
6
|
type BootStatus,
|
|
6
7
|
ClientSessionLeaderThreadProxy,
|
|
7
|
-
|
|
8
|
+
IntentionalShutdownCause,
|
|
8
9
|
type LockStatus,
|
|
9
10
|
type MakeSqliteDb,
|
|
10
11
|
makeClientSession,
|
|
11
12
|
type SyncError,
|
|
12
13
|
type SyncOptions,
|
|
13
|
-
|
|
14
|
+
UnknownError,
|
|
14
15
|
} from '@livestore/common'
|
|
15
16
|
import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
16
17
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
17
18
|
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
18
19
|
import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
19
20
|
import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
|
|
21
|
+
import { omitUndefineds } from '@livestore/utils'
|
|
20
22
|
import {
|
|
21
23
|
Cause,
|
|
22
24
|
Effect,
|
|
23
25
|
Exit,
|
|
24
26
|
FetchHttpClient,
|
|
25
27
|
Fiber,
|
|
28
|
+
FileSystem,
|
|
26
29
|
Layer,
|
|
27
30
|
ParseResult,
|
|
28
31
|
Queue,
|
|
32
|
+
Schedule,
|
|
29
33
|
Schema,
|
|
30
34
|
Stream,
|
|
35
|
+
Subscribable,
|
|
31
36
|
SubscriptionRef,
|
|
32
37
|
Worker,
|
|
33
38
|
WorkerError,
|
|
@@ -50,6 +55,13 @@ export interface NodeAdapterOptions {
|
|
|
50
55
|
*/
|
|
51
56
|
sessionId?: string
|
|
52
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Warning: This will reset both the app and eventlog database. This should only be used during development.
|
|
60
|
+
*
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
resetPersistence?: boolean
|
|
64
|
+
|
|
53
65
|
devtools?: {
|
|
54
66
|
schemaPath: string | URL
|
|
55
67
|
/**
|
|
@@ -89,13 +101,19 @@ export const makeAdapter = ({
|
|
|
89
101
|
*/
|
|
90
102
|
export const makeWorkerAdapter = ({
|
|
91
103
|
workerUrl,
|
|
104
|
+
workerExtraArgs,
|
|
92
105
|
...options
|
|
93
106
|
}: NodeAdapterOptions & {
|
|
94
107
|
/**
|
|
95
108
|
* Example: `new URL('./livestore.worker.ts', import.meta.url)`
|
|
96
109
|
*/
|
|
97
110
|
workerUrl: URL
|
|
98
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Extra arguments to pass to the worker which can be accessed in the worker
|
|
113
|
+
* via `getWorkerArgs()`
|
|
114
|
+
*/
|
|
115
|
+
workerExtraArgs?: Schema.JsonValue
|
|
116
|
+
}): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'multi-threaded', workerUrl, workerExtraArgs } })
|
|
99
117
|
|
|
100
118
|
const makeAdapterImpl = ({
|
|
101
119
|
storage,
|
|
@@ -104,6 +122,7 @@ const makeAdapterImpl = ({
|
|
|
104
122
|
// TODO make this dynamic and actually support multiple sessions
|
|
105
123
|
sessionId = 'static',
|
|
106
124
|
testing,
|
|
125
|
+
resetPersistence = false,
|
|
107
126
|
leaderThread: leaderThreadInput,
|
|
108
127
|
}: NodeAdapterOptions & {
|
|
109
128
|
leaderThread:
|
|
@@ -114,11 +133,13 @@ const makeAdapterImpl = ({
|
|
|
114
133
|
| {
|
|
115
134
|
_tag: 'multi-threaded'
|
|
116
135
|
workerUrl: URL
|
|
136
|
+
workerExtraArgs: Schema.JsonValue | undefined
|
|
117
137
|
}
|
|
118
138
|
}): Adapter =>
|
|
119
139
|
((adapterArgs) =>
|
|
120
140
|
Effect.gen(function* () {
|
|
121
|
-
const { storeId, devtoolsEnabled, shutdown, bootStatusQueue,
|
|
141
|
+
const { storeId, devtoolsEnabled, shutdown, bootStatusQueue, syncPayloadEncoded, syncPayloadSchema, schema } =
|
|
142
|
+
adapterArgs
|
|
122
143
|
|
|
123
144
|
yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
|
|
124
145
|
|
|
@@ -135,6 +156,14 @@ const makeAdapterImpl = ({
|
|
|
135
156
|
|
|
136
157
|
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
137
158
|
|
|
159
|
+
if (resetPersistence === true) {
|
|
160
|
+
yield* shutdownChannel
|
|
161
|
+
.send(IntentionalShutdownCause.make({ reason: 'adapter-reset' }))
|
|
162
|
+
.pipe(UnknownError.mapToUnknownError)
|
|
163
|
+
|
|
164
|
+
yield* resetNodePersistence({ storage, storeId })
|
|
165
|
+
}
|
|
166
|
+
|
|
138
167
|
yield* shutdownChannel.listen.pipe(
|
|
139
168
|
Stream.flatten(),
|
|
140
169
|
Stream.tap((cause) =>
|
|
@@ -173,22 +202,26 @@ const makeAdapterImpl = ({
|
|
|
173
202
|
clientId,
|
|
174
203
|
schema,
|
|
175
204
|
makeSqliteDb,
|
|
176
|
-
syncOptions: leaderThreadInput.sync,
|
|
177
|
-
syncPayload,
|
|
178
205
|
devtools: devtoolsOptions,
|
|
179
206
|
storage,
|
|
180
|
-
|
|
181
|
-
|
|
207
|
+
...omitUndefineds({
|
|
208
|
+
syncOptions: leaderThreadInput.sync,
|
|
209
|
+
syncPayloadEncoded,
|
|
210
|
+
syncPayloadSchema,
|
|
211
|
+
testing,
|
|
212
|
+
}),
|
|
213
|
+
}).pipe(UnknownError.mapToUnknownError)
|
|
182
214
|
: yield* makeWorkerLeaderThread({
|
|
183
215
|
shutdown,
|
|
184
216
|
storeId,
|
|
185
217
|
clientId,
|
|
186
218
|
sessionId,
|
|
187
219
|
workerUrl: leaderThreadInput.workerUrl,
|
|
220
|
+
workerExtraArgs: leaderThreadInput.workerExtraArgs,
|
|
188
221
|
storage,
|
|
189
222
|
devtools: devtoolsOptions,
|
|
190
223
|
bootStatusQueue,
|
|
191
|
-
|
|
224
|
+
syncPayloadEncoded,
|
|
192
225
|
})
|
|
193
226
|
|
|
194
227
|
syncInMemoryDb.import(initialSnapshot)
|
|
@@ -214,22 +247,52 @@ const makeAdapterImpl = ({
|
|
|
214
247
|
isLeader: true,
|
|
215
248
|
// Not really applicable for node as there is no "reload the app" concept
|
|
216
249
|
registerBeforeUnload: (_onBeforeUnload) => () => {},
|
|
250
|
+
origin: undefined,
|
|
217
251
|
})
|
|
218
252
|
|
|
219
253
|
return clientSession
|
|
220
254
|
}).pipe(
|
|
221
255
|
Effect.withSpan('@livestore/adapter-node:adapter'),
|
|
222
|
-
Effect.provide(PlatformNode.NodeFileSystem.layer),
|
|
223
|
-
Effect.provide(FetchHttpClient.layer),
|
|
256
|
+
Effect.provide(Layer.mergeAll(PlatformNode.NodeFileSystem.layer, FetchHttpClient.layer)),
|
|
224
257
|
)) satisfies Adapter
|
|
225
258
|
|
|
259
|
+
const resetNodePersistence = ({
|
|
260
|
+
storage,
|
|
261
|
+
storeId,
|
|
262
|
+
}: {
|
|
263
|
+
storage: WorkerSchema.StorageType
|
|
264
|
+
storeId: string
|
|
265
|
+
}): Effect.Effect<void, UnknownError, FileSystem.FileSystem> => {
|
|
266
|
+
if (storage.type !== 'fs') {
|
|
267
|
+
return Effect.void
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const directory = path.join(storage.baseDirectory ?? '', storeId)
|
|
271
|
+
|
|
272
|
+
return Effect.gen(function* () {
|
|
273
|
+
const fs = yield* FileSystem.FileSystem
|
|
274
|
+
|
|
275
|
+
const directoryExists = yield* fs.exists(directory).pipe(UnknownError.mapToUnknownError)
|
|
276
|
+
|
|
277
|
+
if (directoryExists === false) {
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
yield* fs.remove(directory, { recursive: true }).pipe(UnknownError.mapToUnknownError)
|
|
282
|
+
}).pipe(
|
|
283
|
+
Effect.retry({ schedule: Schedule.exponentialBackoff10Sec }),
|
|
284
|
+
Effect.withSpan('@livestore/adapter-node:resetPersistence', { attributes: { directory } }),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
226
288
|
const makeLocalLeaderThread = ({
|
|
227
289
|
storeId,
|
|
228
290
|
clientId,
|
|
229
291
|
schema,
|
|
230
292
|
makeSqliteDb,
|
|
231
293
|
syncOptions,
|
|
232
|
-
|
|
294
|
+
syncPayloadEncoded,
|
|
295
|
+
syncPayloadSchema,
|
|
233
296
|
storage,
|
|
234
297
|
devtools,
|
|
235
298
|
testing,
|
|
@@ -240,7 +303,8 @@ const makeLocalLeaderThread = ({
|
|
|
240
303
|
makeSqliteDb: MakeSqliteDb
|
|
241
304
|
syncOptions: SyncOptions | undefined
|
|
242
305
|
storage: WorkerSchema.StorageType
|
|
243
|
-
|
|
306
|
+
syncPayloadEncoded: Schema.JsonValue | undefined
|
|
307
|
+
syncPayloadSchema: Schema.Schema<any>
|
|
244
308
|
devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
|
|
245
309
|
testing?: {
|
|
246
310
|
overrides?: TestingOverrides
|
|
@@ -254,15 +318,17 @@ const makeLocalLeaderThread = ({
|
|
|
254
318
|
schema,
|
|
255
319
|
syncOptions,
|
|
256
320
|
storage,
|
|
257
|
-
|
|
321
|
+
syncPayloadEncoded,
|
|
322
|
+
syncPayloadSchema,
|
|
258
323
|
devtools,
|
|
259
324
|
makeSqliteDb,
|
|
260
|
-
testing: testing?.overrides,
|
|
325
|
+
...omitUndefineds({ testing: testing?.overrides }),
|
|
261
326
|
}).pipe(Layer.unwrapScoped),
|
|
262
327
|
)
|
|
263
328
|
|
|
264
329
|
return yield* Effect.gen(function* () {
|
|
265
|
-
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } =
|
|
330
|
+
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState, networkStatus } =
|
|
331
|
+
yield* LeaderThreadCtx
|
|
266
332
|
|
|
267
333
|
const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
|
|
268
334
|
|
|
@@ -272,17 +338,18 @@ const makeLocalLeaderThread = ({
|
|
|
272
338
|
pull: ({ cursor }) => syncProcessor.pull({ cursor }),
|
|
273
339
|
push: (batch) =>
|
|
274
340
|
syncProcessor.push(
|
|
275
|
-
batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
|
|
341
|
+
batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)),
|
|
276
342
|
{ waitForProcessing: true },
|
|
277
343
|
),
|
|
278
344
|
},
|
|
279
345
|
initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
|
|
280
346
|
export: Effect.sync(() => dbState.export()),
|
|
281
347
|
getEventlogData: Effect.sync(() => dbEventlog.export()),
|
|
282
|
-
|
|
348
|
+
syncState: syncProcessor.syncState,
|
|
283
349
|
sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
|
|
350
|
+
networkStatus,
|
|
284
351
|
},
|
|
285
|
-
{ overrides: testing?.overrides?.clientSession?.leaderThreadProxy },
|
|
352
|
+
{ ...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }) },
|
|
286
353
|
)
|
|
287
354
|
|
|
288
355
|
const initialSnapshot = dbState.export()
|
|
@@ -297,21 +364,23 @@ const makeWorkerLeaderThread = ({
|
|
|
297
364
|
clientId,
|
|
298
365
|
sessionId,
|
|
299
366
|
workerUrl,
|
|
367
|
+
workerExtraArgs,
|
|
300
368
|
storage,
|
|
301
369
|
devtools,
|
|
302
370
|
bootStatusQueue,
|
|
303
|
-
|
|
371
|
+
syncPayloadEncoded,
|
|
304
372
|
testing,
|
|
305
373
|
}: {
|
|
306
|
-
shutdown: (cause: Exit.Exit<IntentionalShutdownCause,
|
|
374
|
+
shutdown: (cause: Exit.Exit<IntentionalShutdownCause, UnknownError | SyncError>) => Effect.Effect<void>
|
|
307
375
|
storeId: string
|
|
308
376
|
clientId: string
|
|
309
377
|
sessionId: string
|
|
310
378
|
workerUrl: URL
|
|
379
|
+
workerExtraArgs: Schema.JsonValue | undefined
|
|
311
380
|
storage: WorkerSchema.StorageType
|
|
312
381
|
devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
|
|
313
382
|
bootStatusQueue: Queue.Queue<BootStatus>
|
|
314
|
-
|
|
383
|
+
syncPayloadEncoded: Schema.JsonValue | undefined
|
|
315
384
|
testing?: {
|
|
316
385
|
overrides?: TestingOverrides
|
|
317
386
|
}
|
|
@@ -319,7 +388,7 @@ const makeWorkerLeaderThread = ({
|
|
|
319
388
|
Effect.gen(function* () {
|
|
320
389
|
const nodeWorker = new WT.Worker(workerUrl, {
|
|
321
390
|
execArgv: process.env.DEBUG_WORKER ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
|
|
322
|
-
argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId })],
|
|
391
|
+
argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId, extraArgs: workerExtraArgs })],
|
|
323
392
|
})
|
|
324
393
|
const nodeWorkerLayer = yield* Layer.build(PlatformNode.NodeWorker.layer(() => nodeWorker))
|
|
325
394
|
|
|
@@ -332,11 +401,11 @@ const makeWorkerLeaderThread = ({
|
|
|
332
401
|
clientId,
|
|
333
402
|
storage,
|
|
334
403
|
devtools,
|
|
335
|
-
|
|
404
|
+
syncPayloadEncoded,
|
|
336
405
|
}),
|
|
337
406
|
}).pipe(
|
|
338
407
|
Effect.provide(nodeWorkerLayer),
|
|
339
|
-
|
|
408
|
+
UnknownError.mapToUnknownError,
|
|
340
409
|
Effect.tapErrorCause((cause) => shutdown(Exit.failCause(cause))),
|
|
341
410
|
Effect.withSpan('@livestore/adapter-node:adapter:setupLeaderThread'),
|
|
342
411
|
)
|
|
@@ -344,7 +413,7 @@ const makeWorkerLeaderThread = ({
|
|
|
344
413
|
const runInWorker = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
|
|
345
414
|
req: TReq,
|
|
346
415
|
): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
|
|
347
|
-
? Effect.Effect<A,
|
|
416
|
+
? Effect.Effect<A, UnknownError, R>
|
|
348
417
|
: never =>
|
|
349
418
|
(worker.executeEffect(req) as any).pipe(
|
|
350
419
|
Effect.logWarnIfTakesLongerThan({
|
|
@@ -353,26 +422,26 @@ const makeWorkerLeaderThread = ({
|
|
|
353
422
|
}),
|
|
354
423
|
Effect.withSpan(`@livestore/adapter-node:client-session:runInWorker:${req._tag}`),
|
|
355
424
|
Effect.mapError((cause) =>
|
|
356
|
-
Schema.is(
|
|
425
|
+
Schema.is(UnknownError)(cause)
|
|
357
426
|
? cause
|
|
358
427
|
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
359
|
-
? new
|
|
428
|
+
? new UnknownError({ cause })
|
|
360
429
|
: cause,
|
|
361
430
|
),
|
|
362
|
-
Effect.catchAllDefect((cause) => new
|
|
431
|
+
Effect.catchAllDefect((cause) => new UnknownError({ cause })),
|
|
363
432
|
) as any
|
|
364
433
|
|
|
365
434
|
const runInWorkerStream = <TReq extends typeof WorkerSchema.LeaderWorkerInnerRequest.Type>(
|
|
366
435
|
req: TReq,
|
|
367
436
|
): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
|
|
368
|
-
? Stream.Stream<A,
|
|
437
|
+
? Stream.Stream<A, UnknownError, R>
|
|
369
438
|
: never =>
|
|
370
439
|
worker.execute(req as any).pipe(
|
|
371
440
|
Stream.mapError((cause) =>
|
|
372
|
-
Schema.is(
|
|
441
|
+
Schema.is(UnknownError)(cause)
|
|
373
442
|
? cause
|
|
374
443
|
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
375
|
-
? new
|
|
444
|
+
? new UnknownError({ cause })
|
|
376
445
|
: cause,
|
|
377
446
|
),
|
|
378
447
|
Stream.withSpan(`@livestore/adapter-node:client-session:runInWorkerStream:${req._tag}`),
|
|
@@ -397,7 +466,7 @@ const makeWorkerLeaderThread = ({
|
|
|
397
466
|
|
|
398
467
|
const bootResult = yield* runInWorker(new WorkerSchema.LeaderWorkerInnerGetRecreateSnapshot()).pipe(
|
|
399
468
|
Effect.timeout(10_000),
|
|
400
|
-
|
|
469
|
+
UnknownError.mapToUnknownError,
|
|
401
470
|
Effect.withSpan('@livestore/adapter-node:client-session:export'),
|
|
402
471
|
)
|
|
403
472
|
|
|
@@ -419,22 +488,29 @@ const makeWorkerLeaderThread = ({
|
|
|
419
488
|
},
|
|
420
489
|
export: runInWorker(new WorkerSchema.LeaderWorkerInnerExport()).pipe(
|
|
421
490
|
Effect.timeout(10_000),
|
|
422
|
-
|
|
491
|
+
UnknownError.mapToUnknownError,
|
|
423
492
|
Effect.withSpan('@livestore/adapter-node:client-session:export'),
|
|
424
493
|
),
|
|
425
494
|
getEventlogData: Effect.dieMessage('Not implemented'),
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
495
|
+
syncState: Subscribable.make({
|
|
496
|
+
get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
|
|
497
|
+
UnknownError.mapToUnknownError,
|
|
498
|
+
Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
|
|
499
|
+
),
|
|
500
|
+
changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerSyncStateStream()).pipe(Stream.orDie),
|
|
501
|
+
}),
|
|
430
502
|
sendDevtoolsMessage: (message) =>
|
|
431
503
|
runInWorker(new WorkerSchema.LeaderWorkerInnerExtraDevtoolsMessage({ message })).pipe(
|
|
432
|
-
|
|
504
|
+
UnknownError.mapToUnknownError,
|
|
433
505
|
Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
|
|
434
506
|
),
|
|
507
|
+
networkStatus: Subscribable.make({
|
|
508
|
+
get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetNetworkStatus()).pipe(Effect.orDie),
|
|
509
|
+
changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerNetworkStatusStream()).pipe(Stream.orDie),
|
|
510
|
+
}),
|
|
435
511
|
},
|
|
436
512
|
{
|
|
437
|
-
overrides: testing?.overrides?.clientSession?.leaderThreadProxy,
|
|
513
|
+
...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }),
|
|
438
514
|
},
|
|
439
515
|
)
|
|
440
516
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
|
|
3
3
|
import type { Devtools } from '@livestore/common'
|
|
4
|
-
import {
|
|
4
|
+
import { UnknownError } from '@livestore/common'
|
|
5
5
|
import { livestoreDevtoolsPlugin } from '@livestore/devtools-vite'
|
|
6
6
|
import { isReadonlyArray } from '@livestore/utils'
|
|
7
7
|
import { Effect } from '@livestore/utils/effect'
|
|
@@ -26,11 +26,11 @@ export type ViteDevtoolsOptions = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// NOTE this is currently also used in @livestore/devtools-expo
|
|
29
|
-
export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<Vite.ViteDevServer,
|
|
29
|
+
export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<Vite.ViteDevServer, UnknownError> =>
|
|
30
30
|
Effect.gen(function* () {
|
|
31
31
|
const cwd = process.cwd()
|
|
32
32
|
|
|
33
|
-
const hmrPort = yield* getFreePort.pipe(
|
|
33
|
+
const hmrPort = yield* getFreePort.pipe(UnknownError.mapToUnknownError)
|
|
34
34
|
|
|
35
35
|
const defaultViteConfig = Vite.defineConfig({
|
|
36
36
|
server: {
|
|
@@ -58,9 +58,7 @@ export const makeViteMiddleware = (options: ViteDevtoolsOptions): Effect.Effect<
|
|
|
58
58
|
|
|
59
59
|
const viteConfig = options.viteConfig?.(defaultViteConfig) ?? defaultViteConfig
|
|
60
60
|
|
|
61
|
-
const viteServer = yield* Effect.promise(() => Vite.createServer(viteConfig)).pipe(
|
|
62
|
-
UnexpectedError.mapToUnexpectedError,
|
|
63
|
-
)
|
|
61
|
+
const viteServer = yield* Effect.promise(() => Vite.createServer(viteConfig)).pipe(UnknownError.mapToUnknownError)
|
|
64
62
|
|
|
65
63
|
return viteServer
|
|
66
64
|
}).pipe(Effect.withSpan('@livestore/adapter-node:devtools:makeViteServer'))
|
|
@@ -7,7 +7,7 @@ if (process.execArgv.includes('--inspect')) {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
import type { ClientSessionLeaderThreadProxy, MakeSqliteDb, SqliteDb, SyncOptions } from '@livestore/common'
|
|
10
|
-
import { Devtools, liveStoreStorageFormatVersion, migrateDb,
|
|
10
|
+
import { Devtools, liveStoreStorageFormatVersion, migrateDb, UnknownError } from '@livestore/common'
|
|
11
11
|
import type { DevtoolsOptions, LeaderSqliteDb, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
12
12
|
import { configureConnection, makeLeaderThreadLayer } from '@livestore/common/leader-thread'
|
|
13
13
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
@@ -30,7 +30,7 @@ export type TestingOverrides = {
|
|
|
30
30
|
dbEventlog: SqliteDb
|
|
31
31
|
dbState: SqliteDb
|
|
32
32
|
},
|
|
33
|
-
|
|
33
|
+
UnknownError
|
|
34
34
|
>
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -42,7 +42,8 @@ export interface MakeLeaderThreadArgs {
|
|
|
42
42
|
makeSqliteDb: MakeNodeSqliteDb
|
|
43
43
|
devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
|
|
44
44
|
schema: LiveStoreSchema
|
|
45
|
-
|
|
45
|
+
syncPayloadEncoded: Schema.JsonValue | undefined
|
|
46
|
+
syncPayloadSchema: Schema.Schema<any> | undefined
|
|
46
47
|
testing: TestingOverrides | undefined
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -54,11 +55,12 @@ export const makeLeaderThread = ({
|
|
|
54
55
|
storage,
|
|
55
56
|
devtools,
|
|
56
57
|
schema,
|
|
57
|
-
|
|
58
|
+
syncPayloadEncoded,
|
|
59
|
+
syncPayloadSchema,
|
|
58
60
|
testing,
|
|
59
61
|
}: MakeLeaderThreadArgs): Effect.Effect<
|
|
60
|
-
Layer.Layer<LeaderThreadCtx,
|
|
61
|
-
|
|
62
|
+
Layer.Layer<LeaderThreadCtx, UnknownError, Scope.Scope | HttpClient.HttpClient | FileSystem.FileSystem>,
|
|
63
|
+
UnknownError,
|
|
62
64
|
Scope.Scope
|
|
63
65
|
> =>
|
|
64
66
|
Effect.gen(function* () {
|
|
@@ -113,11 +115,12 @@ export const makeLeaderThread = ({
|
|
|
113
115
|
dbEventlog,
|
|
114
116
|
devtoolsOptions,
|
|
115
117
|
shutdownChannel,
|
|
116
|
-
|
|
118
|
+
syncPayloadEncoded,
|
|
119
|
+
syncPayloadSchema,
|
|
117
120
|
})
|
|
118
121
|
}).pipe(
|
|
119
122
|
Effect.tapCauseLogPretty,
|
|
120
|
-
|
|
123
|
+
UnknownError.mapToUnknownError,
|
|
121
124
|
Effect.withSpan('@livestore/adapter-node:makeLeaderThread', {
|
|
122
125
|
attributes: { storeId, clientId, storage, devtools, syncOptions },
|
|
123
126
|
}),
|
|
@@ -137,7 +140,7 @@ const makeDevtoolsOptions = ({
|
|
|
137
140
|
storeId: string
|
|
138
141
|
clientId: string
|
|
139
142
|
devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
|
|
140
|
-
}): Effect.Effect<DevtoolsOptions,
|
|
143
|
+
}): Effect.Effect<DevtoolsOptions, UnknownError, Scope.Scope> =>
|
|
141
144
|
Effect.gen(function* () {
|
|
142
145
|
if (devtools.enabled === false) {
|
|
143
146
|
return {
|
|
@@ -161,6 +164,7 @@ const makeDevtoolsOptions = ({
|
|
|
161
164
|
sessionId: 'static', // TODO make this dynamic
|
|
162
165
|
schemaAlias: devtools.schemaAlias,
|
|
163
166
|
isLeader: true,
|
|
167
|
+
origin: undefined,
|
|
164
168
|
}),
|
|
165
169
|
port: devtools.port,
|
|
166
170
|
host: devtools.host,
|
|
@@ -8,24 +8,13 @@ if (process.execArgv.includes('--inspect')) {
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
import type { SyncOptions } from '@livestore/common'
|
|
11
|
-
import {
|
|
11
|
+
import { LogConfig, UnknownError } from '@livestore/common'
|
|
12
12
|
import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
13
13
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
14
14
|
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
15
15
|
import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
16
16
|
import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
|
|
17
|
-
import {
|
|
18
|
-
Effect,
|
|
19
|
-
FetchHttpClient,
|
|
20
|
-
identity,
|
|
21
|
-
Layer,
|
|
22
|
-
Logger,
|
|
23
|
-
LogLevel,
|
|
24
|
-
OtelTracer,
|
|
25
|
-
Schema,
|
|
26
|
-
Stream,
|
|
27
|
-
WorkerRunner,
|
|
28
|
-
} from '@livestore/utils/effect'
|
|
17
|
+
import { Effect, FetchHttpClient, Layer, OtelTracer, Schema, Stream, WorkerRunner } from '@livestore/utils/effect'
|
|
29
18
|
import { PlatformNode } from '@livestore/utils/node'
|
|
30
19
|
import type * as otel from '@opentelemetry/api'
|
|
31
20
|
|
|
@@ -36,21 +25,19 @@ import * as WorkerSchema from './worker-schema.ts'
|
|
|
36
25
|
export type WorkerOptions = {
|
|
37
26
|
schema: LiveStoreSchema
|
|
38
27
|
sync?: SyncOptions
|
|
28
|
+
syncPayloadSchema?: Schema.Schema<any>
|
|
39
29
|
otelOptions?: {
|
|
40
30
|
tracer?: otel.Tracer
|
|
41
31
|
/** @default 'livestore-node-leader-thread' */
|
|
42
32
|
serviceName?: string
|
|
43
33
|
}
|
|
44
34
|
testing?: TestingOverrides
|
|
45
|
-
}
|
|
35
|
+
} & LogConfig.WithLoggerOptions
|
|
46
36
|
|
|
47
37
|
export const getWorkerArgs = () => Schema.decodeSync(WorkerSchema.WorkerArgv)(process.argv[2]!)
|
|
48
38
|
|
|
49
39
|
export const makeWorker = (options: WorkerOptions) => {
|
|
50
|
-
makeWorkerEffect(options).pipe(
|
|
51
|
-
Effect.provide(Logger.prettyWithThread(options.otelOptions?.serviceName ?? 'livestore-node-leader-thread')),
|
|
52
|
-
PlatformNode.NodeRuntime.runMain,
|
|
53
|
-
)
|
|
40
|
+
makeWorkerEffect(options).pipe(PlatformNode.NodeRuntime.runMain)
|
|
54
41
|
}
|
|
55
42
|
|
|
56
43
|
export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
@@ -60,6 +47,13 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
60
47
|
)
|
|
61
48
|
: undefined
|
|
62
49
|
|
|
50
|
+
// Merge the runtime dependencies once so we can provide them together without chaining Effect.provide.
|
|
51
|
+
const runtimeLayer = Layer.mergeAll(
|
|
52
|
+
FetchHttpClient.layer,
|
|
53
|
+
PlatformNode.NodeFileSystem.layer,
|
|
54
|
+
TracingLive ?? Layer.empty,
|
|
55
|
+
)
|
|
56
|
+
|
|
63
57
|
return WorkerRunner.layerSerialized(WorkerSchema.LeaderWorkerInnerRequest, {
|
|
64
58
|
InitialMessage: (args) =>
|
|
65
59
|
Effect.gen(function* () {
|
|
@@ -73,12 +67,14 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
73
67
|
schema: options.schema,
|
|
74
68
|
testing: options.testing,
|
|
75
69
|
makeSqliteDb,
|
|
70
|
+
syncPayloadEncoded: args.syncPayloadEncoded,
|
|
71
|
+
syncPayloadSchema: options.syncPayloadSchema,
|
|
76
72
|
})
|
|
77
73
|
}).pipe(Layer.unwrapScoped),
|
|
78
74
|
PushToLeader: ({ batch }) =>
|
|
79
75
|
Effect.andThen(LeaderThreadCtx, (_) =>
|
|
80
76
|
_.syncProcessor.push(
|
|
81
|
-
batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
|
|
77
|
+
batch.map((item) => new LiveStoreEvent.Client.EncodedWithMeta(item)),
|
|
82
78
|
// We'll wait in order to keep back pressure on the client session
|
|
83
79
|
{ waitForProcessing: true },
|
|
84
80
|
),
|
|
@@ -92,27 +88,39 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
92
88
|
}).pipe(Stream.unwrapScoped),
|
|
93
89
|
Export: () =>
|
|
94
90
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
|
|
95
|
-
|
|
91
|
+
UnknownError.mapToUnknownError,
|
|
96
92
|
Effect.withSpan('@livestore/adapter-node:worker:Export'),
|
|
97
93
|
),
|
|
98
94
|
ExportEventlog: () =>
|
|
99
95
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
|
|
100
|
-
|
|
96
|
+
UnknownError.mapToUnknownError,
|
|
101
97
|
Effect.withSpan('@livestore/adapter-node:worker:ExportEventlog'),
|
|
102
98
|
),
|
|
103
99
|
GetLeaderHead: () =>
|
|
104
100
|
Effect.gen(function* () {
|
|
105
101
|
const workerCtx = yield* LeaderThreadCtx
|
|
106
102
|
return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
|
|
107
|
-
}).pipe(
|
|
103
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetLeaderHead')),
|
|
108
104
|
GetLeaderSyncState: () =>
|
|
109
105
|
Effect.gen(function* () {
|
|
110
106
|
const workerCtx = yield* LeaderThreadCtx
|
|
111
107
|
return yield* workerCtx.syncProcessor.syncState
|
|
112
|
-
}).pipe(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetLeaderSyncState')),
|
|
109
|
+
SyncStateStream: () =>
|
|
110
|
+
Effect.gen(function* () {
|
|
111
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
112
|
+
return workerCtx.syncProcessor.syncState.changes
|
|
113
|
+
}).pipe(Stream.unwrapScoped),
|
|
114
|
+
GetNetworkStatus: () =>
|
|
115
|
+
Effect.gen(function* () {
|
|
116
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
117
|
+
return yield* workerCtx.networkStatus
|
|
118
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetNetworkStatus')),
|
|
119
|
+
NetworkStatusStream: () =>
|
|
120
|
+
Effect.gen(function* () {
|
|
121
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
122
|
+
return workerCtx.networkStatus.changes
|
|
123
|
+
}).pipe(Stream.unwrapScoped),
|
|
116
124
|
GetRecreateSnapshot: () =>
|
|
117
125
|
Effect.gen(function* () {
|
|
118
126
|
const workerCtx = yield* LeaderThreadCtx
|
|
@@ -123,11 +131,9 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
123
131
|
// return cachedSnapshot ?? workerCtx.db.export()
|
|
124
132
|
const snapshot = workerCtx.dbState.export()
|
|
125
133
|
return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
|
|
126
|
-
}).pipe(
|
|
127
|
-
UnexpectedError.mapToUnexpectedError,
|
|
128
|
-
Effect.withSpan('@livestore/adapter-node:worker:GetRecreateSnapshot'),
|
|
129
|
-
),
|
|
134
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:GetRecreateSnapshot')),
|
|
130
135
|
Shutdown: () =>
|
|
136
|
+
// @effect-diagnostics-next-line unnecessaryEffectGen:off
|
|
131
137
|
Effect.gen(function* () {
|
|
132
138
|
// const { db, dbEventlog } = yield* LeaderThreadCtx
|
|
133
139
|
yield* Effect.logDebug('[@livestore/adapter-node:worker] Shutdown')
|
|
@@ -141,10 +147,10 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
141
147
|
// Buy some time for Otel to flush
|
|
142
148
|
// TODO find a cleaner way to do this
|
|
143
149
|
// yield* Effect.sleep(1000)
|
|
144
|
-
}).pipe(
|
|
150
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-node:worker:Shutdown')),
|
|
145
151
|
ExtraDevtoolsMessage: ({ message }) =>
|
|
146
152
|
Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
|
|
147
|
-
|
|
153
|
+
UnknownError.mapToUnknownError,
|
|
148
154
|
Effect.withSpan('@livestore/adapter-node:worker:ExtraDevtoolsMessage'),
|
|
149
155
|
),
|
|
150
156
|
}).pipe(
|
|
@@ -156,12 +162,13 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
156
162
|
thread: options.otelOptions?.serviceName ?? 'livestore-node-leader-thread',
|
|
157
163
|
processId: process.pid,
|
|
158
164
|
}),
|
|
165
|
+
LogConfig.withLoggerConfig(
|
|
166
|
+
{ logger: options.logger, logLevel: options.logLevel },
|
|
167
|
+
{ threadName: options.otelOptions?.serviceName ?? 'livestore-node-leader-thread' },
|
|
168
|
+
),
|
|
159
169
|
// TODO bring back with Effect 4 once it's easier to work with replacing loggers.
|
|
160
170
|
// We basically only want to provide this logger if it's replacing the default logger, not if there's a custom logger already provided.
|
|
161
171
|
// Effect.provide(Logger.prettyWithThread(options.otelOptions?.serviceName ?? 'livestore-node-leader-thread')),
|
|
162
|
-
Effect.provide(
|
|
163
|
-
Effect.provide(PlatformNode.NodeFileSystem.layer),
|
|
164
|
-
TracingLive ? Effect.provide(TracingLive) : identity,
|
|
165
|
-
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
172
|
+
Effect.provide(runtimeLayer),
|
|
166
173
|
)
|
|
167
174
|
}
|