qumra-pos-storage 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +122 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +122 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -124,10 +124,28 @@ ALTER TABLE pending_operations ADD COLUMN inflight_at TEXT;
|
|
|
124
124
|
`
|
|
125
125
|
);
|
|
126
126
|
|
|
127
|
+
// src/schema/003_documents.ts
|
|
128
|
+
var SQL_003 = (
|
|
129
|
+
/* sql */
|
|
130
|
+
`
|
|
131
|
+
CREATE TABLE documents (
|
|
132
|
+
id TEXT PRIMARY KEY, -- UUIDv7 \u0628\u064A\u062A\u0648\u0644\u0651\u062F \u062A\u0644\u0642\u0627\u0626\u064A\u0627\u064B (\u0645\u0631\u0633\u0627\u0629 \u0627\u0644\u0640 idempotency)
|
|
133
|
+
collection TEXT NOT NULL, -- "orders" | "products" | \u0623\u064A \u0627\u0633\u0645 \u0628\u062A\u0627\u0639\u0643
|
|
134
|
+
data TEXT NOT NULL, -- JSON \u2014 \u0623\u064A keys \u0625\u0646\u062A \u0639\u0627\u064A\u0632\u0647\u0627
|
|
135
|
+
created_at TEXT NOT NULL,
|
|
136
|
+
updated_at TEXT NOT NULL,
|
|
137
|
+
deleted INTEGER NOT NULL DEFAULT 0, -- \u062D\u0630\u0641 \u0646\u0627\u0639\u0645 (tombstone) \u2014 \u0627\u0644\u0628\u064A\u0627\u0646\u0627\u062A \u0645\u0627\u0628\u062A\u062A\u0645\u0633\u0651\u062D
|
|
138
|
+
sync_status TEXT NOT NULL DEFAULT 'pending'
|
|
139
|
+
);
|
|
140
|
+
CREATE INDEX idx_documents_collection ON documents(collection, updated_at);
|
|
141
|
+
`
|
|
142
|
+
);
|
|
143
|
+
|
|
127
144
|
// src/schema/index.ts
|
|
128
145
|
var MIGRATIONS = [
|
|
129
146
|
{ version: 1, sql: SQL_001 },
|
|
130
|
-
{ version: 2, sql: SQL_002 }
|
|
147
|
+
{ version: 2, sql: SQL_002 },
|
|
148
|
+
{ version: 3, sql: SQL_003 }
|
|
131
149
|
];
|
|
132
150
|
|
|
133
151
|
// src/time.ts
|
|
@@ -160,6 +178,99 @@ async function runMigrations(adapter) {
|
|
|
160
178
|
}
|
|
161
179
|
var newId = () => uuidv7.uuidv7();
|
|
162
180
|
|
|
181
|
+
// src/repositories/DocumentsRepository.ts
|
|
182
|
+
var DocumentsRepository = class {
|
|
183
|
+
constructor(adapter, outbox) {
|
|
184
|
+
this.adapter = adapter;
|
|
185
|
+
this.outbox = outbox;
|
|
186
|
+
}
|
|
187
|
+
adapter;
|
|
188
|
+
outbox;
|
|
189
|
+
/** إنشاء مستند جديد بـ UUID تلقائي. بيرجّع المستند بالـ id. */
|
|
190
|
+
async create(collection, data) {
|
|
191
|
+
return this.write(collection, newId(), data, false);
|
|
192
|
+
}
|
|
193
|
+
/** إدراج/تحديث مستند بـ id معروف (لو موجود بيتحدّث، لو لأ بيتعمل). */
|
|
194
|
+
async upsert(collection, id, data) {
|
|
195
|
+
return this.write(collection, id, data, false);
|
|
196
|
+
}
|
|
197
|
+
/** حذف ناعم (tombstone) — البيانات مابتتمسّح، بس بتتعلّم deleted وتتزامن كحذف. */
|
|
198
|
+
async remove(collection, id) {
|
|
199
|
+
const existing = await this.get(collection, id);
|
|
200
|
+
if (!existing) return;
|
|
201
|
+
await this.write(collection, id, existing.data, true);
|
|
202
|
+
}
|
|
203
|
+
async get(collection, id) {
|
|
204
|
+
const rows = await this.adapter.query(
|
|
205
|
+
`SELECT * FROM documents WHERE id = ? AND collection = ?`,
|
|
206
|
+
[id, collection]
|
|
207
|
+
);
|
|
208
|
+
const row = rows[0];
|
|
209
|
+
return row ? this.toDoc(row) : null;
|
|
210
|
+
}
|
|
211
|
+
/** كل مستندات مجموعة (أحدث أولاً). بيتجاهل المحذوف افتراضياً. */
|
|
212
|
+
async all(collection, opts = {}) {
|
|
213
|
+
const limit = opts.limit ?? 100;
|
|
214
|
+
const offset = opts.offset ?? 0;
|
|
215
|
+
const guard = opts.includeDeleted ? "" : "AND deleted = 0";
|
|
216
|
+
const rows = await this.adapter.query(
|
|
217
|
+
`SELECT * FROM documents
|
|
218
|
+
WHERE collection = ? ${guard}
|
|
219
|
+
ORDER BY updated_at DESC
|
|
220
|
+
LIMIT ? OFFSET ?`,
|
|
221
|
+
[collection, limit, offset]
|
|
222
|
+
);
|
|
223
|
+
return rows.map((r) => this.toDoc(r));
|
|
224
|
+
}
|
|
225
|
+
async write(collection, id, data, deleted) {
|
|
226
|
+
const now = nowISO();
|
|
227
|
+
const dataJson = JSON.stringify(data);
|
|
228
|
+
const opType = deleted ? "doc.delete" : "doc.upsert";
|
|
229
|
+
await this.adapter.transaction(async (tx) => {
|
|
230
|
+
await tx.execute(
|
|
231
|
+
`INSERT INTO documents
|
|
232
|
+
(id, collection, data, created_at, updated_at, deleted, sync_status)
|
|
233
|
+
VALUES (?, ?, ?, ?, ?, ?, 'pending')
|
|
234
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
235
|
+
data = excluded.data,
|
|
236
|
+
updated_at = excluded.updated_at,
|
|
237
|
+
deleted = excluded.deleted,
|
|
238
|
+
sync_status = 'pending'`,
|
|
239
|
+
[id, collection, dataJson, now, now, deleted ? 1 : 0]
|
|
240
|
+
);
|
|
241
|
+
await this.outbox.enqueue(tx, {
|
|
242
|
+
opType,
|
|
243
|
+
payload: { op: deleted ? "delete" : "upsert", id, collection, data, updatedAt: now },
|
|
244
|
+
// فريد لكل كتابة (opId جديد) علشان التحديثات المتتالية تتزامن كلها؛
|
|
245
|
+
// والـ docId جوه المفتاح علشان الـ settle يعكس sync_status على المستند.
|
|
246
|
+
idempotencyKey: `${opType}:${id}:${newId()}`,
|
|
247
|
+
clientTs: now
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
const stored = await this.get(collection, id);
|
|
251
|
+
return stored ?? {
|
|
252
|
+
id,
|
|
253
|
+
collection,
|
|
254
|
+
data,
|
|
255
|
+
createdAt: now,
|
|
256
|
+
updatedAt: now,
|
|
257
|
+
deleted,
|
|
258
|
+
syncStatus: "pending"
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
toDoc(row) {
|
|
262
|
+
return {
|
|
263
|
+
id: row.id,
|
|
264
|
+
collection: row.collection,
|
|
265
|
+
data: JSON.parse(row.data),
|
|
266
|
+
createdAt: row.created_at,
|
|
267
|
+
updatedAt: row.updated_at,
|
|
268
|
+
deleted: row.deleted === 1,
|
|
269
|
+
syncStatus: row.sync_status
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
163
274
|
// src/repositories/OrdersRepository.ts
|
|
164
275
|
var OrdersRepository = class {
|
|
165
276
|
constructor(adapter, outbox) {
|
|
@@ -414,6 +525,14 @@ var OutboxRepository = class {
|
|
|
414
525
|
outcome,
|
|
415
526
|
orderId
|
|
416
527
|
]);
|
|
528
|
+
} else if (op.op_type === "doc.upsert" || op.op_type === "doc.delete") {
|
|
529
|
+
const docId = op.idempotency_key.slice(op.op_type.length + 1).split(":")[0];
|
|
530
|
+
if (docId) {
|
|
531
|
+
await tx.execute(`UPDATE documents SET sync_status = ? WHERE id = ?`, [
|
|
532
|
+
outcome,
|
|
533
|
+
docId
|
|
534
|
+
]);
|
|
535
|
+
}
|
|
417
536
|
}
|
|
418
537
|
}
|
|
419
538
|
});
|
|
@@ -731,6 +850,7 @@ async function createStorage(adapter) {
|
|
|
731
850
|
orders: new OrdersRepository(adapter, outbox),
|
|
732
851
|
products: new ProductsRepository(adapter, outbox),
|
|
733
852
|
reports: new ReportsRepository(adapter),
|
|
853
|
+
docs: new DocumentsRepository(adapter, outbox),
|
|
734
854
|
outbox,
|
|
735
855
|
adapter,
|
|
736
856
|
close: () => adapter.close()
|
|
@@ -1314,6 +1434,7 @@ exports.CASHIERS_RESOURCE = CASHIERS_RESOURCE;
|
|
|
1314
1434
|
exports.CONFORMANCE_CASES = CONFORMANCE_CASES;
|
|
1315
1435
|
exports.DEFAULT_BACKOFF = DEFAULT_BACKOFF;
|
|
1316
1436
|
exports.DEFAULT_RESOURCES = DEFAULT_RESOURCES;
|
|
1437
|
+
exports.DocumentsRepository = DocumentsRepository;
|
|
1317
1438
|
exports.MIGRATIONS = MIGRATIONS;
|
|
1318
1439
|
exports.OrdersRepository = OrdersRepository;
|
|
1319
1440
|
exports.OutboxRepository = OutboxRepository;
|