@hexaijs/sqlite 0.3.0 → 0.5.0
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/README.md +55 -26
- package/dist/index.d.ts +6 -2
- package/dist/index.js +47 -5
- package/dist/index.js.map +1 -1
- package/dist/test/index.d.ts +3 -3
- package/dist/test/index.js +20 -25
- package/dist/test/index.js.map +1 -1
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npm install @hexaijs/sqlite
|
|
|
28
28
|
**Peer dependencies:**
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npm install @hexaijs/core
|
|
31
|
+
npm install @hexaijs/core better-sqlite3
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## Core Concepts
|
|
@@ -38,15 +38,11 @@ npm install @hexaijs/core sqlite sqlite3
|
|
|
38
38
|
The `SqliteUnitOfWork` implements `UnitOfWork<Database>` from `@hexaijs/core`. It manages transaction lifecycle for a given SQLite database connection.
|
|
39
39
|
|
|
40
40
|
```typescript
|
|
41
|
-
import
|
|
42
|
-
import sqlite3 from "sqlite3";
|
|
41
|
+
import Database from "better-sqlite3";
|
|
43
42
|
import { SqliteUnitOfWork } from "@hexaijs/sqlite";
|
|
44
43
|
|
|
45
44
|
// Create an in-memory database
|
|
46
|
-
const db =
|
|
47
|
-
filename: ":memory:",
|
|
48
|
-
driver: sqlite3.Database,
|
|
49
|
-
});
|
|
45
|
+
const db = new Database(":memory:");
|
|
50
46
|
|
|
51
47
|
// Create unit of work
|
|
52
48
|
const unitOfWork = new SqliteUnitOfWork(db);
|
|
@@ -61,8 +57,8 @@ Use `scope()` to define a transaction boundary:
|
|
|
61
57
|
```typescript
|
|
62
58
|
const result = await unitOfWork.scope(async () => {
|
|
63
59
|
const db = unitOfWork.getClient();
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
db.prepare("INSERT INTO orders (id, status) VALUES (?, ?)").run(orderId, "pending");
|
|
61
|
+
db.prepare("INSERT INTO order_items (order_id, product_id) VALUES (?, ?)").run(orderId, productId);
|
|
66
62
|
return { orderId };
|
|
67
63
|
});
|
|
68
64
|
// Transaction commits if successful
|
|
@@ -73,7 +69,7 @@ const result = await unitOfWork.scope(async () => {
|
|
|
73
69
|
```typescript
|
|
74
70
|
/** @deprecated Use scope() instead. */
|
|
75
71
|
const result = await unitOfWork.wrap(async (db) => {
|
|
76
|
-
|
|
72
|
+
db.prepare("INSERT INTO orders (id, status) VALUES (?, ?)").run(orderId, "pending");
|
|
77
73
|
return { orderId };
|
|
78
74
|
});
|
|
79
75
|
```
|
|
@@ -84,7 +80,7 @@ If an error is thrown, the transaction rolls back:
|
|
|
84
80
|
try {
|
|
85
81
|
await unitOfWork.scope(async () => {
|
|
86
82
|
const db = unitOfWork.getClient();
|
|
87
|
-
|
|
83
|
+
db.prepare("INSERT INTO orders (id, status) VALUES (?, ?)").run(orderId, "pending");
|
|
88
84
|
throw new Error("Something went wrong");
|
|
89
85
|
});
|
|
90
86
|
} catch (error) {
|
|
@@ -99,11 +95,44 @@ Within a transaction, access the database through `getClient()`:
|
|
|
99
95
|
```typescript
|
|
100
96
|
// Inside a command handler
|
|
101
97
|
const db = ctx.getUnitOfWork().getClient();
|
|
102
|
-
|
|
98
|
+
db.prepare("UPDATE orders SET status = ? WHERE id = ?").run("confirmed", orderId);
|
|
103
99
|
```
|
|
104
100
|
|
|
105
101
|
Note: `getClient()` throws an error if called outside of a `scope()` or `wrap()` call.
|
|
106
102
|
|
|
103
|
+
### Transaction Lifecycle Hooks
|
|
104
|
+
|
|
105
|
+
Register callbacks that execute at specific points in the transaction lifecycle:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
await unitOfWork.scope(async () => {
|
|
109
|
+
unitOfWork.beforeCommit(() => {
|
|
110
|
+
// Validate before committing
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
unitOfWork.afterCommit(() => {
|
|
114
|
+
// Notify after successful commit
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
unitOfWork.afterRollback(() => {
|
|
118
|
+
// Clean up on failure
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const db = unitOfWork.getClient();
|
|
122
|
+
db.prepare("INSERT INTO orders (id, status) VALUES (?, ?)").run(orderId, "pending");
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Hooks follow the same semantics as `@hexaijs/postgres`:
|
|
127
|
+
|
|
128
|
+
| Hook | When | On failure |
|
|
129
|
+
|------|------|------------|
|
|
130
|
+
| `beforeCommit` | Before COMMIT | Transaction rolls back instead |
|
|
131
|
+
| `afterCommit` | After COMMIT | Best-effort (errors → `AggregateError`) |
|
|
132
|
+
| `afterRollback` | After ROLLBACK | Best-effort (errors → `AggregateError`) |
|
|
133
|
+
|
|
134
|
+
Hooks are scope-local and can only be registered inside an active `scope()`.
|
|
135
|
+
|
|
107
136
|
### Nested Transactions
|
|
108
137
|
|
|
109
138
|
Nested `scope()` calls participate in the same transaction:
|
|
@@ -111,11 +140,11 @@ Nested `scope()` calls participate in the same transaction:
|
|
|
111
140
|
```typescript
|
|
112
141
|
await unitOfWork.scope(async () => {
|
|
113
142
|
const db = unitOfWork.getClient();
|
|
114
|
-
|
|
143
|
+
db.prepare("INSERT INTO orders (id) VALUES (?)").run("order-1");
|
|
115
144
|
|
|
116
145
|
await unitOfWork.scope(async () => {
|
|
117
146
|
const db = unitOfWork.getClient();
|
|
118
|
-
|
|
147
|
+
db.prepare("INSERT INTO order_items (order_id) VALUES (?)").run("order-1");
|
|
119
148
|
});
|
|
120
149
|
// Both inserts are in the same transaction
|
|
121
150
|
});
|
|
@@ -128,11 +157,11 @@ If any nested call throws, the entire transaction rolls back:
|
|
|
128
157
|
try {
|
|
129
158
|
await unitOfWork.scope(async () => {
|
|
130
159
|
const db = unitOfWork.getClient();
|
|
131
|
-
|
|
160
|
+
db.prepare("INSERT INTO orders (id) VALUES (?)").run("order-1");
|
|
132
161
|
|
|
133
162
|
await unitOfWork.scope(async () => {
|
|
134
163
|
const db = unitOfWork.getClient();
|
|
135
|
-
|
|
164
|
+
db.prepare("INSERT INTO order_items (order_id) VALUES (?)").run("order-1");
|
|
136
165
|
throw new Error("Nested failure");
|
|
137
166
|
});
|
|
138
167
|
});
|
|
@@ -148,7 +177,7 @@ try {
|
|
|
148
177
|
Use the test utilities for fast, isolated integration tests:
|
|
149
178
|
|
|
150
179
|
```typescript
|
|
151
|
-
import type { Database } from "
|
|
180
|
+
import type { Database } from "better-sqlite3";
|
|
152
181
|
import { SqliteUnitOfWork } from "@hexaijs/sqlite";
|
|
153
182
|
import { getSqliteConnection } from "@hexaijs/sqlite/test";
|
|
154
183
|
|
|
@@ -156,12 +185,12 @@ describe("OrderRepository", () => {
|
|
|
156
185
|
let db: Database;
|
|
157
186
|
let unitOfWork: SqliteUnitOfWork;
|
|
158
187
|
|
|
159
|
-
beforeEach(
|
|
188
|
+
beforeEach(() => {
|
|
160
189
|
// Create fresh in-memory database
|
|
161
|
-
db =
|
|
190
|
+
db = getSqliteConnection();
|
|
162
191
|
|
|
163
192
|
// Create schema
|
|
164
|
-
|
|
193
|
+
db.exec(`
|
|
165
194
|
CREATE TABLE orders (
|
|
166
195
|
id TEXT PRIMARY KEY,
|
|
167
196
|
status TEXT NOT NULL
|
|
@@ -171,17 +200,17 @@ describe("OrderRepository", () => {
|
|
|
171
200
|
unitOfWork = new SqliteUnitOfWork(db);
|
|
172
201
|
});
|
|
173
202
|
|
|
174
|
-
afterEach(
|
|
175
|
-
|
|
203
|
+
afterEach(() => {
|
|
204
|
+
db.close();
|
|
176
205
|
});
|
|
177
206
|
|
|
178
207
|
it("should persist orders", async () => {
|
|
179
208
|
await unitOfWork.scope(async () => {
|
|
180
209
|
const db = unitOfWork.getClient();
|
|
181
|
-
|
|
210
|
+
db.prepare("INSERT INTO orders (id, status) VALUES (?, ?)").run("order-1", "pending");
|
|
182
211
|
});
|
|
183
212
|
|
|
184
|
-
const result =
|
|
213
|
+
const result = db.prepare("SELECT * FROM orders WHERE id = ?").get("order-1") as any;
|
|
185
214
|
expect(result.status).toBe("pending");
|
|
186
215
|
});
|
|
187
216
|
});
|
|
@@ -218,7 +247,7 @@ interface OrderMemento {
|
|
|
218
247
|
}
|
|
219
248
|
|
|
220
249
|
// Create repository
|
|
221
|
-
const db =
|
|
250
|
+
const db = getSqliteConnection();
|
|
222
251
|
const orderRepository = new SqliteRepositoryForTest<Order, OrderMemento>(db, {
|
|
223
252
|
namespace: "orders",
|
|
224
253
|
hydrate: (m) => new Order(new OrderId(m.id), m.status),
|
|
@@ -242,7 +271,7 @@ For scenarios requiring persistence across test runs or debugging:
|
|
|
242
271
|
import { getSqliteConnection } from "@hexaijs/sqlite/test";
|
|
243
272
|
|
|
244
273
|
// File-based database instead of in-memory
|
|
245
|
-
const db =
|
|
274
|
+
const db = getSqliteConnection("./test-database.sqlite");
|
|
246
275
|
```
|
|
247
276
|
|
|
248
277
|
## API Highlights
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
import { Database } from '
|
|
2
|
-
import { UnitOfWork } from '@hexaijs/core';
|
|
1
|
+
import { Database } from 'better-sqlite3';
|
|
2
|
+
import { UnitOfWork, TransactionHook } from '@hexaijs/core';
|
|
3
3
|
|
|
4
4
|
declare class SqliteUnitOfWork implements UnitOfWork<Database> {
|
|
5
5
|
private db;
|
|
6
6
|
private static transactions;
|
|
7
7
|
constructor(db: Database);
|
|
8
8
|
getClient(): Database;
|
|
9
|
+
beforeCommit(hook: TransactionHook): void;
|
|
10
|
+
afterCommit(hook: TransactionHook): void;
|
|
11
|
+
afterRollback(hook: TransactionHook): void;
|
|
9
12
|
scope<T>(fn: () => Promise<T>): Promise<T>;
|
|
10
13
|
wrap<T>(fn: (client: Database) => Promise<T>): Promise<T>;
|
|
14
|
+
private getRequiredState;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export { SqliteUnitOfWork };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { TransactionHooks } from '@hexaijs/core';
|
|
2
|
+
|
|
1
3
|
// src/sqlite-unit-of-work.ts
|
|
2
4
|
var SqliteUnitOfWork = class _SqliteUnitOfWork {
|
|
3
5
|
constructor(db) {
|
|
@@ -5,7 +7,8 @@ var SqliteUnitOfWork = class _SqliteUnitOfWork {
|
|
|
5
7
|
if (!_SqliteUnitOfWork.transactions.has(db)) {
|
|
6
8
|
_SqliteUnitOfWork.transactions.set(db, {
|
|
7
9
|
level: 0,
|
|
8
|
-
aborted: false
|
|
10
|
+
aborted: false,
|
|
11
|
+
hooks: new TransactionHooks()
|
|
9
12
|
});
|
|
10
13
|
}
|
|
11
14
|
}
|
|
@@ -17,31 +20,70 @@ var SqliteUnitOfWork = class _SqliteUnitOfWork {
|
|
|
17
20
|
}
|
|
18
21
|
return this.db;
|
|
19
22
|
}
|
|
23
|
+
beforeCommit(hook) {
|
|
24
|
+
const current = this.getRequiredState("beforeCommit");
|
|
25
|
+
current.hooks.addBeforeCommit(hook);
|
|
26
|
+
}
|
|
27
|
+
afterCommit(hook) {
|
|
28
|
+
const current = this.getRequiredState("afterCommit");
|
|
29
|
+
current.hooks.addAfterCommit(hook);
|
|
30
|
+
}
|
|
31
|
+
afterRollback(hook) {
|
|
32
|
+
const current = this.getRequiredState("afterRollback");
|
|
33
|
+
current.hooks.addAfterRollback(hook);
|
|
34
|
+
}
|
|
20
35
|
async scope(fn) {
|
|
21
36
|
return this.wrap(fn);
|
|
22
37
|
}
|
|
23
38
|
async wrap(fn) {
|
|
24
39
|
const current = _SqliteUnitOfWork.transactions.get(this.db);
|
|
25
40
|
if (++current.level === 1) {
|
|
26
|
-
|
|
41
|
+
this.db.exec("BEGIN TRANSACTION");
|
|
27
42
|
}
|
|
43
|
+
let abortError;
|
|
28
44
|
try {
|
|
29
45
|
return await fn(this.db);
|
|
30
46
|
} catch (e) {
|
|
31
47
|
if (!current.aborted) {
|
|
32
48
|
current.aborted = true;
|
|
33
49
|
}
|
|
50
|
+
abortError = e;
|
|
34
51
|
throw e;
|
|
35
52
|
} finally {
|
|
36
53
|
if (--current.level === 0) {
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
const hooks = current.hooks;
|
|
55
|
+
const wasAborted = current.aborted;
|
|
56
|
+
current.hooks = new TransactionHooks();
|
|
57
|
+
current.aborted = false;
|
|
58
|
+
if (wasAborted) {
|
|
59
|
+
await hooks.executeRollback(
|
|
60
|
+
async () => {
|
|
61
|
+
this.db.exec("ROLLBACK");
|
|
62
|
+
},
|
|
63
|
+
abortError
|
|
64
|
+
);
|
|
39
65
|
} else {
|
|
40
|
-
await
|
|
66
|
+
await hooks.executeCommit(
|
|
67
|
+
async () => {
|
|
68
|
+
this.db.exec("COMMIT");
|
|
69
|
+
},
|
|
70
|
+
async () => {
|
|
71
|
+
this.db.exec("ROLLBACK");
|
|
72
|
+
}
|
|
73
|
+
);
|
|
41
74
|
}
|
|
42
75
|
}
|
|
43
76
|
}
|
|
44
77
|
}
|
|
78
|
+
getRequiredState(hookName) {
|
|
79
|
+
const current = _SqliteUnitOfWork.transactions.get(this.db);
|
|
80
|
+
if (!current || current.level === 0) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Cannot register ${hookName} hook outside of a transaction scope`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return current;
|
|
86
|
+
}
|
|
45
87
|
};
|
|
46
88
|
|
|
47
89
|
export { SqliteUnitOfWork };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sqlite-unit-of-work.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/sqlite-unit-of-work.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Database } from \"better-sqlite3\";\n\nimport { TransactionHooks, UnitOfWork } from \"@hexaijs/core\";\nimport type { TransactionHook } from \"@hexaijs/core\";\n\nexport class SqliteUnitOfWork implements UnitOfWork<Database> {\n private static transactions = new WeakMap<\n Database,\n {\n level: number;\n aborted: boolean;\n hooks: TransactionHooks;\n }\n >();\n\n constructor(private db: Database) {\n if (!SqliteUnitOfWork.transactions.has(db)) {\n SqliteUnitOfWork.transactions.set(db, {\n level: 0,\n aborted: false,\n hooks: new TransactionHooks(),\n });\n }\n }\n\n getClient(): Database {\n const current = SqliteUnitOfWork.transactions.get(this.db);\n if (!current || current.level === 0) {\n throw new Error(\"No transaction is active\");\n }\n return this.db;\n }\n\n beforeCommit(hook: TransactionHook): void {\n const current = this.getRequiredState(\"beforeCommit\");\n current.hooks.addBeforeCommit(hook);\n }\n\n afterCommit(hook: TransactionHook): void {\n const current = this.getRequiredState(\"afterCommit\");\n current.hooks.addAfterCommit(hook);\n }\n\n afterRollback(hook: TransactionHook): void {\n const current = this.getRequiredState(\"afterRollback\");\n current.hooks.addAfterRollback(hook);\n }\n\n async scope<T>(fn: () => Promise<T>): Promise<T> {\n return this.wrap(fn);\n }\n\n async wrap<T>(fn: (client: Database) => Promise<T>): Promise<T> {\n const current = SqliteUnitOfWork.transactions.get(this.db)!;\n if (++current.level === 1) {\n this.db.exec(\"BEGIN TRANSACTION\");\n }\n\n let abortError: unknown;\n try {\n return await fn(this.db);\n } catch (e) {\n if (!current.aborted) {\n current.aborted = true;\n }\n abortError = e;\n\n throw e;\n } finally {\n if (--current.level === 0) {\n const hooks = current.hooks;\n const wasAborted = current.aborted;\n\n current.hooks = new TransactionHooks();\n current.aborted = false;\n\n if (wasAborted) {\n await hooks.executeRollback(\n async () => { this.db.exec(\"ROLLBACK\"); },\n abortError\n );\n } else {\n await hooks.executeCommit(\n async () => { this.db.exec(\"COMMIT\"); },\n async () => { this.db.exec(\"ROLLBACK\"); }\n );\n }\n }\n }\n }\n\n private getRequiredState(hookName: string) {\n const current = SqliteUnitOfWork.transactions.get(this.db);\n if (!current || current.level === 0) {\n throw new Error(\n `Cannot register ${hookName} hook outside of a transaction scope`\n );\n }\n return current;\n }\n}\n"]}
|
package/dist/test/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Database } from '
|
|
1
|
+
import { Database } from 'better-sqlite3';
|
|
2
2
|
import { Identifiable, Repository, IdOf } from '@hexaijs/core';
|
|
3
3
|
|
|
4
4
|
declare class SqliteRepositoryForTest<E extends Identifiable<any>, M> implements Repository<E> {
|
|
@@ -15,9 +15,9 @@ declare class SqliteRepositoryForTest<E extends Identifiable<any>, M> implements
|
|
|
15
15
|
add(entity: E): Promise<void>;
|
|
16
16
|
update(entity: E): Promise<void>;
|
|
17
17
|
count(): Promise<number>;
|
|
18
|
-
protected ensureTableExists():
|
|
18
|
+
protected ensureTableExists(): void;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
declare function getSqliteConnection(filename?: string):
|
|
21
|
+
declare function getSqliteConnection(filename?: string): Database;
|
|
22
22
|
|
|
23
23
|
export { SqliteRepositoryForTest, getSqliteConnection };
|
package/dist/test/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ObjectNotFoundError, DuplicateObjectError } from '@hexaijs/core';
|
|
2
|
+
import Database from 'better-sqlite3';
|
|
2
3
|
|
|
3
4
|
// src/test/sqlite-repository-for-test.ts
|
|
4
5
|
var SqliteRepositoryForTest = class {
|
|
@@ -16,11 +17,10 @@ var SqliteRepositoryForTest = class {
|
|
|
16
17
|
hydrate;
|
|
17
18
|
dehydrate;
|
|
18
19
|
async get(id) {
|
|
19
|
-
|
|
20
|
-
const row =
|
|
21
|
-
`SELECT * FROM ${this.namespace} WHERE id =
|
|
22
|
-
|
|
23
|
-
);
|
|
20
|
+
this.ensureTableExists();
|
|
21
|
+
const row = this.db.prepare(
|
|
22
|
+
`SELECT * FROM ${this.namespace} WHERE id = ?`
|
|
23
|
+
).get(id.getValue());
|
|
24
24
|
if (!row) {
|
|
25
25
|
throw new ObjectNotFoundError(
|
|
26
26
|
`entity with id '${id.getValue()}' not found`
|
|
@@ -29,11 +29,12 @@ var SqliteRepositoryForTest = class {
|
|
|
29
29
|
return this.hydrate(JSON.parse(row.data));
|
|
30
30
|
}
|
|
31
31
|
async add(entity) {
|
|
32
|
-
|
|
32
|
+
this.ensureTableExists();
|
|
33
33
|
try {
|
|
34
|
-
|
|
34
|
+
this.db.prepare(
|
|
35
35
|
`INSERT INTO ${this.namespace} (id, data)
|
|
36
|
-
VALUES (?, ?)
|
|
36
|
+
VALUES (?, ?)`
|
|
37
|
+
).run(
|
|
37
38
|
entity.getId().getValue(),
|
|
38
39
|
JSON.stringify(this.dehydrate(entity))
|
|
39
40
|
);
|
|
@@ -47,11 +48,12 @@ var SqliteRepositoryForTest = class {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
async update(entity) {
|
|
50
|
-
|
|
51
|
-
const result =
|
|
51
|
+
this.ensureTableExists();
|
|
52
|
+
const result = this.db.prepare(
|
|
52
53
|
`UPDATE ${this.namespace}
|
|
53
54
|
SET data = ?
|
|
54
|
-
WHERE id =
|
|
55
|
+
WHERE id = ?`
|
|
56
|
+
).run(
|
|
55
57
|
JSON.stringify(this.dehydrate(entity)),
|
|
56
58
|
entity.getId().getValue()
|
|
57
59
|
);
|
|
@@ -62,14 +64,14 @@ var SqliteRepositoryForTest = class {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
async count() {
|
|
65
|
-
|
|
66
|
-
const result =
|
|
67
|
+
this.ensureTableExists();
|
|
68
|
+
const result = this.db.prepare(
|
|
67
69
|
`SELECT COUNT(*) AS count FROM ${this.namespace}`
|
|
68
|
-
);
|
|
70
|
+
).get();
|
|
69
71
|
return result.count;
|
|
70
72
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
ensureTableExists() {
|
|
74
|
+
this.db.exec(`
|
|
73
75
|
CREATE TABLE IF NOT EXISTS ${this.namespace} (
|
|
74
76
|
id TEXT NOT NULL PRIMARY KEY UNIQUE,
|
|
75
77
|
data TEXT NOT NULL
|
|
@@ -77,15 +79,8 @@ var SqliteRepositoryForTest = class {
|
|
|
77
79
|
`);
|
|
78
80
|
}
|
|
79
81
|
};
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
async function getSqliteConnection(filename = ":memory:") {
|
|
83
|
-
const sqlite = await import('sqlite');
|
|
84
|
-
const sqlite3 = await import('sqlite3');
|
|
85
|
-
return await sqlite.open({
|
|
86
|
-
filename,
|
|
87
|
-
driver: sqlite3.default.Database
|
|
88
|
-
});
|
|
82
|
+
function getSqliteConnection(filename = ":memory:") {
|
|
83
|
+
return new Database(filename);
|
|
89
84
|
}
|
|
90
85
|
|
|
91
86
|
export { SqliteRepositoryForTest, getSqliteConnection };
|
package/dist/test/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/test/sqlite-repository-for-test.ts","../../src/test/utils.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/test/sqlite-repository-for-test.ts","../../src/test/utils.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { Database } from \"better-sqlite3\";\n\nimport {\n DuplicateObjectError,\n Identifiable,\n IdOf,\n ObjectNotFoundError,\n Repository,\n} from \"@hexaijs/core\";\n\nexport class SqliteRepositoryForTest<\n E extends Identifiable<any>,\n M,\n> implements Repository<E> {\n protected namespace: string;\n protected hydrate: (memento: M) => E;\n protected dehydrate: (entity: E) => M;\n\n constructor(\n protected db: Database,\n {\n namespace,\n hydrate,\n dehydrate,\n }: {\n namespace: string;\n hydrate: (memento: M) => E;\n dehydrate: (entity: E) => M;\n }\n ) {\n this.namespace = namespace;\n this.hydrate = hydrate;\n this.dehydrate = dehydrate;\n }\n\n async get(id: IdOf<E>): Promise<E> {\n this.ensureTableExists();\n\n const row = this.db.prepare(\n `SELECT * FROM ${this.namespace} WHERE id = ?`\n ).get(id.getValue()) as { data: string } | undefined;\n if (!row) {\n throw new ObjectNotFoundError(\n `entity with id '${id.getValue()}' not found`\n );\n }\n\n return this.hydrate(JSON.parse(row.data));\n }\n\n async add(entity: E): Promise<void> {\n this.ensureTableExists();\n\n try {\n this.db.prepare(\n `INSERT INTO ${this.namespace} (id, data)\n VALUES (?, ?)`\n ).run(\n entity.getId().getValue(),\n JSON.stringify(this.dehydrate(entity))\n );\n } catch (e) {\n if ((e as Error).message.includes(\"UNIQUE constraint failed\")) {\n throw new DuplicateObjectError(\n `entity with id '${entity\n .getId()\n .getValue()}' already exists`\n );\n }\n\n throw e;\n }\n }\n\n async update(entity: E): Promise<void> {\n this.ensureTableExists();\n\n const result = this.db.prepare(\n `UPDATE ${this.namespace}\n SET data = ?\n WHERE id = ?`\n ).run(\n JSON.stringify(this.dehydrate(entity)),\n entity.getId().getValue()\n );\n\n if (result.changes === 0) {\n throw new ObjectNotFoundError(\n `entity with id '${entity.getId().getValue()}' not found`\n );\n }\n }\n\n async count(): Promise<number> {\n this.ensureTableExists();\n\n const result = this.db.prepare(\n `SELECT COUNT(*) AS count FROM ${this.namespace}`\n ).get() as { count: number };\n\n return result.count;\n }\n\n protected ensureTableExists(): void {\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS ${this.namespace} (\n id TEXT NOT NULL PRIMARY KEY UNIQUE,\n data TEXT NOT NULL\n )\n `);\n }\n}\n","import Database from \"better-sqlite3\";\nimport type { Database as DatabaseInstance } from \"better-sqlite3\";\n\nexport function getSqliteConnection(\n filename = \":memory:\"\n): DatabaseInstance {\n return new Database(filename);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"description": "SQLite support for hexai",
|
|
9
9
|
"license": "MIT",
|
|
@@ -50,14 +50,13 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@hexaijs/core": "^0.
|
|
54
|
-
"
|
|
55
|
-
"sqlite3": "^5.1.7"
|
|
53
|
+
"@hexaijs/core": "^0.9.0",
|
|
54
|
+
"better-sqlite3": "^12.5.0"
|
|
56
55
|
},
|
|
57
56
|
"devDependencies": {
|
|
58
57
|
"@hexaijs/core": "workspace:^",
|
|
59
58
|
"@hexaijs/tooling": "workspace:*",
|
|
60
|
-
"
|
|
61
|
-
"sqlite3": "^5.
|
|
59
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
60
|
+
"better-sqlite3": "^12.5.0"
|
|
62
61
|
}
|
|
63
62
|
}
|