@effect/sql-sqlite-wasm 0.20.6 → 0.21.1

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/esm/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ export * as OpfsWorker from "./OpfsWorker.js";
1
5
  /**
2
6
  * @since 1.0.0
3
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["SqliteClient","SqliteMigrator"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAGA,OAAO,KAAKA,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,cAAc,MAAM,qBAAqB","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["OpfsWorker","SqliteClient","SqliteMigrator"],"sources":["../../src/index.ts"],"sourcesContent":[null],"mappings":"AAAA;;;AAGA,OAAO,KAAKA,UAAU,MAAM,iBAAiB;AAE7C;;;AAGA,OAAO,KAAKC,YAAY,MAAM,mBAAmB;AAEjD;;;AAGA,OAAO,KAAKC,cAAc,MAAM,qBAAqB","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=opfsWorker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opfsWorker.js","names":[],"sources":["../../../src/internal/opfsWorker.ts"],"sourcesContent":[null],"mappings":"","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/sql-sqlite-wasm",
3
- "version": "0.20.6",
3
+ "version": "0.21.1",
4
4
  "description": "A SQLite toolkit for Effect",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,11 +10,11 @@
10
10
  },
11
11
  "sideEffects": [],
12
12
  "dependencies": {
13
- "@opentelemetry/semantic-conventions": "^1.25.1",
14
- "@sqlite.org/sqlite-wasm": "3.46.0-build2"
13
+ "@opentelemetry/semantic-conventions": "^1.25.1"
15
14
  },
16
15
  "peerDependencies": {
17
- "@effect/sql": "^0.20.6",
16
+ "@effect/wa-sqlite": "^0.1.2",
17
+ "@effect/sql": "^0.20.8",
18
18
  "effect": "^3.10.15"
19
19
  },
20
20
  "publishConfig": {
@@ -30,6 +30,11 @@
30
30
  "import": "./dist/esm/index.js",
31
31
  "default": "./dist/cjs/index.js"
32
32
  },
33
+ "./OpfsWorker": {
34
+ "types": "./dist/dts/OpfsWorker.d.ts",
35
+ "import": "./dist/esm/OpfsWorker.js",
36
+ "default": "./dist/cjs/OpfsWorker.js"
37
+ },
33
38
  "./SqliteClient": {
34
39
  "types": "./dist/dts/SqliteClient.d.ts",
35
40
  "import": "./dist/esm/SqliteClient.js",
@@ -48,6 +53,9 @@
48
53
  },
49
54
  "typesVersions": {
50
55
  "*": {
56
+ "OpfsWorker": [
57
+ "./dist/dts/OpfsWorker.d.ts"
58
+ ],
51
59
  "SqliteClient": [
52
60
  "./dist/dts/SqliteClient.d.ts"
53
61
  ],
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ /// <reference lib="webworker" />
5
+ import { SqlError } from "@effect/sql/SqlError"
6
+ import * as WaSqlite from "@effect/wa-sqlite"
7
+ import SQLiteESMFactory from "@effect/wa-sqlite/dist/wa-sqlite.mjs"
8
+ import { AccessHandlePoolVFS } from "@effect/wa-sqlite/src/examples/AccessHandlePoolVFS.js"
9
+ import * as Effect from "effect/Effect"
10
+ import type { OpfsWorkerMessage } from "./internal/opfsWorker.js"
11
+
12
+ /**
13
+ * @category models
14
+ * @since 1.0.0
15
+ */
16
+ export interface OpfsWorkerConfig {
17
+ readonly port: EventTarget & Pick<MessagePort, "postMessage" | "close">
18
+ readonly dbName: string
19
+ }
20
+
21
+ /**
22
+ * @category constructor
23
+ * @since 1.0.0
24
+ */
25
+ export const run = (
26
+ options: OpfsWorkerConfig
27
+ ): Effect.Effect<void, SqlError> =>
28
+ Effect.gen(function*() {
29
+ const factory = yield* Effect.promise(() => SQLiteESMFactory())
30
+ const sqlite3 = WaSqlite.Factory(factory)
31
+ const vfs = yield* Effect.promise(() => AccessHandlePoolVFS.create("opfs", factory))
32
+ sqlite3.vfs_register(vfs, false)
33
+ const db = yield* Effect.acquireRelease(
34
+ Effect.try({
35
+ try: () => sqlite3.open_v2(options.dbName, undefined, "opfs"),
36
+ catch: (cause) => new SqlError({ cause, message: "Failed to open database" })
37
+ }),
38
+ (db) => Effect.sync(() => sqlite3.close(db))
39
+ )
40
+
41
+ return yield* Effect.async<void>((resume) => {
42
+ const onMessage = (event: any) => {
43
+ let messageId: number
44
+ const message = event.data as OpfsWorkerMessage
45
+ try {
46
+ switch (message[0]) {
47
+ case "close": {
48
+ options.port.close()
49
+ return resume(Effect.void)
50
+ }
51
+ case "import": {
52
+ const [, id, data] = message
53
+ messageId = id
54
+ sqlite3.deserialize(db, "main", data, data.length, data.length, 1 | 2)
55
+ options.port.postMessage([id, void 0, void 0])
56
+ return
57
+ }
58
+ case "export": {
59
+ const [, id] = message
60
+ messageId = id
61
+ const data = sqlite3.serialize(db, "main")
62
+ options.port.postMessage([id, undefined, data], [data.buffer])
63
+ return
64
+ }
65
+ case "update_hook": {
66
+ messageId = -1
67
+ sqlite3.update_hook(db, (_op, _db, table, rowid) => {
68
+ if (!table) return
69
+ options.port.postMessage(["update_hook", table, Number(rowid)])
70
+ })
71
+ return
72
+ }
73
+ default: {
74
+ const [id, sql, params] = message
75
+ messageId = id
76
+ const results: Array<any> = []
77
+ let columns: Array<string> | undefined
78
+ for (const stmt of sqlite3.statements(db, sql)) {
79
+ sqlite3.bind_collection(stmt, params as any)
80
+ while (sqlite3.step(stmt) === WaSqlite.SQLITE_ROW) {
81
+ columns = columns ?? sqlite3.column_names(stmt)
82
+ const row = sqlite3.row(stmt)
83
+ results.push(row)
84
+ }
85
+ }
86
+ options.port.postMessage([id, undefined, [columns, results]])
87
+ return
88
+ }
89
+ }
90
+ } catch (e: any) {
91
+ const message = "message" in e ? e.message : String(e)
92
+ options.port.postMessage([messageId!, message, undefined])
93
+ }
94
+ }
95
+ options.port.addEventListener("message", onMessage)
96
+ options.port.postMessage(["ready", undefined, undefined])
97
+ return Effect.sync(() => {
98
+ options.port.removeEventListener("message", onMessage)
99
+ })
100
+ })
101
+ }).pipe(Effect.scoped)
@@ -1,20 +1,31 @@
1
1
  /**
2
2
  * @since 1.0.0
3
3
  */
