qumra-pos-storage 0.1.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 +128 -0
- package/dist/StorageAdapter-B2IgHcUF.d.cts +27 -0
- package/dist/StorageAdapter-B2IgHcUF.d.ts +27 -0
- package/dist/better-sqlite3.cjs +96 -0
- package/dist/better-sqlite3.cjs.map +1 -0
- package/dist/better-sqlite3.d.cts +94 -0
- package/dist/better-sqlite3.d.ts +94 -0
- package/dist/better-sqlite3.js +90 -0
- package/dist/better-sqlite3.js.map +1 -0
- package/dist/chunk-CELALQHK.js +38 -0
- package/dist/chunk-CELALQHK.js.map +1 -0
- package/dist/chunk-HXRUTEU7.js +163 -0
- package/dist/chunk-HXRUTEU7.js.map +1 -0
- package/dist/conformance.cjs +170 -0
- package/dist/conformance.cjs.map +1 -0
- package/dist/conformance.d.cts +42 -0
- package/dist/conformance.d.ts +42 -0
- package/dist/conformance.js +3 -0
- package/dist/conformance.js.map +1 -0
- package/dist/index.cjs +1335 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +518 -0
- package/dist/index.d.ts +518 -0
- package/dist/index.js +1154 -0
- package/dist/index.js.map +1 -0
- package/dist/op-sqlite.cjs +73 -0
- package/dist/op-sqlite.cjs.map +1 -0
- package/dist/op-sqlite.d.cts +23 -0
- package/dist/op-sqlite.d.ts +23 -0
- package/dist/op-sqlite.js +38 -0
- package/dist/op-sqlite.js.map +1 -0
- package/dist/wa-sqlite.cjs +68 -0
- package/dist/wa-sqlite.cjs.map +1 -0
- package/dist/wa-sqlite.d.cts +39 -0
- package/dist/wa-sqlite.d.ts +39 -0
- package/dist/wa-sqlite.js +32 -0
- package/dist/wa-sqlite.js.map +1 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# qumra-pos-storage
|
|
2
|
+
|
|
3
|
+
Offline-first storage core for POS and local-first apps. All the logic lives in the
|
|
4
|
+
core; the SQLite engine is a **swappable adapter**, so the same schema, migrations,
|
|
5
|
+
repositories, outbox and sync engine run on **React Native, Web, Desktop (Electron)
|
|
6
|
+
and Node** — you only pick the adapter per platform.
|
|
7
|
+
|
|
8
|
+
Zero dependency on any other `@qumra` package. Only runtime dep is `uuidv7`.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm i qumra-pos-storage
|
|
14
|
+
# or
|
|
15
|
+
yarn add qumra-pos-storage
|
|
16
|
+
# or
|
|
17
|
+
pnpm add qumra-pos-storage
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then add the driver for your platform (only the one you use):
|
|
21
|
+
|
|
22
|
+
| Platform | Driver (install yourself) | Import |
|
|
23
|
+
| --- | --- | --- |
|
|
24
|
+
| Node / Electron (main) | `better-sqlite3` | `qumra-pos-storage/better-sqlite3` |
|
|
25
|
+
| React Native | `@op-engineering/op-sqlite` | `qumra-pos-storage/op-sqlite` |
|
|
26
|
+
| Web | `wa-sqlite` (or any wasm SQLite) | `qumra-pos-storage/wa-sqlite` |
|
|
27
|
+
|
|
28
|
+
`better-sqlite3` is an **optional peer dependency** — install it only for the Node/Electron adapter.
|
|
29
|
+
|
|
30
|
+
## Quick start (Node / Electron)
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createStorage } from "qumra-pos-storage";
|
|
34
|
+
import { BetterSqlite3Adapter } from "qumra-pos-storage/better-sqlite3";
|
|
35
|
+
|
|
36
|
+
const storage = await createStorage(
|
|
37
|
+
BetterSqlite3Adapter.open({ filename: "/path/to/pos.db" }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const shift = await storage.shifts.open({ cashierId, openingCash: 10000 });
|
|
41
|
+
await storage.orders.create({
|
|
42
|
+
shiftId: shift.id,
|
|
43
|
+
cashierId,
|
|
44
|
+
items: [{ productId, nameSnapshot: "Coffee", unitPrice: 2500, qty: 2 }],
|
|
45
|
+
payments: [{ method: "cash", amount: 5000 }],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### React Native (op-sqlite) & Web (wa-sqlite)
|
|
50
|
+
|
|
51
|
+
These adapters take a driver **you open and inject**, so the package never imports the
|
|
52
|
+
native/wasm libraries directly:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
// React Native
|
|
56
|
+
import { open } from "@op-engineering/op-sqlite";
|
|
57
|
+
import { openOpSqliteAdapter } from "qumra-pos-storage/op-sqlite";
|
|
58
|
+
const storage = await createStorage(openOpSqliteAdapter(open({ name: "pos.db" })));
|
|
59
|
+
|
|
60
|
+
// Web — wrap your wa-sqlite instance in a small { exec(sql, params) } object
|
|
61
|
+
import { openWaSqliteAdapter } from "qumra-pos-storage/wa-sqlite";
|
|
62
|
+
const storage = await createStorage(openWaSqliteAdapter(myWaSqliteDb));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What you get
|
|
66
|
+
|
|
67
|
+
- **`StorageAdapter` port** — four async methods (`execute`, `query`, `transaction`, `close`). Implement it once to support any engine.
|
|
68
|
+
- **Schema + forward-only migrations** — `_migrations` bookkeeping, idempotent.
|
|
69
|
+
- **Repositories** — `shifts`, `orders`, `products`, `reports`, `outbox`.
|
|
70
|
+
- **Invariants enforced in the core** (not the UI): every write enqueues a `pending_operations` outbox row **in the same transaction**; `orders.create` rejects unless `sum(payments) === total`; money is integer piasters; IDs are UUIDv7 (chronological); items snapshot name + price at sale time.
|
|
71
|
+
- **Sync engine** — `SyncEngine` drains the outbox (batched, idempotent, exponential backoff + jitter) and pulls reference data (delta + tombstones). **It never talks to your server** — you inject a `SyncTransport`.
|
|
72
|
+
- **Conformance suite** — `qumra-pos-storage/conformance` exposes a battery every adapter must pass, so a new adapter is provably correct.
|
|
73
|
+
|
|
74
|
+
## Sync engine — you own the network
|
|
75
|
+
|
|
76
|
+
The engine handles batching, idempotency, UUIDv7 ordering, backoff and the outbox. It has
|
|
77
|
+
**zero endpoint code**: you inject a `SyncTransport` (REST, GraphQL — your choice) and you
|
|
78
|
+
hand it the auth token. The library takes a token from you and never contacts an auth
|
|
79
|
+
server.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { SyncEngine, SyncNetworkError, type SyncTransport } from "qumra-pos-storage";
|
|
83
|
+
|
|
84
|
+
const transport: SyncTransport = {
|
|
85
|
+
// push the outbox — return one result per op: applied | duplicate | rejected
|
|
86
|
+
async pushBatch(ops, ctx) {
|
|
87
|
+
const res = await fetch("https://your-api/whatever", {
|
|
88
|
+
method: "POST",
|
|
89
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${ctx.token}` },
|
|
90
|
+
body: JSON.stringify({ operations: ops }),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok) throw new SyncNetworkError(`HTTP ${res.status}`); // retryable → backoff
|
|
93
|
+
return res.json(); // { results: [{ idempotency_key, status, error? }] }
|
|
94
|
+
},
|
|
95
|
+
async pullDelta(resource, since, cursor, ctx) {
|
|
96
|
+
// your GET / GraphQL query …
|
|
97
|
+
return { changed: [], deleted: [], cursor: "", hasMore: false };
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const engine = new SyncEngine(storage, {
|
|
102
|
+
transport,
|
|
103
|
+
getAuthToken: () => myToken, // ← you provide the token; null ⇒ sync pauses (not an error)
|
|
104
|
+
});
|
|
105
|
+
engine.start();
|
|
106
|
+
engine.onStatusChange((s) => renderBar(s)); // { online, paused, pendingCount, failedOps, ... }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Rules: **throw** on network / 5xx (retryable, backoff); return a per-op
|
|
110
|
+
`status: "rejected"` for a permanent business rejection (surfaces to the manager, the rest
|
|
111
|
+
still syncs). REST vs GraphQL is entirely your call — the engine doesn't care.
|
|
112
|
+
|
|
113
|
+
## Testing / adding an adapter
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { describe, it } from "vitest";
|
|
117
|
+
import { runAdapterConformance } from "qumra-pos-storage/conformance";
|
|
118
|
+
import { BetterSqlite3Adapter } from "qumra-pos-storage/better-sqlite3";
|
|
119
|
+
|
|
120
|
+
runAdapterConformance("my-adapter", () => BetterSqlite3Adapter.open(), { describe, it });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Publishing
|
|
124
|
+
|
|
125
|
+
Ships dual **ESM + CJS + `.d.ts`** (built with tsup). To publish under a different name,
|
|
126
|
+
change `name` in `package.json`. `npm publish` builds `dist` via `prepublishOnly`.
|
|
127
|
+
|
|
128
|
+
MIT.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* العقد الوحيد اللي كل محرك على أي منصة لازم يطبّقه. أربع دوال بس، مفيش أكتر.
|
|
3
|
+
*
|
|
4
|
+
* كل حاجة async حتى لو المحرك متزامن (better-sqlite3)، لأن wa-sqlite و op-sqlite
|
|
5
|
+
* async بطبيعتهم — التوحيد أهم من الأداء الطفيف هنا.
|
|
6
|
+
*
|
|
7
|
+
* دالتي execute/query مشتركة بين الـ adapter نفسه والـ Tx اللي بيتسلّم لدالة الـ
|
|
8
|
+
* transaction، فالـ repositories بتشتغل بنفس الكود جوه أو برا transaction.
|
|
9
|
+
*/
|
|
10
|
+
type SqlParam = string | number | bigint | null | Uint8Array;
|
|
11
|
+
interface Tx {
|
|
12
|
+
/**
|
|
13
|
+
* تنفيذ أمر من غير نتيجة راجعة.
|
|
14
|
+
* - مع params: أمر واحد بمعاملات (prepare + run).
|
|
15
|
+
* - من غير params: ممكن يحتوي أكتر من أمر (بتستخدمها الـ migrations / DDL).
|
|
16
|
+
*/
|
|
17
|
+
execute(sql: string, params?: SqlParam[]): Promise<void>;
|
|
18
|
+
/** تنفيذ استعلام وإرجاع كل الصفوف. استعلامات بمعاملات فقط — ممنوع string interpolation. */
|
|
19
|
+
query<T = Record<string, unknown>>(sql: string, params?: SqlParam[]): Promise<T[]>;
|
|
20
|
+
}
|
|
21
|
+
interface StorageAdapter extends Tx {
|
|
22
|
+
/** تنفيذ fn جوه transaction واحدة atomic. أي throw بيعمل rollback للكل. */
|
|
23
|
+
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type { SqlParam as S, Tx as T, StorageAdapter as a };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* العقد الوحيد اللي كل محرك على أي منصة لازم يطبّقه. أربع دوال بس، مفيش أكتر.
|
|
3
|
+
*
|
|
4
|
+
* كل حاجة async حتى لو المحرك متزامن (better-sqlite3)، لأن wa-sqlite و op-sqlite
|
|
5
|
+
* async بطبيعتهم — التوحيد أهم من الأداء الطفيف هنا.
|
|
6
|
+
*
|
|
7
|
+
* دالتي execute/query مشتركة بين الـ adapter نفسه والـ Tx اللي بيتسلّم لدالة الـ
|
|
8
|
+
* transaction، فالـ repositories بتشتغل بنفس الكود جوه أو برا transaction.
|
|
9
|
+
*/
|
|
10
|
+
type SqlParam = string | number | bigint | null | Uint8Array;
|
|
11
|
+
interface Tx {
|
|
12
|
+
/**
|
|
13
|
+
* تنفيذ أمر من غير نتيجة راجعة.
|
|
14
|
+
* - مع params: أمر واحد بمعاملات (prepare + run).
|
|
15
|
+
* - من غير params: ممكن يحتوي أكتر من أمر (بتستخدمها الـ migrations / DDL).
|
|
16
|
+
*/
|
|
17
|
+
execute(sql: string, params?: SqlParam[]): Promise<void>;
|
|
18
|
+
/** تنفيذ استعلام وإرجاع كل الصفوف. استعلامات بمعاملات فقط — ممنوع string interpolation. */
|
|
19
|
+
query<T = Record<string, unknown>>(sql: string, params?: SqlParam[]): Promise<T[]>;
|
|
20
|
+
}
|
|
21
|
+
interface StorageAdapter extends Tx {
|
|
22
|
+
/** تنفيذ fn جوه transaction واحدة atomic. أي throw بيعمل rollback للكل. */
|
|
23
|
+
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type { SqlParam as S, Tx as T, StorageAdapter as a };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var Database = require('better-sqlite3');
|
|
4
|
+
|
|
5
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
6
|
+
|
|
7
|
+
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
8
|
+
|
|
9
|
+
// src/adapters/better-sqlite3/index.ts
|
|
10
|
+
var Executor = class {
|
|
11
|
+
constructor(db) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
}
|
|
14
|
+
db;
|
|
15
|
+
/**
|
|
16
|
+
* تنفيذ أوامر:
|
|
17
|
+
* INSERT
|
|
18
|
+
* UPDATE
|
|
19
|
+
* DELETE
|
|
20
|
+
* CREATE TABLE6
|
|
21
|
+
* ALTER TABLE
|
|
22
|
+
*
|
|
23
|
+
* إذا تم تمرير معاملات (params) يتم استخدام prepare().run().
|
|
24
|
+
* أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ
|
|
25
|
+
* أكثر من SQL statement في نفس النص (مفيد للـ migrations).
|
|
26
|
+
*/
|
|
27
|
+
async execute(sql, params) {
|
|
28
|
+
if (params && params.length > 0) {
|
|
29
|
+
this.db.prepare(sql).run(...params);
|
|
30
|
+
} else {
|
|
31
|
+
this.db.exec(sql);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.
|
|
36
|
+
*
|
|
37
|
+
* مثال:
|
|
38
|
+
* SELECT * FROM products
|
|
39
|
+
* SELECT * FROM orders WHERE id = ?
|
|
40
|
+
*/
|
|
41
|
+
async query(sql, params) {
|
|
42
|
+
const stmt = this.db.prepare(sql);
|
|
43
|
+
const rows = params && params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
44
|
+
return rows;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
var BetterSqlite3Adapter = class _BetterSqlite3Adapter extends Executor {
|
|
48
|
+
constructor(db) {
|
|
49
|
+
super(db);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* فتح قاعدة البيانات وإنشاء الـ Adapter.
|
|
53
|
+
*/
|
|
54
|
+
static open(options = {}) {
|
|
55
|
+
const db = new Database__default.default(options.filename ?? ":memory:");
|
|
56
|
+
db.pragma("foreign_keys = ON");
|
|
57
|
+
if (options.wal ?? Boolean(options.filename)) {
|
|
58
|
+
db.pragma("journal_mode = WAL");
|
|
59
|
+
}
|
|
60
|
+
return new _BetterSqlite3Adapter(db);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* تنفيذ مجموعة عمليات داخل Transaction واحدة.
|
|
64
|
+
*
|
|
65
|
+
* إذا نجحت جميع العمليات:
|
|
66
|
+
* COMMIT
|
|
67
|
+
*
|
|
68
|
+
* إذا فشلت أي عملية:
|
|
69
|
+
* ROLLBACK
|
|
70
|
+
*
|
|
71
|
+
* ملاحظة:
|
|
72
|
+
* نستخدم BEGIN / COMMIT / ROLLBACK يدويًا
|
|
73
|
+
* بدل db.transaction() لأن الـ callback هنا async.
|
|
74
|
+
*/
|
|
75
|
+
async transaction(fn) {
|
|
76
|
+
this.db.exec("BEGIN");
|
|
77
|
+
try {
|
|
78
|
+
const result = await fn(new Executor(this.db));
|
|
79
|
+
this.db.exec("COMMIT");
|
|
80
|
+
return result;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
this.db.exec("ROLLBACK");
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* إغلاق اتصال قاعدة البيانات وتحرير الموارد.
|
|
88
|
+
*/
|
|
89
|
+
async close() {
|
|
90
|
+
this.db.close();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
exports.BetterSqlite3Adapter = BetterSqlite3Adapter;
|
|
95
|
+
//# sourceMappingURL=better-sqlite3.cjs.map
|
|
96
|
+
//# sourceMappingURL=better-sqlite3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/better-sqlite3/index.ts"],"names":["Database"],"mappings":";;;;;;;;;AAWA,IAAM,WAAN,MAA6B;AAAA,EAC3B,YAA+B,EAAA,EAAuB;AAAvB,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AAAA,EAAwB;AAAA,EAAxB,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc/B,MAAM,OAAA,CAAQ,GAAA,EAAa,MAAA,EAAoC;AAC7D,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC/B,MAAA,IAAA,CAAK,GAAG,OAAA,CAAQ,GAAG,CAAA,CAAE,GAAA,CAAI,GAAG,MAAM,CAAA;AAAA,IACpC,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,GAAG,CAAA;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAA,CACJ,GAAA,EACA,MAAA,EACc;AACd,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA;AAEhC,IAAA,MAAM,IAAA,GACJ,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,GACtB,IAAA,CAAK,GAAA,CAAI,GAAG,MAAM,CAAA,GAClB,IAAA,CAAK,GAAA,EAAI;AAEf,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAmCO,IAAM,oBAAA,GAAN,MAAM,qBAAA,SACH,QAAA,CAEV;AAAA,EACU,YAAY,EAAA,EAAuB;AACzC,IAAA,KAAA,CAAM,EAAE,CAAA;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAA,CACL,OAAA,GAAgC,EAAC,EACX;AACtB,IAAA,MAAM,EAAA,GAAK,IAAIA,yBAAA,CAAS,OAAA,CAAQ,YAAY,UAAU,CAAA;AAQtD,IAAA,EAAA,CAAG,OAAO,mBAAmB,CAAA;AAU7B,IAAA,IAAI,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC5C,MAAA,EAAA,CAAG,OAAO,oBAAoB,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,IAAI,sBAAqB,EAAE,CAAA;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,YACJ,EAAA,EACY;AACZ,IAAA,IAAA,CAAK,EAAA,CAAG,KAAK,OAAO,CAAA;AAEpB,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,EAAA,CAAG,IAAI,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA;AAE7C,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,QAAQ,CAAA;AAErB,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,UAAU,CAAA;AAEvB,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAA,CAAK,GAAG,KAAA,EAAM;AAAA,EAChB;AACF","file":"better-sqlite3.cjs","sourcesContent":["import Database from \"better-sqlite3\";\nimport type { SqlParam, StorageAdapter, Tx } from \"../../ports/StorageAdapter\";\n\n/**\n * تنفيذ أوامر SQL المشتركة بين الـ Adapter والـ Transaction.\n *\n * مكتبة better-sqlite3 تعمل بشكل متزامن (Synchronous)،\n * لكن واجهة الـ Core تعتمد على Promise، لذلك نقوم بتغليف\n * النتائج داخل دوال async للحفاظ على نفس العقد (Contract)\n * بين جميع المنصات (Electron / Web / React Native).\n */\nclass Executor implements Tx {\n constructor(protected readonly db: Database.Database) {}\n\n /**\n * تنفيذ أوامر:\n * INSERT\n * UPDATE\n * DELETE\n * CREATE TABLE6\n * ALTER TABLE\n *\n * إذا تم تمرير معاملات (params) يتم استخدام prepare().run().\n * أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ\n * أكثر من SQL statement في نفس النص (مفيد للـ migrations).\n */\n async execute(sql: string, params?: SqlParam[]): Promise<void> {\n if (params && params.length > 0) {\n this.db.prepare(sql).run(...params);\n } else {\n this.db.exec(sql);\n }\n }\n\n /**\n * تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.\n *\n * مثال:\n * SELECT * FROM products\n * SELECT * FROM orders WHERE id = ?\n */\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlParam[],\n ): Promise<T[]> {\n const stmt = this.db.prepare(sql);\n\n const rows =\n params && params.length > 0\n ? stmt.all(...params)\n : stmt.all();\n\n return rows as T[];\n }\n}\n\nexport interface BetterSqlite3Options {\n /**\n * مسار ملف قاعدة البيانات.\n *\n * إذا لم يتم تحديده سيتم استخدام \":memory:\"\n * لإنشاء قاعدة بيانات داخل الذاكرة فقط.\n *\n * مفيد للاختبارات.\n */\n filename?: string;\n\n /**\n * تفعيل وضع WAL.\n *\n * افتراضيًا يتم تفعيله عند استخدام قاعدة بيانات\n * محفوظة على القرص.\n */\n wal?: boolean;\n}\n\n/**\n * Adapter خاص بـ Electron يعتمد على better-sqlite3.\n *\n * هذا الكلاس هو الطبقة التي تربط الـ Core بقاعدة SQLite.\n *\n * الـ Core لا يعرف أي شيء عن SQLite أو Electron،\n * بل يتعامل فقط مع واجهة StorageAdapter.\n *\n * لذلك يمكن استبدال هذا الـ Adapter مستقبلًا بـ:\n * - wa-sqlite (Web)\n * - op-sqlite (React Native)\n * - أي محرك تخزين آخر\n */\nexport class BetterSqlite3Adapter\n extends Executor\n implements StorageAdapter\n{\n private constructor(db: Database.Database) {\n super(db);\n }\n\n /**\n * فتح قاعدة البيانات وإنشاء الـ Adapter.\n */\n static open(\n options: BetterSqlite3Options = {},\n ): BetterSqlite3Adapter {\n const db = new Database(options.filename ?? \":memory:\");\n\n /**\n * تفعيل Foreign Keys.\n *\n * SQLite لا يفرض العلاقات بشكل افتراضي،\n * لذلك يجب تشغيلها يدويًا.\n */\n db.pragma(\"foreign_keys = ON\");\n\n /**\n * تفعيل WAL (Write Ahead Logging).\n *\n * فوائده:\n * - تحسين الأداء.\n * - السماح بالقراءة أثناء الكتابة.\n * - تقليل قفل قاعدة البيانات.\n */\n if (options.wal ?? Boolean(options.filename)) {\n db.pragma(\"journal_mode = WAL\");\n }\n\n return new BetterSqlite3Adapter(db);\n }\n\n /**\n * تنفيذ مجموعة عمليات داخل Transaction واحدة.\n *\n * إذا نجحت جميع العمليات:\n * COMMIT\n *\n * إذا فشلت أي عملية:\n * ROLLBACK\n *\n * ملاحظة:\n * نستخدم BEGIN / COMMIT / ROLLBACK يدويًا\n * بدل db.transaction() لأن الـ callback هنا async.\n */\n async transaction<T>(\n fn: (tx: Tx) => Promise<T>,\n ): Promise<T> {\n this.db.exec(\"BEGIN\");\n\n try {\n const result = await fn(new Executor(this.db));\n\n this.db.exec(\"COMMIT\");\n\n return result;\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n\n throw err;\n }\n }\n\n /**\n * إغلاق اتصال قاعدة البيانات وتحرير الموارد.\n */\n async close(): Promise<void> {\n this.db.close();\n }\n}"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { T as Tx, S as SqlParam, a as StorageAdapter } from './StorageAdapter-B2IgHcUF.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* تنفيذ أوامر SQL المشتركة بين الـ Adapter والـ Transaction.
|
|
6
|
+
*
|
|
7
|
+
* مكتبة better-sqlite3 تعمل بشكل متزامن (Synchronous)،
|
|
8
|
+
* لكن واجهة الـ Core تعتمد على Promise، لذلك نقوم بتغليف
|
|
9
|
+
* النتائج داخل دوال async للحفاظ على نفس العقد (Contract)
|
|
10
|
+
* بين جميع المنصات (Electron / Web / React Native).
|
|
11
|
+
*/
|
|
12
|
+
declare class Executor implements Tx {
|
|
13
|
+
protected readonly db: Database.Database;
|
|
14
|
+
constructor(db: Database.Database);
|
|
15
|
+
/**
|
|
16
|
+
* تنفيذ أوامر:
|
|
17
|
+
* INSERT
|
|
18
|
+
* UPDATE
|
|
19
|
+
* DELETE
|
|
20
|
+
* CREATE TABLE6
|
|
21
|
+
* ALTER TABLE
|
|
22
|
+
*
|
|
23
|
+
* إذا تم تمرير معاملات (params) يتم استخدام prepare().run().
|
|
24
|
+
* أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ
|
|
25
|
+
* أكثر من SQL statement في نفس النص (مفيد للـ migrations).
|
|
26
|
+
*/
|
|
27
|
+
execute(sql: string, params?: SqlParam[]): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.
|
|
30
|
+
*
|
|
31
|
+
* مثال:
|
|
32
|
+
* SELECT * FROM products
|
|
33
|
+
* SELECT * FROM orders WHERE id = ?
|
|
34
|
+
*/
|
|
35
|
+
query<T = Record<string, unknown>>(sql: string, params?: SqlParam[]): Promise<T[]>;
|
|
36
|
+
}
|
|
37
|
+
interface BetterSqlite3Options {
|
|
38
|
+
/**
|
|
39
|
+
* مسار ملف قاعدة البيانات.
|
|
40
|
+
*
|
|
41
|
+
* إذا لم يتم تحديده سيتم استخدام ":memory:"
|
|
42
|
+
* لإنشاء قاعدة بيانات داخل الذاكرة فقط.
|
|
43
|
+
*
|
|
44
|
+
* مفيد للاختبارات.
|
|
45
|
+
*/
|
|
46
|
+
filename?: string;
|
|
47
|
+
/**
|
|
48
|
+
* تفعيل وضع WAL.
|
|
49
|
+
*
|
|
50
|
+
* افتراضيًا يتم تفعيله عند استخدام قاعدة بيانات
|
|
51
|
+
* محفوظة على القرص.
|
|
52
|
+
*/
|
|
53
|
+
wal?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Adapter خاص بـ Electron يعتمد على better-sqlite3.
|
|
57
|
+
*
|
|
58
|
+
* هذا الكلاس هو الطبقة التي تربط الـ Core بقاعدة SQLite.
|
|
59
|
+
*
|
|
60
|
+
* الـ Core لا يعرف أي شيء عن SQLite أو Electron،
|
|
61
|
+
* بل يتعامل فقط مع واجهة StorageAdapter.
|
|
62
|
+
*
|
|
63
|
+
* لذلك يمكن استبدال هذا الـ Adapter مستقبلًا بـ:
|
|
64
|
+
* - wa-sqlite (Web)
|
|
65
|
+
* - op-sqlite (React Native)
|
|
66
|
+
* - أي محرك تخزين آخر
|
|
67
|
+
*/
|
|
68
|
+
declare class BetterSqlite3Adapter extends Executor implements StorageAdapter {
|
|
69
|
+
private constructor();
|
|
70
|
+
/**
|
|
71
|
+
* فتح قاعدة البيانات وإنشاء الـ Adapter.
|
|
72
|
+
*/
|
|
73
|
+
static open(options?: BetterSqlite3Options): BetterSqlite3Adapter;
|
|
74
|
+
/**
|
|
75
|
+
* تنفيذ مجموعة عمليات داخل Transaction واحدة.
|
|
76
|
+
*
|
|
77
|
+
* إذا نجحت جميع العمليات:
|
|
78
|
+
* COMMIT
|
|
79
|
+
*
|
|
80
|
+
* إذا فشلت أي عملية:
|
|
81
|
+
* ROLLBACK
|
|
82
|
+
*
|
|
83
|
+
* ملاحظة:
|
|
84
|
+
* نستخدم BEGIN / COMMIT / ROLLBACK يدويًا
|
|
85
|
+
* بدل db.transaction() لأن الـ callback هنا async.
|
|
86
|
+
*/
|
|
87
|
+
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
88
|
+
/**
|
|
89
|
+
* إغلاق اتصال قاعدة البيانات وتحرير الموارد.
|
|
90
|
+
*/
|
|
91
|
+
close(): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { BetterSqlite3Adapter, type BetterSqlite3Options };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { T as Tx, S as SqlParam, a as StorageAdapter } from './StorageAdapter-B2IgHcUF.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* تنفيذ أوامر SQL المشتركة بين الـ Adapter والـ Transaction.
|
|
6
|
+
*
|
|
7
|
+
* مكتبة better-sqlite3 تعمل بشكل متزامن (Synchronous)،
|
|
8
|
+
* لكن واجهة الـ Core تعتمد على Promise، لذلك نقوم بتغليف
|
|
9
|
+
* النتائج داخل دوال async للحفاظ على نفس العقد (Contract)
|
|
10
|
+
* بين جميع المنصات (Electron / Web / React Native).
|
|
11
|
+
*/
|
|
12
|
+
declare class Executor implements Tx {
|
|
13
|
+
protected readonly db: Database.Database;
|
|
14
|
+
constructor(db: Database.Database);
|
|
15
|
+
/**
|
|
16
|
+
* تنفيذ أوامر:
|
|
17
|
+
* INSERT
|
|
18
|
+
* UPDATE
|
|
19
|
+
* DELETE
|
|
20
|
+
* CREATE TABLE6
|
|
21
|
+
* ALTER TABLE
|
|
22
|
+
*
|
|
23
|
+
* إذا تم تمرير معاملات (params) يتم استخدام prepare().run().
|
|
24
|
+
* أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ
|
|
25
|
+
* أكثر من SQL statement في نفس النص (مفيد للـ migrations).
|
|
26
|
+
*/
|
|
27
|
+
execute(sql: string, params?: SqlParam[]): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.
|
|
30
|
+
*
|
|
31
|
+
* مثال:
|
|
32
|
+
* SELECT * FROM products
|
|
33
|
+
* SELECT * FROM orders WHERE id = ?
|
|
34
|
+
*/
|
|
35
|
+
query<T = Record<string, unknown>>(sql: string, params?: SqlParam[]): Promise<T[]>;
|
|
36
|
+
}
|
|
37
|
+
interface BetterSqlite3Options {
|
|
38
|
+
/**
|
|
39
|
+
* مسار ملف قاعدة البيانات.
|
|
40
|
+
*
|
|
41
|
+
* إذا لم يتم تحديده سيتم استخدام ":memory:"
|
|
42
|
+
* لإنشاء قاعدة بيانات داخل الذاكرة فقط.
|
|
43
|
+
*
|
|
44
|
+
* مفيد للاختبارات.
|
|
45
|
+
*/
|
|
46
|
+
filename?: string;
|
|
47
|
+
/**
|
|
48
|
+
* تفعيل وضع WAL.
|
|
49
|
+
*
|
|
50
|
+
* افتراضيًا يتم تفعيله عند استخدام قاعدة بيانات
|
|
51
|
+
* محفوظة على القرص.
|
|
52
|
+
*/
|
|
53
|
+
wal?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Adapter خاص بـ Electron يعتمد على better-sqlite3.
|
|
57
|
+
*
|
|
58
|
+
* هذا الكلاس هو الطبقة التي تربط الـ Core بقاعدة SQLite.
|
|
59
|
+
*
|
|
60
|
+
* الـ Core لا يعرف أي شيء عن SQLite أو Electron،
|
|
61
|
+
* بل يتعامل فقط مع واجهة StorageAdapter.
|
|
62
|
+
*
|
|
63
|
+
* لذلك يمكن استبدال هذا الـ Adapter مستقبلًا بـ:
|
|
64
|
+
* - wa-sqlite (Web)
|
|
65
|
+
* - op-sqlite (React Native)
|
|
66
|
+
* - أي محرك تخزين آخر
|
|
67
|
+
*/
|
|
68
|
+
declare class BetterSqlite3Adapter extends Executor implements StorageAdapter {
|
|
69
|
+
private constructor();
|
|
70
|
+
/**
|
|
71
|
+
* فتح قاعدة البيانات وإنشاء الـ Adapter.
|
|
72
|
+
*/
|
|
73
|
+
static open(options?: BetterSqlite3Options): BetterSqlite3Adapter;
|
|
74
|
+
/**
|
|
75
|
+
* تنفيذ مجموعة عمليات داخل Transaction واحدة.
|
|
76
|
+
*
|
|
77
|
+
* إذا نجحت جميع العمليات:
|
|
78
|
+
* COMMIT
|
|
79
|
+
*
|
|
80
|
+
* إذا فشلت أي عملية:
|
|
81
|
+
* ROLLBACK
|
|
82
|
+
*
|
|
83
|
+
* ملاحظة:
|
|
84
|
+
* نستخدم BEGIN / COMMIT / ROLLBACK يدويًا
|
|
85
|
+
* بدل db.transaction() لأن الـ callback هنا async.
|
|
86
|
+
*/
|
|
87
|
+
transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T>;
|
|
88
|
+
/**
|
|
89
|
+
* إغلاق اتصال قاعدة البيانات وتحرير الموارد.
|
|
90
|
+
*/
|
|
91
|
+
close(): Promise<void>;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export { BetterSqlite3Adapter, type BetterSqlite3Options };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
|
|
3
|
+
// src/adapters/better-sqlite3/index.ts
|
|
4
|
+
var Executor = class {
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
db;
|
|
9
|
+
/**
|
|
10
|
+
* تنفيذ أوامر:
|
|
11
|
+
* INSERT
|
|
12
|
+
* UPDATE
|
|
13
|
+
* DELETE
|
|
14
|
+
* CREATE TABLE6
|
|
15
|
+
* ALTER TABLE
|
|
16
|
+
*
|
|
17
|
+
* إذا تم تمرير معاملات (params) يتم استخدام prepare().run().
|
|
18
|
+
* أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ
|
|
19
|
+
* أكثر من SQL statement في نفس النص (مفيد للـ migrations).
|
|
20
|
+
*/
|
|
21
|
+
async execute(sql, params) {
|
|
22
|
+
if (params && params.length > 0) {
|
|
23
|
+
this.db.prepare(sql).run(...params);
|
|
24
|
+
} else {
|
|
25
|
+
this.db.exec(sql);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.
|
|
30
|
+
*
|
|
31
|
+
* مثال:
|
|
32
|
+
* SELECT * FROM products
|
|
33
|
+
* SELECT * FROM orders WHERE id = ?
|
|
34
|
+
*/
|
|
35
|
+
async query(sql, params) {
|
|
36
|
+
const stmt = this.db.prepare(sql);
|
|
37
|
+
const rows = params && params.length > 0 ? stmt.all(...params) : stmt.all();
|
|
38
|
+
return rows;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
var BetterSqlite3Adapter = class _BetterSqlite3Adapter extends Executor {
|
|
42
|
+
constructor(db) {
|
|
43
|
+
super(db);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* فتح قاعدة البيانات وإنشاء الـ Adapter.
|
|
47
|
+
*/
|
|
48
|
+
static open(options = {}) {
|
|
49
|
+
const db = new Database(options.filename ?? ":memory:");
|
|
50
|
+
db.pragma("foreign_keys = ON");
|
|
51
|
+
if (options.wal ?? Boolean(options.filename)) {
|
|
52
|
+
db.pragma("journal_mode = WAL");
|
|
53
|
+
}
|
|
54
|
+
return new _BetterSqlite3Adapter(db);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* تنفيذ مجموعة عمليات داخل Transaction واحدة.
|
|
58
|
+
*
|
|
59
|
+
* إذا نجحت جميع العمليات:
|
|
60
|
+
* COMMIT
|
|
61
|
+
*
|
|
62
|
+
* إذا فشلت أي عملية:
|
|
63
|
+
* ROLLBACK
|
|
64
|
+
*
|
|
65
|
+
* ملاحظة:
|
|
66
|
+
* نستخدم BEGIN / COMMIT / ROLLBACK يدويًا
|
|
67
|
+
* بدل db.transaction() لأن الـ callback هنا async.
|
|
68
|
+
*/
|
|
69
|
+
async transaction(fn) {
|
|
70
|
+
this.db.exec("BEGIN");
|
|
71
|
+
try {
|
|
72
|
+
const result = await fn(new Executor(this.db));
|
|
73
|
+
this.db.exec("COMMIT");
|
|
74
|
+
return result;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
this.db.exec("ROLLBACK");
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* إغلاق اتصال قاعدة البيانات وتحرير الموارد.
|
|
82
|
+
*/
|
|
83
|
+
async close() {
|
|
84
|
+
this.db.close();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export { BetterSqlite3Adapter };
|
|
89
|
+
//# sourceMappingURL=better-sqlite3.js.map
|
|
90
|
+
//# sourceMappingURL=better-sqlite3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/better-sqlite3/index.ts"],"names":[],"mappings":";;;AAWA,IAAM,WAAN,MAA6B;AAAA,EAC3B,YAA+B,EAAA,EAAuB;AAAvB,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AAAA,EAAwB;AAAA,EAAxB,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc/B,MAAM,OAAA,CAAQ,GAAA,EAAa,MAAA,EAAoC;AAC7D,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC/B,MAAA,IAAA,CAAK,GAAG,OAAA,CAAQ,GAAG,CAAA,CAAE,GAAA,CAAI,GAAG,MAAM,CAAA;AAAA,IACpC,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,GAAG,CAAA;AAAA,IAClB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAA,CACJ,GAAA,EACA,MAAA,EACc;AACd,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,EAAA,CAAG,OAAA,CAAQ,GAAG,CAAA;AAEhC,IAAA,MAAM,IAAA,GACJ,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,GACtB,IAAA,CAAK,GAAA,CAAI,GAAG,MAAM,CAAA,GAClB,IAAA,CAAK,GAAA,EAAI;AAEf,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAmCO,IAAM,oBAAA,GAAN,MAAM,qBAAA,SACH,QAAA,CAEV;AAAA,EACU,YAAY,EAAA,EAAuB;AACzC,IAAA,KAAA,CAAM,EAAE,CAAA;AAAA,EACV;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,IAAA,CACL,OAAA,GAAgC,EAAC,EACX;AACtB,IAAA,MAAM,EAAA,GAAK,IAAI,QAAA,CAAS,OAAA,CAAQ,YAAY,UAAU,CAAA;AAQtD,IAAA,EAAA,CAAG,OAAO,mBAAmB,CAAA;AAU7B,IAAA,IAAI,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,OAAA,CAAQ,QAAQ,CAAA,EAAG;AAC5C,MAAA,EAAA,CAAG,OAAO,oBAAoB,CAAA;AAAA,IAChC;AAEA,IAAA,OAAO,IAAI,sBAAqB,EAAE,CAAA;AAAA,EACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,YACJ,EAAA,EACY;AACZ,IAAA,IAAA,CAAK,EAAA,CAAG,KAAK,OAAO,CAAA;AAEpB,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,EAAA,CAAG,IAAI,QAAA,CAAS,IAAA,CAAK,EAAE,CAAC,CAAA;AAE7C,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,QAAQ,CAAA;AAErB,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,IAAA,CAAK,EAAA,CAAG,KAAK,UAAU,CAAA;AAEvB,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAA,CAAK,GAAG,KAAA,EAAM;AAAA,EAChB;AACF","file":"better-sqlite3.js","sourcesContent":["import Database from \"better-sqlite3\";\nimport type { SqlParam, StorageAdapter, Tx } from \"../../ports/StorageAdapter\";\n\n/**\n * تنفيذ أوامر SQL المشتركة بين الـ Adapter والـ Transaction.\n *\n * مكتبة better-sqlite3 تعمل بشكل متزامن (Synchronous)،\n * لكن واجهة الـ Core تعتمد على Promise، لذلك نقوم بتغليف\n * النتائج داخل دوال async للحفاظ على نفس العقد (Contract)\n * بين جميع المنصات (Electron / Web / React Native).\n */\nclass Executor implements Tx {\n constructor(protected readonly db: Database.Database) {}\n\n /**\n * تنفيذ أوامر:\n * INSERT\n * UPDATE\n * DELETE\n * CREATE TABLE6\n * ALTER TABLE\n *\n * إذا تم تمرير معاملات (params) يتم استخدام prepare().run().\n * أما إذا لم توجد معاملات يتم استخدام exec() للسماح بتنفيذ\n * أكثر من SQL statement في نفس النص (مفيد للـ migrations).\n */\n async execute(sql: string, params?: SqlParam[]): Promise<void> {\n if (params && params.length > 0) {\n this.db.prepare(sql).run(...params);\n } else {\n this.db.exec(sql);\n }\n }\n\n /**\n * تنفيذ استعلام SELECT وإرجاع النتائج كمصفوفة.\n *\n * مثال:\n * SELECT * FROM products\n * SELECT * FROM orders WHERE id = ?\n */\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlParam[],\n ): Promise<T[]> {\n const stmt = this.db.prepare(sql);\n\n const rows =\n params && params.length > 0\n ? stmt.all(...params)\n : stmt.all();\n\n return rows as T[];\n }\n}\n\nexport interface BetterSqlite3Options {\n /**\n * مسار ملف قاعدة البيانات.\n *\n * إذا لم يتم تحديده سيتم استخدام \":memory:\"\n * لإنشاء قاعدة بيانات داخل الذاكرة فقط.\n *\n * مفيد للاختبارات.\n */\n filename?: string;\n\n /**\n * تفعيل وضع WAL.\n *\n * افتراضيًا يتم تفعيله عند استخدام قاعدة بيانات\n * محفوظة على القرص.\n */\n wal?: boolean;\n}\n\n/**\n * Adapter خاص بـ Electron يعتمد على better-sqlite3.\n *\n * هذا الكلاس هو الطبقة التي تربط الـ Core بقاعدة SQLite.\n *\n * الـ Core لا يعرف أي شيء عن SQLite أو Electron،\n * بل يتعامل فقط مع واجهة StorageAdapter.\n *\n * لذلك يمكن استبدال هذا الـ Adapter مستقبلًا بـ:\n * - wa-sqlite (Web)\n * - op-sqlite (React Native)\n * - أي محرك تخزين آخر\n */\nexport class BetterSqlite3Adapter\n extends Executor\n implements StorageAdapter\n{\n private constructor(db: Database.Database) {\n super(db);\n }\n\n /**\n * فتح قاعدة البيانات وإنشاء الـ Adapter.\n */\n static open(\n options: BetterSqlite3Options = {},\n ): BetterSqlite3Adapter {\n const db = new Database(options.filename ?? \":memory:\");\n\n /**\n * تفعيل Foreign Keys.\n *\n * SQLite لا يفرض العلاقات بشكل افتراضي،\n * لذلك يجب تشغيلها يدويًا.\n */\n db.pragma(\"foreign_keys = ON\");\n\n /**\n * تفعيل WAL (Write Ahead Logging).\n *\n * فوائده:\n * - تحسين الأداء.\n * - السماح بالقراءة أثناء الكتابة.\n * - تقليل قفل قاعدة البيانات.\n */\n if (options.wal ?? Boolean(options.filename)) {\n db.pragma(\"journal_mode = WAL\");\n }\n\n return new BetterSqlite3Adapter(db);\n }\n\n /**\n * تنفيذ مجموعة عمليات داخل Transaction واحدة.\n *\n * إذا نجحت جميع العمليات:\n * COMMIT\n *\n * إذا فشلت أي عملية:\n * ROLLBACK\n *\n * ملاحظة:\n * نستخدم BEGIN / COMMIT / ROLLBACK يدويًا\n * بدل db.transaction() لأن الـ callback هنا async.\n */\n async transaction<T>(\n fn: (tx: Tx) => Promise<T>,\n ): Promise<T> {\n this.db.exec(\"BEGIN\");\n\n try {\n const result = await fn(new Executor(this.db));\n\n this.db.exec(\"COMMIT\");\n\n return result;\n } catch (err) {\n this.db.exec(\"ROLLBACK\");\n\n throw err;\n }\n }\n\n /**\n * إغلاق اتصال قاعدة البيانات وتحرير الموارد.\n */\n async close(): Promise<void> {\n this.db.close();\n }\n}"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/adapters/generic/GenericSqlAdapter.ts
|
|
2
|
+
var GenericExecutor = class {
|
|
3
|
+
constructor(driver) {
|
|
4
|
+
this.driver = driver;
|
|
5
|
+
}
|
|
6
|
+
driver;
|
|
7
|
+
async execute(sql, params) {
|
|
8
|
+
if (params && params.length > 0) {
|
|
9
|
+
await this.driver.run(sql, params);
|
|
10
|
+
} else {
|
|
11
|
+
await this.driver.runBatch(sql);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async query(sql, params) {
|
|
15
|
+
const rows = await this.driver.select(sql, params ?? []);
|
|
16
|
+
return rows;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var GenericSqlAdapter = class extends GenericExecutor {
|
|
20
|
+
async transaction(fn) {
|
|
21
|
+
await this.driver.runBatch("BEGIN");
|
|
22
|
+
try {
|
|
23
|
+
const result = await fn(new GenericExecutor(this.driver));
|
|
24
|
+
await this.driver.runBatch("COMMIT");
|
|
25
|
+
return result;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
await this.driver.runBatch("ROLLBACK");
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async close() {
|
|
32
|
+
await this.driver.close();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export { GenericSqlAdapter };
|
|
37
|
+
//# sourceMappingURL=chunk-CELALQHK.js.map
|
|
38
|
+
//# sourceMappingURL=chunk-CELALQHK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/generic/GenericSqlAdapter.ts"],"names":[],"mappings":";AAkBA,IAAM,kBAAN,MAAoC;AAAA,EAClC,YAA+B,MAAA,EAAmB;AAAnB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAoB;AAAA,EAApB,MAAA;AAAA,EAE/B,MAAM,OAAA,CAAQ,GAAA,EAAa,MAAA,EAAoC;AAC7D,IAAA,IAAI,MAAA,IAAU,MAAA,CAAO,MAAA,GAAS,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAA,EAAK,MAAM,CAAA;AAAA,IACnC,CAAA,MAAO;AACL,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,GAAG,CAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,MAAM,KAAA,CACJ,GAAA,EACA,MAAA,EACc;AACd,IAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,MAAA,CAAO,OAAO,GAAA,EAAK,MAAA,IAAU,EAAE,CAAA;AACvD,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAOO,IAAM,iBAAA,GAAN,cAAgC,eAAA,CAA0C;AAAA,EAC/E,MAAM,YAAe,EAAA,EAAwC;AAC3D,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,OAAO,CAAA;AAClC,IAAA,IAAI;AACF,MAAA,MAAM,SAAS,MAAM,EAAA,CAAG,IAAI,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAC,CAAA;AACxD,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,QAAQ,CAAA;AACnC,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,GAAA,EAAK;AACZ,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,CAAS,UAAU,CAAA;AACrC,MAAA,MAAM,GAAA;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAM,IAAA,CAAK,OAAO,KAAA,EAAM;AAAA,EAC1B;AACF","file":"chunk-CELALQHK.js","sourcesContent":["import type { SqlParam, StorageAdapter, Tx } from \"../../ports/StorageAdapter\";\n\n/**\n * منفذ رفيع لأي محرك SQLite async (op-sqlite على RN، wa-sqlite على الويب). كل\n * محرك بيوصّل الأربع دوال دي بس، والـ GenericSqlAdapter بيبني عليها عقد\n * StorageAdapter كامل (بما فيه الـ transactions) — فمنطق الـ BEGIN/COMMIT/ROLLBACK\n * يتكتب مرة واحدة ويتشارك بين المنصتين.\n */\nexport interface SqlDriver {\n /** SELECT بمعاملات → صفوف ككائنات. */\n select(sql: string, params: SqlParam[]): Promise<Record<string, unknown>[]>;\n /** أمر واحد بمعاملات (INSERT/UPDATE/DELETE). */\n run(sql: string, params: SqlParam[]): Promise<void>;\n /** نص فيه أمر أو أكتر بدون معاملات (DDL/migrations، وBEGIN/COMMIT/ROLLBACK). */\n runBatch(sql: string): Promise<void>;\n close(): Promise<void>;\n}\n\nclass GenericExecutor implements Tx {\n constructor(protected readonly driver: SqlDriver) {}\n\n async execute(sql: string, params?: SqlParam[]): Promise<void> {\n if (params && params.length > 0) {\n await this.driver.run(sql, params);\n } else {\n await this.driver.runBatch(sql);\n }\n }\n\n async query<T = Record<string, unknown>>(\n sql: string,\n params?: SqlParam[],\n ): Promise<T[]> {\n const rows = await this.driver.select(sql, params ?? []);\n return rows as T[];\n }\n}\n\n/**\n * StorageAdapter فوق SqlDriver. الـ transaction يدوي بـ BEGIN/COMMIT/ROLLBACK\n * (زي better-sqlite3 adapter) — بيخلّي الـ callback الـ async يشتغل صح. مفيش\n * transactions متداخلة (نفس حد المحركات التانية).\n */\nexport class GenericSqlAdapter extends GenericExecutor implements StorageAdapter {\n async transaction<T>(fn: (tx: Tx) => Promise<T>): Promise<T> {\n await this.driver.runBatch(\"BEGIN\");\n try {\n const result = await fn(new GenericExecutor(this.driver));\n await this.driver.runBatch(\"COMMIT\");\n return result;\n } catch (err) {\n await this.driver.runBatch(\"ROLLBACK\");\n throw err;\n }\n }\n\n async close(): Promise<void> {\n await this.driver.close();\n }\n}\n"]}
|