@playfast/reform-db-sqlite 0.1.0 → 1.0.2
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/package.json +2 -2
- package/src/engine.ts +6 -37
- package/src/index.ts +0 -17
- package/src/sqlite.bun.spec.ts +119 -105
- package/src/sqlite.ts +1 -14
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playfast/reform-db-sqlite",
|
|
3
3
|
"playbook": "./playbook",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "1.0.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "bun:sqlite engine for @playfast/reform-db — a Db driver backed by Bun's built-in SQLite, with emulated reactivity and serialize()/deserialize() snapshots.",
|
|
7
7
|
"keywords": [
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"clean": "rm -rf dist .tsbuildinfo",
|
|
37
37
|
"check": "tsc --noEmit",
|
|
38
38
|
"build": "tsc -p tsconfig.build.json",
|
|
39
|
-
"test": "bun
|
|
39
|
+
"test": "bun --bun run vitest run --config vitest.config.ts",
|
|
40
40
|
"lint": "oxlint src",
|
|
41
41
|
"lint:fix": "oxlint --fix src"
|
|
42
42
|
},
|
package/src/engine.ts
CHANGED
|
@@ -1,38 +1,15 @@
|
|
|
1
1
|
import { type DbEngine, DbError, type EngineBackend, type Migration } from '@playfast/reform-db'
|
|
2
2
|
import { Effect, Option } from 'effect'
|
|
3
3
|
import { createQueryId, type RootOperationNode, SqliteQueryCompiler } from 'kysely'
|
|
4
|
-
// Type-only: erased at build
|
|
5
|
-
// `bun:sqlite` module does not exist); the real module is imported lazily at open time.
|
|
4
|
+
// Type-only: erased at build so package imports cleanly on Node (bun:sqlite absent).
|
|
6
5
|
import type { Database } from 'bun:sqlite'
|
|
7
6
|
|
|
8
|
-
// The bun:sqlite engine: Bun's built-in SQLite. It compiles operation nodes to SQLite
|
|
9
|
-
// SQL, opens a `Database` lazily (so importing this package never touches the
|
|
10
|
-
// Bun-only `bun:sqlite` module on Node), and runs migrations. SQLite has NO native
|
|
11
|
-
// live queries, so `live` is `Option.none` — `makeDriver` emulates reactivity by
|
|
12
|
-
// re-running reads after each write. `dump` uses `Database.serialize()` (a full
|
|
13
|
-
// on-the-wire snapshot Buffer), restored via `Database.deserialize` on the next open.
|
|
14
|
-
|
|
15
|
-
// A value SQLite can bind (bun:sqlite's `SQLQueryBindings`). Booleans ride as `0`/`1`,
|
|
16
|
-
// JSON columns as text — matching the engine-tolerant column `Schema`s in
|
|
17
|
-
// `@playfast/reform-db` that decode either shape. `ExternalApi`-named: it mirrors the
|
|
18
|
-
// driver's own binding shape, which admits a raw `null`.
|
|
19
7
|
type BindingExternalApi = string | number | bigint | boolean | Uint8Array | null
|
|
20
8
|
|
|
21
9
|
export interface SqliteOptionsExternalApi {
|
|
22
|
-
/** Database file path; omit for an ephemeral in-memory (`:memory:`) database. */
|
|
23
10
|
readonly filename?: string
|
|
24
|
-
|
|
25
|
-
* DDL/seed migrations to run on open. Use `Migration.fromSchema(AppDb, Migration.sqlite)`
|
|
26
|
-
* so column types render for SQLite; `Sqlite.layer`/`Sqlite.memory` do NOT pick the
|
|
27
|
-
* dialect for you.
|
|
28
|
-
*/
|
|
11
|
+
// Use Migration.fromSchema(AppDb, Migration.sqlite) — layer does not pick dialect.
|
|
29
12
|
readonly migrations?: ReadonlyArray<Migration.Migration>
|
|
30
|
-
/**
|
|
31
|
-
* Seed the database from a previous `dump` snapshot (`Database.serialize()` bytes,
|
|
32
|
-
* wrapped in a `Blob`). When present the database is opened via `Database.deserialize`
|
|
33
|
-
* — an in-memory database restored from the snapshot — before migrations run
|
|
34
|
-
* (`CREATE TABLE IF NOT EXISTS` then no-ops). `filename` is ignored in this case.
|
|
35
|
-
*/
|
|
36
13
|
readonly loadDataDir?: Blob
|
|
37
14
|
}
|
|
38
15
|
|
|
@@ -52,8 +29,7 @@ const bindable = (candidate: unknown): candidate is BindingExternalApi =>
|
|
|
52
29
|
typeof candidate === 'bigint' ||
|
|
53
30
|
typeof candidate === 'boolean'
|
|
54
31
|
|
|
55
|
-
// SQLite binds
|
|
56
|
-
// other object — so a json-column value (an object/array) is serialized to JSON text.
|
|
32
|
+
// SQLite throws on object binds — serialize json-column objects/arrays to text.
|
|
57
33
|
const coerceParam = (param: unknown): BindingExternalApi => {
|
|
58
34
|
if (param === undefined || param === null) {
|
|
59
35
|
return null
|
|
@@ -62,9 +38,7 @@ const coerceParam = (param: unknown): BindingExternalApi => {
|
|
|
62
38
|
return bindable(param) ? param : JSON.stringify(param)
|
|
63
39
|
}
|
|
64
40
|
|
|
65
|
-
//
|
|
66
|
-
// bun:sqlite is synchronous, this is the ASYNC-shaped work `makeDriver` defers to a
|
|
67
|
-
// background fiber so the `Db` service can be acquired SYNCHRONOUSLY.
|
|
41
|
+
// Lazy bun:sqlite import + migrations: async-shaped so makeDriver acquires Db sync.
|
|
68
42
|
const openSqlite = Effect.fn('openSqlite')(function* (
|
|
69
43
|
options: SqliteOptions,
|
|
70
44
|
): Effect.fn.Return<Database, DbError> {
|
|
@@ -72,8 +46,7 @@ const openSqlite = Effect.fn('openSqlite')(function* (
|
|
|
72
46
|
try: () => import('bun:sqlite'),
|
|
73
47
|
catch: (cause) => new DbError({ reason: 'bun:sqlite unavailable (Bun runtime required)', cause }),
|
|
74
48
|
})
|
|
75
|
-
//
|
|
76
|
-
// fully in memory (it came from a `dump`), so read it async here, then open sync below.
|
|
49
|
+
// deserialize wants bytes sync; Blob is already in memory — read async then open sync.
|
|
77
50
|
const seed = yield* Option.match(Option.fromNullable(options.loadDataDir), {
|
|
78
51
|
onNone: () => Effect.succeed(Option.none<Uint8Array>()),
|
|
79
52
|
onSome: (blob) =>
|
|
@@ -86,7 +59,6 @@ const openSqlite = Effect.fn('openSqlite')(function* (
|
|
|
86
59
|
try: () =>
|
|
87
60
|
Option.match(seed, {
|
|
88
61
|
onNone: () => new Database(options.filename ?? ':memory:'),
|
|
89
|
-
// A snapshot restore is an in-memory database seeded from the serialized bytes.
|
|
90
62
|
onSome: (bytes) => Database.deserialize(bytes),
|
|
91
63
|
}),
|
|
92
64
|
catch: (cause) => new DbError({ reason: 'open failed', cause }),
|
|
@@ -112,10 +84,8 @@ const backendOf = (db: Database): EngineBackend => ({
|
|
|
112
84
|
try: () => db.query(sql).all(...parameters.map(coerceParam)),
|
|
113
85
|
catch: (cause) => new DbError({ reason: 'query failed', sql, cause }),
|
|
114
86
|
}),
|
|
115
|
-
// No native
|
|
87
|
+
// No native live — makeDriver emulates by re-running after write.
|
|
116
88
|
live: Option.none(),
|
|
117
|
-
// `serialize()` returns the full database as a Buffer; wrap it as a portable Blob so
|
|
118
|
-
// it round-trips through the same dump→save→reload contract as PGlite's tarball.
|
|
119
89
|
dump: Effect.try({
|
|
120
90
|
try: () => new Blob([new Uint8Array(db.serialize())]),
|
|
121
91
|
catch: (cause) => new DbError({ reason: 'dump failed', cause }),
|
|
@@ -123,7 +93,6 @@ const backendOf = (db: Database): EngineBackend => ({
|
|
|
123
93
|
close: Effect.sync(() => db.close()),
|
|
124
94
|
})
|
|
125
95
|
|
|
126
|
-
/** The bun:sqlite `DbEngine`. Feed to `makeDriver` (see `Sqlite.layer`/`Sqlite.memory`). */
|
|
127
96
|
export const make = (options: SqliteOptions): DbEngine => ({
|
|
128
97
|
compile,
|
|
129
98
|
open: () => openSqlite(options).pipe(Effect.map(backendOf)),
|
package/src/index.ts
CHANGED
|
@@ -1,21 +1,4 @@
|
|
|
1
|
-
// @playfast/reform-db-sqlite — the bun:sqlite engine for @playfast/reform-db.
|
|
2
|
-
//
|
|
3
|
-
// Provides the `Db` driver layer backed by Bun's built-in SQLite (`bun:sqlite`):
|
|
4
|
-
// synchronous, dependency-free, with emulated reactivity (live `DbQuery`s re-run
|
|
5
|
-
// after each write) and `serialize()`/`deserialize()` snapshots. Pair it with the
|
|
6
|
-
// engine-neutral primitives (`DbColumn`/`DbTable`/`DbSchema`/`DbQuery`/`Migration`)
|
|
7
|
-
// from `@playfast/reform-db` — remember the SQLite dialect for migrations:
|
|
8
|
-
//
|
|
9
|
-
// import { Sqlite } from '@playfast/reform-db-sqlite'
|
|
10
|
-
// import { Migration } from '@playfast/reform-db'
|
|
11
|
-
// const DataLayer = QLive.pipe(
|
|
12
|
-
// Layer.provideMerge(
|
|
13
|
-
// Sqlite.layer({ filename: 'app.db', migrations: Migration.fromSchema(AppDb, Migration.sqlite) }),
|
|
14
|
-
// ),
|
|
15
|
-
// )
|
|
16
|
-
|
|
17
1
|
export * as Sqlite from './sqlite'
|
|
18
2
|
export type { MemoryOptions } from './sqlite'
|
|
19
3
|
export type { SqliteOptions } from './engine'
|
|
20
|
-
// The engine itself, for advanced callers wiring `makeDriver` directly.
|
|
21
4
|
export { make as makeSqliteEngine } from './engine'
|
package/src/sqlite.bun.spec.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
// Bun-
|
|
2
|
-
// runner (`bun:test`) because `bun:sqlite` only exists under Bun — and vitest runs its
|
|
3
|
-
// workers in Node, where that module is absent. Run it with `bun test` (the package's
|
|
4
|
-
// `test` script); vitest's `*.test.ts` glob does not match `.spec.ts`, so the
|
|
5
|
-
// workspace `vitest run` ignores it.
|
|
1
|
+
// Bun-only: bun:sqlite is absent under Node. Run with Vitest on Bun.
|
|
6
2
|
import {
|
|
7
3
|
Db,
|
|
8
4
|
DbColumn,
|
|
@@ -11,17 +7,14 @@ import {
|
|
|
11
7
|
DbTable,
|
|
12
8
|
Migration,
|
|
13
9
|
type QueryData,
|
|
14
|
-
} from
|
|
15
|
-
import { expect,
|
|
16
|
-
import { Context, Effect, Exit, Layer, Schedule, Schema, Scope } from
|
|
17
|
-
import { Sqlite } from
|
|
10
|
+
} from "@playfast/reform-db";
|
|
11
|
+
import { expect, it } from "@effect/vitest";
|
|
12
|
+
import { Context, Effect, Exit, Layer, Schedule, Schema, Scope } from "effect";
|
|
13
|
+
import { Sqlite } from "./index";
|
|
18
14
|
|
|
19
|
-
const Meta = Schema.Struct({ note: Schema.String })
|
|
15
|
+
const Meta = Schema.Struct({ note: Schema.String });
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
// (stored 0/1) and a json column (stored as text). The engine-tolerant column Schemas
|
|
23
|
-
// in @playfast/reform-db must still decode them back to `boolean` / object.
|
|
24
|
-
const Items = DbTable.make('items', {
|
|
17
|
+
const Items = DbTable.make("items", {
|
|
25
18
|
columns: {
|
|
26
19
|
id: DbColumn.text({ primaryKey: true }),
|
|
27
20
|
label: DbColumn.text(),
|
|
@@ -29,109 +22,130 @@ const Items = DbTable.make('items', {
|
|
|
29
22
|
done: DbColumn.boolean(),
|
|
30
23
|
meta: DbColumn.json(Meta),
|
|
31
24
|
},
|
|
32
|
-
primaryKey:
|
|
33
|
-
})
|
|
25
|
+
primaryKey: "id",
|
|
26
|
+
});
|
|
34
27
|
|
|
35
|
-
const AppDb = DbSchema.make(
|
|
36
|
-
const migrations = Migration.fromSchema(AppDb, Migration.sqlite)
|
|
28
|
+
const AppDb = DbSchema.make("app", { tables: [Items] });
|
|
29
|
+
const migrations = Migration.fromSchema(AppDb, Migration.sqlite);
|
|
37
30
|
|
|
38
|
-
const ItemsQuery = DbQuery.make(
|
|
31
|
+
const ItemsQuery = DbQuery.make("items", { output: Items.Row });
|
|
39
32
|
|
|
40
33
|
type Item = {
|
|
41
|
-
readonly id: string
|
|
42
|
-
readonly label: string
|
|
43
|
-
readonly rank: number
|
|
44
|
-
readonly done: boolean
|
|
45
|
-
readonly meta: { readonly note: string }
|
|
46
|
-
}
|
|
34
|
+
readonly id: string;
|
|
35
|
+
readonly label: string;
|
|
36
|
+
readonly rank: number;
|
|
37
|
+
readonly done: boolean;
|
|
38
|
+
readonly meta: { readonly note: string };
|
|
39
|
+
};
|
|
47
40
|
|
|
48
41
|
const rowsOf = (data: QueryData<Item>): ReadonlyArray<Item> =>
|
|
49
|
-
data._tag ===
|
|
42
|
+
data._tag === "Success" ? data.value : [];
|
|
50
43
|
|
|
51
44
|
const waitUntil = (predicate: () => boolean) =>
|
|
52
45
|
Effect.sync(predicate).pipe(
|
|
53
|
-
Effect.repeat({
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
Effect.repeat({
|
|
47
|
+
until: (ready) => ready,
|
|
48
|
+
schedule: Schedule.spaced("20 millis"),
|
|
49
|
+
}),
|
|
50
|
+
Effect.timeout("5 seconds"),
|
|
51
|
+
);
|
|
56
52
|
|
|
57
53
|
const liveLayer = (dataLayer: Layer.Layer<Db>) =>
|
|
58
54
|
DbQuery.live(ItemsQuery, {
|
|
59
55
|
schema: AppDb,
|
|
60
56
|
inputs: [],
|
|
61
|
-
build: (_inputs, db) => db.selectFrom(
|
|
62
|
-
}).pipe(Layer.provideMerge(dataLayer))
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
expect(rows.map((row) => row.label)).toEqual(['first', 'second'])
|
|
82
|
-
// boolean decoded from SQLite's 0/1, json decoded from stored text.
|
|
83
|
-
expect(rows.map((row) => row.done)).toEqual([true, false])
|
|
84
|
-
expect(rows.map((row) => row.meta.note)).toEqual(['one', 'two'])
|
|
85
|
-
}).pipe(Effect.provide(liveLayer(Sqlite.memory({ migrations }))))
|
|
86
|
-
|
|
87
|
-
await Effect.runPromise(program)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('dump() → loadDataDir round-trips rows into a fresh in-memory db', async () => {
|
|
91
|
-
const dump = await Effect.runPromise(
|
|
92
|
-
Effect.scoped(
|
|
93
|
-
Effect.gen(function* () {
|
|
94
|
-
const context = yield* Layer.build(Sqlite.memory({ migrations }))
|
|
95
|
-
const driver = Context.get(context, Db)
|
|
96
|
-
yield* Db.exec(
|
|
57
|
+
build: (_inputs, db) => db.selectFrom("items").selectAll().orderBy("rank"),
|
|
58
|
+
}).pipe(Layer.provideMerge(dataLayer));
|
|
59
|
+
|
|
60
|
+
it.live(
|
|
61
|
+
"DbQuery reflects writes (emulated reactivity), decoding boolean + json columns",
|
|
62
|
+
() => {
|
|
63
|
+
const program = Effect.gen(function* () {
|
|
64
|
+
const store = yield* ItemsQuery.store;
|
|
65
|
+
|
|
66
|
+
yield* waitUntil(() => store.getSnapshot()._tag === "Success");
|
|
67
|
+
expect(rowsOf(store.getSnapshot())).toEqual([]);
|
|
68
|
+
|
|
69
|
+
const insert = (
|
|
70
|
+
id: string,
|
|
71
|
+
label: string,
|
|
72
|
+
rank: number,
|
|
73
|
+
done: boolean,
|
|
74
|
+
note: string,
|
|
75
|
+
) =>
|
|
76
|
+
Db.exec(
|
|
97
77
|
AppDb.kysely
|
|
98
|
-
.insertInto(
|
|
99
|
-
.values({ id
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
78
|
+
.insertInto("items")
|
|
79
|
+
.values({ id, label, rank, done, meta: { note } }),
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
yield* insert("b", "second", 2, false, "two");
|
|
83
|
+
yield* insert("a", "first", 1, true, "one");
|
|
84
|
+
|
|
85
|
+
yield* waitUntil(() => rowsOf(store.getSnapshot()).length === 2);
|
|
86
|
+
const rows = rowsOf(store.getSnapshot());
|
|
87
|
+
expect(rows.map((row) => row.label)).toEqual(["first", "second"]);
|
|
88
|
+
expect(rows.map((row) => row.done)).toEqual([true, false]);
|
|
89
|
+
expect(rows.map((row) => row.meta.note)).toEqual(["one", "two"]);
|
|
90
|
+
}).pipe(Effect.provide(liveLayer(Sqlite.memory({ migrations }))));
|
|
91
|
+
|
|
92
|
+
return program;
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
it.scopedLive(
|
|
97
|
+
"dump() → loadDataDir round-trips rows into a fresh in-memory db",
|
|
98
|
+
() =>
|
|
99
|
+
Effect.gen(function* () {
|
|
100
|
+
const dump = yield* Effect.scoped(
|
|
101
|
+
Effect.gen(function* () {
|
|
102
|
+
const context = yield* Layer.build(Sqlite.memory({ migrations }));
|
|
103
|
+
const driver = Context.get(context, Db);
|
|
104
|
+
yield* Db.exec(
|
|
105
|
+
AppDb.kysely
|
|
106
|
+
.insertInto("items")
|
|
107
|
+
.values({
|
|
108
|
+
id: "a",
|
|
109
|
+
label: "first",
|
|
110
|
+
rank: 1,
|
|
111
|
+
done: true,
|
|
112
|
+
meta: { note: "one" },
|
|
113
|
+
}),
|
|
114
|
+
).pipe(Effect.provideService(Db, driver));
|
|
115
|
+
return yield* driver.dump();
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
yield* Effect.scoped(
|
|
120
|
+
Effect.gen(function* () {
|
|
121
|
+
const store = yield* ItemsQuery.store;
|
|
122
|
+
yield* waitUntil(() => rowsOf(store.getSnapshot()).length === 1);
|
|
123
|
+
const row = rowsOf(store.getSnapshot())[0];
|
|
124
|
+
expect(row?.label).toBe("first");
|
|
125
|
+
expect(row?.done).toBe(true);
|
|
126
|
+
expect(row?.meta.note).toBe("one");
|
|
127
|
+
}).pipe(
|
|
128
|
+
Effect.provide(
|
|
129
|
+
liveLayer(Sqlite.memory({ migrations, loadDataDir: dump })),
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
it.scopedLive(
|
|
137
|
+
"Sqlite.memory layer is sync-providable: store starts loading, then resolves to Success",
|
|
138
|
+
() =>
|
|
139
|
+
Effect.gen(function* () {
|
|
140
|
+
const layer = liveLayer(Sqlite.memory({ migrations }));
|
|
141
|
+
|
|
142
|
+
// Sync build (opens on background fiber) — must not throw "Fiber cannot be resolved synchronously".
|
|
143
|
+
const scope = Effect.runSync(Scope.make());
|
|
144
|
+
const context = Effect.runSync(Layer.buildWithScope(layer, scope));
|
|
145
|
+
yield* Effect.addFinalizer(() => Scope.close(scope, Exit.void));
|
|
146
|
+
const store = Context.get(context, ItemsQuery.store);
|
|
147
|
+
expect(store.getSnapshot()._tag).toBe("Loading");
|
|
148
|
+
yield* waitUntil(() => store.getSnapshot()._tag === "Success");
|
|
149
|
+
expect(rowsOf(store.getSnapshot())).toEqual([]);
|
|
150
|
+
}),
|
|
151
|
+
);
|
package/src/sqlite.ts
CHANGED
|
@@ -2,17 +2,6 @@ import { Db, makeDriver } from '@playfast/reform-db'
|
|
|
2
2
|
import { Layer } from 'effect'
|
|
3
3
|
import { make, type SqliteOptions } from './engine'
|
|
4
4
|
|
|
5
|
-
// The bun:sqlite `Db` layers, built on `@playfast/reform-db`'s engine-neutral
|
|
6
|
-
// `makeDriver` and the SQLite engine here. Both build the `Db` service synchronously
|
|
7
|
-
// (bun:sqlite opens on a background fiber inside `makeDriver`), so they are providable
|
|
8
|
-
// under reform's `runSync` scene/proof mount. Reactivity is emulated by the core:
|
|
9
|
-
// SQLite has no native live queries, so live `DbQuery`s re-run after each write.
|
|
10
|
-
//
|
|
11
|
-
// NOTE: migrations must be rendered for SQLite — pass
|
|
12
|
-
// `Migration.fromSchema(AppDb, Migration.sqlite)`.
|
|
13
|
-
|
|
14
|
-
// Options for `Sqlite.memory` — an in-memory database can still be migrated and seeded
|
|
15
|
-
// from a `dump` snapshot, so it accepts everything but `filename`.
|
|
16
5
|
export interface MemoryOptionsExternalApi {
|
|
17
6
|
readonly migrations?: SqliteOptions['migrations']
|
|
18
7
|
readonly loadDataDir?: SqliteOptions['loadDataDir']
|
|
@@ -20,15 +9,13 @@ export interface MemoryOptionsExternalApi {
|
|
|
20
9
|
|
|
21
10
|
export type MemoryOptions = MemoryOptionsExternalApi
|
|
22
11
|
|
|
23
|
-
/** A persistent (or in-memory, if `filename` is omitted) bun:sqlite-backed `Db` driver. */
|
|
24
12
|
export const layer = (options: SqliteOptions = {}): Layer.Layer<Db> =>
|
|
25
13
|
Layer.scoped(Db, makeDriver(make(options)))
|
|
26
14
|
|
|
27
|
-
/** An ephemeral in-memory bun:sqlite-backed `Db` driver — the default for tests/proofs. */
|
|
28
15
|
export const memory = (options: MemoryOptions = {}): Layer.Layer<Db> =>
|
|
29
16
|
Layer.scoped(
|
|
30
17
|
Db,
|
|
31
|
-
// Spread
|
|
18
|
+
// Spread only when set so bare `Sqlite.memory()` open stays unchanged.
|
|
32
19
|
makeDriver(
|
|
33
20
|
make({
|
|
34
21
|
...(options.migrations !== undefined ? { migrations: options.migrations } : {}),
|