@livestore/adapter-node 0.0.0-snapshot-057a9e3a18ca69a310d4eb8cf35a34e94fa1841e → 0.0.0-snapshot-8fd59846e2580d37bdd37ae607e9db2a458a8b63

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.
@@ -1,16 +1,16 @@
1
1
  import { hostname } from 'node:os'
2
2
  import * as WT from 'node:worker_threads'
3
- import {
4
- type Adapter,
5
- type BootStatus,
3
+
4
+ import type {
5
+ Adapter,
6
+ BootStatus,
6
7
  ClientSessionLeaderThreadProxy,
7
- type IntentionalShutdownCause,
8
- type LockStatus,
9
- type MakeSqliteDb,
10
- makeClientSession,
11
- type SyncOptions,
12
- UnexpectedError,
8
+ IntentionalShutdownCause,
9
+ LockStatus,
10
+ MakeSqliteDb,
11
+ SyncOptions,
13
12
  } from '@livestore/common'
13
+ import { makeClientSession, UnexpectedError } from '@livestore/common'
14
14
  import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
15
15
  import type { LiveStoreSchema } from '@livestore/common/schema'
16
16
  import { LiveStoreEvent } from '@livestore/common/schema'
@@ -188,7 +188,6 @@ const makeAdapterImpl = ({
188
188
  })
189
189
 
190
190
  syncInMemoryDb.import(initialSnapshot)
191
- syncInMemoryDb.debug.head = leaderThread.initialState.leaderHead
192
191
 
193
192
  const clientSession = yield* makeClientSession({
194
193
  ...adapterArgs,
@@ -215,6 +214,7 @@ const makeAdapterImpl = ({
215
214
  return clientSession
216
215
  }).pipe(
217
216
  Effect.withSpan('@livestore/adapter-node:adapter'),
217
+ Effect.parallelFinalizers,
218
218
  Effect.provide(PlatformNode.NodeFileSystem.layer),
219
219
  Effect.provide(FetchHttpClient.layer),
220
220
  )) satisfies Adapter
@@ -262,24 +262,23 @@ const makeLocalLeaderThread = ({
262
262
 
263
263
  const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
264
264
 
265
- const leaderThread = ClientSessionLeaderThreadProxy.of(
266
- {
267
- events: {
268
- pull: ({ cursor }) => syncProcessor.pull({ cursor }),
269
- push: (batch) =>
270
- syncProcessor.push(
271
- batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
272
- { waitForProcessing: true },
273
- ),
274
- },
275
- initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
276
- export: Effect.sync(() => dbState.export()),
277
- getEventlogData: Effect.sync(() => dbEventlog.export()),
278
- getSyncState: syncProcessor.syncState,
279
- sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
265
+ const leaderThread = {
266
+ events: {
267
+ pull:
268
+ testing?.overrides?.clientSession?.leaderThreadProxy?.events?.pull ??
269
+ (({ cursor }) => syncProcessor.pull({ cursor })),
270
+ push: (batch) =>
271
+ syncProcessor.push(
272
+ batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
273
+ { waitForProcessing: true },
274
+ ),
280
275
  },
281
- { overrides: testing?.overrides?.clientSession?.leaderThreadProxy },
282
- )
276
+ initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
277
+ export: Effect.sync(() => dbState.export()),
278
+ getEventlogData: Effect.sync(() => dbEventlog.export()),
279
+ getSyncState: syncProcessor.syncState,
280
+ sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
281
+ } satisfies ClientSessionLeaderThreadProxy
283
282
 
284
283
  const initialSnapshot = dbState.export()
285
284
 
@@ -337,6 +336,20 @@ const makeWorkerLeaderThread = ({
337
336
  Effect.withSpan('@livestore/adapter-node:adapter:setupLeaderThread'),
338
337
  )
339
338
 
339
+ yield* Effect.addFinalizer(() =>
340
+ Effect.gen(function* () {
341
+ // We first try to gracefully shutdown the leader worker and then forcefully terminate it
342
+ yield* Effect.raceFirst(
343
+ runInWorker(new WorkerSchema.LeaderWorkerInner.Shutdown()).pipe(Effect.andThen(() => nodeWorker.terminate())),
344
+
345
+ Effect.sync(() => {
346
+ console.warn('[@livestore/adapter-node:adapter] Worker did not gracefully shutdown in time, terminating it')
347
+ nodeWorker.terminate()
348
+ }).pipe(Effect.delay(1000)),
349
+ ).pipe(Effect.exit) // The disconnect is to prevent the interrupt to bubble out
350
+ }).pipe(Effect.withSpan('@livestore/adapter-node:adapter:shutdown'), Effect.tapCauseLogPretty, Effect.orDie),
351
+ )
352
+
340
353
  const runInWorker = <TReq extends typeof WorkerSchema.LeaderWorkerInner.Request.Type>(
341
354
  req: TReq,
342
355
  ): TReq extends Schema.WithResult<infer A, infer _I, infer _E, infer _EI, infer R>
@@ -397,42 +410,39 @@ const makeWorkerLeaderThread = ({
397
410
  Effect.withSpan('@livestore/adapter-node:client-session:export'),
398
411
  )
399
412
 
400
- const leaderThread = ClientSessionLeaderThreadProxy.of(
401
- {
402
- events: {
403
- pull: ({ cursor }) =>
404
- runInWorkerStream(new WorkerSchema.LeaderWorkerInner.PullStream({ cursor })).pipe(Stream.orDie),
405
- push: (batch) =>
406
- runInWorker(new WorkerSchema.LeaderWorkerInner.PushToLeader({ batch })).pipe(
407
- Effect.withSpan('@livestore/adapter-node:client-session:pushToLeader', {
408
- attributes: { batchSize: batch.length },
409
- }),
410
- ),
411
- },
412
- initialState: {
413
- leaderHead: initialLeaderHead,
414
- migrationsReport: bootResult.migrationsReport,
415
- },
416
- export: runInWorker(new WorkerSchema.LeaderWorkerInner.Export()).pipe(
417
- Effect.timeout(10_000),
418
- UnexpectedError.mapToUnexpectedError,
419
- Effect.withSpan('@livestore/adapter-node:client-session:export'),
420
- ),
421
- getEventlogData: Effect.dieMessage('Not implemented'),
422
- getSyncState: runInWorker(new WorkerSchema.LeaderWorkerInner.GetLeaderSyncState()).pipe(
423
- UnexpectedError.mapToUnexpectedError,
424
- Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
425
- ),
426
- sendDevtoolsMessage: (message) =>
427
- runInWorker(new WorkerSchema.LeaderWorkerInner.ExtraDevtoolsMessage({ message })).pipe(
428
- UnexpectedError.mapToUnexpectedError,
429
- Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
413
+ const leaderThread = {
414
+ events: {
415
+ pull:
416
+ testing?.overrides?.clientSession?.leaderThreadProxy?.events?.pull ??
417
+ (({ cursor }) =>
418
+ runInWorkerStream(new WorkerSchema.LeaderWorkerInner.PullStream({ cursor })).pipe(Stream.orDie)),
419
+ push: (batch) =>
420
+ runInWorker(new WorkerSchema.LeaderWorkerInner.PushToLeader({ batch })).pipe(
421
+ Effect.withSpan('@livestore/adapter-node:client-session:pushToLeader', {
422
+ attributes: { batchSize: batch.length },
423
+ }),
430
424
  ),
431
425
  },
432
- {
433
- overrides: testing?.overrides?.clientSession?.leaderThreadProxy,
426
+ initialState: {
427
+ leaderHead: initialLeaderHead,
428
+ migrationsReport: bootResult.migrationsReport,
434
429
  },
435
- )
430
+ export: runInWorker(new WorkerSchema.LeaderWorkerInner.Export()).pipe(
431
+ Effect.timeout(10_000),
432
+ UnexpectedError.mapToUnexpectedError,
433
+ Effect.withSpan('@livestore/adapter-node:client-session:export'),
434
+ ),
435
+ getEventlogData: Effect.dieMessage('Not implemented'),
436
+ getSyncState: runInWorker(new WorkerSchema.LeaderWorkerInner.GetLeaderSyncState()).pipe(
437
+ UnexpectedError.mapToUnexpectedError,
438
+ Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
439
+ ),
440
+ sendDevtoolsMessage: (message) =>
441
+ runInWorker(new WorkerSchema.LeaderWorkerInner.ExtraDevtoolsMessage({ message })).pipe(
442
+ UnexpectedError.mapToUnexpectedError,
443
+ Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
444
+ ),
445
+ } satisfies ClientSessionLeaderThreadProxy
436
446
 
437
447
  return { leaderThread, initialSnapshot: bootResult.snapshot }
438
448
  })
@@ -1,3 +1,3 @@
1
- export { startDevtoolsServer } from './devtools-server.js'
2
- export type { ViteDevtoolsOptions } from './vite-dev-server.js'
3
1
  export { makeViteMiddleware } from './vite-dev-server.js'
2
+ export type { ViteDevtoolsOptions } from './vite-dev-server.js'
3
+ export { startDevtoolsServer } from './devtools-server.js'
@@ -21,17 +21,11 @@ import type * as WorkerSchema from './worker-schema.js'
21
21
 
22
22
  export type TestingOverrides = {
23
23
  clientSession?: {
24
- leaderThreadProxy?: (
25
- original: ClientSessionLeaderThreadProxy.ClientSessionLeaderThreadProxy,
26
- ) => Partial<ClientSessionLeaderThreadProxy.ClientSessionLeaderThreadProxy>
24
+ leaderThreadProxy?: Partial<ClientSessionLeaderThreadProxy>
25
+ }
26
+ makeLeaderThread?: {
27
+ dbEventlog?: (makeSqliteDb: MakeSqliteDb) => Effect.Effect<SqliteDb, UnexpectedError>
27
28
  }
28
- makeLeaderThread?: (makeSqliteDb: MakeSqliteDb) => Effect.Effect<
29
- {
30
- dbEventlog: SqliteDb
31
- dbState: SqliteDb
32
- },
33
- UnexpectedError
34
- >
35
29
  }
36
30
 
37
31
  export interface MakeLeaderThreadArgs {
@@ -68,10 +62,8 @@ export const makeLeaderThread = ({
68
62
  schema.state.sqlite.migrations.strategy === 'manual' ? 'fixed' : schema.state.sqlite.hash.toString()
69
63
 
70
64
  const makeDb = (kind: 'state' | 'eventlog') => {
71
- if (testing?.makeLeaderThread) {
72
- return testing
73
- .makeLeaderThread(makeSqliteDb)
74
- .pipe(Effect.map(({ dbEventlog, dbState }) => (kind === 'state' ? dbState : dbEventlog)))
65
+ if (testing?.makeLeaderThread?.dbEventlog && kind === 'eventlog') {
66
+ return testing.makeLeaderThread.dbEventlog(makeSqliteDb)
75
67
  }
76
68
 
77
69
  return storage.type === 'in-memory'
@@ -47,7 +47,7 @@ export type WorkerOptions = {
47
47
  export const getWorkerArgs = () => Schema.decodeSync(WorkerSchema.WorkerArgv)(process.argv[2]!)
48
48
 
49
49
  export const makeWorker = (options: WorkerOptions) => {
50
- makeWorkerEffect(options).pipe(PlatformNode.NodeRuntime.runMain)
50
+ makeWorkerEffect(options).pipe(Effect.runFork)
51
51
  }
52
52
 
53
53
  export const makeWorkerEffect = (options: WorkerOptions) => {
package/src/webchannel.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { BroadcastChannel as NodeBroadcastChannel } from 'node:worker_threads'
1
+ import { type BroadcastChannel as NodeBroadcastChannel } from 'node:worker_threads'
2
2
 
3
3
  import type { Either, ParseResult } from '@livestore/utils/effect'
4
4
  import { Deferred, Effect, Exit, Schema, Scope, Stream, WebChannel } from '@livestore/utils/effect'
@@ -37,6 +37,7 @@ export const makeBroadcastChannel = <Msg, MsgEncoded>({
37
37
  const listen = Stream.asyncPush<Either.Either<Msg, ParseResult.ParseError>>((emit) =>
38
38
  Effect.acquireRelease(
39
39
  Effect.gen(function* () {
40
+ // eslint-disable-next-line unicorn/prefer-add-event-listener
40
41
  channel.onmessage = (event: any) => {
41
42
  return emit.single(Schema.decodeEither(schema)(event.data))
42
43
  }
@@ -1,4 +1,12 @@
1
- import { BootStatus, Devtools, LeaderAheadError, MigrationsReport, SyncState, UnexpectedError } from '@livestore/common'
1
+ import {
2
+ BootStatus,
3
+ Devtools,
4
+ LeaderAheadError,
5
+ LeaderPullCursor,
6
+ MigrationsReport,
7
+ SyncState,
8
+ UnexpectedError,
9
+ } from '@livestore/common'
2
10
  import { EventSequenceNumber, LiveStoreEvent } from '@livestore/common/schema'
3
11
  import { Schema, Transferable } from '@livestore/utils/effect'
4
12
 
@@ -83,10 +91,11 @@ export namespace LeaderWorkerInner {
83
91
 
84
92
  export class PullStream extends Schema.TaggedRequest<PullStream>()('PullStream', {
85
93
  payload: {
86
- cursor: EventSequenceNumber.EventSequenceNumber,
94
+ cursor: LeaderPullCursor,
87
95
  },
88
96
  success: Schema.Struct({
89
97
  payload: SyncState.PayloadUpstream,
98
+ mergeCounter: Schema.Number,
90
99
  }),
91
100
  failure: UnexpectedError,
92
101
  }) {}