@livestore/adapter-web 0.4.0-dev.2 → 0.4.0-dev.21
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/in-memory/in-memory-adapter.d.ts +49 -5
- package/dist/in-memory/in-memory-adapter.d.ts.map +1 -1
- package/dist/in-memory/in-memory-adapter.js +69 -16
- package/dist/in-memory/in-memory-adapter.js.map +1 -1
- package/dist/web-worker/client-session/client-session-devtools.d.ts +1 -1
- package/dist/web-worker/client-session/client-session-devtools.d.ts.map +1 -1
- package/dist/web-worker/client-session/client-session-devtools.js +14 -3
- package/dist/web-worker/client-session/client-session-devtools.js.map +1 -1
- package/dist/web-worker/client-session/persisted-adapter.d.ts +15 -0
- package/dist/web-worker/client-session/persisted-adapter.d.ts.map +1 -1
- package/dist/web-worker/client-session/persisted-adapter.js +68 -46
- package/dist/web-worker/client-session/persisted-adapter.js.map +1 -1
- package/dist/web-worker/client-session/sqlite-loader.d.ts +2 -0
- package/dist/web-worker/client-session/sqlite-loader.d.ts.map +1 -0
- package/dist/web-worker/client-session/sqlite-loader.js +16 -0
- package/dist/web-worker/client-session/sqlite-loader.js.map +1 -0
- package/dist/web-worker/common/persisted-sqlite.d.ts +23 -7
- package/dist/web-worker/common/persisted-sqlite.d.ts.map +1 -1
- package/dist/web-worker/common/persisted-sqlite.js +125 -76
- package/dist/web-worker/common/persisted-sqlite.js.map +1 -1
- package/dist/web-worker/common/shutdown-channel.d.ts +3 -2
- package/dist/web-worker/common/shutdown-channel.d.ts.map +1 -1
- package/dist/web-worker/common/shutdown-channel.js +2 -2
- package/dist/web-worker/common/shutdown-channel.js.map +1 -1
- package/dist/web-worker/common/worker-disconnect-channel.d.ts +2 -6
- package/dist/web-worker/common/worker-disconnect-channel.d.ts.map +1 -1
- package/dist/web-worker/common/worker-disconnect-channel.js +3 -2
- package/dist/web-worker/common/worker-disconnect-channel.js.map +1 -1
- package/dist/web-worker/common/worker-schema.d.ts +147 -56
- package/dist/web-worker/common/worker-schema.d.ts.map +1 -1
- package/dist/web-worker/common/worker-schema.js +55 -36
- package/dist/web-worker/common/worker-schema.js.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts +4 -2
- package/dist/web-worker/leader-worker/make-leader-worker.d.ts.map +1 -1
- package/dist/web-worker/leader-worker/make-leader-worker.js +63 -27
- package/dist/web-worker/leader-worker/make-leader-worker.js.map +1 -1
- package/dist/web-worker/shared-worker/make-shared-worker.d.ts +2 -1
- package/dist/web-worker/shared-worker/make-shared-worker.d.ts.map +1 -1
- package/dist/web-worker/shared-worker/make-shared-worker.js +66 -49
- package/dist/web-worker/shared-worker/make-shared-worker.js.map +1 -1
- package/dist/web-worker/vite-dev-polyfill.js +1 -0
- package/dist/web-worker/vite-dev-polyfill.js.map +1 -1
- package/package.json +8 -9
- package/src/in-memory/in-memory-adapter.ts +83 -21
- package/src/web-worker/ambient.d.ts +7 -24
- package/src/web-worker/client-session/client-session-devtools.ts +18 -3
- package/src/web-worker/client-session/persisted-adapter.ts +117 -59
- package/src/web-worker/client-session/sqlite-loader.ts +19 -0
- package/src/web-worker/common/persisted-sqlite.ts +225 -107
- package/src/web-worker/common/shutdown-channel.ts +10 -3
- package/src/web-worker/common/worker-disconnect-channel.ts +10 -3
- package/src/web-worker/common/worker-schema.ts +74 -35
- package/src/web-worker/leader-worker/make-leader-worker.ts +86 -41
- package/src/web-worker/shared-worker/make-shared-worker.ts +96 -75
- package/src/web-worker/vite-dev-polyfill.ts +1 -0
- package/dist/opfs-utils.d.ts +0 -5
- package/dist/opfs-utils.d.ts.map +0 -1
- package/dist/opfs-utils.js +0 -43
- package/dist/opfs-utils.js.map +0 -1
- package/src/opfs-utils.ts +0 -61
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { SqliteDb, SyncOptions } from '@livestore/common'
|
|
2
|
-
import { Devtools,
|
|
3
|
-
import type { DevtoolsOptions } from '@livestore/common/leader-thread'
|
|
4
|
-
import {
|
|
2
|
+
import { Devtools, LogConfig, UnknownError } from '@livestore/common'
|
|
3
|
+
import type { DevtoolsOptions, StreamEventsOptions } from '@livestore/common/leader-thread'
|
|
4
|
+
import {
|
|
5
|
+
configureConnection,
|
|
6
|
+
Eventlog,
|
|
7
|
+
LeaderThreadCtx,
|
|
8
|
+
makeLeaderThreadLayer,
|
|
9
|
+
streamEventsWithSyncState,
|
|
10
|
+
} from '@livestore/common/leader-thread'
|
|
5
11
|
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
6
12
|
import { LiveStoreEvent } from '@livestore/common/schema'
|
|
7
13
|
import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
|
|
@@ -10,37 +16,36 @@ import { loadSqlite3Wasm } from '@livestore/sqlite-wasm/load-wasm'
|
|
|
10
16
|
import { isDevEnv, LS_DEV } from '@livestore/utils'
|
|
11
17
|
import type { HttpClient, Scope, WorkerError } from '@livestore/utils/effect'
|
|
12
18
|
import {
|
|
13
|
-
BrowserWorkerRunner,
|
|
14
19
|
Effect,
|
|
15
20
|
FetchHttpClient,
|
|
16
21
|
identity,
|
|
17
22
|
Layer,
|
|
18
|
-
Logger,
|
|
19
|
-
LogLevel,
|
|
20
23
|
OtelTracer,
|
|
21
24
|
Scheduler,
|
|
25
|
+
type Schema,
|
|
22
26
|
Stream,
|
|
23
27
|
TaskTracing,
|
|
24
28
|
WorkerRunner,
|
|
25
29
|
} from '@livestore/utils/effect'
|
|
30
|
+
import { BrowserWorkerRunner, Opfs } from '@livestore/utils/effect/browser'
|
|
26
31
|
import type * as otel from '@opentelemetry/api'
|
|
27
32
|
|
|
28
|
-
import
|
|
29
|
-
import { getStateDbFileName, sanitizeOpfsDir } from '../common/persisted-sqlite.ts'
|
|
33
|
+
import { cleanupOldStateDbFiles, getStateDbFileName, sanitizeOpfsDir } from '../common/persisted-sqlite.ts'
|
|
30
34
|
import { makeShutdownChannel } from '../common/shutdown-channel.ts'
|
|
31
35
|
import * as WorkerSchema from '../common/worker-schema.ts'
|
|
32
36
|
|
|
33
37
|
export type WorkerOptions = {
|
|
34
38
|
schema: LiveStoreSchema
|
|
35
39
|
sync?: SyncOptions
|
|
40
|
+
syncPayloadSchema?: Schema.Schema<any>
|
|
36
41
|
otelOptions?: {
|
|
37
42
|
tracer?: otel.Tracer
|
|
38
43
|
}
|
|
39
|
-
}
|
|
44
|
+
} & LogConfig.WithLoggerOptions
|
|
40
45
|
|
|
41
46
|
if (isDevEnv()) {
|
|
42
47
|
globalThis.__debugLiveStoreUtils = {
|
|
43
|
-
opfs:
|
|
48
|
+
opfs: Opfs.debugUtils,
|
|
44
49
|
blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
|
|
45
50
|
URL.createObjectURL(new Blob([buffer], { type: 'application/octet-stream' })),
|
|
46
51
|
runSync: (effect: Effect.Effect<any, any, never>) => Effect.runSync(effect),
|
|
@@ -59,23 +64,23 @@ export const makeWorkerEffect = (options: WorkerOptions) => {
|
|
|
59
64
|
)
|
|
60
65
|
: undefined
|
|
61
66
|
|
|
67
|
+
const runtimeLayer = Layer.mergeAll(FetchHttpClient.layer, TracingLive ?? Layer.empty)
|
|
68
|
+
|
|
62
69
|
return makeWorkerRunnerOuter(options).pipe(
|
|
63
70
|
Layer.provide(BrowserWorkerRunner.layer),
|
|
64
71
|
WorkerRunner.launch,
|
|
65
72
|
Effect.scoped,
|
|
66
73
|
Effect.tapCauseLogPretty,
|
|
67
74
|
Effect.annotateLogs({ thread: self.name }),
|
|
68
|
-
Effect.provide(
|
|
69
|
-
Effect.provide(FetchHttpClient.layer),
|
|
75
|
+
Effect.provide(runtimeLayer),
|
|
70
76
|
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
71
|
-
TracingLive ? Effect.provide(TracingLive) : identity,
|
|
72
77
|
// We're using this custom scheduler to improve op batching behaviour and reduce the overhead
|
|
73
78
|
// of the Effect fiber runtime given we have different tradeoffs on a worker thread.
|
|
74
79
|
// Despite the "message channel" name, is has nothing to do with the `incomingRequestsPort` above.
|
|
75
80
|
Effect.withScheduler(Scheduler.messageChannel()),
|
|
76
81
|
// We're increasing the Effect ops limit here to allow for larger chunks of operations at a time
|
|
77
82
|
Effect.withMaxOpsBeforeYield(4096),
|
|
78
|
-
|
|
83
|
+
LogConfig.withLoggerConfig({ logger: options.logger, logLevel: options.logLevel }, { threadName: self.name }),
|
|
79
84
|
)
|
|
80
85
|
}
|
|
81
86
|
|
|
@@ -93,7 +98,12 @@ const makeWorkerRunnerOuter = (
|
|
|
93
98
|
Effect.withSpan('@livestore/adapter-web:worker:wrapper:InitialMessage:innerFiber'),
|
|
94
99
|
Effect.tapCauseLogPretty,
|
|
95
100
|
Effect.provide(
|
|
96
|
-
|
|
101
|
+
Layer.mergeAll(
|
|
102
|
+
Opfs.Opfs.Default,
|
|
103
|
+
WebmeshWorker.CacheService.layer({
|
|
104
|
+
nodeName: Devtools.makeNodeName.client.leader({ storeId, clientId }),
|
|
105
|
+
}),
|
|
106
|
+
),
|
|
97
107
|
),
|
|
98
108
|
Effect.forkScoped,
|
|
99
109
|
)
|
|
@@ -102,18 +112,19 @@ const makeWorkerRunnerOuter = (
|
|
|
102
112
|
}).pipe(Effect.withSpan('@livestore/adapter-web:worker:wrapper:InitialMessage'), Layer.unwrapScoped),
|
|
103
113
|
})
|
|
104
114
|
|
|
105
|
-
const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
115
|
+
const makeWorkerRunnerInner = ({ schema, sync: syncOptions, syncPayloadSchema }: WorkerOptions) =>
|
|
106
116
|
WorkerRunner.layerSerialized(WorkerSchema.LeaderWorkerInnerRequest, {
|
|
107
|
-
InitialMessage: ({ storageOptions, storeId, clientId, devtoolsEnabled, debugInstanceId,
|
|
117
|
+
InitialMessage: ({ storageOptions, storeId, clientId, devtoolsEnabled, debugInstanceId, syncPayloadEncoded }) =>
|
|
108
118
|
Effect.gen(function* () {
|
|
109
119
|
const sqlite3 = yield* Effect.promise(() => loadSqlite3Wasm())
|
|
110
120
|
const makeSqliteDb = sqliteDbFactory({ sqlite3 })
|
|
121
|
+
const opfsDirectory = yield* sanitizeOpfsDir(storageOptions.directory, storeId)
|
|
111
122
|
const runtime = yield* Effect.runtime<never>()
|
|
112
123
|
|
|
113
124
|
const makeDb = (kind: 'state' | 'eventlog') =>
|
|
114
125
|
makeSqliteDb({
|
|
115
126
|
_tag: 'opfs',
|
|
116
|
-
opfsDirectory
|
|
127
|
+
opfsDirectory,
|
|
117
128
|
fileName: kind === 'state' ? getStateDbFileName(schema) : 'eventlog.db',
|
|
118
129
|
configureDb: (db) =>
|
|
119
130
|
configureConnection(db, {
|
|
@@ -131,6 +142,16 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
131
142
|
concurrency: 2,
|
|
132
143
|
})
|
|
133
144
|
|
|
145
|
+
// Clean up old state database files after successful database creation
|
|
146
|
+
// This prevents OPFS file pool capacity exhaustion from accumulated state db files after schema changes/migrations
|
|
147
|
+
if (dbState.metadata._tag === 'opfs') {
|
|
148
|
+
yield* cleanupOldStateDbFiles({
|
|
149
|
+
vfs: dbState.metadata.vfs,
|
|
150
|
+
currentSchema: schema,
|
|
151
|
+
opfsDirectory: dbState.metadata.persistenceInfo.opfsDirectory,
|
|
152
|
+
})
|
|
153
|
+
}
|
|
154
|
+
|
|
134
155
|
const devtoolsOptions = yield* makeDevtoolsOptions({ devtoolsEnabled, dbState, dbEventlog })
|
|
135
156
|
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
136
157
|
|
|
@@ -144,11 +165,12 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
144
165
|
dbEventlog,
|
|
145
166
|
devtoolsOptions,
|
|
146
167
|
shutdownChannel,
|
|
147
|
-
|
|
168
|
+
syncPayloadEncoded,
|
|
169
|
+
syncPayloadSchema,
|
|
148
170
|
})
|
|
149
171
|
}).pipe(
|
|
150
172
|
Effect.tapCauseLogPretty,
|
|
151
|
-
|
|
173
|
+
UnknownError.mapToUnknownError,
|
|
152
174
|
Effect.withPerformanceMeasure('@livestore/adapter-web:worker:InitialMessage'),
|
|
153
175
|
Effect.withSpan('@livestore/adapter-web:worker:InitialMessage'),
|
|
154
176
|
Effect.annotateSpans({ debugInstanceId }),
|
|
@@ -166,13 +188,10 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
166
188
|
|
|
167
189
|
const snapshot = workerCtx.dbState.export()
|
|
168
190
|
return { snapshot, migrationsReport: workerCtx.initialState.migrationsReport }
|
|
169
|
-
}).pipe(
|
|
170
|
-
UnexpectedError.mapToUnexpectedError,
|
|
171
|
-
Effect.withSpan('@livestore/adapter-web:worker:GetRecreateSnapshot'),
|
|
172
|
-
),
|
|
191
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetRecreateSnapshot')),
|
|
173
192
|
PullStream: ({ cursor }) =>
|
|
174
193
|
Effect.gen(function* () {
|
|
175
|
-
const { syncProcessor } = yield* LeaderThreadCtx
|
|
194
|
+
const { syncProcessor } = yield* LeaderThreadCtx // <- syncState comes from here
|
|
176
195
|
return syncProcessor.pull({ cursor })
|
|
177
196
|
}).pipe(
|
|
178
197
|
Stream.unwrapScoped,
|
|
@@ -182,19 +201,33 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
182
201
|
PushToLeader: ({ batch }) =>
|
|
183
202
|
Effect.andThen(LeaderThreadCtx, ({ syncProcessor }) =>
|
|
184
203
|
syncProcessor.push(
|
|
185
|
-
batch.map((event) => new LiveStoreEvent.EncodedWithMeta(event)),
|
|
204
|
+
batch.map((event) => new LiveStoreEvent.Client.EncodedWithMeta(event)),
|
|
186
205
|
// We'll wait in order to keep back pressure on the client session
|
|
187
206
|
{ waitForProcessing: true },
|
|
188
207
|
),
|
|
189
208
|
).pipe(Effect.uninterruptible, Effect.withSpan('@livestore/adapter-web:worker:PushToLeader')),
|
|
209
|
+
StreamEvents: (options) =>
|
|
210
|
+
LeaderThreadCtx.pipe(
|
|
211
|
+
Effect.map(({ dbEventlog, syncProcessor }) => {
|
|
212
|
+
const { _tag: _ignored, ...payload } = options as any
|
|
213
|
+
const streamOptions = payload as StreamEventsOptions
|
|
214
|
+
return streamEventsWithSyncState({
|
|
215
|
+
dbEventlog,
|
|
216
|
+
syncState: syncProcessor.syncState,
|
|
217
|
+
options: streamOptions,
|
|
218
|
+
})
|
|
219
|
+
}),
|
|
220
|
+
Stream.unwrapScoped,
|
|
221
|
+
Stream.withSpan('@livestore/adapter-web:worker:StreamEvents'),
|
|
222
|
+
),
|
|
190
223
|
Export: () =>
|
|
191
224
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbState.export()).pipe(
|
|
192
|
-
|
|
225
|
+
UnknownError.mapToUnknownError,
|
|
193
226
|
Effect.withSpan('@livestore/adapter-web:worker:Export'),
|
|
194
227
|
),
|
|
195
228
|
ExportEventlog: () =>
|
|
196
229
|
Effect.andThen(LeaderThreadCtx, (_) => _.dbEventlog.export()).pipe(
|
|
197
|
-
|
|
230
|
+
UnknownError.mapToUnknownError,
|
|
198
231
|
Effect.withSpan('@livestore/adapter-web:worker:ExportEventlog'),
|
|
199
232
|
),
|
|
200
233
|
BootStatusStream: () =>
|
|
@@ -203,15 +236,27 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
203
236
|
Effect.gen(function* () {
|
|
204
237
|
const workerCtx = yield* LeaderThreadCtx
|
|
205
238
|
return Eventlog.getClientHeadFromDb(workerCtx.dbEventlog)
|
|
206
|
-
}).pipe(
|
|
239
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetLeaderHead')),
|
|
207
240
|
GetLeaderSyncState: () =>
|
|
208
241
|
Effect.gen(function* () {
|
|
209
242
|
const workerCtx = yield* LeaderThreadCtx
|
|
210
243
|
return yield* workerCtx.syncProcessor.syncState
|
|
211
|
-
}).pipe(
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
244
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetLeaderSyncState')),
|
|
245
|
+
SyncStateStream: () =>
|
|
246
|
+
Effect.gen(function* () {
|
|
247
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
248
|
+
return workerCtx.syncProcessor.syncState.changes
|
|
249
|
+
}).pipe(Stream.unwrapScoped),
|
|
250
|
+
GetNetworkStatus: () =>
|
|
251
|
+
Effect.gen(function* () {
|
|
252
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
253
|
+
return yield* workerCtx.networkStatus
|
|
254
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:GetNetworkStatus')),
|
|
255
|
+
NetworkStatusStream: () =>
|
|
256
|
+
Effect.gen(function* () {
|
|
257
|
+
const workerCtx = yield* LeaderThreadCtx
|
|
258
|
+
return workerCtx.networkStatus.changes
|
|
259
|
+
}).pipe(Stream.unwrapScoped),
|
|
215
260
|
Shutdown: () =>
|
|
216
261
|
Effect.gen(function* () {
|
|
217
262
|
yield* Effect.logDebug('[@livestore/adapter-web:worker] Shutdown')
|
|
@@ -219,10 +264,10 @@ const makeWorkerRunnerInner = ({ schema, sync: syncOptions }: WorkerOptions) =>
|
|
|
219
264
|
// Buy some time for Otel to flush
|
|
220
265
|
// TODO find a cleaner way to do this
|
|
221
266
|
yield* Effect.sleep(300)
|
|
222
|
-
}).pipe(
|
|
267
|
+
}).pipe(UnknownError.mapToUnknownError, Effect.withSpan('@livestore/adapter-web:worker:Shutdown')),
|
|
223
268
|
ExtraDevtoolsMessage: ({ message }) =>
|
|
224
269
|
Effect.andThen(LeaderThreadCtx, (_) => _.extraIncomingMessagesQueue.offer(message)).pipe(
|
|
225
|
-
|
|
270
|
+
UnknownError.mapToUnknownError,
|
|
226
271
|
Effect.withSpan('@livestore/adapter-web:worker:ExtraDevtoolsMessage'),
|
|
227
272
|
),
|
|
228
273
|
'DevtoolsWebCommon.CreateConnection': WebmeshWorker.CreateConnection,
|
|
@@ -236,7 +281,7 @@ const makeDevtoolsOptions = ({
|
|
|
236
281
|
devtoolsEnabled: boolean
|
|
237
282
|
dbState: SqliteDb
|
|
238
283
|
dbEventlog: SqliteDb
|
|
239
|
-
}): Effect.Effect<DevtoolsOptions,
|
|
284
|
+
}): Effect.Effect<DevtoolsOptions, UnknownError, Scope.Scope | WebmeshWorker.CacheService> =>
|
|
240
285
|
Effect.gen(function* () {
|
|
241
286
|
if (devtoolsEnabled === false) {
|
|
242
287
|
return { enabled: false }
|
|
@@ -246,13 +291,13 @@ const makeDevtoolsOptions = ({
|
|
|
246
291
|
|
|
247
292
|
return {
|
|
248
293
|
enabled: true,
|
|
249
|
-
boot: Effect.
|
|
250
|
-
|
|
294
|
+
boot: Effect.succeed({
|
|
295
|
+
node,
|
|
296
|
+
persistenceInfo: {
|
|
251
297
|
state: dbState.metadata.persistenceInfo,
|
|
252
298
|
eventlog: dbEventlog.metadata.persistenceInfo,
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return { node, persistenceInfo, mode: 'direct' }
|
|
299
|
+
},
|
|
300
|
+
mode: 'direct' as const,
|
|
256
301
|
}),
|
|
257
302
|
}
|
|
258
303
|
})
|
|
@@ -1,18 +1,14 @@
|
|
|
1
|
-
import { Devtools,
|
|
1
|
+
import { Devtools, LogConfig, liveStoreVersion, UnknownError } from '@livestore/common'
|
|
2
2
|
import * as DevtoolsWeb from '@livestore/devtools-web-common/web-channel'
|
|
3
3
|
import * as WebmeshWorker from '@livestore/devtools-web-common/worker'
|
|
4
4
|
import { isDevEnv, isNotUndefined, LS_DEV } from '@livestore/utils'
|
|
5
5
|
import {
|
|
6
|
-
BrowserWorker,
|
|
7
|
-
BrowserWorkerRunner,
|
|
8
6
|
Deferred,
|
|
9
7
|
Effect,
|
|
10
8
|
Exit,
|
|
11
9
|
FetchHttpClient,
|
|
12
10
|
identity,
|
|
13
11
|
Layer,
|
|
14
|
-
Logger,
|
|
15
|
-
LogLevel,
|
|
16
12
|
ParseResult,
|
|
17
13
|
Ref,
|
|
18
14
|
Schema,
|
|
@@ -24,10 +20,25 @@ import {
|
|
|
24
20
|
WorkerError,
|
|
25
21
|
WorkerRunner,
|
|
26
22
|
} from '@livestore/utils/effect'
|
|
23
|
+
import { BrowserWorker, BrowserWorkerRunner } from '@livestore/utils/effect/browser'
|
|
27
24
|
|
|
28
25
|
import { makeShutdownChannel } from '../common/shutdown-channel.ts'
|
|
29
26
|
import * as WorkerSchema from '../common/worker-schema.ts'
|
|
30
27
|
|
|
28
|
+
// Extract from `livestore-shared-worker-${storeId}`
|
|
29
|
+
const storeId = self.name.replace('livestore-shared-worker-', '')
|
|
30
|
+
|
|
31
|
+
// We acquire a lock that is held as long as this shared worker is alive.
|
|
32
|
+
// This way, when the shared worker is terminated (e.g. by the browser when the page is closed),
|
|
33
|
+
// the lock is released and any thread waiting for the lock can be notified.
|
|
34
|
+
const LIVESTORE_SHARED_WORKER_TERMINATION_LOCK = `livestore-shared-worker-termination-lock-${storeId}`
|
|
35
|
+
navigator.locks.request(
|
|
36
|
+
LIVESTORE_SHARED_WORKER_TERMINATION_LOCK,
|
|
37
|
+
{ steal: true },
|
|
38
|
+
// We use a never-resolving promise to hold the lock
|
|
39
|
+
async () => new Promise(() => {}),
|
|
40
|
+
)
|
|
41
|
+
|
|
31
42
|
if (isDevEnv()) {
|
|
32
43
|
globalThis.__debugLiveStoreUtils = {
|
|
33
44
|
blobUrl: (buffer: Uint8Array<ArrayBuffer>) =>
|
|
@@ -46,22 +57,21 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
46
57
|
| undefined
|
|
47
58
|
>(undefined)
|
|
48
59
|
|
|
49
|
-
const initialMessagePayloadDeferredRef = yield* Deferred.make<
|
|
50
|
-
typeof WorkerSchema.SharedWorkerInitialMessagePayloadFromClientSession.Type
|
|
51
|
-
>().pipe(Effect.andThen(Ref.make))
|
|
52
|
-
|
|
53
60
|
const waitForWorker = SubscriptionRef.waitUntil(leaderWorkerContextSubRef, isNotUndefined).pipe(
|
|
54
61
|
Effect.map((_) => _.worker),
|
|
55
62
|
)
|
|
56
63
|
|
|
57
64
|
const forwardRequest = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
|
|
58
65
|
req: TReq,
|
|
59
|
-
):
|
|
60
|
-
|
|
61
|
-
|
|
66
|
+
): Effect.Effect<
|
|
67
|
+
Schema.WithResult.Success<TReq>,
|
|
68
|
+
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
69
|
+
Schema.WithResult.Context<TReq>
|
|
70
|
+
> =>
|
|
71
|
+
// Forward the request to the active worker and normalize platform errors into UnknownError.
|
|
62
72
|
waitForWorker.pipe(
|
|
63
73
|
// Effect.logBefore(`forwardRequest: ${req._tag}`),
|
|
64
|
-
Effect.andThen((worker) => worker.executeEffect(req) as Effect.Effect<unknown, unknown,
|
|
74
|
+
Effect.andThen((worker) => worker.executeEffect(req) as Effect.Effect<unknown, unknown, unknown>),
|
|
65
75
|
// Effect.tap((_) => Effect.log(`forwardRequest: ${req._tag}`, _)),
|
|
66
76
|
// Effect.tapError((cause) => Effect.logError(`forwardRequest err: ${req._tag}`, cause)),
|
|
67
77
|
Effect.interruptible,
|
|
@@ -70,25 +80,31 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
70
80
|
duration: 500,
|
|
71
81
|
}),
|
|
72
82
|
Effect.mapError((cause) =>
|
|
73
|
-
Schema.is(
|
|
83
|
+
Schema.is(UnknownError)(cause)
|
|
74
84
|
? cause
|
|
75
85
|
: ParseResult.isParseError(cause) || Schema.is(WorkerError.WorkerError)(cause)
|
|
76
|
-
? new
|
|
86
|
+
? new UnknownError({ cause })
|
|
77
87
|
: cause,
|
|
78
88
|
),
|
|
79
|
-
Effect.catchAllDefect((cause) => new
|
|
89
|
+
Effect.catchAllDefect((cause) => new UnknownError({ cause })),
|
|
80
90
|
Effect.tapCauseLogPretty,
|
|
81
|
-
) as
|
|
91
|
+
) as Effect.Effect<
|
|
92
|
+
Schema.WithResult.Success<TReq>,
|
|
93
|
+
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
94
|
+
Schema.WithResult.Context<TReq>
|
|
95
|
+
>
|
|
82
96
|
|
|
83
97
|
const forwardRequestStream = <TReq extends WorkerSchema.LeaderWorkerInnerRequest>(
|
|
84
98
|
req: TReq,
|
|
85
|
-
):
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
): Stream.Stream<
|
|
100
|
+
Schema.WithResult.Success<TReq>,
|
|
101
|
+
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
102
|
+
Schema.WithResult.Context<TReq>
|
|
103
|
+
> =>
|
|
88
104
|
Effect.gen(function* () {
|
|
89
105
|
yield* Effect.logDebug(`forwardRequestStream: ${req._tag}`)
|
|
90
106
|
const { worker, scope } = yield* SubscriptionRef.waitUntil(leaderWorkerContextSubRef, isNotUndefined)
|
|
91
|
-
const stream = worker.execute(req) as Stream.Stream<unknown, unknown,
|
|
107
|
+
const stream = worker.execute(req) as Stream.Stream<unknown, unknown, unknown>
|
|
92
108
|
|
|
93
109
|
// It seems the request stream is not automatically interrupted when the scope shuts down
|
|
94
110
|
// so we need to manually interrupt it when the scope shuts down
|
|
@@ -104,12 +120,16 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
104
120
|
return Stream.merge(stream, scopeShutdownStream, { haltStrategy: 'either' })
|
|
105
121
|
}).pipe(
|
|
106
122
|
Effect.interruptible,
|
|
107
|
-
|
|
123
|
+
UnknownError.mapToUnknownError,
|
|
108
124
|
Effect.tapCauseLogPretty,
|
|
109
125
|
Stream.unwrap,
|
|
110
126
|
Stream.ensuring(Effect.logDebug(`shutting down stream for ${req._tag}`)),
|
|
111
|
-
|
|
112
|
-
) as
|
|
127
|
+
UnknownError.mapToUnknownErrorStream,
|
|
128
|
+
) as Stream.Stream<
|
|
129
|
+
Schema.WithResult.Success<TReq>,
|
|
130
|
+
UnknownError | Schema.WithResult.Failure<TReq>,
|
|
131
|
+
Schema.WithResult.Context<TReq>
|
|
132
|
+
>
|
|
113
133
|
|
|
114
134
|
const resetCurrentWorkerCtx = Effect.gen(function* () {
|
|
115
135
|
const prevWorker = yield* SubscriptionRef.get(leaderWorkerContextSubRef)
|
|
@@ -128,62 +148,59 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
128
148
|
}
|
|
129
149
|
}).pipe(Effect.withSpan('@livestore/adapter-web:shared-worker:resetCurrentWorkerCtx'))
|
|
130
150
|
|
|
131
|
-
// const devtoolsWebBridge = yield* makeDevtoolsWebBridge
|
|
132
|
-
|
|
133
151
|
const reset = Effect.gen(function* () {
|
|
134
152
|
yield* Effect.logDebug('reset')
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
yield* Ref.set(initialMessagePayloadDeferredRef, initialMessagePayloadDeferred)
|
|
139
|
-
|
|
153
|
+
// Clear cached invariants so a fresh configuration can be accepted after shutdown
|
|
154
|
+
yield* Ref.set(invariantsRef, undefined)
|
|
155
|
+
// Tear down current leader worker context
|
|
140
156
|
yield* resetCurrentWorkerCtx
|
|
141
|
-
// yield* devtoolsWebBridge.reset
|
|
142
157
|
})
|
|
143
158
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const messageSchema = WorkerSchema.LeaderWorkerInnerInitialMessage.pipe(
|
|
156
|
-
Schema.omit('devtoolsEnabled', 'debugInstanceId'),
|
|
157
|
-
)
|
|
158
|
-
const isEqual = Schema.equivalence(messageSchema)
|
|
159
|
-
if (isEqual(initialMessage, previousInitialMessage.initialMessage) === false) {
|
|
160
|
-
const diff = Schema.debugDiff(messageSchema)(previousInitialMessage.initialMessage, initialMessage)
|
|
159
|
+
// Cache first-applied invariants to enforce stability across leader transitions
|
|
160
|
+
const InvariantsSchema = Schema.Struct({
|
|
161
|
+
storeId: Schema.String,
|
|
162
|
+
storageOptions: WorkerSchema.StorageType,
|
|
163
|
+
syncPayloadEncoded: Schema.UndefinedOr(Schema.JsonValue),
|
|
164
|
+
liveStoreVersion: Schema.Literal(liveStoreVersion),
|
|
165
|
+
devtoolsEnabled: Schema.Boolean,
|
|
166
|
+
})
|
|
167
|
+
type Invariants = typeof InvariantsSchema.Type
|
|
168
|
+
const invariantsRef = yield* Ref.make<Invariants | undefined>(undefined)
|
|
169
|
+
const sameInvariants = Schema.equivalence(InvariantsSchema)
|
|
161
170
|
|
|
162
|
-
|
|
163
|
-
cause: 'Initial message already sent and was different now',
|
|
164
|
-
payload: {
|
|
165
|
-
diff,
|
|
166
|
-
previousInitialMessage: previousInitialMessage.initialMessage,
|
|
167
|
-
newInitialMessage: initialMessage,
|
|
168
|
-
},
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
} else {
|
|
172
|
-
yield* Deferred.succeed(initialMessagePayloadDeferred, message.payload)
|
|
173
|
-
}
|
|
174
|
-
}),
|
|
171
|
+
return WorkerRunner.layerSerialized(WorkerSchema.SharedWorkerRequest, {
|
|
175
172
|
// Whenever the client session leader changes (and thus creates a new leader thread), the new client session leader
|
|
176
173
|
// sends a new MessagePort to the shared worker which proxies messages to the new leader thread.
|
|
177
|
-
UpdateMessagePort: ({ port }) =>
|
|
174
|
+
UpdateMessagePort: ({ port, initial, liveStoreVersion: clientLiveStoreVersion }) =>
|
|
178
175
|
Effect.gen(function* () {
|
|
179
|
-
|
|
176
|
+
// Enforce invariants: storeId, storageOptions, syncPayloadEncoded, liveStoreVersion must remain stable
|
|
177
|
+
const invariants: Invariants = {
|
|
178
|
+
storeId: initial.storeId,
|
|
179
|
+
storageOptions: initial.storageOptions,
|
|
180
|
+
syncPayloadEncoded: initial.syncPayloadEncoded,
|
|
181
|
+
liveStoreVersion: clientLiveStoreVersion,
|
|
182
|
+
devtoolsEnabled: initial.devtoolsEnabled,
|
|
183
|
+
}
|
|
184
|
+
const prev = yield* Ref.get(invariantsRef)
|
|
185
|
+
// Early return on mismatch to keep happy path linear
|
|
186
|
+
if (prev !== undefined && !sameInvariants(prev, invariants)) {
|
|
187
|
+
const diff = Schema.debugDiff(InvariantsSchema)(prev, invariants)
|
|
188
|
+
return yield* new UnknownError({
|
|
189
|
+
cause: 'Store invariants changed across leader transitions',
|
|
190
|
+
payload: { diff, previous: prev, next: invariants },
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
// First writer records invariants
|
|
194
|
+
if (prev === undefined) {
|
|
195
|
+
yield* Ref.set(invariantsRef, invariants)
|
|
196
|
+
}
|
|
180
197
|
|
|
181
198
|
yield* resetCurrentWorkerCtx
|
|
182
199
|
|
|
183
200
|
const scope = yield* Scope.make()
|
|
184
201
|
|
|
185
202
|
yield* Effect.gen(function* () {
|
|
186
|
-
const shutdownChannel = yield* makeShutdownChannel(
|
|
203
|
+
const shutdownChannel = yield* makeShutdownChannel(initial.storeId)
|
|
187
204
|
|
|
188
205
|
yield* shutdownChannel.listen.pipe(
|
|
189
206
|
Stream.flatten(),
|
|
@@ -198,7 +215,7 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
198
215
|
const worker = yield* Worker.makePoolSerialized<WorkerSchema.LeaderWorkerInnerRequest>({
|
|
199
216
|
size: 1,
|
|
200
217
|
concurrency: 100,
|
|
201
|
-
initialMessage: () =>
|
|
218
|
+
initialMessage: () => initial,
|
|
202
219
|
}).pipe(
|
|
203
220
|
Effect.provide(workerLayer),
|
|
204
221
|
Effect.withSpan('@livestore/adapter-web:shared-worker:makeWorkerProxyFromPort'),
|
|
@@ -206,7 +223,7 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
206
223
|
|
|
207
224
|
// Prepare the web mesh connection for leader worker to be able to connect to the devtools
|
|
208
225
|
const { node } = yield* WebmeshWorker.CacheService
|
|
209
|
-
const { storeId, clientId } =
|
|
226
|
+
const { storeId, clientId } = initial
|
|
210
227
|
|
|
211
228
|
yield* DevtoolsWeb.connectViaWorker({
|
|
212
229
|
node,
|
|
@@ -218,7 +235,7 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
218
235
|
}).pipe(Effect.tapCauseLogPretty, Scope.extend(scope), Effect.forkIn(scope))
|
|
219
236
|
}).pipe(
|
|
220
237
|
Effect.withSpan('@livestore/adapter-web:shared-worker:updateMessagePort'),
|
|
221
|
-
|
|
238
|
+
UnknownError.mapToUnknownError,
|
|
222
239
|
Effect.tapCauseLogPretty,
|
|
223
240
|
),
|
|
224
241
|
|
|
@@ -226,12 +243,16 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
226
243
|
BootStatusStream: forwardRequestStream,
|
|
227
244
|
PushToLeader: forwardRequest,
|
|
228
245
|
PullStream: forwardRequestStream,
|
|
246
|
+
StreamEvents: forwardRequestStream,
|
|
229
247
|
Export: forwardRequest,
|
|
230
248
|
GetRecreateSnapshot: forwardRequest,
|
|
231
249
|
ExportEventlog: forwardRequest,
|
|
232
250
|
Setup: forwardRequest,
|
|
233
251
|
GetLeaderSyncState: forwardRequest,
|
|
252
|
+
SyncStateStream: forwardRequestStream,
|
|
234
253
|
GetLeaderHead: forwardRequest,
|
|
254
|
+
GetNetworkStatus: forwardRequest,
|
|
255
|
+
NetworkStatusStream: forwardRequestStream,
|
|
235
256
|
Shutdown: forwardRequest,
|
|
236
257
|
ExtraDevtoolsMessage: forwardRequest,
|
|
237
258
|
|
|
@@ -240,9 +261,11 @@ const makeWorkerRunner = Effect.gen(function* () {
|
|
|
240
261
|
})
|
|
241
262
|
}).pipe(Layer.unwrapScoped)
|
|
242
263
|
|
|
243
|
-
export const makeWorker = () => {
|
|
244
|
-
|
|
245
|
-
|
|
264
|
+
export const makeWorker = (options?: LogConfig.WithLoggerOptions): void => {
|
|
265
|
+
const runtimeLayer = Layer.mergeAll(
|
|
266
|
+
FetchHttpClient.layer,
|
|
267
|
+
WebmeshWorker.CacheService.layer({ nodeName: DevtoolsWeb.makeNodeName.sharedWorker({ storeId }) }),
|
|
268
|
+
)
|
|
246
269
|
|
|
247
270
|
makeWorkerRunner.pipe(
|
|
248
271
|
Layer.provide(BrowserWorkerRunner.layer),
|
|
@@ -251,13 +274,11 @@ export const makeWorker = () => {
|
|
|
251
274
|
Effect.scoped,
|
|
252
275
|
Effect.tapCauseLogPretty,
|
|
253
276
|
Effect.annotateLogs({ thread: self.name }),
|
|
254
|
-
Effect.provide(
|
|
255
|
-
Effect.provide(FetchHttpClient.layer),
|
|
256
|
-
Effect.provide(WebmeshWorker.CacheService.layer({ nodeName: DevtoolsWeb.makeNodeName.sharedWorker({ storeId }) })),
|
|
277
|
+
Effect.provide(runtimeLayer),
|
|
257
278
|
LS_DEV ? TaskTracing.withAsyncTaggingTracing((name) => (console as any).createTask(name)) : identity,
|
|
258
279
|
// TODO remove type-cast (currently needed to silence a tsc bug)
|
|
259
280
|
(_) => _ as any as Effect.Effect<void, any>,
|
|
260
|
-
|
|
281
|
+
LogConfig.withLoggerConfig(options, { threadName: self.name }),
|
|
261
282
|
Effect.runFork,
|
|
262
283
|
)
|
|
263
284
|
}
|
|
@@ -5,6 +5,7 @@ globalThis.$RefreshReg$ = () => {}
|
|
|
5
5
|
// @ts-expect-error TODO remove when Vite does proper treeshaking during dev
|
|
6
6
|
globalThis.$RefreshSig$ = () => (type: any) => type
|
|
7
7
|
|
|
8
|
+
// biome-ignore lint/suspicious/noTsIgnore: sometimes @types/node is there, sometimes not.
|
|
8
9
|
// @ts-ignore
|
|
9
10
|
globalThis.process = globalThis.process ?? { env: {} }
|
|
10
11
|
|
package/dist/opfs-utils.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export declare const rootHandlePromise: Promise<FileSystemDirectoryHandle>;
|
|
2
|
-
export declare const getDirHandle: (absDirPath: string | undefined) => Promise<FileSystemDirectoryHandle>;
|
|
3
|
-
export declare const printTree: (directoryHandle_?: FileSystemDirectoryHandle | Promise<FileSystemDirectoryHandle>, depth?: number, prefix?: string) => Promise<void>;
|
|
4
|
-
export declare const deleteAll: (directoryHandle: FileSystemDirectoryHandle) => Promise<void>;
|
|
5
|
-
//# sourceMappingURL=opfs-utils.d.ts.map
|
package/dist/opfs-utils.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"opfs-utils.d.ts","sourceRoot":"","sources":["../src/opfs-utils.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,iBAAiB,oCAYQ,CAAA;AAEtC,eAAO,MAAM,YAAY,GAAU,YAAY,MAAM,GAAG,SAAS,uCAWhE,CAAA;AAED,eAAO,MAAM,SAAS,GACpB,mBAAkB,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAqB,EACpG,QAAO,MAAiC,EACxC,eAAW,KACV,OAAO,CAAC,IAAI,CAgBd,CAAA;AAED,eAAO,MAAM,SAAS,GAAU,iBAAiB,yBAAyB,kBAMzE,CAAA"}
|
package/dist/opfs-utils.js
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// NOTE we're already firing off this promise call here since we'll need it anyway and need it cached
|
|
2
|
-
import { prettyBytes } from '@livestore/utils';
|
|
3
|
-
// To improve LiveStore compatibility with e.g. Node.js we're guarding for `navigator` / `navigator.storage` to be defined.
|
|
4
|
-
export const rootHandlePromise = typeof navigator === 'undefined' || navigator.storage === undefined
|
|
5
|
-
? // We're using a proxy here to make the promise reject lazy
|
|
6
|
-
new Proxy({}, {
|
|
7
|
-
get: () => Promise.reject(new Error(`Can't get OPFS root handle in this environment as navigator.storage is undefined`)),
|
|
8
|
-
})
|
|
9
|
-
: navigator.storage.getDirectory();
|
|
10
|
-
export const getDirHandle = async (absDirPath) => {
|
|
11
|
-
const rootHandle = await rootHandlePromise;
|
|
12
|
-
if (absDirPath === undefined)
|
|
13
|
-
return rootHandle;
|
|
14
|
-
let dirHandle = rootHandle;
|
|
15
|
-
const directoryStack = absDirPath?.split('/').filter(Boolean);
|
|
16
|
-
while (directoryStack.length > 0) {
|
|
17
|
-
dirHandle = await dirHandle.getDirectoryHandle(directoryStack.shift());
|
|
18
|
-
}
|
|
19
|
-
return dirHandle;
|
|
20
|
-
};
|
|
21
|
-
export const printTree = async (directoryHandle_ = rootHandlePromise, depth = Number.POSITIVE_INFINITY, prefix = '') => {
|
|
22
|
-
if (depth < 0)
|
|
23
|
-
return;
|
|
24
|
-
const directoryHandle = await directoryHandle_;
|
|
25
|
-
const entries = directoryHandle.values();
|
|
26
|
-
for await (const entry of entries) {
|
|
27
|
-
const isDirectory = entry.kind === 'directory';
|
|
28
|
-
const size = entry.kind === 'file' ? await entry.getFile().then((file) => prettyBytes(file.size)) : undefined;
|
|
29
|
-
console.log(`${prefix}${isDirectory ? '📁' : '📄'} ${entry.name} ${size ? `(${size})` : ''}`);
|
|
30
|
-
if (isDirectory) {
|
|
31
|
-
const nestedDirectoryHandle = await directoryHandle.getDirectoryHandle(entry.name);
|
|
32
|
-
await printTree(nestedDirectoryHandle, depth - 1, `${prefix} `);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
export const deleteAll = async (directoryHandle) => {
|
|
37
|
-
if (directoryHandle.kind !== 'directory')
|
|
38
|
-
return;
|
|
39
|
-
for await (const entryName of directoryHandle.keys()) {
|
|
40
|
-
await directoryHandle.removeEntry(entryName, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//# sourceMappingURL=opfs-utils.js.map
|
package/dist/opfs-utils.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"opfs-utils.js","sourceRoot":"","sources":["../src/opfs-utils.ts"],"names":[],"mappings":"AAAA,qGAAqG;AAErG,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAA;AAE9C,2HAA2H;AAC3H,MAAM,CAAC,MAAM,iBAAiB,GAC5B,OAAO,SAAS,KAAK,WAAW,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS;IACjE,CAAC,CAAC,2DAA2D;QAC1D,IAAI,KAAK,CACR,EAAE,EACF;YACE,GAAG,EAAE,GAAG,EAAE,CACR,OAAO,CAAC,MAAM,CACZ,IAAI,KAAK,CAAC,kFAAkF,CAAC,CAC9F;SACJ,CACQ;IACb,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;AAEtC,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,UAA8B,EAAE,EAAE;IACnE,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAA;IAC1C,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,CAAA;IAE/C,IAAI,SAAS,GAAG,UAAU,CAAA;IAC1B,MAAM,cAAc,GAAG,UAAU,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;IAC7D,OAAO,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,SAAS,GAAG,MAAM,SAAS,CAAC,kBAAkB,CAAC,cAAc,CAAC,KAAK,EAAG,CAAC,CAAA;IACzE,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAC5B,mBAAmF,iBAAiB,EACpG,QAAgB,MAAM,CAAC,iBAAiB,EACxC,MAAM,GAAG,EAAE,EACI,EAAE;IACjB,IAAI,KAAK,GAAG,CAAC;QAAE,OAAM;IAErB,MAAM,eAAe,GAAG,MAAM,gBAAgB,CAAA;IAC9C,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,EAAE,CAAA;IAExC,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAClC,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,KAAK,WAAW,CAAA;QAC9C,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAC7G,OAAO,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;QAE7F,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,qBAAqB,GAAG,MAAM,eAAe,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAClF,MAAM,SAAS,CAAC,qBAAqB,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,MAAM,IAAI,CAAC,CAAA;QAClE,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,eAA0C,EAAE,EAAE;IAC5E,IAAI,eAAe,CAAC,IAAI,KAAK,WAAW;QAAE,OAAM;IAEhD,IAAI,KAAK,EAAE,MAAM,SAAS,IAAI,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC;QACrD,MAAM,eAAe,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACnE,CAAC;AACH,CAAC,CAAA"}
|