@livestore/sqlite-wasm 0.0.0-snapshot-1d99fea7d2ce2c7a5d9ed0a3752f8a7bda6bc3db

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.
Files changed (69) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/FacadeVFS.d.ts +243 -0
  3. package/dist/FacadeVFS.d.ts.map +1 -0
  4. package/dist/FacadeVFS.js +474 -0
  5. package/dist/FacadeVFS.js.map +1 -0
  6. package/dist/browser/mod.d.ts +44 -0
  7. package/dist/browser/mod.d.ts.map +1 -0
  8. package/dist/browser/mod.js +51 -0
  9. package/dist/browser/mod.js.map +1 -0
  10. package/dist/browser/opfs/AccessHandlePoolVFS.d.ts +47 -0
  11. package/dist/browser/opfs/AccessHandlePoolVFS.d.ts.map +1 -0
  12. package/dist/browser/opfs/AccessHandlePoolVFS.js +355 -0
  13. package/dist/browser/opfs/AccessHandlePoolVFS.js.map +1 -0
  14. package/dist/browser/opfs/index.d.ts +12 -0
  15. package/dist/browser/opfs/index.d.ts.map +1 -0
  16. package/dist/browser/opfs/index.js +19 -0
  17. package/dist/browser/opfs/index.js.map +1 -0
  18. package/dist/browser/opfs/opfs-sah-pool.d.ts +3 -0
  19. package/dist/browser/opfs/opfs-sah-pool.d.ts.map +1 -0
  20. package/dist/browser/opfs/opfs-sah-pool.js +55 -0
  21. package/dist/browser/opfs/opfs-sah-pool.js.map +1 -0
  22. package/dist/in-memory-vfs.d.ts +7 -0
  23. package/dist/in-memory-vfs.d.ts.map +1 -0
  24. package/dist/in-memory-vfs.js +15 -0
  25. package/dist/in-memory-vfs.js.map +1 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +2 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/index_.d.ts +3 -0
  31. package/dist/index_.d.ts.map +1 -0
  32. package/dist/index_.js +3 -0
  33. package/dist/index_.js.map +1 -0
  34. package/dist/load-wasm/mod.browser.d.ts +2 -0
  35. package/dist/load-wasm/mod.browser.d.ts.map +1 -0
  36. package/dist/load-wasm/mod.browser.js +12 -0
  37. package/dist/load-wasm/mod.browser.js.map +1 -0
  38. package/dist/load-wasm/mod.node.d.ts +2 -0
  39. package/dist/load-wasm/mod.node.d.ts.map +1 -0
  40. package/dist/load-wasm/mod.node.js +13 -0
  41. package/dist/load-wasm/mod.node.js.map +1 -0
  42. package/dist/make-sqlite-db.d.ts +11 -0
  43. package/dist/make-sqlite-db.d.ts.map +1 -0
  44. package/dist/make-sqlite-db.js +181 -0
  45. package/dist/make-sqlite-db.js.map +1 -0
  46. package/dist/node/NodeFS.d.ts +20 -0
  47. package/dist/node/NodeFS.d.ts.map +1 -0
  48. package/dist/node/NodeFS.js +174 -0
  49. package/dist/node/NodeFS.js.map +1 -0
  50. package/dist/node/mod.d.ts +41 -0
  51. package/dist/node/mod.d.ts.map +1 -0
  52. package/dist/node/mod.js +61 -0
  53. package/dist/node/mod.js.map +1 -0
  54. package/package.json +38 -0
  55. package/src/FacadeVFS.ts +510 -0
  56. package/src/ambient.d.ts +18 -0
  57. package/src/browser/mod.ts +109 -0
  58. package/src/browser/opfs/AccessHandlePoolVFS.ts +404 -0
  59. package/src/browser/opfs/index.ts +35 -0
  60. package/src/browser/opfs/opfs-sah-pool.ts +68 -0
  61. package/src/in-memory-vfs.ts +20 -0
  62. package/src/index.ts +1 -0
  63. package/src/index_.ts +2 -0
  64. package/src/load-wasm/mod.browser.ts +12 -0
  65. package/src/load-wasm/mod.node.ts +13 -0
  66. package/src/make-sqlite-db.ts +220 -0
  67. package/src/node/NodeFS.ts +190 -0
  68. package/src/node/mod.ts +132 -0
  69. package/tsconfig.json +10 -0
