@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/OpfsWorker/package.json +6 -0
- package/dist/cjs/OpfsWorker.js +103 -0
- package/dist/cjs/OpfsWorker.js.map +1 -0
- package/dist/cjs/SqliteClient.js +254 -32
- package/dist/cjs/SqliteClient.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/opfsWorker.js +6 -0
- package/dist/cjs/internal/opfsWorker.js.map +1 -0
- package/dist/dts/OpfsWorker.d.ts +19 -0
- package/dist/dts/OpfsWorker.d.ts.map +1 -0
- package/dist/dts/SqliteClient.d.ts +48 -14
- package/dist/dts/SqliteClient.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/opfsWorker.d.ts +2 -0
- package/dist/dts/internal/opfsWorker.d.ts.map +1 -0
- package/dist/esm/OpfsWorker.js +92 -0
- package/dist/esm/OpfsWorker.js.map +1 -0
- package/dist/esm/SqliteClient.js +247 -29
- package/dist/esm/SqliteClient.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/opfsWorker.js +2 -0
- package/dist/esm/internal/opfsWorker.js.map +1 -0
- package/package.json +12 -4
- package/src/OpfsWorker.ts +101 -0
- package/src/SqliteClient.ts +360 -57
- package/src/index.ts +5 -0
- package/src/internal/opfsWorker.ts +9 -0
- package/src/sqlite-wasm.d.ts +10 -38
package/dist/esm/index.js
CHANGED
package/dist/esm/index.js.map
CHANGED
|
@@ -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 @@
|
|
|
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.
|
|
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/
|
|
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)
|
package/src/SqliteClient.ts
CHANGED
|
@@ -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:
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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.
|
|
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
|
|
85
|
-
options:
|
|
86
|
-
): Effect.Effect<SqliteClient,
|
|
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*
|
|
122
|
+
const makeConnection = Effect.gen(function*() {
|
|
123
|
+
const sqlite3 = yield* initEffect
|
|
95
124
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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:
|
|
151
|
+
rowMode: "object" | "array" = "object"
|
|
112
152
|
) =>
|
|
113
153
|
Effect.try({
|
|
114
154
|
try: () => {
|
|
115
155
|
const results: Array<any> = []
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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.
|
|
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.
|
|
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*
|
|
158
|
-
const connection = yield*
|
|
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
|
-
|
|
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
|
|
197
|
-
config: Config.Config.Wrap<
|
|
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(
|
|
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))
|