@livestore/adapter-expo 0.0.0-snapshot-a39a3b63b870a2878f22f82ce2979a6989076053 → 0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0
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/index.d.ts +15 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +129 -151
- package/dist/index.js.map +1 -1
- package/dist/make-sqlite-db.d.ts +20 -0
- package/dist/make-sqlite-db.d.ts.map +1 -0
- package/dist/make-sqlite-db.js +162 -0
- package/dist/make-sqlite-db.js.map +1 -0
- package/dist/shutdown-channel.d.ts +3 -0
- package/dist/shutdown-channel.d.ts.map +1 -0
- package/dist/shutdown-channel.js +8 -0
- package/dist/shutdown-channel.js.map +1 -0
- package/package.json +11 -6
- package/src/index.ts +248 -218
- package/src/make-sqlite-db.ts +199 -0
- package/src/shutdown-channel.ts +9 -0
- package/tsconfig.json +6 -1
- package/dist/common.d.ts +0 -13
- package/dist/common.d.ts.map +0 -1
- package/dist/common.js +0 -100
- package/dist/common.js.map +0 -1
- package/dist/devtools.d.ts +0 -22
- package/dist/devtools.d.ts.map +0 -1
- package/dist/devtools.js +0 -177
- package/dist/devtools.js.map +0 -1
- package/src/common.ts +0 -115
- package/src/devtools.ts +0 -292
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@livestore/adapter-expo",
|
|
3
|
-
"version": "0.0.0-snapshot-
|
|
3
|
+
"version": "0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"exports": {
|
|
@@ -12,22 +12,27 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts",
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@opentelemetry/api": "1.9.0",
|
|
15
|
-
"@livestore/common": "0.0.0-snapshot-
|
|
16
|
-
"@livestore/devtools-expo-common": "0.0.0-snapshot-
|
|
17
|
-
"@livestore/utils": "0.0.0-snapshot-
|
|
15
|
+
"@livestore/common": "0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0",
|
|
16
|
+
"@livestore/devtools-expo-common": "0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0",
|
|
17
|
+
"@livestore/utils": "0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0",
|
|
18
|
+
"@livestore/webmesh": "0.0.0-snapshot-97ca7eac46b6a583b22d40189126d06a377ec1b0"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
21
|
"expo-file-system": "*",
|
|
21
|
-
"expo-sqlite": "^15.
|
|
22
|
+
"expo-sqlite": "^15.1.2"
|
|
22
23
|
},
|
|
23
24
|
"peerDependencies": {
|
|
24
25
|
"expo-file-system": "*",
|
|
25
|
-
"expo-sqlite": "~15.
|
|
26
|
+
"expo-sqlite": "~15.1.2"
|
|
26
27
|
},
|
|
27
28
|
"publishConfig": {
|
|
28
29
|
"access": "public"
|
|
29
30
|
},
|
|
31
|
+
"scripts_": {
|
|
32
|
+
"postinstall": "This is needed to avoid a Expo/PNPM related issue"
|
|
33
|
+
},
|
|
30
34
|
"scripts": {
|
|
35
|
+
"postinstall": "rm -f node_modules/expo-sqlite",
|
|
31
36
|
"test": "echo No tests yet"
|
|
32
37
|
}
|
|
33
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,246 +1,112 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
rehydrateFromMutationLog,
|
|
9
|
-
sql,
|
|
10
|
-
UnexpectedError,
|
|
1
|
+
import type {
|
|
2
|
+
Adapter,
|
|
3
|
+
BootStatus,
|
|
4
|
+
ClientSession,
|
|
5
|
+
ClientSessionLeaderThreadProxy,
|
|
6
|
+
LockStatus,
|
|
7
|
+
SyncOptions,
|
|
11
8
|
} from '@livestore/common'
|
|
12
|
-
import
|
|
13
|
-
import type {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
9
|
+
import { Devtools, liveStoreStorageFormatVersion, UnexpectedError } from '@livestore/common'
|
|
10
|
+
import type { DevtoolsOptions, LeaderSqliteDb } from '@livestore/common/leader-thread'
|
|
11
|
+
import { getClientHeadFromDb, LeaderThreadCtx, makeLeaderThreadLayer } from '@livestore/common/leader-thread'
|
|
12
|
+
import type { LiveStoreSchema } from '@livestore/common/schema'
|
|
13
|
+
import { MutationEvent } from '@livestore/common/schema'
|
|
14
|
+
import {
|
|
15
|
+
makeChannelForConnectedMeshNode,
|
|
16
|
+
makeExpoDevtoolsConnectedMeshNode,
|
|
17
|
+
} from '@livestore/devtools-expo-common/web-channel'
|
|
18
|
+
import type { Scope } from '@livestore/utils/effect'
|
|
19
|
+
import { Cause, Effect, FetchHttpClient, Fiber, Layer, Queue, Stream, SubscriptionRef } from '@livestore/utils/effect'
|
|
20
|
+
import type { MeshNode } from '@livestore/webmesh'
|
|
18
21
|
import * as SQLite from 'expo-sqlite'
|
|
19
22
|
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import {
|
|
23
|
+
import type { MakeExpoSqliteDb } from './make-sqlite-db.js'
|
|
24
|
+
import { makeSqliteDb } from './make-sqlite-db.js'
|
|
25
|
+
import { makeShutdownChannel } from './shutdown-channel.js'
|
|
23
26
|
|
|
24
27
|
export type MakeDbOptions = {
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
sync?: SyncOptions
|
|
29
|
+
storage?: {
|
|
30
|
+
/**
|
|
31
|
+
* Relative to expo-sqlite's default directory
|
|
32
|
+
*
|
|
33
|
+
* Example of a resulting path for `subDirectory: 'my-app'`:
|
|
34
|
+
* `/data/Containers/Data/Application/<APP_UUID>/Documents/ExponentExperienceData/@<USERNAME>/<APPNAME>/SQLite/my-app/<STORE_ID>/livestore-mutationlog@3.db`
|
|
35
|
+
*/
|
|
36
|
+
subDirectory?: string
|
|
37
|
+
}
|
|
27
38
|
// syncBackend?: TODO
|
|
39
|
+
/** @default 'expo' */
|
|
40
|
+
clientId?: string
|
|
41
|
+
/** @default 'expo' */
|
|
42
|
+
sessionId?: string
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
// TODO refactor with leader-thread code from `@livestore/common/leader-thread`
|
|
31
46
|
export const makeAdapter =
|
|
32
|
-
(options
|
|
33
|
-
({ schema, connectDevtoolsToStore, shutdown, devtoolsEnabled }) =>
|
|
47
|
+
(options: MakeDbOptions = {}): Adapter =>
|
|
48
|
+
({ schema, connectDevtoolsToStore, shutdown, devtoolsEnabled, storeId, bootStatusQueue }) =>
|
|
34
49
|
Effect.gen(function* () {
|
|
35
|
-
const {
|
|
36
|
-
const migrationOptions = schema.migrationOptions
|
|
37
|
-
const subDirectoryPath = subDirectory ? subDirectory.replace(/\/$/, '') + '/' : ''
|
|
38
|
-
const fullDbFilePath = `${subDirectoryPath}${fileNamePrefix ?? 'livestore-'}${schema.hash}@${liveStoreStorageFormatVersion}.db`
|
|
39
|
-
const db = SQLite.openDatabaseSync(fullDbFilePath)
|
|
40
|
-
|
|
41
|
-
const dbRef = { current: { db, sqliteDb: makeSqliteDb(db) } }
|
|
42
|
-
|
|
43
|
-
const dbWasEmptyWhenOpened = dbRef.current.sqliteDb.select('SELECT 1 FROM sqlite_master').length === 0
|
|
44
|
-
|
|
45
|
-
const dbMutationLog = SQLite.openDatabaseSync(
|
|
46
|
-
`${subDirectory ?? ''}${fileNamePrefix ?? 'livestore-'}mutationlog@${liveStoreStorageFormatVersion}.db`,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
const dbMutationLogRef = { current: { db: dbMutationLog, sqliteDb: makeSqliteDb(dbMutationLog) } }
|
|
50
|
-
|
|
51
|
-
const dbMutationLogWasEmptyWhenOpened =
|
|
52
|
-
dbMutationLogRef.current.sqliteDb.select('SELECT 1 FROM sqlite_master').length === 0
|
|
53
|
-
|
|
54
|
-
yield* Effect.addFinalizer(() =>
|
|
55
|
-
Effect.gen(function* () {
|
|
56
|
-
// Ignoring in case the database is already closed
|
|
57
|
-
yield* Effect.try(() => db.closeSync()).pipe(Effect.ignore)
|
|
58
|
-
yield* Effect.try(() => dbMutationLog.closeSync()).pipe(Effect.ignore)
|
|
59
|
-
}),
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
if (dbMutationLogWasEmptyWhenOpened) {
|
|
63
|
-
yield* migrateTable({
|
|
64
|
-
db: dbMutationLogRef.current.sqliteDb,
|
|
65
|
-
behaviour: 'create-if-not-exists',
|
|
66
|
-
tableAst: mutationLogMetaTable.sqliteDef.ast,
|
|
67
|
-
skipMetaTable: true,
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (dbWasEmptyWhenOpened) {
|
|
72
|
-
yield* migrateDb({ db: dbRef.current.sqliteDb, schema })
|
|
73
|
-
|
|
74
|
-
initializeSingletonTables(schema, dbRef.current.sqliteDb)
|
|
75
|
-
|
|
76
|
-
switch (migrationOptions.strategy) {
|
|
77
|
-
case 'from-mutation-log': {
|
|
78
|
-
// TODO bring back
|
|
79
|
-
// yield* rehydrateFromMutationLog({
|
|
80
|
-
// db: dbRef.current.sqliteDb,
|
|
81
|
-
// logDb: dbMutationLogRef.current.sqliteDb,
|
|
82
|
-
// schema,
|
|
83
|
-
// migrationOptions,
|
|
84
|
-
// onProgress: () => Effect.void,
|
|
85
|
-
// })
|
|
86
|
-
|
|
87
|
-
break
|
|
88
|
-
}
|
|
89
|
-
case 'hard-reset': {
|
|
90
|
-
// This is already the case by note doing anything now
|
|
91
|
-
|
|
92
|
-
break
|
|
93
|
-
}
|
|
94
|
-
case 'manual': {
|
|
95
|
-
// const migrateFn = migrationStrategy.migrate
|
|
96
|
-
console.warn('Manual migration strategy not implemented yet')
|
|
97
|
-
|
|
98
|
-
// TODO figure out a way to get previous database file to pass to the migration function
|
|
99
|
-
|
|
100
|
-
break
|
|
101
|
-
}
|
|
102
|
-
default: {
|
|
103
|
-
casesHandled(migrationOptions)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const mutationLogExclude =
|
|
109
|
-
migrationOptions.strategy === 'from-mutation-log'
|
|
110
|
-
? (migrationOptions.excludeMutations ?? new Set(['livestore.RawSql']))
|
|
111
|
-
: new Set(['livestore.RawSql'])
|
|
112
|
-
|
|
113
|
-
const mutationEventSchema = MutationEvent.makeMutationEventSchema(schema)
|
|
114
|
-
const mutationDefSchemaHashMap = new Map(
|
|
115
|
-
[...schema.mutations.entries()].map(([k, v]) => [k, Schema.hash(v.schema)] as const),
|
|
116
|
-
)
|
|
117
|
-
|
|
118
|
-
const newMutationLogStmt = dbMutationLogRef.current.sqliteDb.prepare(
|
|
119
|
-
insertRowPrepared({ tableName: MUTATION_LOG_META_TABLE, columns: mutationLogMetaTable.sqliteDef.columns }),
|
|
120
|
-
)
|
|
50
|
+
const { storage, clientId = 'expo', sessionId = 'expo', sync: syncOptions } = options
|
|
121
51
|
|
|
122
|
-
|
|
52
|
+
yield* Queue.offer(bootStatusQueue, { stage: 'loading' })
|
|
123
53
|
|
|
124
|
-
const
|
|
125
|
-
Effect.acquireRelease(Queue.shutdown),
|
|
126
|
-
)
|
|
54
|
+
const lockStatus = yield* SubscriptionRef.make<LockStatus>('has-lock')
|
|
127
55
|
|
|
128
|
-
const
|
|
129
|
-
Schema.pick('idGlobal', 'idClient'),
|
|
130
|
-
Schema.transform(EventId.EventId, {
|
|
131
|
-
encode: (_) => ({ idGlobal: _.global, idClient: _.client }),
|
|
132
|
-
decode: (_) => EventId.make({ global: _.idGlobal, client: _.idClient }),
|
|
133
|
-
strict: false,
|
|
134
|
-
}),
|
|
135
|
-
Schema.Array,
|
|
136
|
-
Schema.headOrElse(() => EventId.make({ global: 0, client: 0 })),
|
|
137
|
-
)
|
|
56
|
+
const shutdownChannel = yield* makeShutdownChannel(storeId)
|
|
138
57
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
58
|
+
yield* shutdownChannel.listen.pipe(
|
|
59
|
+
Stream.flatten(),
|
|
60
|
+
Stream.tap((error) => Effect.sync(() => shutdown(Cause.fail(error)))),
|
|
61
|
+
Stream.runDrain,
|
|
62
|
+
Effect.interruptible,
|
|
63
|
+
Effect.tapCauseLogPretty,
|
|
64
|
+
Effect.forkScoped,
|
|
143
65
|
)
|
|
144
66
|
|
|
145
|
-
|
|
67
|
+
const devtoolsWebmeshNode = devtoolsEnabled
|
|
68
|
+
? yield* makeExpoDevtoolsConnectedMeshNode({
|
|
69
|
+
nodeName: `expo-${storeId}-${clientId}-${sessionId}`,
|
|
70
|
+
target: `devtools-${storeId}-${clientId}-${sessionId}`,
|
|
71
|
+
})
|
|
72
|
+
: undefined
|
|
73
|
+
|
|
74
|
+
const { leaderThread, initialSnapshot } = yield* makeLeaderThread({
|
|
75
|
+
storeId,
|
|
76
|
+
clientId,
|
|
77
|
+
sessionId,
|
|
78
|
+
schema,
|
|
79
|
+
makeSqliteDb,
|
|
80
|
+
syncOptions,
|
|
81
|
+
storage: storage ?? {},
|
|
82
|
+
devtoolsEnabled,
|
|
83
|
+
devtoolsWebmeshNode,
|
|
84
|
+
bootStatusQueue,
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const sqliteDb = yield* makeSqliteDb({ _tag: 'in-memory' })
|
|
88
|
+
sqliteDb.import(initialSnapshot)
|
|
146
89
|
|
|
147
90
|
const clientSession = {
|
|
148
91
|
devtools: { enabled: false },
|
|
149
92
|
lockStatus,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
leaderThread: {
|
|
154
|
-
mutations: {
|
|
155
|
-
pull: Stream.fromQueue(incomingSyncMutationsQueue),
|
|
156
|
-
push: (batch): Effect.Effect<void, UnexpectedError> =>
|
|
157
|
-
Effect.gen(function* () {
|
|
158
|
-
for (const mutationEventEncoded of batch) {
|
|
159
|
-
if (migrationOptions.strategy !== 'from-mutation-log') return
|
|
160
|
-
|
|
161
|
-
const mutation = mutationEventEncoded.mutation
|
|
162
|
-
const mutationDef =
|
|
163
|
-
schema.mutations.get(mutation) ?? shouldNeverHappen(`Unknown mutation: ${mutation}`)
|
|
164
|
-
|
|
165
|
-
const execArgsArr = getExecArgsFromMutation({
|
|
166
|
-
mutationDef,
|
|
167
|
-
mutationEvent: { decoded: undefined, encoded: mutationEventEncoded },
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
// write to mutation_log
|
|
171
|
-
if (
|
|
172
|
-
mutationLogExclude.has(mutation) === false &&
|
|
173
|
-
execArgsArr.some((_) => _.statementSql.includes('__livestore')) === false
|
|
174
|
-
) {
|
|
175
|
-
const mutationDefSchemaHash =
|
|
176
|
-
mutationDefSchemaHashMap.get(mutation) ?? shouldNeverHappen(`Unknown mutation: ${mutation}`)
|
|
177
|
-
|
|
178
|
-
const argsJson = JSON.stringify(mutationEventEncoded.args ?? {})
|
|
179
|
-
const mutationLogRowValues = {
|
|
180
|
-
idGlobal: mutationEventEncoded.id.global,
|
|
181
|
-
idClient: mutationEventEncoded.id.client,
|
|
182
|
-
mutation: mutationEventEncoded.mutation,
|
|
183
|
-
argsJson,
|
|
184
|
-
schemaHash: mutationDefSchemaHash,
|
|
185
|
-
syncMetadataJson: Option.none(),
|
|
186
|
-
parentIdGlobal: mutationEventEncoded.parentId.global,
|
|
187
|
-
parentIdClient: mutationEventEncoded.parentId.client,
|
|
188
|
-
clientId: 'expo',
|
|
189
|
-
sessionId: 'expo',
|
|
190
|
-
} satisfies MutationLogMetaRow
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
newMutationLogStmt.execute(
|
|
194
|
-
makeBindValues({
|
|
195
|
-
columns: mutationLogMetaTable.sqliteDef.columns,
|
|
196
|
-
values: mutationLogRowValues,
|
|
197
|
-
variablePrefix: '$',
|
|
198
|
-
}) as PreparedBindValues,
|
|
199
|
-
)
|
|
200
|
-
} catch (e) {
|
|
201
|
-
console.error('Error writing to mutation_log', e, mutationLogRowValues)
|
|
202
|
-
debugger
|
|
203
|
-
throw e
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
// console.debug('livestore-webworker: skipping mutation log write', mutation, statementSql, bindValues)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
yield* devtools?.onMutation({ mutationEventEncoded }) ?? Effect.void
|
|
210
|
-
}
|
|
211
|
-
}),
|
|
212
|
-
},
|
|
213
|
-
initialState: {
|
|
214
|
-
migrationsReport: {
|
|
215
|
-
migrations: [],
|
|
216
|
-
},
|
|
217
|
-
leaderHead: initialMutationEventId,
|
|
218
|
-
},
|
|
219
|
-
export: Effect.sync(() => dbRef.current.sqliteDb.export()),
|
|
220
|
-
getMutationLogData: Effect.sync(() => dbMutationLogRef.current.sqliteDb.export()),
|
|
221
|
-
networkStatus: SubscriptionRef.make({ isConnected: false, timestampMs: Date.now(), latchClosed: false }).pipe(
|
|
222
|
-
Effect.runSync,
|
|
223
|
-
),
|
|
224
|
-
sendDevtoolsMessage: () => Effect.dieMessage('Not implemented'),
|
|
225
|
-
getSyncState: Effect.dieMessage('Not implemented'),
|
|
226
|
-
},
|
|
93
|
+
clientId,
|
|
94
|
+
sessionId,
|
|
95
|
+
leaderThread,
|
|
227
96
|
shutdown: () => Effect.dieMessage('TODO implement shutdown'),
|
|
228
|
-
sqliteDb
|
|
97
|
+
sqliteDb,
|
|
229
98
|
} satisfies ClientSession
|
|
230
99
|
|
|
231
100
|
if (devtoolsEnabled) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}).pipe(
|
|
241
|
-
Effect.tapCauseLogPretty,
|
|
242
|
-
Effect.catchAll(() => Effect.succeed(undefined)),
|
|
243
|
-
)
|
|
101
|
+
yield* Effect.gen(function* () {
|
|
102
|
+
const storeDevtoolsChannel = yield* makeChannelForConnectedMeshNode({
|
|
103
|
+
target: `devtools-${storeId}-${clientId}-${sessionId}`,
|
|
104
|
+
node: devtoolsWebmeshNode!,
|
|
105
|
+
schema: { listen: Devtools.ClientSession.MessageToApp, send: Devtools.ClientSession.MessageFromApp },
|
|
106
|
+
channelType: 'clientSession',
|
|
107
|
+
})
|
|
108
|
+
yield* connectDevtoolsToStore(storeDevtoolsChannel)
|
|
109
|
+
}).pipe(Effect.tapCauseLogPretty, Effect.forkScoped)
|
|
244
110
|
}
|
|
245
111
|
|
|
246
112
|
return clientSession
|
|
@@ -248,3 +114,167 @@ export const makeAdapter =
|
|
|
248
114
|
Effect.mapError((cause) => (cause._tag === 'LiveStore.UnexpectedError' ? cause : new UnexpectedError({ cause }))),
|
|
249
115
|
Effect.tapCauseLogPretty,
|
|
250
116
|
)
|
|
117
|
+
|
|
118
|
+
const makeLeaderThread = ({
|
|
119
|
+
storeId,
|
|
120
|
+
clientId,
|
|
121
|
+
sessionId,
|
|
122
|
+
schema,
|
|
123
|
+
makeSqliteDb,
|
|
124
|
+
syncOptions,
|
|
125
|
+
storage,
|
|
126
|
+
devtoolsEnabled,
|
|
127
|
+
devtoolsWebmeshNode,
|
|
128
|
+
bootStatusQueue: bootStatusQueueClientSession,
|
|
129
|
+
}: {
|
|
130
|
+
storeId: string
|
|
131
|
+
clientId: string
|
|
132
|
+
sessionId: string
|
|
133
|
+
schema: LiveStoreSchema
|
|
134
|
+
makeSqliteDb: MakeExpoSqliteDb
|
|
135
|
+
syncOptions: SyncOptions | undefined
|
|
136
|
+
storage: {
|
|
137
|
+
subDirectory?: string
|
|
138
|
+
}
|
|
139
|
+
devtoolsEnabled: boolean
|
|
140
|
+
devtoolsWebmeshNode: MeshNode | undefined
|
|
141
|
+
bootStatusQueue: Queue.Queue<BootStatus>
|
|
142
|
+
}) =>
|
|
143
|
+
Effect.gen(function* () {
|
|
144
|
+
const subDirectory = storage.subDirectory ? storage.subDirectory.replace(/\/$/, '') + '/' : ''
|
|
145
|
+
const pathJoin = (...paths: string[]) => paths.join('/').replaceAll(/\/+/g, '/')
|
|
146
|
+
const directory = pathJoin(SQLite.defaultDatabaseDirectory, subDirectory, storeId)
|
|
147
|
+
|
|
148
|
+
const readModelDatabaseName = `${'livestore-'}${schema.hash}@${liveStoreStorageFormatVersion}.db`
|
|
149
|
+
const dbMutationLogPath = `${'livestore-'}mutationlog@${liveStoreStorageFormatVersion}.db`
|
|
150
|
+
|
|
151
|
+
const dbReadModel = yield* makeSqliteDb({ _tag: 'expo', databaseName: readModelDatabaseName, directory })
|
|
152
|
+
const dbMutationLog = yield* makeSqliteDb({ _tag: 'expo', databaseName: dbMutationLogPath, directory })
|
|
153
|
+
|
|
154
|
+
const devtoolsOptions = yield* makeDevtoolsOptions({
|
|
155
|
+
devtoolsEnabled,
|
|
156
|
+
dbReadModel,
|
|
157
|
+
dbMutationLog,
|
|
158
|
+
storeId,
|
|
159
|
+
clientId,
|
|
160
|
+
sessionId,
|
|
161
|
+
devtoolsWebmeshNode,
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const layer = yield* Layer.memoize(
|
|
165
|
+
makeLeaderThreadLayer({
|
|
166
|
+
clientId,
|
|
167
|
+
dbReadModel,
|
|
168
|
+
dbMutationLog,
|
|
169
|
+
devtoolsOptions,
|
|
170
|
+
makeSqliteDb,
|
|
171
|
+
schema,
|
|
172
|
+
// NOTE we're creating a separate channel here since you can't listen to your own channel messages
|
|
173
|
+
shutdownChannel: yield* makeShutdownChannel(storeId),
|
|
174
|
+
storeId,
|
|
175
|
+
syncOptions,
|
|
176
|
+
}).pipe(Layer.provideMerge(FetchHttpClient.layer)),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return yield* Effect.gen(function* () {
|
|
180
|
+
const {
|
|
181
|
+
dbReadModel: db,
|
|
182
|
+
dbMutationLog,
|
|
183
|
+
syncProcessor,
|
|
184
|
+
connectedClientSessionPullQueues,
|
|
185
|
+
extraIncomingMessagesQueue,
|
|
186
|
+
initialState,
|
|
187
|
+
bootStatusQueue,
|
|
188
|
+
} = yield* LeaderThreadCtx
|
|
189
|
+
|
|
190
|
+
const bootStatusFiber = yield* Queue.takeBetween(bootStatusQueue, 1, 1000).pipe(
|
|
191
|
+
Effect.tap((bootStatus) => Queue.offerAll(bootStatusQueueClientSession, bootStatus)),
|
|
192
|
+
Effect.interruptible,
|
|
193
|
+
Effect.tapCauseLogPretty,
|
|
194
|
+
Effect.forkScoped,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
yield* Queue.awaitShutdown(bootStatusQueueClientSession).pipe(
|
|
198
|
+
Effect.andThen(Fiber.interrupt(bootStatusFiber)),
|
|
199
|
+
Effect.tapCauseLogPretty,
|
|
200
|
+
Effect.forkScoped,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
const initialLeaderHead = getClientHeadFromDb(dbMutationLog)
|
|
204
|
+
const pullQueue = yield* connectedClientSessionPullQueues.makeQueue(initialLeaderHead)
|
|
205
|
+
|
|
206
|
+
const leaderThread = {
|
|
207
|
+
mutations: {
|
|
208
|
+
pull: Stream.fromQueue(pullQueue),
|
|
209
|
+
push: (batch) =>
|
|
210
|
+
syncProcessor
|
|
211
|
+
.push(
|
|
212
|
+
batch.map((item) => new MutationEvent.EncodedWithMeta(item)),
|
|
213
|
+
{ waitForProcessing: true },
|
|
214
|
+
)
|
|
215
|
+
.pipe(Effect.provide(layer), Effect.scoped),
|
|
216
|
+
},
|
|
217
|
+
initialState: { leaderHead: initialLeaderHead, migrationsReport: initialState.migrationsReport },
|
|
218
|
+
export: Effect.sync(() => db.export()),
|
|
219
|
+
getMutationLogData: Effect.sync(() => dbMutationLog.export()),
|
|
220
|
+
// TODO
|
|
221
|
+
networkStatus: SubscriptionRef.make({ isConnected: false, timestampMs: Date.now(), latchClosed: false }).pipe(
|
|
222
|
+
Effect.runSync,
|
|
223
|
+
),
|
|
224
|
+
getSyncState: syncProcessor.syncState,
|
|
225
|
+
sendDevtoolsMessage: (message) => extraIncomingMessagesQueue.offer(message),
|
|
226
|
+
} satisfies ClientSessionLeaderThreadProxy
|
|
227
|
+
|
|
228
|
+
const initialSnapshot = db.export()
|
|
229
|
+
|
|
230
|
+
return { leaderThread, initialSnapshot }
|
|
231
|
+
}).pipe(Effect.provide(layer))
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
const makeDevtoolsOptions = ({
|
|
235
|
+
devtoolsEnabled,
|
|
236
|
+
dbReadModel,
|
|
237
|
+
dbMutationLog,
|
|
238
|
+
storeId,
|
|
239
|
+
clientId,
|
|
240
|
+
sessionId,
|
|
241
|
+
devtoolsWebmeshNode,
|
|
242
|
+
}: {
|
|
243
|
+
devtoolsEnabled: boolean
|
|
244
|
+
dbReadModel: LeaderSqliteDb
|
|
245
|
+
dbMutationLog: LeaderSqliteDb
|
|
246
|
+
storeId: string
|
|
247
|
+
clientId: string
|
|
248
|
+
sessionId: string
|
|
249
|
+
devtoolsWebmeshNode: MeshNode | undefined
|
|
250
|
+
}): Effect.Effect<DevtoolsOptions, UnexpectedError, Scope.Scope> =>
|
|
251
|
+
Effect.gen(function* () {
|
|
252
|
+
if (devtoolsEnabled === false) {
|
|
253
|
+
return {
|
|
254
|
+
enabled: false,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
enabled: true,
|
|
260
|
+
makeBootContext: Effect.gen(function* () {
|
|
261
|
+
return {
|
|
262
|
+
// devtoolsWebChannel: yield* makeExpoDevtoolsChannel({
|
|
263
|
+
// nodeName: `leader-${storeId}-${clientId}`,
|
|
264
|
+
devtoolsWebChannel: yield* makeChannelForConnectedMeshNode({
|
|
265
|
+
node: devtoolsWebmeshNode!,
|
|
266
|
+
target: `devtools-${storeId}-${clientId}-${sessionId}`,
|
|
267
|
+
schema: {
|
|
268
|
+
listen: Devtools.Leader.MessageToApp,
|
|
269
|
+
send: Devtools.Leader.MessageFromApp,
|
|
270
|
+
},
|
|
271
|
+
channelType: 'leader',
|
|
272
|
+
}),
|
|
273
|
+
persistenceInfo: {
|
|
274
|
+
readModel: dbReadModel.metadata.persistenceInfo,
|
|
275
|
+
mutationLog: dbMutationLog.metadata.persistenceInfo,
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
}),
|
|
279
|
+
}
|
|
280
|
+
})
|