@livestore/adapter-node 0.0.0-snapshot-5a6440f111a5a7c2441a649866587d5d51e22820.3 → 0.0.0-snapshot-412a36a7e6c9b0e9e237b553fd0522aed285228f
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 +44 -0
- package/dist/client-session/adapter.d.ts.map +1 -0
- package/dist/client-session/adapter.js +199 -0
- package/dist/client-session/adapter.js.map +1 -0
- package/dist/client-session/in-memory-adapter.d.ts +145 -10
- package/dist/client-session/in-memory-adapter.d.ts.map +1 -1
- package/dist/client-session/in-memory-adapter.js +2 -3
- package/dist/client-session/in-memory-adapter.js.map +1 -1
- package/dist/client-session/persisted-adapter.d.ts +26 -14
- package/dist/client-session/persisted-adapter.d.ts.map +1 -1
- package/dist/client-session/persisted-adapter.js +81 -24
- package/dist/client-session/persisted-adapter.js.map +1 -1
- package/dist/devtools/devtools-server.d.ts.map +1 -1
- package/dist/devtools/devtools-server.js +3 -1
- package/dist/devtools/devtools-server.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/leader-shared.d.ts +29 -0
- package/dist/leader-shared.d.ts.map +1 -0
- package/dist/leader-shared.js +87 -0
- package/dist/leader-shared.js.map +1 -0
- package/dist/leader-thread-lazy.js +4 -0
- package/dist/leader-thread-lazy.js.map +1 -1
- package/dist/leader-thread-shared.d.ts +29 -0
- package/dist/leader-thread-shared.d.ts.map +1 -0
- package/dist/leader-thread-shared.js +88 -0
- package/dist/leader-thread-shared.js.map +1 -0
- package/dist/make-leader-worker.d.ts +5 -1
- package/dist/make-leader-worker.d.ts.map +1 -1
- package/dist/make-leader-worker.js +14 -81
- package/dist/make-leader-worker.js.map +1 -1
- package/dist/shutdown-channel.d.ts +1 -1
- package/dist/worker-schema.d.ts +35 -19
- package/dist/worker-schema.d.ts.map +1 -1
- package/dist/worker-schema.js +14 -19
- package/dist/worker-schema.js.map +1 -1
- package/package.json +6 -10
- package/src/client-session/{persisted-adapter.ts → adapter.ts} +190 -57
- package/src/devtools/devtools-server.ts +3 -0
- package/src/index.ts +1 -2
- package/src/leader-thread-lazy.ts +5 -0
- package/src/leader-thread-shared.ts +167 -0
- package/src/make-leader-worker.ts +20 -138
- package/src/worker-schema.ts +23 -26
- package/src/client-session/in-memory-adapter.ts +0 -169
|
@@ -8,8 +8,13 @@ import type {
|
|
|
8
8
|
ClientSessionLeaderThreadProxy,
|
|
9
9
|
IntentionalShutdownCause,
|
|
10
10
|
LockStatus,
|
|
11
|
+
MakeSqliteDb,
|
|
12
|
+
SyncOptions,
|
|
11
13
|
} from '@livestore/common'
|
|
12
14
|
import { Devtools, UnexpectedError } from '@livestore/common'
|
|
15
|
+
import { Eventlog, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
16
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
17
|
+
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
13
18
|
import * as DevtoolsNode from '@livestore/devtools-node-common/web-channel'
|
|
14
19
|
import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
15
20
|
import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
|
|
@@ -29,49 +34,82 @@ import {
|
|
|
29
34
|
} from '@livestore/utils/effect'
|
|
30
35
|
import { PlatformNode } from '@livestore/utils/node'
|
|
31
36
|
|
|
37
|
+
import type { TestingOverrides } from '../leader-thread-shared.js'
|
|
38
|
+
import { makeLeaderThread } from '../leader-thread-shared.js'
|
|
32
39
|
import { makeShutdownChannel } from '../shutdown-channel.js'
|
|
33
40
|
import * as WorkerSchema from '../worker-schema.js'
|
|
34
41
|
|
|
35
42
|
export interface NodeAdapterOptions {
|
|
36
|
-
|
|
37
|
-
* Example: `new URL('./livestore.worker.js', import.meta.url)`
|
|
38
|
-
*/
|
|
39
|
-
workerUrl: URL
|
|
40
|
-
/** Needed for the worker and the devtools */
|
|
41
|
-
schemaPath: string
|
|
42
|
-
/** Where to store the database files */
|
|
43
|
-
baseDirectory?: string
|
|
43
|
+
storage: WorkerSchema.StorageType
|
|
44
44
|
/** The default is the hostname of the current machine */
|
|
45
45
|
clientId?: string
|
|
46
|
-
/**
|
|
46
|
+
/**
|
|
47
|
+
* Warning: This adapter doesn't currently support multiple client sessions for the same client (i.e. same storeId + clientId)
|
|
48
|
+
* @default 'static'
|
|
49
|
+
*/
|
|
47
50
|
sessionId?: string
|
|
51
|
+
|
|
48
52
|
devtools?: {
|
|
53
|
+
schemaPath: string
|
|
49
54
|
/**
|
|
50
55
|
* Where to run the devtools server (via Vite)
|
|
51
56
|
*
|
|
52
57
|
* @default 4242
|
|
53
58
|
*/
|
|
54
|
-
port
|
|
59
|
+
port?: number
|
|
55
60
|
/**
|
|
56
61
|
* @default 'localhost'
|
|
57
62
|
*/
|
|
58
|
-
host
|
|
63
|
+
host?: string
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Only used internally for testing */
|
|
67
|
+
testing?: {
|
|
68
|
+
overrides?: TestingOverrides
|
|
59
69
|
}
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
/** Runs everything in the same thread. Use `makeWorkerAdapter` for multi-threaded implementation. */
|
|
73
|
+
export const makeAdapter = ({
|
|
74
|
+
sync,
|
|
75
|
+
...options
|
|
76
|
+
}: NodeAdapterOptions & {
|
|
77
|
+
sync?: SyncOptions
|
|
78
|
+
}): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'single-threaded', sync } })
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
|
-
*
|
|
81
|
+
* Runs persistence and syncing in a worker thread.
|
|
64
82
|
*/
|
|
65
|
-
export const
|
|
83
|
+
export const makeWorkerAdapter = ({
|
|
66
84
|
workerUrl,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
85
|
+
...options
|
|
86
|
+
}: NodeAdapterOptions & {
|
|
87
|
+
/**
|
|
88
|
+
* Example: `new URL('./livestore.worker.js', import.meta.url)`
|
|
89
|
+
*/
|
|
90
|
+
workerUrl: URL
|
|
91
|
+
}): Adapter => makeAdapterImpl({ ...options, leaderThread: { _tag: 'multi-threaded', workerUrl } })
|
|
92
|
+
|
|
93
|
+
const makeAdapterImpl = ({
|
|
94
|
+
storage,
|
|
95
|
+
devtools: devtoolsOptionsInput,
|
|
70
96
|
clientId = hostname(),
|
|
71
97
|
// TODO make this dynamic and actually support multiple sessions
|
|
72
98
|
sessionId = 'static',
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
testing,
|
|
100
|
+
leaderThread: leaderThreadInput,
|
|
101
|
+
}: NodeAdapterOptions & {
|
|
102
|
+
leaderThread:
|
|
103
|
+
| {
|
|
104
|
+
_tag: 'single-threaded'
|
|
105
|
+
sync: SyncOptions | undefined
|
|
106
|
+
}
|
|
107
|
+
| {
|
|
108
|
+
_tag: 'multi-threaded'
|
|
109
|
+
workerUrl: URL
|
|
110
|
+
}
|
|
111
|
+
}): Adapter =>
|
|
112
|
+
(({ storeId, devtoolsEnabled, shutdown, connectDevtoolsToStore, bootStatusQueue, syncPayload, schema }) =>
|
|
75
113
|
Effect.gen(function* () {
|
|
76
114
|
yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
|
|
77
115
|
|
|
@@ -86,39 +124,62 @@ export const makePersistedAdapter = ({
|
|
|
86
124
|
// yield* Effect.logWarning('Failed to load database file', fileData.left)
|
|
87
125
|
// }
|
|
88
126
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
if (leaderThreadInput._tag === 'multi-threaded') {
|
|
128
|
+
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
129
|
+
|
|
130
|
+
yield* shutdownChannel.listen.pipe(
|
|
131
|
+
Stream.flatten(),
|
|
132
|
+
Stream.tap((error) => Effect.sync(() => shutdown(Cause.fail(error)))),
|
|
133
|
+
Stream.runDrain,
|
|
134
|
+
Effect.interruptible,
|
|
135
|
+
Effect.tapCauseLogPretty,
|
|
136
|
+
Effect.forkScoped,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
99
139
|
|
|
100
140
|
const syncInMemoryDb = yield* makeSqliteDb({ _tag: 'in-memory' }).pipe(Effect.orDie)
|
|
101
141
|
|
|
102
142
|
// TODO actually implement this multi-session support
|
|
103
143
|
const lockStatus = yield* SubscriptionRef.make<LockStatus>('has-lock')
|
|
104
144
|
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
const devtoolsOptions: WorkerSchema.LeaderWorkerInner.InitialMessage['devtools'] =
|
|
146
|
+
devtoolsEnabled && devtoolsOptionsInput !== undefined
|
|
147
|
+
? {
|
|
148
|
+
enabled: true,
|
|
149
|
+
schemaPath: devtoolsOptionsInput.schemaPath,
|
|
150
|
+
port: devtoolsOptionsInput.port ?? 4242,
|
|
151
|
+
host: devtoolsOptionsInput.host ?? 'localhost',
|
|
152
|
+
}
|
|
153
|
+
: { enabled: false }
|
|
154
|
+
|
|
155
|
+
const { leaderThread, initialSnapshot } =
|
|
156
|
+
leaderThreadInput._tag === 'single-threaded'
|
|
157
|
+
? yield* makeLocalLeaderThread({
|
|
158
|
+
storeId,
|
|
159
|
+
clientId,
|
|
160
|
+
schema,
|
|
161
|
+
makeSqliteDb,
|
|
162
|
+
syncOptions: leaderThreadInput.sync,
|
|
163
|
+
syncPayload,
|
|
164
|
+
devtools: devtoolsOptions,
|
|
165
|
+
storage,
|
|
166
|
+
testing,
|
|
167
|
+
}).pipe(UnexpectedError.mapToUnexpectedError)
|
|
168
|
+
: yield* makeWorkerLeaderThread({
|
|
169
|
+
shutdown,
|
|
170
|
+
storeId,
|
|
171
|
+
clientId,
|
|
172
|
+
sessionId,
|
|
173
|
+
workerUrl: leaderThreadInput.workerUrl,
|
|
174
|
+
storage,
|
|
175
|
+
devtools: devtoolsOptions,
|
|
176
|
+
bootStatusQueue,
|
|
177
|
+
syncPayload,
|
|
178
|
+
})
|
|
118
179
|
|
|
119
180
|
syncInMemoryDb.import(initialSnapshot)
|
|
120
181
|
|
|
121
|
-
if (
|
|
182
|
+
if (devtoolsOptions.enabled) {
|
|
122
183
|
yield* Effect.gen(function* () {
|
|
123
184
|
const webmeshNode = yield* DevtoolsNode.makeNodeDevtoolsConnectedMeshNode({
|
|
124
185
|
url: `ws://${devtoolsOptions.host}:${devtoolsOptions.port}`,
|
|
@@ -142,7 +203,11 @@ export const makePersistedAdapter = ({
|
|
|
142
203
|
})
|
|
143
204
|
|
|
144
205
|
yield* connectDevtoolsToStore(storeDevtoolsChannel)
|
|
145
|
-
}).pipe(
|
|
206
|
+
}).pipe(
|
|
207
|
+
Effect.tapCauseLogPretty,
|
|
208
|
+
Effect.withSpan('@livestore/adapter-node:client-session:devtools'),
|
|
209
|
+
Effect.forkScoped,
|
|
210
|
+
)
|
|
146
211
|
}
|
|
147
212
|
|
|
148
213
|
const devtools: ClientSession['devtools'] = devtoolsEnabled
|
|
@@ -167,30 +232,97 @@ export const makePersistedAdapter = ({
|
|
|
167
232
|
Effect.provide(FetchHttpClient.layer),
|
|
168
233
|
)) satisfies Adapter
|
|
169
234
|
|
|
170
|
-
const
|
|
235
|
+
const makeLocalLeaderThread = ({
|
|
236
|
+
storeId,
|
|
237
|
+
clientId,
|
|
238
|
+
schema,
|
|
239
|
+
makeSqliteDb,
|
|
240
|
+
syncOptions,
|
|
241
|
+
syncPayload,
|
|
242
|
+
storage,
|
|
243
|
+
devtools,
|
|
244
|
+
testing,
|
|
245
|
+
}: {
|
|
246
|
+
storeId: string
|
|
247
|
+
clientId: string
|
|
248
|
+
schema: LiveStoreSchema
|
|
249
|
+
makeSqliteDb: MakeSqliteDb
|
|
250
|
+
syncOptions: SyncOptions | undefined
|
|
251
|
+
storage: WorkerSchema.StorageType
|
|
252
|
+
syncPayload: Schema.JsonValue | undefined
|
|
253
|
+
devtools: WorkerSchema.LeaderWorkerInner.InitialMessage['devtools']
|
|
254
|
+
testing?: {
|
|
255
|
+
overrides?: TestingOverrides
|
|
256
|
+
}
|
|
257
|
+
}) =>
|
|
258
|
+
Effect.gen(function* () {
|
|
259
|
+
const layer = yield* Layer.build(
|
|
260
|
+
makeLeaderThread({
|
|
261
|
+
storeId,
|
|
262
|
+
clientId,
|
|
263
|
+
schema,
|
|
264
|
+
syncOptions,
|
|
265
|
+
storage,
|
|
266
|
+
syncPayload,
|
|
267
|
+
devtools,
|
|
268
|
+
makeSqliteDb,
|
|
269
|
+
testing: testing?.overrides,
|
|
270
|
+
}).pipe(Layer.unwrapScoped),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return yield* Effect.gen(function* () {
|
|
274
|
+
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } = yield* LeaderThreadCtx
|
|
275
|
+
|
|
276
|
+
const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
|
|
277
|
+
|
|
278
|
+
const leaderThread = {
|
|
279
|
+
events: {
|
|
280
|
+
pull:
|
|
281
|
+
testing?.overrides?.clientSession?.leaderThreadProxy?.events?.pull ??
|
|
282
|
+
(({ cursor }) => syncProcessor.pull({ cursor })),
|
|
283
|
+
push: (batch) =>
|
|
284
|
+
syncProcessor.push(
|
|
285
|
+
batch.map((item) => new LiveStoreEvent.EncodedWithMeta(item)),
|
|
286
|
+
{ waitForProcessing: true },
|
|
287
|
+
),
|
|
288
|
+
},
|
|
289
|
+
initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
|
|
290
|
+
export: Effect.sync(() => dbState.export()),
|
|
291
|
+
getEventlogData: Effect.sync(() => dbEventlog.export()),
|
|
292
|
+
getSyncState: syncProcessor.syncState,
|
|
293
|
+
sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
|
|
294
|
+
} satisfies ClientSessionLeaderThreadProxy
|
|
295
|
+
|
|
296
|
+
const initialSnapshot = dbState.export()
|
|
297
|
+
|
|
298
|
+
return { leaderThread, initialSnapshot }
|
|
299
|
+
}).pipe(Effect.provide(layer))
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
const makeWorkerLeaderThread = ({
|
|
171
303
|
shutdown,
|
|
172
304
|
storeId,
|
|
173
305
|
clientId,
|
|
174
306
|
sessionId,
|
|
175
307
|
workerUrl,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
devtoolsOptions,
|
|
179
|
-
schemaPath,
|
|
308
|
+
storage,
|
|
309
|
+
devtools,
|
|
180
310
|
bootStatusQueue,
|
|
181
311
|
syncPayload,
|
|
312
|
+
testing,
|
|
182
313
|
}: {
|
|
183
314
|
shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => void
|
|
184
315
|
storeId: string
|
|
185
316
|
clientId: string
|
|
186
317
|
sessionId: string
|
|
187
318
|
workerUrl: URL
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
devtoolsOptions: { port: number; host: string }
|
|
191
|
-
schemaPath: string
|
|
319
|
+
storage: WorkerSchema.StorageType
|
|
320
|
+
devtools: WorkerSchema.LeaderWorkerInner.InitialMessage['devtools']
|
|
192
321
|
bootStatusQueue: Queue.Queue<BootStatus>
|
|
193
322
|
syncPayload: Schema.JsonValue | undefined
|
|
323
|
+
testing?: {
|
|
324
|
+
overrides?: TestingOverrides
|
|
325
|
+
}
|
|
194
326
|
}) =>
|
|
195
327
|
Effect.gen(function* () {
|
|
196
328
|
const nodeWorker = new WT.Worker(workerUrl, {
|
|
@@ -206,9 +338,8 @@ const makeLeaderThread = ({
|
|
|
206
338
|
new WorkerSchema.LeaderWorkerInner.InitialMessage({
|
|
207
339
|
storeId,
|
|
208
340
|
clientId,
|
|
209
|
-
|
|
210
|
-
devtools
|
|
211
|
-
schemaPath,
|
|
341
|
+
storage,
|
|
342
|
+
devtools,
|
|
212
343
|
syncPayload,
|
|
213
344
|
}),
|
|
214
345
|
}).pipe(
|
|
@@ -296,8 +427,10 @@ const makeLeaderThread = ({
|
|
|
296
427
|
|
|
297
428
|
const leaderThread = {
|
|
298
429
|
events: {
|
|
299
|
-
pull:
|
|
300
|
-
|
|
430
|
+
pull:
|
|
431
|
+
testing?.overrides?.clientSession?.leaderThreadProxy?.events?.pull ??
|
|
432
|
+
(({ cursor }) =>
|
|
433
|
+
runInWorkerStream(new WorkerSchema.LeaderWorkerInner.PullStream({ cursor })).pipe(Stream.orDie)),
|
|
301
434
|
push: (batch) =>
|
|
302
435
|
runInWorker(new WorkerSchema.LeaderWorkerInner.PushToLeader({ batch })).pipe(
|
|
303
436
|
Effect.withSpan('@livestore/adapter-node:client-session:pushToLeader', {
|
|
@@ -111,6 +111,9 @@ export const startDevtoolsServer = ({
|
|
|
111
111
|
|
|
112
112
|
return HttpServer.serve(handler, HttpMiddleware.logger)
|
|
113
113
|
}).pipe(
|
|
114
|
+
Effect.withSpan('@livestore/adapter-node:startDevtoolsServer', {
|
|
115
|
+
attributes: { storeId, clientId, sessionId, port, host, schemaPath },
|
|
116
|
+
}),
|
|
114
117
|
Layer.unwrapScoped,
|
|
115
118
|
// HttpServer.withLogAddress,
|
|
116
119
|
Layer.provide(PlatformNode.NodeHttpServer.layer(() => http.createServer(), { port, host })),
|
package/src/index.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export { makePersistedAdapter } from './client-session/persisted-adapter.js'
|
|
1
|
+
export { makeAdapter, makeWorkerAdapter } from './client-session/adapter.js'
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
// NOTE This file isn't currently used but was part of an experiment to see whether we can improve
|
|
2
|
+
// the Node startup time by lazy loading the leader thread bundle.
|
|
3
|
+
// This indeed provided a nice speedup but it takes quite a bit of tooling to set up and comes with
|
|
4
|
+
// other downsides (e.g. treeshaking).
|
|
5
|
+
|
|
1
6
|
const run = async () => {
|
|
2
7
|
const start = Date.now()
|
|
3
8
|
// @ts-expect-error todo
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import inspector from 'node:inspector'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
if (process.execArgv.includes('--inspect')) {
|
|
5
|
+
inspector.open()
|
|
6
|
+
inspector.waitForDebugger()
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
import type { ClientSessionLeaderThreadProxy, MakeSqliteDb, SqliteDb, SyncOptions } from '@livestore/common'
|
|
10
|
+
import { Devtools, liveStoreStorageFormatVersion, UnexpectedError } from '@livestore/common'
|
|
11
|
+
import type { DevtoolsOptions, LeaderSqliteDb, LeaderThreadCtx } from '@livestore/common/leader-thread'
|
|
12
|
+
import { configureConnection, makeLeaderThreadLayer } from '@livestore/common/leader-thread'
|
|
13
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
14
|
+
import { makeNodeDevtoolsChannel } from '@livestore/devtools-node-common/web-channel'
|
|
15
|
+
import type { MakeNodeSqliteDb } from '@livestore/sqlite-wasm/node'
|
|
16
|
+
import type { FileSystem, HttpClient, Layer, Schema, Scope } from '@livestore/utils/effect'
|
|
17
|
+
import { Effect, FetchHttpClient } from '@livestore/utils/effect'
|
|
18
|
+
|
|
19
|
+
import { makeShutdownChannel } from './shutdown-channel.js'
|
|
20
|
+
import type * as WorkerSchema from './worker-schema.js'
|
|
21
|
+
|
|
22
|
+
export type TestingOverrides = {
|
|
23
|
+
clientSession?: {
|
|
24
|
+
leaderThreadProxy?: Partial<ClientSessionLeaderThreadProxy>
|
|
25
|
+
}
|
|
26
|
+
makeLeaderThread?: {
|
|
27
|
+
dbEventlog?: (makeSqliteDb: MakeSqliteDb) => Effect.Effect<SqliteDb, UnexpectedError>
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MakeLeaderThreadArgs {
|
|
32
|
+
storeId: string
|
|
33
|
+
clientId: string
|
|
34
|
+
syncOptions: SyncOptions | undefined
|
|
35
|
+
storage: WorkerSchema.StorageType
|
|
36
|
+
makeSqliteDb: MakeNodeSqliteDb
|
|
37
|
+
devtools: WorkerSchema.LeaderWorkerInner.InitialMessage['devtools']
|
|
38
|
+
schema: LiveStoreSchema
|
|
39
|
+
syncPayload: Schema.JsonValue | undefined
|
|
40
|
+
testing: TestingOverrides | undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const makeLeaderThread = ({
|
|
44
|
+
storeId,
|
|
45
|
+
clientId,
|
|
46
|
+
syncOptions,
|
|
47
|
+
makeSqliteDb,
|
|
48
|
+
storage,
|
|
49
|
+
devtools,
|
|
50
|
+
schema,
|
|
51
|
+
syncPayload,
|
|
52
|
+
testing,
|
|
53
|
+
}: MakeLeaderThreadArgs): Effect.Effect<
|
|
54
|
+
Layer.Layer<LeaderThreadCtx, UnexpectedError, Scope.Scope | HttpClient.HttpClient | FileSystem.FileSystem>,
|
|
55
|
+
UnexpectedError,
|
|
56
|
+
Scope.Scope
|
|
57
|
+
> =>
|
|
58
|
+
Effect.gen(function* () {
|
|
59
|
+
const runtime = yield* Effect.runtime<never>()
|
|
60
|
+
|
|
61
|
+
const schemaHashSuffix =
|
|
62
|
+
schema.state.sqlite.migrations.strategy === 'manual' ? 'fixed' : schema.state.sqlite.hash.toString()
|
|
63
|
+
|
|
64
|
+
const makeDb = (kind: 'state' | 'eventlog') => {
|
|
65
|
+
if (testing?.makeLeaderThread?.dbEventlog && kind === 'eventlog') {
|
|
66
|
+
return testing.makeLeaderThread.dbEventlog(makeSqliteDb)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return storage.type === 'in-memory'
|
|
70
|
+
? makeSqliteDb({
|
|
71
|
+
_tag: 'in-memory',
|
|
72
|
+
configureDb: (db) =>
|
|
73
|
+
configureConnection(db, { foreignKeys: true }).pipe(Effect.provide(runtime), Effect.runSync),
|
|
74
|
+
})
|
|
75
|
+
: makeSqliteDb({
|
|
76
|
+
_tag: 'fs',
|
|
77
|
+
directory: path.join(storage.baseDirectory ?? '', storeId),
|
|
78
|
+
fileName:
|
|
79
|
+
kind === 'state' ? getStateDbFileName(schemaHashSuffix) : `eventlog@${liveStoreStorageFormatVersion}.db`,
|
|
80
|
+
// TODO enable WAL for nodejs
|
|
81
|
+
configureDb: (db) =>
|
|
82
|
+
configureConnection(db, { foreignKeys: true }).pipe(Effect.provide(runtime), Effect.runSync),
|
|
83
|
+
}).pipe(Effect.acquireRelease((db) => Effect.sync(() => db.close())))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Might involve some async work, so we're running them concurrently
|
|
87
|
+
const [dbState, dbEventlog] = yield* Effect.all([makeDb('state'), makeDb('eventlog')], { concurrency: 2 })
|
|
88
|
+
|
|
89
|
+
const devtoolsOptions = yield* makeDevtoolsOptions({ devtools, dbState, dbEventlog, storeId, clientId })
|
|
90
|
+
|
|
91
|
+
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
92
|
+
|
|
93
|
+
return makeLeaderThreadLayer({
|
|
94
|
+
schema,
|
|
95
|
+
storeId,
|
|
96
|
+
clientId,
|
|
97
|
+
makeSqliteDb,
|
|
98
|
+
syncOptions,
|
|
99
|
+
dbState,
|
|
100
|
+
dbEventlog,
|
|
101
|
+
devtoolsOptions,
|
|
102
|
+
shutdownChannel,
|
|
103
|
+
syncPayload,
|
|
104
|
+
})
|
|
105
|
+
}).pipe(
|
|
106
|
+
Effect.tapCauseLogPretty,
|
|
107
|
+
UnexpectedError.mapToUnexpectedError,
|
|
108
|
+
Effect.withSpan('@livestore/adapter-node:makeLeaderThread', {
|
|
109
|
+
attributes: { storeId, clientId, storage, devtools, syncOptions },
|
|
110
|
+
}),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
const getStateDbFileName = (suffix: string) => `state${suffix}@${liveStoreStorageFormatVersion}.db`
|
|
114
|
+
|
|
115
|
+
const makeDevtoolsOptions = ({
|
|
116
|
+
dbState,
|
|
117
|
+
dbEventlog,
|
|
118
|
+
storeId,
|
|
119
|
+
clientId,
|
|
120
|
+
devtools,
|
|
121
|
+
}: {
|
|
122
|
+
dbState: LeaderSqliteDb
|
|
123
|
+
dbEventlog: LeaderSqliteDb
|
|
124
|
+
storeId: string
|
|
125
|
+
clientId: string
|
|
126
|
+
devtools: WorkerSchema.LeaderWorkerInner.InitialMessage['devtools']
|
|
127
|
+
}): Effect.Effect<DevtoolsOptions, UnexpectedError, Scope.Scope> =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
if (devtools.enabled === false) {
|
|
130
|
+
return {
|
|
131
|
+
enabled: false,
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
enabled: true,
|
|
137
|
+
makeBootContext: Effect.gen(function* () {
|
|
138
|
+
// Lazy import to improve startup time
|
|
139
|
+
const { startDevtoolsServer } = yield* Effect.promise(() => import('./devtools/devtools-server.js'))
|
|
140
|
+
|
|
141
|
+
// TODO instead of failing when the port is already in use, we should try to use that WS server instead of starting a new one
|
|
142
|
+
yield* startDevtoolsServer({
|
|
143
|
+
schemaPath: devtools.schemaPath,
|
|
144
|
+
storeId,
|
|
145
|
+
clientId,
|
|
146
|
+
sessionId: 'static', // TODO make this dynamic
|
|
147
|
+
port: devtools.port,
|
|
148
|
+
host: devtools.host,
|
|
149
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
150
|
+
|
|
151
|
+
const devtoolsWebChannel = yield* makeNodeDevtoolsChannel({
|
|
152
|
+
nodeName: `leader-${storeId}-${clientId}`,
|
|
153
|
+
target: `devtools-${storeId}-${clientId}-static`,
|
|
154
|
+
url: `ws://localhost:${devtools.port}`,
|
|
155
|
+
schema: { listen: Devtools.Leader.MessageToApp, send: Devtools.Leader.MessageFromApp },
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
devtoolsWebChannel,
|
|
160
|
+
persistenceInfo: {
|
|
161
|
+
state: dbState.metadata.persistenceInfo,
|
|
162
|
+
eventlog: dbEventlog.metadata.persistenceInfo,
|
|
163
|
+
},
|
|
164
|
+
}
|
|
165
|
+
}).pipe(Effect.provide(FetchHttpClient.layer)),
|
|
166
|
+
}
|
|
167
|
+
})
|