4
+ import * as Reactivity from "@effect/experimental/Reactivity"
4
5
  import * as Client from "@effect/sql/SqlClient"
5
6
  import type { Connection } from "@effect/sql/SqlConnection"
6
7
  import { SqlError } from "@effect/sql/SqlError"
7
8
  import * as Statement from "@effect/sql/Statement"
9
+ import * as WaSqlite from "@effect/wa-sqlite"
10
+ import SQLiteESMFactory from "@effect/wa-sqlite/dist/wa-sqlite.mjs"
11
+ import { MemoryVFS } from "@effect/wa-sqlite/src/examples/MemoryVFS.js"
8
12
  import * as Otel from "@opentelemetry/semantic-conventions"
9
- import type { DB, OpenMode, RowMode } from "@sqlite.org/sqlite-wasm"
10
- import sqliteInit from "@sqlite.org/sqlite-wasm"
11
13
  import * as Config from "effect/Config"
12
14
  import type { ConfigError } from "effect/ConfigError"
13
15
  import * as Context from "effect/Context"
16
+ import * as Deferred from "effect/Deferred"
14
17
  import * as Effect from "effect/Effect"
18
+ import * as Exit from "effect/Exit"
19
+ import * as FiberRef from "effect/FiberRef"
15
20
  import { identity } from "effect/Function"
21
+ import { globalValue } from "effect/GlobalValue"
16
22
  import * as Layer from "effect/Layer"