@@ -0,0 +1,220 @@
1
+ import type {
2
+ PersistenceInfo,
3
+ PreparedBindValues,
4
+ PreparedStatement,
5
+ SqliteDb,
6
+ SqliteDbChangeset,
7
+ } from '@livestore/common'
8
+ import { SqliteError } from '@livestore/common'
9
+ import * as SqliteConstants from '@livestore/wa-sqlite/src/sqlite-constants.js'
10
+
11
+ import { makeInMemoryDb } from './in-memory-vfs.js'
12
+
13
+ export const makeSqliteDb = <
14
+ TMetadata extends {
15
+ dbPointer: number
16
+ persistenceInfo: PersistenceInfo
17
+ deleteDb: () => void
18
+ configureDb: (db: SqliteDb<TMetadata>) => void
19
+ },
20
+ >({
21
+ sqlite3,
22
+ metadata,
23
+ }: {
24
+ sqlite3: SQLiteAPI
25
+ metadata: TMetadata
26
+ }): SqliteDb<TMetadata> => {
27
+ const preparedStmts: PreparedStatement[] = []
28
+ const { dbPointer } = metadata
29
+
30
+ let isClosed = false
31
+
32
+ const sqliteDb: SqliteDb<TMetadata> = {
33
+ _tag: 'SqliteDb',
34
+ metadata,
35
+ prepare: (queryStr) => {
36
+ try {
37
+ const stmts = sqlite3.statements(dbPointer, queryStr.trim(), { unscoped: true })
38
+
39
+ let isFinalized = false
40
+
41
+ const preparedStmt = {
42
+ execute: (bindValues, options) => {
43
+ for (const stmt of stmts) {
44
+ if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
45
+ sqlite3.bind_collection(stmt, bindValues as any)
46
+ }
47
+
48
+ try {
49
+ sqlite3.step(stmt)
50
+ } finally {
51
+ if (options?.onRowsChanged) {
52
+ options.onRowsChanged(sqlite3.changes(dbPointer))
53
+ }
54
+
55
+ sqlite3.reset(stmt) // Reset is needed for next execution
56
+ }
57
+ }
58
+ },
59
+ select: <T>(bindValues: PreparedBindValues) => {
60
+ if (stmts.length !== 1) {
61
+ throw new SqliteError({
62
+ query: { bindValues, sql: queryStr },
63
+ code: -1,
64
+ cause: 'Expected only one statement when using `select`',
65
+ })
66
+ }
67
+
68
+ const stmt = stmts[0]!
69
+
70
+ if (bindValues !== undefined && Object.keys(bindValues).length > 0) {
71
+ sqlite3.bind_collection(stmt, bindValues as any)
72
+ }
73
+
74
+ const results: T[] = []
75
+
76
+ try {
77
+ // NOTE `column_names` only works for `SELECT` statements, ignoring other statements for now
78
+ let columns = undefined
79
+ try {
80
+ columns = sqlite3.column_names(stmt)
81
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
82
+ } catch (_e) {}
83
+
84
+ while (sqlite3.step(stmt) === SqliteConstants.SQLITE_ROW) {
85
+ if (columns !== undefined) {
86
+ const obj: { [key: string]: any } = {}
87
+ for (let i = 0; i < columns.length; i++) {
88
+ obj[columns[i]!] = sqlite3.column(stmt, i)
89
+ }
90
+ results.push(obj as unknown as T)
91
+ }
92
+ }
93
+ } catch (e) {
94
+ throw new SqliteError({
95
+ query: { bindValues, sql: queryStr },
96
+ code: (e as any).code,
97
+ cause: e,
98
+ })
99
+ } finally {
100
+ // reset the cached statement so we can use it again in the future
101
+ sqlite3.reset(stmt)
102
+ }
103
+
104
+ return results
105
+ },
106
+ finalize: () => {
107
+ // Avoid double finalization which leads to a crash
108
+ if (isFinalized) {
109
+ return
110
+ }
111
+
112
+ isFinalized = true
113
+
114
+ for (const stmt of stmts) {
115
+ sqlite3.finalize(stmt)
116
+ }
117
+ },
118
+ sql: queryStr,
119
+ } satisfies PreparedStatement
120
+
121
+ preparedStmts.push(preparedStmt)
122
+
123
+ return preparedStmt
124
+ } catch (e) {
125
+ throw new SqliteError({
126
+ query: { sql: queryStr, bindValues: {} },
127
+ code: (e as any).code,
128
+ cause: e,
129
+ })
130
+ }
131
+ },
132
+ export: () => sqlite3.serialize(dbPointer, 'main'),
133
+ execute: (queryStr, bindValues, options) => {
134
+ const stmt = sqliteDb.prepare(queryStr)
135
+ stmt.execute(bindValues, options)
136
+ stmt.finalize()
137
+ },
138
+ select: (queryStr, bindValues) => {
139
+ const stmt = sqliteDb.prepare(queryStr)
140
+ const results = stmt.select(bindValues)
141
+ stmt.finalize()
142
+ return results as ReadonlyArray<any>
143
+ },
144
+ destroy: () => {
145
+ sqliteDb.close()
146
+
147
+ metadata.deleteDb()
148
+ // if (metadata._tag === 'opfs') {
149
+ // metadata.vfs.resetAccessHandle(metadata.fileName)
150
+ // }
151
+ },
152
+ close: () => {
153
+ if (isClosed) {
154
+ return
155
+ }
156
+
157
+ for (const stmt of preparedStmts) {
158
+ stmt.finalize()
159
+ }
160
+ sqlite3.close(dbPointer)
161
+ isClosed = true
162
+ },
163
+ import: (source) => {
164
+ // https://www.sqlite.org/c3ref/c_deserialize_freeonclose.html
165
+ // #define SQLITE_DESERIALIZE_FREEONCLOSE 1 /* Call sqlite3_free() on close */
166
+ // #define SQLITE_DESERIALIZE_RESIZEABLE 2 /* Resize using sqlite3_realloc64() */
167
+ // #define SQLITE_DESERIALIZE_READONLY 4 /* Database is read-only */
168
+ const FREE_ON_CLOSE = 1
169
+ const RESIZEABLE = 2
170
+
171
+ // NOTE in case we'll have a future use-case where we need a read-only database, we can reuse this code below
172
+ // if (readOnly === true) {
173
+ // sqlite3.deserialize(db, 'main', bytes, bytes.length, bytes.length, FREE_ON_CLOSE | RESIZEABLE)
174
+ // } else {
175
+ if (source instanceof Uint8Array) {
176
+ const tmpDb = makeInMemoryDb(sqlite3)
177
+ // TODO find a way to do this more efficiently with sqlite to avoid either of the deserialize + backup call
178
+ // Maybe this can be done via the VFS API
179
+ sqlite3.deserialize(tmpDb.dbPointer, 'main', source, source.length, source.length, FREE_ON_CLOSE | RESIZEABLE)
180
+ sqlite3.backup(dbPointer, 'main', tmpDb.dbPointer, 'main')
181
+ sqlite3.close(tmpDb.dbPointer)
182
+ } else {
183
+ sqlite3.backup(dbPointer, 'main', source.metadata.dbPointer, 'main')
184
+ }
185
+
186
+ metadata.configureDb(sqliteDb)
187
+ },
188
+ session: () => {
189
+ const sessionPointer = sqlite3.session_create(dbPointer, 'main')
190
+ sqlite3.session_attach(sessionPointer, null)
191
+
192
+ return {
193
+ changeset: () => {
194
+ const res = sqlite3.session_changeset(sessionPointer)
195
+ return res.changeset ?? undefined
196
+ },
197
+ finish: () => {
198
+ sqlite3.session_delete(sessionPointer)
199
+ },
200
+ }
201
+ },
202
+ makeChangeset: (data) => {
203
+ const changeset = {
204
+ invert: () => {
205
+ const inverted = sqlite3.changeset_invert(data)
206
+ return sqliteDb.makeChangeset(inverted)
207
+ },
208
+ apply: () => {
209
+ sqlite3.changeset_apply(dbPointer, data)
210
+ },
211
+ } satisfies SqliteDbChangeset
212
+
213
+ return changeset
214
+ },
215
+ } satisfies SqliteDb<TMetadata>
216
+
217
+ metadata.configureDb(sqliteDb)
218
+
219
+ return sqliteDb
220
+ }
@@ -0,0 +1,190 @@
1
+ /// <reference types="node" />
2
+
3
+ /* eslint-disable prefer-arrow/prefer-arrow-functions */
4
+ import * as fs from 'node:fs'
5
+ import path from 'node:path'
6
+
7
+ import type * as WaSqlite from '@livestore/wa-sqlite'
8
+ import * as VFS from '@livestore/wa-sqlite/src/VFS.js'
9
+
10
+ import { FacadeVFS } from '../FacadeVFS.js'
11
+
12
+ interface NodeFsFile {
13
+ pathname: string
14
+ flags: number
15
+ fileHandle: number | null
16
+ }
17
+
18
+ export class NodeFS extends FacadeVFS {
19
+ private mapIdToFile = new Map<number, NodeFsFile>()
20
+ private lastError: Error | null = null
21
+ private readonly directory: string
22
+ constructor(name: string, sqlite3: WaSqlite.SQLiteAPI, directory: string) {
23
+ super(name, sqlite3)
24
+
25
+ this.directory = directory
26
+ }
27
+
28
+ getFilename(fileId: number): string {
29
+ const pathname = this.mapIdToFile.get(fileId)?.pathname
30
+ return `NodeFS:${pathname}`
31
+ }
32
+
33
+ jOpen(zName: string | null, fileId: number, flags: number, pOutFlags: DataView): number {
34
+ try {
35
+ const pathname = zName ? path.resolve(this.directory, zName) : Math.random().toString(36).slice(2)
36
+ const file: NodeFsFile = { pathname, flags, fileHandle: null }
37
+ this.mapIdToFile.set(fileId, file)
38
+
39
+ const create = !!(flags & VFS.SQLITE_OPEN_CREATE)
40
+ const readwrite = !!(flags & VFS.SQLITE_OPEN_READWRITE)
41
+
42
+ // Convert SQLite flags to Node.js flags
43
+ let fsFlags = 'r'
44
+ if (create && readwrite) {
45
+ // Check if file exists first
46
+ const exists = fs.existsSync(pathname)
47
+ fsFlags = exists ? 'r+' : 'w+' // Use r+ for existing files, w+ only for new files
48
+ } else if (readwrite) {
49
+ fsFlags = 'r+' // Open file for reading and writing
50
+ }
51
+
52
+ try {
53
+ file.fileHandle = fs.openSync(pathname, fsFlags)
54
+ pOutFlags.setInt32(0, flags, true)
55
+ return VFS.SQLITE_OK
56
+ } catch (err: any) {
57
+ if (err.code === 'ENOENT' && !create) {
58
+ return VFS.SQLITE_CANTOPEN
59
+ }
60
+ throw err
61
+ }
62
+ } catch (e: any) {
63
+ this.lastError = e
64
+ return VFS.SQLITE_CANTOPEN
65
+ }
66
+ }
67
+
68
+ jRead(fileId: number, pData: Uint8Array, iOffset: number): number {
69
+ try {
70
+ const file = this.mapIdToFile.get(fileId)
71
+ if (!file?.fileHandle) return VFS.SQLITE_IOERR_READ
72
+
73
+ // const view = new DataView(pData.buffer, pData.byteOffset, pData.length)
74
+ // const bytesRead = fs.readSync(file.fileHandle, view, 0, pData.length, iOffset)
75
+ const bytesRead = fs.readSync(file.fileHandle, pData.subarray(), { position: iOffset })
76
+
77
+ if (bytesRead < pData.length) {
78
+ pData.fill(0, bytesRead)
79
+ return VFS.SQLITE_IOERR_SHORT_READ
80
+ }
81
+ return VFS.SQLITE_OK
82
+ } catch (e: any) {
83
+ this.lastError = e
84
+ return VFS.SQLITE_IOERR_READ
85
+ }
86
+ }
87
+
88
+ jWrite(fileId: number, pData: Uint8Array, iOffset: number): number {
89
+ try {
90
+ const file = this.mapIdToFile.get(fileId)
91
+ if (!file?.fileHandle) return VFS.SQLITE_IOERR_WRITE
92
+
93
+ // const view = new DataView(pData.buffer, pData.byteOffset, pData.length)
94
+ // fs.writeSync(file.fileHandle, view, 0, pData.length, iOffset)
95
+ fs.writeSync(file.fileHandle, Buffer.from(pData.subarray()), 0, pData.length, iOffset)
96
+ return VFS.SQLITE_OK
97
+ } catch (e: any) {
98
+ this.lastError = e
99
+ return VFS.SQLITE_IOERR_WRITE
100
+ }
101
+ }
102
+
103
+ jClose(fileId: number): number {
104
+ try {
105
+ const file = this.mapIdToFile.get(fileId)
106
+ if (!file) return VFS.SQLITE_OK
107
+
108
+ this.mapIdToFile.delete(fileId)
109
+ if (file.fileHandle !== null) {
110
+ fs.closeSync(file.fileHandle)
111
+ }
112
+
113
+ if (file.flags & VFS.SQLITE_OPEN_DELETEONCLOSE) {
114
+ fs.unlinkSync(file.pathname)
115
+ }
116
+ return VFS.SQLITE_OK
117
+ } catch (e: any) {
118
+ this.lastError = e
119
+ return VFS.SQLITE_IOERR_CLOSE
120
+ }
121
+ }
122
+
123
+ jFileSize(fileId: number, pSize64: DataView): number {
124
+ try {
125
+ const file = this.mapIdToFile.get(fileId)
126
+ if (!file?.fileHandle) return VFS.SQLITE_IOERR_FSTAT
127
+
128
+ const stats = fs.fstatSync(file.fileHandle)
129
+ pSize64.setBigInt64(0, BigInt(stats.size), true)
130
+ return VFS.SQLITE_OK
131
+ } catch (e: any) {
132
+ this.lastError = e
133
+ return VFS.SQLITE_IOERR_FSTAT
134
+ }
135
+ }
136
+
137
+ jTruncate(fileId: number, iSize: number): number {
138
+ try {
139
+ const file = this.mapIdToFile.get(fileId)
140
+ if (!file?.fileHandle) return VFS.SQLITE_IOERR_TRUNCATE
141
+
142
+ fs.ftruncateSync(file.fileHandle, iSize)
143
+ return VFS.SQLITE_OK
144
+ } catch (e: any) {
145
+ this.lastError = e
146
+ return VFS.SQLITE_IOERR_TRUNCATE
147
+ }
148
+ }
149
+
150
+ jSync(fileId: number, _flags: number): number {
151
+ try {
152
+ const file = this.mapIdToFile.get(fileId)
153
+ if (!file?.fileHandle) return VFS.SQLITE_OK
154
+
155
+ // TODO do this out of band (for now we disable it to speed up the node vfs)
156
+ // fs.fsyncSync(file.fileHandle)
157
+ return VFS.SQLITE_OK
158
+ } catch (e: any) {
159
+ this.lastError = e
160
+ return VFS.SQLITE_IOERR_FSYNC
161
+ }
162
+ }
163
+
164
+ jDelete(zName: string, _syncDir: number): number {
165
+ try {
166
+ const pathname = path.resolve(this.directory, zName)
167
+ fs.unlinkSync(pathname)
168
+ return VFS.SQLITE_OK
169
+ } catch (e: any) {
170
+ this.lastError = e
171
+ return VFS.SQLITE_IOERR_DELETE
172
+ }
173
+ }
174
+
175
+ jAccess(zName: string, _flags: number, pResOut: DataView): number {
176
+ try {
177
+ const pathname = path.resolve(this.directory, zName)
178
+ const exists = fs.existsSync(pathname)
179
+ pResOut.setInt32(0, exists ? 1 : 0, true)
180
+ return VFS.SQLITE_OK
181
+ } catch (e: any) {
182
+ this.lastError = e
183
+ return VFS.SQLITE_IOERR_ACCESS
184
+ }
185
+ }
186
+
187
+ deleteDb(fileName: string) {
188
+ fs.unlinkSync(path.join(this.directory, fileName))
189
+ }
190
+ }
@@ -0,0 +1,132 @@
1
+ import path from 'node:path'
2
+
3
+ import { type MakeSqliteDb, type PersistenceInfo, type SqliteDb, UnexpectedError } from '@livestore/common'
4
+ import { Effect, FileSystem } from '@livestore/utils/effect'
5
+ import type * as WaSqlite from '@livestore/wa-sqlite'
6
+ import type { MemoryVFS } from '@livestore/wa-sqlite/src/examples/MemoryVFS.js'
7
+
8
+ import { makeInMemoryDb } from '../in-memory-vfs.js'
9
+ import { makeSqliteDb } from '../make-sqlite-db.js'
10
+ import { NodeFS } from './NodeFS.js'
11
+
12
+ export type NodeDatabaseMetadataInMemory = {
13
+ _tag: 'in-memory'
14
+ vfs: MemoryVFS
15
+ dbPointer: number
16
+ persistenceInfo: PersistenceInfo
17
+ deleteDb: () => void
18
+ configureDb: (db: SqliteDb) => void
19
+ }
20
+
21
+ export type NodeDatabaseMetadataFs = {
22
+ _tag: 'fs'
23
+ vfs: NodeFS
24
+ dbPointer: number
25
+ persistenceInfo: PersistenceInfo<{ directory: string }>
26
+ deleteDb: () => void
27
+ configureDb: (db: SqliteDb) => void
28
+ }
29
+
30
+ export type NodeDatabaseMetadata = NodeDatabaseMetadataInMemory | NodeDatabaseMetadataFs
31
+
32
+ export type NodeDatabaseInputInMemory = {
33
+ _tag: 'in-memory'
34
+ configureDb?: (db: SqliteDb) => void
35
+ }
36
+
37
+ export type NodeDatabaseInputFs = {
38
+ _tag: 'fs'
39
+ directory: string
40
+ fileName: string
41
+ configureDb?: (db: SqliteDb) => void
42
+ }
43
+
44
+ export type NodeDatabaseInput = NodeDatabaseInputInMemory | NodeDatabaseInputFs
45
+
46
+ export const sqliteDbFactory = ({
47
+ sqlite3,
48
+ }: {
49
+ sqlite3: SQLiteAPI
50
+ }): Effect.Effect<
51
+ MakeSqliteDb<{ dbPointer: number; persistenceInfo: PersistenceInfo }, NodeDatabaseInput, NodeDatabaseMetadata>,
52
+ never,
53
+ FileSystem.FileSystem
54
+ > =>
55
+ Effect.andThen(
56
+ FileSystem.FileSystem,
57
+ (fs) => (input) =>
58
+ Effect.gen(function* () {
59
+ if (input._tag === 'in-memory') {
60
+ const { dbPointer, vfs } = makeInMemoryDb(sqlite3)
61
+ return makeSqliteDb<NodeDatabaseMetadataInMemory>({
62
+ sqlite3,
63
+ metadata: {
64
+ _tag: 'in-memory',
65
+ vfs,
66
+ dbPointer,
67
+ persistenceInfo: { fileName: ':memory:' },
68
+ deleteDb: () => {},
69
+ configureDb: input.configureDb ?? (() => {}),
70
+ },
71
+ }) as any
72
+ }
73
+
74
+ const { dbPointer, vfs } = yield* makeNodeFsDb({
75
+ sqlite3,
76
+ fileName: input.fileName,
77
+ directory: input.directory,
78
+ fs,
79
+ })
80
+
81
+ const filePath = path.join(input.directory, input.fileName)
82
+
83
+ return makeSqliteDb<NodeDatabaseMetadataFs>({
84
+ sqlite3,
85
+ metadata: {
86
+ _tag: 'fs',
87
+ vfs,
88
+ dbPointer,
89
+ persistenceInfo: { fileName: input.fileName, directory: input.directory },
90
+ deleteDb: () => vfs.deleteDb(filePath),
91
+ configureDb: input.configureDb ?? (() => {}),
92
+ },
93
+ })
94
+ }),
95
+ )
96
+
97
+ let nodeFsVfs: NodeFS | undefined
98
+
99
+ const makeNodeFsDb = ({
100
+ sqlite3,
101
+ fileName,
102
+ directory,
103
+ fs,
104
+ }: {
105
+ sqlite3: WaSqlite.SQLiteAPI
106
+ fileName: string
107
+ directory: string
108
+ fs: FileSystem.FileSystem
109
+ }) =>
110
+ Effect.gen(function* () {
111
+ // NOTE to keep the filePath short, we use the directory name in the vfs name
112
+ // If this is becoming a problem, we can use a hashed version of the directory name
113
+ const vfsName = `node-fs-${directory}`
114
+ if (nodeFsVfs === undefined) {
115
+ // TODO refactor with Effect FileSystem instead of using `node:fs` directly inside of NodeFS
116
+ nodeFsVfs = new NodeFS(vfsName, (sqlite3 as any).module, directory)
117
+ // @ts-expect-error TODO fix types
118
+ sqlite3.vfs_register(nodeFsVfs, false)
119
+ }
120
+
121
+ yield* fs.makeDirectory(directory, { recursive: true })
122
+
123
+ const FILE_NAME_MAX_LENGTH = 56
124
+ if (fileName.length > FILE_NAME_MAX_LENGTH) {
125
+ throw new Error(`File name ${fileName} is too long. Maximum length is ${FILE_NAME_MAX_LENGTH} characters.`)
126
+ }
127
+
128
+ // NOTE SQLite will return a "disk I/O error" if the file path is too long.
129
+ const dbPointer = sqlite3.open_v2Sync(fileName, undefined, vfsName)
130
+
131
+ return { dbPointer, vfs: nodeFsVfs }
132
+ }).pipe(UnexpectedError.mapToUnexpectedError)
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src",
6
+ "tsBuildInfoFile": "./dist/.tsbuildinfo",
7
+ },
8
+ "include": ["./src"],
9
+ "references": [{ "path": "../common" }, { "path": "../utils" }]
10
+ }