@livestore/adapter-expo 0.3.0-dev.18 → 0.3.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/src/common.ts DELETED
@@ -1,115 +0,0 @@
1
- import type { PreparedStatement, SqliteDb } from '@livestore/common'
2
- import { base64, shouldNeverHappen } from '@livestore/utils'
3
- import { Effect } from '@livestore/utils/effect'
4
- import * as ExpoFS from 'expo-file-system'
5
- import type * as SQLite from 'expo-sqlite'
6
-
7
- export const makeSqliteDb = (db: SQLite.SQLiteDatabase): SqliteDb => {
8
- const stmts: PreparedStatement[] = []
9
-
10
- const sqliteDb: SqliteDb<any> = {
11
- metadata: { fileName: db.databasePath },
12
- _tag: 'SqliteDb',
13
- prepare: (queryStr) => {
14
- try {
15
- const dbStmt = db.prepareSync(queryStr)
16
- const stmt = {
17
- execute: (bindValues) => {
18
- // console.log('execute', queryStr, bindValues)
19
- const res = dbStmt.executeSync(bindValues ?? ([] as any))
20
- res.resetSync()
21
- return () => res.changes
22
- },
23
- select: (bindValues) => {
24
- const res = dbStmt.executeSync(bindValues ?? ([] as any))
25
- try {
26
- return res.getAllSync() as any
27
- } finally {
28
- res.resetSync()
29
- }
30
- },
31
- finalize: () => dbStmt.finalizeSync(),
32
- sql: queryStr,
33
- } satisfies PreparedStatement
34
- stmts.push(stmt)
35
- return stmt
36
- } catch (e) {
37
- console.error(`Error preparing statement: ${queryStr}`, e)
38
- return shouldNeverHappen(`Error preparing statement: ${queryStr}`)
39
- }
40
- },
41
- execute: (queryStr, bindValues) => {
42
- const stmt = db.prepareSync(queryStr)
43
- try {
44
- const res = stmt.executeSync(bindValues ?? ([] as any))
45
- return () => res.changes
46
- } finally {
47
- stmt.finalizeSync()
48
- }
49
- },
50
- export: () => {
51
- return db.serializeSync()
52
- },
53
- select: (queryStr, bindValues) => {
54
- const stmt = sqliteDb.prepare(queryStr)
55
- const res = stmt.select(bindValues)
56
- stmt.finalize()
57
- return res as any
58
- },
59
- // TODO
60
- destroy: () => {},
61
- close: () => {
62
- for (const stmt of stmts) {
63
- stmt.finalize()
64
- }
65
- return db.closeSync()
66
- },
67
- import: () => {
68
- throw new Error('Not implemented')
69
- // TODO properly implement this as it seems to require importing to a temporary in-memory db,
70
- // save it to a file, and then reopen the DB from that file? (see `overwriteDbFile` below)
71
- },
72
- session: () => {
73
- return {
74
- changeset: () => new Uint8Array(),
75
- finish: () => {},
76
- }
77
- },
78
- makeChangeset: (data) => {
79
- return {
80
- invert: () => {
81
- return sqliteDb.makeChangeset(data)
82
- },
83
- apply: () => {
84
- // TODO
85
- },
86
- }
87
- },
88
- } satisfies SqliteDb
89
-
90
- return sqliteDb
91
- }
92
-
93
- export type DbPairRef = {
94
- current:
95
- | {
96
- db: SQLite.SQLiteDatabase
97
- sqliteDb: SqliteDb
98
- }
99
- | undefined
100
- }
101
-
102
- export const getDbFilePath = (dbName: string) => {
103
- return `${ExpoFS.documentDirectory}SQLite/${dbName}`
104
- }
105
-
106
- export const overwriteDbFile = (dbName: string, data: Uint8Array) =>
107
- Effect.gen(function* () {
108
- const path = getDbFilePath(dbName)
109
-
110
- yield* Effect.promise(() => ExpoFS.deleteAsync(path, { idempotent: true }))
111
-
112
- // TODO avoid converting to string once the ExpoFS API supports binary data
113
- const b64String = base64.encode(data)
114
- yield* Effect.promise(() => ExpoFS.writeAsStringAsync(path, b64String, { encoding: ExpoFS.EncodingType.Base64 }))
115
- })
package/src/devtools.ts DELETED
@@ -1,292 +0,0 @@
1
- // @ts-nocheck
2
- import type { ClientSession, ConnectDevtoolsToStore } from '@livestore/common'
3
- import {
4
- Devtools,
5
- IntentionalShutdownCause,
6
- liveStoreVersion,
7
- MUTATION_LOG_META_TABLE,
8
- SCHEMA_META_TABLE,
9
- SCHEMA_MUTATIONS_META_TABLE,
10
- UnexpectedError,
11
- } from '@livestore/common'
12
- import type { PullQueueItem } from '@livestore/common/leader-thread'
13
- import type { LiveStoreSchema, MutationEvent } from '@livestore/common/schema'
14
- import { makeExpoDevtoolsChannel } from '@livestore/devtools-expo-common/web-channel'
15
- import type { ParseResult, Scope } from '@livestore/utils/effect'
16
- import { Cause, Effect, Queue, Schema, Stream, SubscriptionRef, WebChannel } from '@livestore/utils/effect'
17
- import * as SQLite from 'expo-sqlite'
18
-
19
- import type { DbPairRef } from './common.js'
20
- import { makeSqliteDb, overwriteDbFile } from './common.js'
21
-
22
- export type BootedDevtools = {
23
- onMutation: ({
24
- mutationEventEncoded,
25
- }: {
26
- mutationEventEncoded: MutationEvent.AnyEncoded
27
- }) => Effect.Effect<void, UnexpectedError, never>
28
- }
29
-
30
- export const bootDevtools = ({
31
- connectDevtoolsToStore,
32
- clientSession,
33
- schema,
34
- shutdown,
35
- dbRef,
36
- dbMutationLogRef,
37
- incomingSyncMutationsQueue,
38
- }: {
39
- connectDevtoolsToStore: ConnectDevtoolsToStore
40
- clientSession: ClientSession
41
- schema: LiveStoreSchema
42
- dbRef: DbPairRef
43
- dbMutationLogRef: DbPairRef
44
- shutdown: (cause: Cause.Cause<UnexpectedError | IntentionalShutdownCause>) => void
45
- incomingSyncMutationsQueue: Queue.Queue<PullQueueItem>
46
- }): Effect.Effect<BootedDevtools, UnexpectedError | ParseResult.ParseError, Scope.Scope> =>
47
- Effect.gen(function* () {
48
- const appHostId = 'expo'
49
- const isLeader = true
50
-
51
- const expoDevtoolsChannel = yield* makeExpoDevtoolsChannel({
52
- listenSchema: Schema.Union(Devtools.MessageToApp, Devtools.MessageToApp),
53
- sendSchema: Schema.Union(Devtools.MessageFromApp, Devtools.MessageFromApp),
54
- })
55
-
56
- const isConnected = yield* SubscriptionRef.make(false)
57
-
58
- /**
59
- * Used to forward messages from `expoDevtoolsChannel` to a "filtered" `storeDevtoolsChannel`
60
- * which is expected by the `connectDevtoolsToStore` function.
61
- */
62
- const storeDevtoolsChannelProxy = yield* WebChannel.queueChannelProxy({
63
- schema: { listen: Devtools.MessageToApp, send: Devtools.MessageFromApp },
64
- })
65
-
66
- yield* storeDevtoolsChannelProxy.sendQueue.pipe(
67
- Stream.fromQueue,
68
- Stream.tap((msg) => expoDevtoolsChannel.send(msg)),
69
- Stream.runDrain,
70
- Effect.forkScoped,
71
- )
72
-
73
- const getDatabaseName = (db: DbPairRef) =>
74
- db.current!.db.databasePath.slice(db.current!.db.databasePath.lastIndexOf('/') + 1)
75
-
76
- yield* expoDevtoolsChannel.listen.pipe(
77
- Stream.flatten(),
78
- Stream.tap((decodedEvent) =>
79
- Effect.gen(function* () {
80
- if (Schema.is(Devtools.MessageToApp)(decodedEvent)) {
81
- yield* storeDevtoolsChannelProxy.listenQueue.pipe(Queue.offer(decodedEvent))
82
- return
83
- }
84
-
85
- // if (decodedEvent._tag === 'LSD.DevtoolsReady') {
86
- // if ((yield* isConnected.get) === false) {
87
- // // yield* expoDevtoolsChannel.send(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }))
88
- // }
89
-
90
- // return
91
- // }
92
-
93
- // if (decodedEvent._tag === 'LSD.DevtoolsConnected') {
94
- // if (yield* isConnected.get) {
95
- // console.warn('devtools already connected')
96
- // return
97
- // }
98
-
99
- // yield* connectDevtoolsToStore(storeDevtoolsChannelProxy.webChannel).pipe(
100
- // Effect.tapCauseLogPretty,
101
- // Effect.forkScoped,
102
- // )
103
-
104
- // yield* SubscriptionRef.set(isConnected, true)
105
- // return
106
- // }
107
-
108
- // if (decodedEvent._tag === 'LSD.Disconnect') {
109
- // yield* SubscriptionRef.set(isConnected, false)
110
-
111
- // // yield* disconnect
112
-
113
- // // TODO is there a better place for this?
114
- // yield* expoDevtoolsChannel.send(Devtools.AppHostReady.make({ appHostId, liveStoreVersion, isLeader }))
115
-
116
- // return
117
- // }
118
-
119
- const { requestId } = decodedEvent
120
- const reqPayload = { requestId, appHostId, liveStoreVersion }
121
-
122
- switch (decodedEvent._tag) {
123
- case 'LSD.Ping': {
124
- yield* expoDevtoolsChannel.send(Devtools.Pong.make({ ...reqPayload }))
125
- return
126
- }
127
- case 'LSD.Leader.SnapshotReq': {
128
- const data = yield* clientSession.leaderThread.export
129
-
130
- yield* expoDevtoolsChannel.send(Devtools.SnapshotRes.make({ snapshot: data!, ...reqPayload }))
131
-
132
- return
133
- }
134
- case 'LSD.Leader.LoadDatabaseFileReq': {
135
- const { data } = decodedEvent
136
-
137
- let tableNames: Set<string>
138
-
139
- try {
140
- const tmpExpoDb = SQLite.deserializeDatabaseSync(data)
141
- const tmpDb = makeSqliteDb(tmpExpoDb)
142
- const tableNameResults = tmpDb.select<{ name: string }>(
143
- `select name from sqlite_master where type = 'table'`,
144
- )
145
-
146
- tableNames = new Set(tableNameResults.map((_) => _.name))
147
-
148
- tmpExpoDb.closeSync()
149
- } catch (e) {
150
- yield* expoDevtoolsChannel.send(
151
- Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-file' }),
152
- )
153
-
154
- console.error(e)
155
-
156
- return
157
- }
158
-
159
- if (tableNames.has(MUTATION_LOG_META_TABLE)) {
160
- // yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
161
-
162
- dbMutationLogRef.current!.db.closeSync()
163
-
164
- yield* overwriteDbFile(getDatabaseName(dbMutationLogRef), data)
165
-
166
- dbMutationLogRef.current = undefined
167
-
168
- dbRef.current!.db.closeSync()
169
- SQLite.deleteDatabaseSync(getDatabaseName(dbRef))
170
- } else if (tableNames.has(SCHEMA_META_TABLE) && tableNames.has(SCHEMA_MUTATIONS_META_TABLE)) {
171
- // yield* SubscriptionRef.set(shutdownStateSubRef, 'shutting-down')
172
-
173
- // yield* db.import(data)
174
-
175
- dbRef.current!.db.closeSync()
176
-
177
- yield* overwriteDbFile(getDatabaseName(dbRef), data)
178
- } else {
179
- yield* expoDevtoolsChannel.send(
180
- Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'unsupported-database' }),
181
- )
182
- return
183
- }
184
-
185
- yield* expoDevtoolsChannel.send(Devtools.LoadDatabaseFileRes.make({ ...reqPayload, status: 'ok' }))
186
-
187
- yield* shutdown(Cause.fail(IntentionalShutdownCause.make({ reason: 'devtools-import' })))
188
-
189
- return
190
- }
191
- case 'LSD.Leader.ResetAllDataReq': {
192
- const { mode } = decodedEvent
193
-
194
- dbRef.current!.db.closeSync()
195
- SQLite.deleteDatabaseSync(getDatabaseName(dbRef))
196
-
197
- if (mode === 'all-data') {
198
- dbMutationLogRef.current!.db.closeSync()
199
- SQLite.deleteDatabaseSync(getDatabaseName(dbMutationLogRef))
200
- }
201
-
202
- yield* expoDevtoolsChannel.send(Devtools.ResetAllDataRes.make({ ...reqPayload }))
203
-
204
- yield* shutdown(Cause.fail(IntentionalShutdownCause.make({ reason: 'devtools-reset' })))
205
-
206
- return
207
- }
208
- case 'LSD.Leader.DatabaseFileInfoReq': {
209
- const dbSizeQuery = `SELECT page_count * page_size as size FROM pragma_page_count(), pragma_page_size();`
210
- const dbFileSize = dbRef.current!.db.prepareSync(dbSizeQuery).executeSync<any>().getFirstSync()!
211
- .size as number
212
- const mutationLogFileSize = dbMutationLogRef
213
- .current!.db.prepareSync(dbSizeQuery)
214
- .executeSync<any>()
215
- .getFirstSync()!.size as number
216
-
217
- yield* expoDevtoolsChannel.send(
218
- Devtools.DatabaseFileInfoRes.make({
219
- readModel: { fileSize: dbFileSize, persistenceInfo: { fileName: 'livestore.db' } },
220
- mutationLog: {
221
- fileSize: mutationLogFileSize,
222
- persistenceInfo: { fileName: 'livestore-mutationlog.db' },
223
- },
224
- ...reqPayload,
225
- }),
226
- )
227
-
228
- return
229
- }
230
- case 'LSD.Leader.MutationLogReq': {
231
- const mutationLog = yield* clientSession.leaderThread.getMutationLogData
232
-
233
- yield* expoDevtoolsChannel.send(Devtools.MutationLogRes.make({ mutationLog, ...reqPayload }))
234
-
235
- return
236
- }
237
- case 'LSD.Leader.RunMutationReq': {
238
- const { mutationEventEncoded: mutationEventEncoded_ } = decodedEvent
239
- const mutationDef = schema.mutations.get(mutationEventEncoded_.mutation)!
240
- // const nextMutationEventIdPair = clientSession.mutations.nextMutationEventIdPair({
241
- // clientOnly: mutationDef.options.clientOnly,
242
- // })
243
-
244
- // const mutationEventEncoded = new MutationEvent.EncodedWithMeta({
245
- // ...mutationEventEncoded_,
246
- // // ...nextMutationEventIdPair,
247
- // })
248
-
249
- // const mutationEventDecoded = yield* Schema.decode(mutationEventSchema)(mutationEventEncoded)
250
- // yield* Queue.offer(incomingSyncMutationsQueue, {
251
- // payload: { _tag: 'upstream-advance', newEvents: [mutationEventEncoded] },
252
- // remaining: 0,
253
- // })
254
-
255
- // const mutationDef =
256
- // schema.mutations.get(mutationEventEncoded.mutation) ??
257
- // shouldNeverHappen(`Unknown mutation: ${mutationEventEncoded.mutation}`)
258
-
259
- // yield* clientSession.mutations.push([mutationEventEncoded])
260
-
261
- yield* expoDevtoolsChannel.send(Devtools.RunMutationRes.make({ ...reqPayload }))
262
-
263
- return
264
- }
265
- case 'LSD.Leader.SyncingInfoReq': {
266
- const syncingInfo = Devtools.SyncingInfo.make({
267
- enabled: false,
268
- metadata: {},
269
- })
270
-
271
- yield* expoDevtoolsChannel.send(Devtools.SyncingInfoRes.make({ syncingInfo, ...reqPayload }))
272
-
273
- return
274
- }
275
- }
276
- }),
277
- ),
278
- Stream.runDrain,
279
- Effect.tapCauseLogPretty,
280
- Effect.forkScoped,
281
- )
282
- // yield* expoDevtoolsChannel.send(Devtools.AppHostReady.make({ appHostId, isLeader, liveStoreVersion }))
283
-
284
- const onMutation = ({ mutationEventEncoded }: { mutationEventEncoded: MutationEvent.AnyEncoded }) =>
285
- expoDevtoolsChannel
286
- .send(Devtools.MutationBroadcast.make({ mutationEventEncoded, liveStoreVersion }))
287
- .pipe(UnexpectedError.mapToUnexpectedError)
288
-
289
- return {
290
- onMutation,
291
- }
292
- }).pipe(Effect.withSpan('@livestore/adapter-expo:bootDevtools'))