@livestore/adapter-node 0.4.0-dev.1 → 0.4.0-dev.11
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 +49 -16
- package/dist/client-session/adapter.js.map +1 -1
- package/dist/make-leader-worker.d.ts +2 -1
- package/dist/make-leader-worker.d.ts.map +1 -1
- package/dist/make-leader-worker.js +16 -2
- package/dist/make-leader-worker.js.map +1 -1
- package/dist/worker-schema.d.ts +32 -4
- package/dist/worker-schema.d.ts.map +1 -1
- package/dist/worker-schema.js +21 -2
- package/dist/worker-schema.js.map +1 -1
- package/package.json +8 -8
- package/src/client-session/adapter.ts +87 -17
- package/src/make-leader-worker.ts +23 -4
- package/src/worker-schema.ts +40 -1
|
@@ -1,10 +1,11 @@
|
|
|
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,
|
|
@@ -17,17 +18,21 @@ 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,6 +133,7 @@ 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) =>
|
|
@@ -135,6 +155,14 @@ const makeAdapterImpl = ({
|
|
|
135
155
|
|
|
136
156
|
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
137
157
|
|
|
158
|
+
if (resetPersistence === true) {
|
|
159
|
+
yield* shutdownChannel
|
|
160
|
+
.send(IntentionalShutdownCause.make({ reason: 'adapter-reset' }))
|
|
161
|
+
.pipe(UnexpectedError.mapToUnexpectedError)
|
|
162
|
+
|
|
163
|
+
yield* resetNodePersistence({ storage, storeId })
|
|
164
|
+
}
|
|
165
|
+
|
|
138
166
|
yield* shutdownChannel.listen.pipe(
|
|
139
167
|
Stream.flatten(),
|
|
140
168
|
Stream.tap((cause) =>
|
|
@@ -173,11 +201,13 @@ const makeAdapterImpl = ({
|
|
|
173
201
|
clientId,
|
|
174
202
|
schema,
|
|
175
203
|
makeSqliteDb,
|
|
176
|
-
syncOptions: leaderThreadInput.sync,
|
|
177
|
-
syncPayload,
|
|
178
204
|
devtools: devtoolsOptions,
|
|
179
205
|
storage,
|
|
180
|
-
|
|
206
|
+
...omitUndefineds({
|
|
207
|
+
syncOptions: leaderThreadInput.sync,
|
|
208
|
+
syncPayload,
|
|
209
|
+
testing,
|
|
210
|
+
}),
|
|
181
211
|
}).pipe(UnexpectedError.mapToUnexpectedError)
|
|
182
212
|
: yield* makeWorkerLeaderThread({
|
|
183
213
|
shutdown,
|
|
@@ -185,6 +215,7 @@ const makeAdapterImpl = ({
|
|
|
185
215
|
clientId,
|
|
186
216
|
sessionId,
|
|
187
217
|
workerUrl: leaderThreadInput.workerUrl,
|
|
218
|
+
workerExtraArgs: leaderThreadInput.workerExtraArgs,
|
|
188
219
|
storage,
|
|
189
220
|
devtools: devtoolsOptions,
|
|
190
221
|
bootStatusQueue,
|
|
@@ -219,10 +250,38 @@ const makeAdapterImpl = ({
|
|
|
219
250
|
return clientSession
|
|
220
251
|
}).pipe(
|
|
221
252
|
Effect.withSpan('@livestore/adapter-node:adapter'),
|
|
222
|
-
Effect.provide(PlatformNode.NodeFileSystem.layer),
|
|
223
|
-
Effect.provide(FetchHttpClient.layer),
|
|
253
|
+
Effect.provide(Layer.mergeAll(PlatformNode.NodeFileSystem.layer, FetchHttpClient.layer)),
|
|
224
254
|
)) satisfies Adapter
|
|
225
255
|
|
|
256
|
+
const resetNodePersistence = ({
|
|
257
|
+
storage,
|
|
258
|
+
storeId,
|
|
259
|
+
}: {
|
|
260
|
+
storage: WorkerSchema.StorageType
|
|
261
|
+
storeId: string
|
|
262
|
+
}): Effect.Effect<void, UnexpectedError, FileSystem.FileSystem> => {
|
|
263
|
+
if (storage.type !== 'fs') {
|
|
264
|
+
return Effect.void
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const directory = path.join(storage.baseDirectory ?? '', storeId)
|
|
268
|
+
|
|
269
|
+
return Effect.gen(function* () {
|
|
270
|
+
const fs = yield* FileSystem.FileSystem
|
|
271
|
+
|
|
272
|
+
const directoryExists = yield* fs.exists(directory).pipe(UnexpectedError.mapToUnexpectedError)
|
|
273
|
+
|
|
274
|
+
if (directoryExists === false) {
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
yield* fs.remove(directory, { recursive: true }).pipe(UnexpectedError.mapToUnexpectedError)
|
|
279
|
+
}).pipe(
|
|
280
|
+
Effect.retry({ schedule: Schedule.exponentialBackoff10Sec }),
|
|
281
|
+
Effect.withSpan('@livestore/adapter-node:resetPersistence', { attributes: { directory } }),
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
226
285
|
const makeLocalLeaderThread = ({
|
|
227
286
|
storeId,
|
|
228
287
|
clientId,
|
|
@@ -257,12 +316,13 @@ const makeLocalLeaderThread = ({
|
|
|
257
316
|
syncPayload,
|
|
258
317
|
devtools,
|
|
259
318
|
makeSqliteDb,
|
|
260
|
-
testing: testing?.overrides,
|
|
319
|
+
...omitUndefineds({ testing: testing?.overrides }),
|
|
261
320
|
}).pipe(Layer.unwrapScoped),
|
|
262
321
|
)
|
|
263
322
|
|
|
264
323
|
return yield* Effect.gen(function* () {
|
|
265
|
-
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState } =
|
|
324
|
+
const { dbState, dbEventlog, syncProcessor, extraIncomingMessagesQueue, initialState, networkStatus } =
|
|
325
|
+
yield* LeaderThreadCtx
|
|
266
326
|
|
|
267
327
|
const initialLeaderHead = Eventlog.getClientHeadFromDb(dbEventlog)
|
|
268
328
|
|
|
@@ -279,10 +339,11 @@ const makeLocalLeaderThread = ({
|
|
|
279
339
|
initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
|
|
280
340
|
export: Effect.sync(() => dbState.export()),
|
|
281
341
|
getEventlogData: Effect.sync(() => dbEventlog.export()),
|
|
282
|
-
|
|
342
|
+
syncState: syncProcessor.syncState,
|
|
283
343
|
sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
|
|
344
|
+
networkStatus,
|
|
284
345
|
},
|
|
285
|
-
{ overrides: testing?.overrides?.clientSession?.leaderThreadProxy },
|
|
346
|
+
{ ...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }) },
|
|
286
347
|
)
|
|
287
348
|
|
|
288
349
|
const initialSnapshot = dbState.export()
|
|
@@ -297,6 +358,7 @@ const makeWorkerLeaderThread = ({
|
|
|
297
358
|
clientId,
|
|
298
359
|
sessionId,
|
|
299
360
|
workerUrl,
|
|
361
|
+
workerExtraArgs,
|
|
300
362
|
storage,
|
|
301
363
|
devtools,
|
|
302
364
|
bootStatusQueue,
|
|
@@ -308,6 +370,7 @@ const makeWorkerLeaderThread = ({
|
|
|
308
370
|
clientId: string
|
|
309
371
|
sessionId: string
|
|
310
372
|
workerUrl: URL
|
|
373
|
+
workerExtraArgs: Schema.JsonValue | undefined
|
|
311
374
|
storage: WorkerSchema.StorageType
|
|
312
375
|
devtools: WorkerSchema.LeaderWorkerInnerInitialMessage['devtools']
|
|
313
376
|
bootStatusQueue: Queue.Queue<BootStatus>
|
|
@@ -319,7 +382,7 @@ const makeWorkerLeaderThread = ({
|
|
|
319
382
|
Effect.gen(function* () {
|
|
320
383
|
const nodeWorker = new WT.Worker(workerUrl, {
|
|
321
384
|
execArgv: process.env.DEBUG_WORKER ? ['--inspect --enable-source-maps'] : ['--enable-source-maps'],
|
|
322
|
-
argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId })],
|
|
385
|
+
argv: [Schema.encodeSync(WorkerSchema.WorkerArgv)({ storeId, clientId, sessionId, extraArgs: workerExtraArgs })],
|
|
323
386
|
})
|
|
324
387
|
const nodeWorkerLayer = yield* Layer.build(PlatformNode.NodeWorker.layer(() => nodeWorker))
|
|
325
388
|
|
|
@@ -423,18 +486,25 @@ const makeWorkerLeaderThread = ({
|
|
|
423
486
|
Effect.withSpan('@livestore/adapter-node:client-session:export'),
|
|
424
487
|
),
|
|
425
488
|
getEventlogData: Effect.dieMessage('Not implemented'),
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
489
|
+
syncState: Subscribable.make({
|
|
490
|
+
get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetLeaderSyncState()).pipe(
|
|
491
|
+
UnexpectedError.mapToUnexpectedError,
|
|
492
|
+
Effect.withSpan('@livestore/adapter-node:client-session:getLeaderSyncState'),
|
|
493
|
+
),
|
|
494
|
+
changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerSyncStateStream()).pipe(Stream.orDie),
|
|
495
|
+
}),
|
|
430
496
|
sendDevtoolsMessage: (message) =>
|
|
431
497
|
runInWorker(new WorkerSchema.LeaderWorkerInnerExtraDevtoolsMessage({ message })).pipe(
|
|
432
498
|
UnexpectedError.mapToUnexpectedError,
|
|
433
499
|
Effect.withSpan('@livestore/adapter-node:client-session:devtoolsMessageForLeader'),
|
|
434
500
|
),
|
|
501
|
+
networkStatus: Subscribable.make({
|
|
502
|
+
get: runInWorker(new WorkerSchema.LeaderWorkerInnerGetNetworkStatus()).pipe(Effect.orDie),
|
|
503
|
+
changes: runInWorkerStream(new WorkerSchema.LeaderWorkerInnerNetworkStatusStream()).pipe(Stream.orDie),
|
|
504
|
+
}),
|
|
435
505
|
},
|
|
436
506
|
{
|
|
437
|
-
overrides: testing?.overrides?.clientSession?.leaderThreadProxy,
|
|
507
|
+
...omitUndefineds({ overrides: testing?.overrides?.clientSession?.leaderThreadProxy }),
|
|
438
508
|
},
|
|
439
509
|
)
|
|
440
510
|
|
|
@@ -17,7 +17,6 @@ import { sqliteDbFactory } from '@livestore/sqlite-wasm/node'
|
|
|
17
17
|
import {
|
|
18
18
|
Effect,
|
|
19
19
|
FetchHttpClient,
|
|
20
|
-
identity,
|
|
21
20
|
Layer,
|
|
22
21
|
Logger,
|
|
23
22
|
LogLevel,
|
|
@@ -60,6 +59,13 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
60
59
|
)
|
|
61
60
|
: undefined
|
|
62
61
|
|
|
62
|
+
// Merge the runtime dependencies once so we can provide them together without chaining Effect.provide.
|
|
63
|
+
const runtimeLayer = Layer.mergeAll(
|
|
64
|
+
FetchHttpClient.layer,
|
|
65
|
+
PlatformNode.NodeFileSystem.layer,
|
|
66
|
+
TracingLive ?? Layer.empty,
|
|
67
|
+
)
|
|
68
|
+
|
|
63
69
|
return WorkerRunner.layerSerialized(WorkerSchema.LeaderWorkerInnerRequest, {
|
|
64
70
|
InitialMessage: (args) =>
|
|
65
71
|
Effect.gen(function* () {
|
|
@@ -113,6 +119,21 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
113
119
|
UnexpectedError.mapToUnexpectedError,
|
|
114
120
|
Effect.withSpan('@livestore/adapter-node:worker:GetLeaderSyncState'),
|
|
115
121
|
),
|
|
122
|
+
SyncStateStream: () =>
|
|
123
|
+
Effect.gen(function* () {
|
|
124
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
125
|
+
return workerCtx.syncProcessor.syncState.changes
|
|
126
|
+
}).pipe(Stream.unwrapScoped),
|
|
127
|
+
GetNetworkStatus: () =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
130
|
+
return yield* workerCtx.networkStatus
|
|
131
|
+
}).pipe(UnexpectedError.mapToUnexpectedError, Effect.withSpan('@livestore/adapter-node:worker:GetNetworkStatus')),
|
|
132
|
+
NetworkStatusStream: () =>
|
|
133
|
+
Effect.gen(function* () {
|
|
134
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
135
|
+
return workerCtx.networkStatus.changes
|
|
136
|
+
}).pipe(Stream.unwrapScoped),
|
|
116
137
|
GetRecreateSnapshot: () =>
|
|
117
138
|
Effect.gen(function* () {
|
|
118
139
|
const workerCtx = yield* LeaderThreadCtx
|
|
@@ -159,9 +180,7 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
159
180
|
// TODO bring back with Effect 4 once it's easier to work with replacing loggers.
|
|
160
181
|
// 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
182
|
// 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,
|
|
183
|
+
Effect.provide(runtimeLayer),
|
|
165
184
|
Logger.withMinimumLogLevel(LogLevel.Debug),
|
|
166
185
|
)
|
|
167
186
|
}
|
package/src/worker-schema.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
BootStatus,
|
|
3
|
+
Devtools,
|
|
4
|
+
LeaderAheadError,
|
|
5
|
+
MigrationsReport,
|
|
6
|
+
SyncBackend,
|
|
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
|
|
|
@@ -7,6 +15,7 @@ export const WorkerArgv = Schema.parseJson(
|
|
|
7
15
|
clientId: Schema.String,
|
|
8
16
|
storeId: Schema.String,
|
|
9
17
|
sessionId: Schema.String,
|
|
18
|
+
extraArgs: Schema.UndefinedOr(Schema.JsonValue),
|
|
10
19
|
}),
|
|
11
20
|
)
|
|
12
21
|
|
|
@@ -158,6 +167,33 @@ export class LeaderWorkerInnerGetLeaderSyncState extends Schema.TaggedRequest<Le
|
|
|
158
167
|
},
|
|
159
168
|
) {}
|
|
160
169
|
|
|
170
|
+
export class LeaderWorkerInnerSyncStateStream extends Schema.TaggedRequest<LeaderWorkerInnerSyncStateStream>()(
|
|
171
|
+
'SyncStateStream',
|
|
172
|
+
{
|
|
173
|
+
payload: {},
|
|
174
|
+
success: SyncState.SyncState,
|
|
175
|
+
failure: UnexpectedError,
|
|
176
|
+
},
|
|
177
|
+
) {}
|
|
178
|
+
|
|
179
|
+
export class LeaderWorkerInnerGetNetworkStatus extends Schema.TaggedRequest<LeaderWorkerInnerGetNetworkStatus>()(
|
|
180
|
+
'GetNetworkStatus',
|
|
181
|
+
{
|
|
182
|
+
payload: {},
|
|
183
|
+
success: SyncBackend.NetworkStatus,
|
|
184
|
+
failure: UnexpectedError,
|
|
185
|
+
},
|
|
186
|
+
) {}
|
|
187
|
+
|
|
188
|
+
export class LeaderWorkerInnerNetworkStatusStream extends Schema.TaggedRequest<LeaderWorkerInnerNetworkStatusStream>()(
|
|
189
|
+
'NetworkStatusStream',
|
|
190
|
+
{
|
|
191
|
+
payload: {},
|
|
192
|
+
success: SyncBackend.NetworkStatus,
|
|
193
|
+
failure: UnexpectedError,
|
|
194
|
+
},
|
|
195
|
+
) {}
|
|
196
|
+
|
|
161
197
|
export class LeaderWorkerInnerShutdown extends Schema.TaggedRequest<LeaderWorkerInnerShutdown>()('Shutdown', {
|
|
162
198
|
payload: {},
|
|
163
199
|
success: Schema.Void,
|
|
@@ -185,6 +221,9 @@ export const LeaderWorkerInnerRequest = Schema.Union(
|
|
|
185
221
|
LeaderWorkerInnerExportEventlog,
|
|
186
222
|
LeaderWorkerInnerGetLeaderHead,
|
|
187
223
|
LeaderWorkerInnerGetLeaderSyncState,
|
|
224
|
+
LeaderWorkerInnerSyncStateStream,
|
|
225
|
+
LeaderWorkerInnerGetNetworkStatus,
|
|
226
|
+
LeaderWorkerInnerNetworkStatusStream,
|
|
188
227
|
LeaderWorkerInnerShutdown,
|
|
189
228
|
LeaderWorkerInnerExtraDevtoolsMessage,
|
|
190
229
|
)
|