23
+ import type * as Mailbox from "effect/Mailbox"
24
+ import type { ReadonlyRecord } from "effect/Record"
17
25
  import * as Scope from "effect/Scope"
26
+ import * as ScopedRef from "effect/ScopedRef"
27
+ import * as Stream from "effect/Stream"
28
+ import type { OpfsWorkerMessage } from "./internal/opfsWorker.js"
18
29
 
19
30
  /**
20
31
  * @category type ids
@@ -34,8 +45,17 @@ export type TypeId = typeof TypeId
34
45
  */
35
46
  export interface SqliteClient extends Client.SqlClient {
36
47
  readonly [TypeId]: TypeId
37
- readonly config: SqliteClientConfig
48
+ readonly config: SqliteClientMemoryConfig
49
+ readonly reactive: <A, E, R>(
50
+ keys: ReadonlyArray<string> | ReadonlyRecord<string, ReadonlyArray<number>>,
51
+ effect: Effect.Effect<A, E, R>
52
+ ) => Stream.Stream<A, E, R>
53
+ readonly reactiveMailbox: <A, E, R>(
54
+ keys: ReadonlyArray<string> | ReadonlyRecord<string, ReadonlyArray<number>>,
55
+ effect: Effect.Effect<A, E, R>
56
+ ) => Effect.Effect<Mailbox.ReadonlyMailbox<A>, E, R | Scope.Scope>
38
57
  readonly export: Effect.Effect<Uint8Array, SqlError>
58
+ readonly import: (data: Uint8Array) => Effect.Effect<void, SqlError>
39
59
 
40
60
  /** Not supported in sqlite */
41
61
  readonly updateValues: never
@@ -51,74 +71,105 @@ export const SqliteClient = Context.GenericTag<SqliteClient>("@effect/sql-sqlite
51
71
  * @category models
52
72
  * @since 1.0.0
53
73
  */
54
- export type SqliteClientConfig =
55
- | {
56
- readonly mode?: "vfs"
57
- readonly dbName?: string
58
- readonly openMode?: OpenMode
59
- readonly spanAttributes?: Record<string, unknown>
60
- readonly transformResultNames?: (str: string) => string
61
- readonly transformQueryNames?: (str: string) => string
62
- }
63
- | {
64
- readonly mode: "opfs"
65
- readonly dbName: string
66
- readonly openMode?: OpenMode
67
- readonly spanAttributes?: Record<string, unknown>
68
- readonly transformResultNames?: (str: string) => string
69
- readonly transformQueryNames?: (str: string) => string
70
- }
74
+ export interface SqliteClientMemoryConfig {
75
+ readonly installReactivityHooks?: boolean
76
+ readonly spanAttributes?: Record<string, unknown>
77
+ readonly transformResultNames?: (str: string) => string
78
+ readonly transformQueryNames?: (str: string) => string
79
+ }
80
+
81
+ /**
82
+ * @category models
83
+ * @since 1.0.0
84
+ */
85
+ export interface SqliteClientConfig {
86
+ readonly worker: Effect.Effect<Worker | SharedWorker | MessagePort, never, Scope.Scope>
87
+ readonly installReactivityHooks?: boolean
88
+ readonly spanAttributes?: Record<string, unknown>
89
+ readonly transformResultNames?: (str: string) => string
90
+ readonly transformQueryNames?: (str: string) => string
91
+ }
71
92
 
72
93
  interface SqliteConnection extends Connection {
73
94
  readonly export: Effect.Effect<Uint8Array, SqlError>
95
+ readonly import: (data: Uint8Array) => Effect.Effect<void, SqlError>
74
96
  }
75
97
 
98
+ const initModule = Effect.runSync(
99
+ Effect.cached(Effect.promise(() => SQLiteESMFactory()))
100
+ )
101
+
76
102
  const initEffect = Effect.runSync(
77
- Effect.cached(Effect.promise(() => sqliteInit()))
103
+ Effect.cached(initModule.pipe(Effect.map((module) => WaSqlite.Factory(module))))
78
104
  )
79
105
 
106
+ const registered = globalValue("@effect/sql-sqlite-wasm/registered", () => new Set<string>())
107
+
80
108
  /**
81
109
  * @category constructor
82
110
  * @since 1.0.0
83
111
  */
84
- export const make = (
85
- options: SqliteClientConfig
86
- ): Effect.Effect<SqliteClient, never, Scope.Scope> =>
87
- Effect.gen(function*(_) {
112
+ export const makeMemory = (
113
+ options: SqliteClientMemoryConfig
114
+ ): Effect.Effect<SqliteClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
115
+ Effect.gen(function*() {
116
+ const reactivity = yield* Reactivity.Reactivity
88
117
  const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
89
118
  const transformRows = Statement.defaultTransforms(
90
119
  options.transformResultNames!
91
120
  ).array
92
121
 
93
- const makeConnection = Effect.gen(function*(_) {
94
- const sqlite3 = yield* _(initEffect)
122
+ const makeConnection = Effect.gen(function*() {
123
+ const sqlite3 = yield* initEffect
95
124
 
96
- let db: DB
97
- if (options.mode === "opfs") {
98
- if (!sqlite3.oo1.OpfsDb) {
99
- yield* _(Effect.dieMessage("opfs mode not available"))
100
- }
101
- db = new sqlite3.oo1.OpfsDb!(options.dbName, options.openMode ?? "c")
102
- } else {
103
- db = new sqlite3.oo1.DB(options.dbName, options.openMode)
125
+ if (registered.has("memory-vfs") === false) {
126
+ registered.add("memory-vfs")
127
+ const module = yield* initModule
128
+ // @ts-expect-error
129
+ const vfs = new MemoryVFS("memory-vfs", module)
130
+ sqlite3.vfs_register(vfs as any, false)
104
131
  }
132
+ const db = yield* Effect.acquireRelease(
133
+ Effect.try({
134
+ try: () => sqlite3.open_v2(":memory:", undefined, "memory-vfs"),
135
+ catch: (cause) => new SqlError({ cause, message: "Failed to open database" })
136
+ }),
137
+ (db) => Effect.sync(() => sqlite3.close(db))
138
+ )
105
139
 
106
- yield* _(Effect.addFinalizer(() => Effect.sync(() => db.close())))
140
+ if (options.installReactivityHooks) {
141
+ sqlite3.update_hook(db, (_op, _db, table, rowid) => {
142
+ if (!table) return
143
+ const id = Number(rowid)
144
+ reactivity.unsafeInvalidate({ [table]: [id] })
145
+ })
146
+ }
107
147
 
108
148
  const run = (
109
149
  sql: string,
110
150
  params: ReadonlyArray<Statement.Primitive> = [],
111
- rowMode: RowMode = "object"
151
+ rowMode: "object" | "array" = "object"
112
152
  ) =>
113
153
  Effect.try({
114
154
  try: () => {
115
155
  const results: Array<any> = []
116
- db.exec({
117
- sql,
118
- bind: params.length ? params : undefined,
119
- rowMode,
120
- resultRows: results
121
- })
156
+ for (const stmt of sqlite3.statements(db, sql)) {
157
+ let columns: Array<string> | undefined
158
+ sqlite3.bind_collection(stmt, params as any)
159
+ while (sqlite3.step(stmt) === WaSqlite.SQLITE_ROW) {
160
+ columns = columns ?? sqlite3.column_names(stmt)
161
+ const row = sqlite3.row(stmt)
162
+ if (rowMode === "object") {
163
+ const obj: Record<string, any> = {}
164
+ for (let i = 0; i < columns.length; i++) {
165
+ obj[columns[i]] = row[i]
166
+ }
167
+ results.push(obj)
168
+ } else {
169
+ results.push(row)
170
+ }
171
+ }
172
+ }
122
173
  return results
123
174
  },
124
175
  catch: (cause) => new SqlError({ cause, message: "Failed to execute statement" })
@@ -144,18 +195,41 @@ export const make = (
144
195
  executeUnprepared(sql, params) {
145
196
  return runTransform(sql, params)
146
197
  },
147
- executeStream() {
148
- return Effect.dieMessage("executeStream not implemented")
198
+ executeStream(sql, params) {
199
+ return Stream.fromIterableEffect(Effect.try({
200
+ *try() {
201
+ for (const stmt of sqlite3.statements(db, sql)) {
202
+ let columns: Array<string> | undefined
203
+ sqlite3.bind_collection(stmt, params as any)
204
+ while (sqlite3.step(stmt) === WaSqlite.SQLITE_ROW) {
205
+ columns = columns ?? sqlite3.column_names(stmt)
206
+ const row = sqlite3.row(stmt)
207
+ const obj: Record<string, any> = {}
208
+ for (let i = 0; i < columns.length; i++) {
209
+ obj[columns[i]] = row[i]
210
+ }
211
+ yield obj
212
+ }
213
+ }
214
+ },
215
+ catch: (cause) => new SqlError({ cause, message: "Failed to execute statement" })
216
+ }))
149
217
  },
150
218
  export: Effect.try({
151
- try: () => sqlite3.capi.sqlite3_js_db_export(db.pointer),
219
+ try: () => sqlite3.serialize(db, "main"),
152
220
  catch: (cause) => new SqlError({ cause, message: "Failed to export database" })
153
- })
221
+ }),
222
+ import(data) {
223
+ return Effect.try({
224
+ try: () => sqlite3.deserialize(db, "main", data, data.length, data.length, 1 | 2),
225
+ catch: (cause) => new SqlError({ cause, message: "Failed to import database" })
226
+ })
227
+ }
154
228
  })
155
229
  })
156
230
 
157
- const semaphore = yield* _(Effect.makeSemaphore(1))
158
- const connection = yield* _(makeConnection)
231
+ const semaphore = yield* Effect.makeSemaphore(1)
232
+ const connection = yield* makeConnection
159
233
 
160
234
  const acquirer = semaphore.withPermits(1)(Effect.succeed(connection))
161
235
  const transactionAcquirer = Effect.uninterruptibleMask((restore) =>
@@ -184,28 +258,239 @@ export const make = (
184
258
  {
185
259
  [TypeId]: TypeId as TypeId,
186
260
  config: options,
187
- export: Effect.flatMap(acquirer, (_) => _.export)
261
+ reactive: reactivity.stream,
262
+ reactiveMailbox: reactivity.query,
263
+ export: semaphore.withPermits(1)(connection.export),
264
+ import(data: Uint8Array) {
265
+ return semaphore.withPermits(1)(connection.import(data))
266
+ }
188
267
  }
189
268
  )
190
269
  })
191
270
 
271
+ /**
272
+ * @category constructor
273
+ * @since 1.0.0
274
+ */
275
+ export const make = (
276
+ options: SqliteClientConfig
277
+ ): Effect.Effect<SqliteClient, SqlError, Scope.Scope | Reactivity.Reactivity> =>
278
+ Effect.gen(function*() {
279
+ const reactivity = yield* Reactivity.Reactivity
280
+ const compiler = Statement.makeCompilerSqlite(options.transformQueryNames)
281
+ const transformRows = Statement.defaultTransforms(
282
+ options.transformResultNames!
283
+ ).array
284
+ const pending = new Map<number, (effect: Exit.Exit<any, SqlError>) => void>()
285
+
286
+ const makeConnection = Effect.gen(function*() {
287
+ let currentId = 0
288
+ const scope = yield* Effect.scope
289
+ const readyDeferred = yield* Deferred.make<void>()
290
+
291
+ const worker = yield* options.worker
292
+ const port = "port" in worker ? worker.port : worker
293
+ const postMessage = (message: OpfsWorkerMessage, transferables?: ReadonlyArray<any>) =>
294
+ port.postMessage(message, transferables as any)
295
+
296
+ yield* Scope.addFinalizer(scope, Effect.sync(() => postMessage(["close"])))
297
+
298
+ const onMessage = (event: any) => {
299
+ const [id, error, results] = event.data
300
+ if (id === "ready") {
301
+ Deferred.unsafeDone(readyDeferred, Exit.void)
302
+ return
303
+ } else if (id === "update_hook") {
304
+ reactivity.unsafeInvalidate({ [error]: [results] })
305
+ return
306
+ } else {
307
+ const resume = pending.get(id)
308
+ if (!resume) return
309
+ pending.delete(id)
310
+ if (error) {
311
+ resume(Exit.fail(new SqlError({ cause: error as string, message: "Failed to execute statement" })))
312
+ } else {
313
+ resume(Exit.succeed(results))
314
+ }
315
+ }
316
+ }
317
+ port.addEventListener("message", onMessage)
318
+
319
+ function onError() {
320
+ Effect.runFork(ScopedRef.set(connectionRef, makeConnection))
321
+ }
322
+ if ("onerror" in worker) {
323
+ worker.addEventListener("error", onError)
324
+ }
325
+
326
+ yield* Scope.addFinalizer(
327
+ scope,
328
+ Effect.sync(() => {
329
+ worker.removeEventListener("message", onMessage)
330
+ worker.removeEventListener("error", onError)
331
+ })
332
+ )
333
+
334
+ yield* Deferred.await(readyDeferred)
335
+
336
+ if (options.installReactivityHooks) {
337
+ postMessage(["update_hook"])
338
+ }
339
+
340
+ const send = (id: number, message: OpfsWorkerMessage, transferables?: ReadonlyArray<any>) =>
341
+ Effect.async<any, SqlError>((resume) => {
342
+ pending.set(id, resume)
343
+ postMessage(message, transferables)
344
+ })
345
+
346
+ const run = (
347
+ sql: string,
348
+ params: ReadonlyArray<Statement.Primitive> = [],
349
+ rowMode: "object" | "array" = "object"
350
+ ): Effect.Effect<Array<any>, SqlError, never> => {
351
+ const rows = Effect.withFiberRuntime<[Array<string>, Array<any>], SqlError>((fiber) => {
352
+ const id = currentId++
353
+ return send(id, [id, sql, params], fiber.getFiberRef(currentTransferables))
354
+ })
355
+ return rowMode === "object"
356
+ ? Effect.map(rows, extractObject)
357
+ : Effect.map(rows, extractRows)
358
+ }
359
+
360
+ const runTransform = options.transformResultNames
361
+ ? (sql: string, params?: ReadonlyArray<Statement.Primitive>) => Effect.map(run(sql, params), transformRows)
362
+ : run
363
+
364
+ return identity<SqliteConnection>({
365
+ execute(sql, params) {
366
+ return runTransform(sql, params)
367
+ },
368
+ executeRaw(sql, params) {
369
+ return run(sql, params)
370
+ },
371
+ executeValues(sql, params) {
372
+ return run(sql, params, "array")
373
+ },
374
+ executeWithoutTransform(sql, params) {
375
+ return run(sql, params)
376
+ },
377
+ executeUnprepared(sql, params) {
378
+ return runTransform(sql, params)
379
+ },
380
+ executeStream() {
381
+ return Effect.dieMessage("executeStream not implemented")
382
+ },
383
+ export: Effect.suspend(() => {
384
+ const id = currentId++
385
+ return send(id, ["export", id])
386
+ }),
387
+ import(data) {
388
+ return Effect.suspend(() => {
389
+ const id = currentId++
390
+ return send(id, ["import", id, data], [data.buffer])
391
+ })
392
+ }
393
+ })
394
+ })
395
+
396
+ const connectionRef = yield* ScopedRef.fromAcquire(makeConnection)
397
+
398
+ const semaphore = yield* Effect.makeSemaphore(1)
399
+ const acquirer = semaphore.withPermits(1)(ScopedRef.get(connectionRef))
400
+ const transactionAcquirer = Effect.uninterruptibleMask((restore) =>
401
+ Effect.zipRight(
402
+ Effect.zipRight(
403
+ restore(semaphore.take(1)),
404
+ Effect.tap(
405
+ Effect.scope,
406
+ (scope) => Scope.addFinalizer(scope, semaphore.release(1))
407
+ )
408
+ ),
409
+ ScopedRef.get(connectionRef)
410
+ )
411
+ )
412
+
413
+ return Object.assign(
414
+ Client.make({
415
+ acquirer,
416
+ compiler,
417
+ transactionAcquirer,
418
+ spanAttributes: [
419
+ ...(options.spanAttributes ? Object.entries(options.spanAttributes) : []),
420
+ [Otel.SEMATTRS_DB_SYSTEM, Otel.DBSYSTEMVALUES_SQLITE]
421
+ ]
422
+ }) as SqliteClient,
423
+ {
424
+ [TypeId]: TypeId as TypeId,
425
+ config: options,
426
+ reactive: reactivity.stream,
427
+ reactiveMailbox: reactivity.query,
428
+ export: Effect.flatMap(acquirer, (connection) => connection.export),
429
+ import(data: Uint8Array) {
430
+ return Effect.flatMap(acquirer, (connection) => connection.import(data))
431
+ }
432
+ }
433
+ )
434
+ })
435
+
436
+ function rowToObject(columns: Array<string>, row: Array<any>) {
437
+ const obj: Record<string, any> = {}
438
+ for (let i = 0; i < columns.length; i++) {
439
+ obj[columns[i]] = row[i]
440
+ }
441
+ return obj
442
+ }
443
+ const extractObject = (rows: [Array<string>, Array<any>]) => rows[1].map((row) => rowToObject(rows[0], row))
444
+ const extractRows = (rows: [Array<string>, Array<any>]) => rows[1]
445
+
446
+ /**
447
+ * @category tranferables
448
+ * @since 1.0.0
449
+ */
450
+ export const currentTransferables: FiberRef.FiberRef<ReadonlyArray<Transferable>> = globalValue(
451
+ "@effect/sql-sqlite-wasm/currentTransferables",
452
+ () => FiberRef.unsafeMake<ReadonlyArray<Transferable>>([])
453
+ )
454
+
455
+ /**
456
+ * @category tranferables
457
+ * @since 1.0.0
458
+ */
459
+ export const withTransferables =
460
+ (transferables: ReadonlyArray<Transferable>) => <A, E, R>(effect: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>
461
+ Effect.locally(effect, currentTransferables, transferables)
462
+
192
463
  /**
193
464
  * @category layers
194
465
  * @since 1.0.0
195
466
  */
196
- export const layerConfig = (
197
- config: Config.Config.Wrap<SqliteClientConfig>
198
- ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError> =>
467
+ export const layerMemoryConfig = (
468
+ config: Config.Config.Wrap<SqliteClientMemoryConfig>
469
+ ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError | SqlError> =>
199
470
  Layer.scopedContext(
200
471
  Config.unwrap(config).pipe(
201
- Effect.flatMap(make),
472
+ Effect.flatMap(makeMemory),
202
473
  Effect.map((client) =>
203
474
  Context.make(SqliteClient, client).pipe(
204
475
  Context.add(Client.SqlClient, client)
205
476
  )
206
477
  )
207
478
  )
208
- )
479
+ ).pipe(Layer.provide(Reactivity.layer))
480
+
481
+ /**
482
+ * @category layers
483
+ * @since 1.0.0
484
+ */
485
+ export const layerMemory = (
486
+ config: SqliteClientMemoryConfig
487
+ ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError | SqlError> =>
488
+ Layer.scopedContext(
489
+ Effect.map(makeMemory(config), (client) =>
490
+ Context.make(SqliteClient, client).pipe(
491
+ Context.add(Client.SqlClient, client)
492
+ ))
493
+ ).pipe(Layer.provide(Reactivity.layer))
209
494
 
210
495
  /**
211
496
  * @category layers
@@ -213,10 +498,28 @@ export const layerConfig = (
213
498
  */
214
499
  export const layer = (
215
500
  config: SqliteClientConfig
216
- ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError> =>
501
+ ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError | SqlError> =>
217
502
  Layer.scopedContext(
218
503
  Effect.map(make(config), (client) =>
219
504
  Context.make(SqliteClient, client).pipe(
220
505
  Context.add(Client.SqlClient, client)
221
506
  ))
222
- )
507
+ ).pipe(Layer.provide(Reactivity.layer))
508
+
509
+ /**
510
+ * @category layers
511
+ * @since 1.0.0
512
+ */
513
+ export const layerConfig = (
514
+ config: Config.Config.Wrap<SqliteClientConfig>
515
+ ): Layer.Layer<SqliteClient | Client.SqlClient, ConfigError | SqlError> =>
516
+ Layer.scopedContext(
517
+ Config.unwrap(config).pipe(
518
+ Effect.flatMap(make),
519
+ Effect.map((client) =>
520
+ Context.make(SqliteClient, client).pipe(
521
+ Context.add(Client.SqlClient, client)
522
+ )
523
+ )
524
+ )
525
+ ).pipe(Layer.provide(Reactivity.layer))