@teincfood/core 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 +44 -0
- package/dist/adapters/auth.d.ts +13 -0
- package/dist/adapters/auth.js +14 -0
- package/dist/adapters/business.d.ts +13 -0
- package/dist/adapters/business.js +14 -0
- package/dist/adapters/connectivity.d.ts +17 -0
- package/dist/adapters/connectivity.js +38 -0
- package/dist/adapters/devices.d.ts +5 -0
- package/dist/adapters/devices.js +14 -0
- package/dist/adapters/http.d.ts +30 -0
- package/dist/adapters/http.js +37 -0
- package/dist/adapters/image-cache.d.ts +8 -0
- package/dist/adapters/image-cache.js +12 -0
- package/dist/adapters/kv-types.d.ts +5 -0
- package/dist/adapters/kv-types.js +2 -0
- package/dist/adapters/kv.d.ts +20 -0
- package/dist/adapters/kv.js +81 -0
- package/dist/adapters/local-node.d.ts +34 -0
- package/dist/adapters/local-node.js +40 -0
- package/dist/adapters/pending-orders.d.ts +17 -0
- package/dist/adapters/pending-orders.js +31 -0
- package/dist/adapters/query-keys.d.ts +17 -0
- package/dist/adapters/query-keys.js +20 -0
- package/dist/adapters/services.d.ts +62 -0
- package/dist/adapters/services.js +95 -0
- package/dist/adapters/sqlite.d.ts +20 -0
- package/dist/adapters/sqlite.js +23 -0
- package/dist/db/connection.d.ts +8 -0
- package/dist/db/connection.js +87 -0
- package/dist/db/migrations.d.ts +9 -0
- package/dist/db/migrations.js +106 -0
- package/dist/db/operations.d.ts +45 -0
- package/dist/db/operations.js +192 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +118 -0
- package/dist/reference/api.d.ts +10 -0
- package/dist/reference/api.js +24 -0
- package/dist/reference/events.d.ts +54 -0
- package/dist/reference/events.js +144 -0
- package/dist/reference/operations.d.ts +11 -0
- package/dist/reference/operations.js +46 -0
- package/dist/reference/service.d.ts +27 -0
- package/dist/reference/service.js +270 -0
- package/dist/reference/types.d.ts +47 -0
- package/dist/reference/types.js +12 -0
- package/dist/repositories/order.repository.d.ts +44 -0
- package/dist/repositories/order.repository.js +132 -0
- package/dist/repositories/pos.repository.d.ts +20 -0
- package/dist/repositories/pos.repository.js +28 -0
- package/dist/sync/command-builder.d.ts +17 -0
- package/dist/sync/command-builder.js +30 -0
- package/dist/sync/command-queue.service.d.ts +41 -0
- package/dist/sync/command-queue.service.js +253 -0
- package/dist/sync/engine.d.ts +53 -0
- package/dist/sync/engine.js +216 -0
- package/dist/sync/order-number.d.ts +20 -0
- package/dist/sync/order-number.js +58 -0
- package/dist/sync/order-snapshots.service.d.ts +46 -0
- package/dist/sync/order-snapshots.service.js +151 -0
- package/dist/sync/pending-orders.service.d.ts +45 -0
- package/dist/sync/pending-orders.service.js +163 -0
- package/dist/sync/transport-local-node.d.ts +14 -0
- package/dist/sync/transport-local-node.js +67 -0
- package/dist/sync/transport-rest.d.ts +17 -0
- package/dist/sync/transport-rest.js +112 -0
- package/dist/sync/types.d.ts +102 -0
- package/dist/sync/types.js +13 -0
- package/dist/sync/use-sync-reconciliation.d.ts +10 -0
- package/dist/sync/use-sync-reconciliation.js +106 -0
- package/dist/types/api/auth.api.d.ts +61 -0
- package/dist/types/api/auth.api.js +3 -0
- package/dist/types/api/business.api.d.ts +137 -0
- package/dist/types/api/business.api.js +3 -0
- package/dist/types/api/conversation.api.d.ts +46 -0
- package/dist/types/api/conversation.api.js +2 -0
- package/dist/types/api/error.api.d.ts +8 -0
- package/dist/types/api/error.api.js +2 -0
- package/dist/types/api/fleet.api.d.ts +98 -0
- package/dist/types/api/fleet.api.js +3 -0
- package/dist/types/api/index.d.ts +11 -0
- package/dist/types/api/index.js +27 -0
- package/dist/types/api/kiosk.api.d.ts +4 -0
- package/dist/types/api/kiosk.api.js +2 -0
- package/dist/types/api/menu.api.d.ts +90 -0
- package/dist/types/api/menu.api.js +2 -0
- package/dist/types/api/pagination.d.ts +10 -0
- package/dist/types/api/pagination.js +2 -0
- package/dist/types/api/rider.api.d.ts +179 -0
- package/dist/types/api/rider.api.js +2 -0
- package/dist/types/api/seller.api.d.ts +164 -0
- package/dist/types/api/seller.api.js +2 -0
- package/dist/types/api/wallet.api.d.ts +27 -0
- package/dist/types/api/wallet.api.js +2 -0
- package/dist/types/pos.types.d.ts +33 -0
- package/dist/types/pos.types.js +5 -0
- package/dist/utils/constants.d.ts +85 -0
- package/dist/utils/constants.js +72 -0
- package/dist/utils/copyToClipBoard.d.ts +5 -0
- package/dist/utils/copyToClipBoard.js +23 -0
- package/dist/utils/currency.d.ts +9 -0
- package/dist/utils/currency.js +37 -0
- package/dist/utils/error.d.ts +4 -0
- package/dist/utils/error.js +73 -0
- package/dist/utils/formatCurrency.d.ts +4 -0
- package/dist/utils/formatCurrency.js +16 -0
- package/dist/utils/formatDate.d.ts +8 -0
- package/dist/utils/formatDate.js +42 -0
- package/dist/utils/phone.d.ts +10 -0
- package/dist/utils/phone.js +71 -0
- package/dist/utils/responsive.d.ts +13 -0
- package/dist/utils/responsive.js +21 -0
- package/dist/utils/storage.d.ts +15 -0
- package/dist/utils/storage.js +60 -0
- package/dist/utils/sync-logger.d.ts +22 -0
- package/dist/utils/sync-logger.js +79 -0
- package/dist/utils/uuid.d.ts +7 -0
- package/dist/utils/uuid.js +47 -0
- package/dist/utils/validation.d.ts +31 -0
- package/dist/utils/validation.js +170 -0
- package/package.json +49 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reference Data Synchronization Service.
|
|
4
|
+
*
|
|
5
|
+
* Responsible for downloading, versioning, and caching reference data
|
|
6
|
+
* (menus, categories, modifiers, taxes, printers, etc.) locally.
|
|
7
|
+
*
|
|
8
|
+
* This service is separate from the Sync Engine, which handles transactional
|
|
9
|
+
* commands and events.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.referenceSyncService = void 0;
|
|
13
|
+
const connection_1 = require("../db/connection");
|
|
14
|
+
const sync_logger_1 = require("../utils/sync-logger");
|
|
15
|
+
const uuid_1 = require("../utils/uuid");
|
|
16
|
+
const services_1 = require("../adapters/services");
|
|
17
|
+
const image_cache_1 = require("../adapters/image-cache");
|
|
18
|
+
const operations_1 = require("./operations");
|
|
19
|
+
const api_1 = require("./api");
|
|
20
|
+
const DEFAULT_SYNC_KEYS = [
|
|
21
|
+
"menu:items",
|
|
22
|
+
"menu:categories",
|
|
23
|
+
"roles",
|
|
24
|
+
"members",
|
|
25
|
+
"tax-rules",
|
|
26
|
+
"business:profile",
|
|
27
|
+
"operating-hours",
|
|
28
|
+
"subscription:entitlements",
|
|
29
|
+
];
|
|
30
|
+
class ReferenceSyncService {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.isSyncing = false;
|
|
33
|
+
}
|
|
34
|
+
async sync(options) {
|
|
35
|
+
if (this.isSyncing) {
|
|
36
|
+
return {
|
|
37
|
+
synced: [],
|
|
38
|
+
skipped: [],
|
|
39
|
+
failed: [],
|
|
40
|
+
errors: {},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
this.isSyncing = true;
|
|
44
|
+
sync_logger_1.syncLogger.info("ReferenceSync", "Starting reference data sync", {
|
|
45
|
+
businessId: options.businessId,
|
|
46
|
+
keys: options.keys,
|
|
47
|
+
});
|
|
48
|
+
const result = {
|
|
49
|
+
synced: [],
|
|
50
|
+
skipped: [],
|
|
51
|
+
failed: [],
|
|
52
|
+
errors: {},
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
const db = await (0, connection_1.openSyncDatabase)();
|
|
56
|
+
const keys = options.keys ?? DEFAULT_SYNC_KEYS;
|
|
57
|
+
// Attempt version-aware sync. If the backend does not support versioning
|
|
58
|
+
// endpoints, fall back to legacy full-fetch per key.
|
|
59
|
+
let versions = null;
|
|
60
|
+
try {
|
|
61
|
+
versions = await (0, api_1.fetchReferenceVersions)(options.businessId);
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
sync_logger_1.syncLogger.warn("ReferenceSync", "Version endpoint unavailable, using fallback", {
|
|
65
|
+
error: String(error),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const key of keys) {
|
|
69
|
+
try {
|
|
70
|
+
const synced = await this.syncKey(db, key, options, versions);
|
|
71
|
+
if (synced) {
|
|
72
|
+
result.synced.push(key);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
result.skipped.push(key);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
result.failed.push(key);
|
|
80
|
+
result.errors[key] = String(error);
|
|
81
|
+
sync_logger_1.syncLogger.error("ReferenceSync", `Failed to sync ${key}`, {
|
|
82
|
+
error: String(error),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// After a sync pass, download the images referenced by the freshly
|
|
87
|
+
// cached payloads (menu items, business logo/cover) so the device can
|
|
88
|
+
// display them while offline, then reclaim space from orphaned files.
|
|
89
|
+
if (result.synced.length > 0) {
|
|
90
|
+
await this.cacheReferenceImages(db, options.businessId, result.synced);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
this.isSyncing = false;
|
|
95
|
+
}
|
|
96
|
+
sync_logger_1.syncLogger.info("ReferenceSync", "Reference data sync complete", {
|
|
97
|
+
synced: result.synced,
|
|
98
|
+
skipped: result.skipped,
|
|
99
|
+
failed: result.failed,
|
|
100
|
+
});
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
async syncKey(db, key, options, versions) {
|
|
104
|
+
const { businessId } = options;
|
|
105
|
+
const localVersion = await (0, operations_1.getReferenceVersion)(db, businessId, key);
|
|
106
|
+
// A key the backend does not version (e.g. during a gradual rollout) is
|
|
107
|
+
// fetched with a full fetch + local hash so it is still cached offline.
|
|
108
|
+
const serverVersion = versions?.find((v) => v.key === key)?.version;
|
|
109
|
+
if (!options.force && localVersion && !options.incremental) {
|
|
110
|
+
// In non-incremental mode without force, we still re-sync if version
|
|
111
|
+
// info says there is a newer version on the server.
|
|
112
|
+
if (serverVersion && serverVersion === localVersion) {
|
|
113
|
+
sync_logger_1.syncLogger.debug("ReferenceSync", `Up to date: ${key}`, {
|
|
114
|
+
version: localVersion,
|
|
115
|
+
});
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
let payload;
|
|
120
|
+
let version;
|
|
121
|
+
if (versions && serverVersion) {
|
|
122
|
+
if (localVersion && serverVersion !== localVersion) {
|
|
123
|
+
try {
|
|
124
|
+
const changeSet = await (0, api_1.fetchReferenceChanges)(businessId, key, localVersion);
|
|
125
|
+
payload = changeSet.changes;
|
|
126
|
+
version = changeSet.to_version;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Fall back to full fetch if changes endpoint fails.
|
|
130
|
+
const data = await (0, api_1.fetchReferenceData)(businessId, [key]);
|
|
131
|
+
payload = data[key];
|
|
132
|
+
version = serverVersion;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
const data = await (0, api_1.fetchReferenceData)(businessId, [key]);
|
|
137
|
+
payload = data[key];
|
|
138
|
+
version = serverVersion;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
// Backend does not support versioning for this key (or at all):
|
|
143
|
+
// full fetch + local hash so the data is still cached offline.
|
|
144
|
+
const fallback = await this.fetchFallback(key, businessId);
|
|
145
|
+
payload = fallback.payload;
|
|
146
|
+
version = fallback.version;
|
|
147
|
+
}
|
|
148
|
+
await (0, operations_1.setReferenceData)(db, businessId, {
|
|
149
|
+
key,
|
|
150
|
+
// Defensive: never write a NULL version. If the backend ever omits the
|
|
151
|
+
// version, generate one so the row is still usable; the next version
|
|
152
|
+
// comparison will simply see a mismatch and re-sync.
|
|
153
|
+
version: version ?? (0, uuid_1.generateUUID)(),
|
|
154
|
+
payload,
|
|
155
|
+
downloadedAt: new Date().toISOString(),
|
|
156
|
+
});
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
async fetchFallback(key, businessId) {
|
|
160
|
+
switch (key) {
|
|
161
|
+
case "menu:items": {
|
|
162
|
+
const response = await services_1.menuService.fetchBusinessMenuItems(businessId, 1);
|
|
163
|
+
return {
|
|
164
|
+
payload: response,
|
|
165
|
+
version: this.hashPayload(response),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
case "menu:categories": {
|
|
169
|
+
const response = await services_1.menuService.fetchBusinessCategories(businessId);
|
|
170
|
+
return {
|
|
171
|
+
payload: response,
|
|
172
|
+
version: this.hashPayload(response),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
case "roles": {
|
|
176
|
+
const response = await services_1.rolesService.fetchBusinessRoles();
|
|
177
|
+
return {
|
|
178
|
+
payload: response,
|
|
179
|
+
version: this.hashPayload(response),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
case "members": {
|
|
183
|
+
const response = await services_1.businessService.fetchBusinessMembers(businessId);
|
|
184
|
+
return {
|
|
185
|
+
payload: response,
|
|
186
|
+
version: this.hashPayload(response),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
case "tax-rules": {
|
|
190
|
+
const response = await services_1.businessService.fetchBusinessTaxRules(businessId);
|
|
191
|
+
return {
|
|
192
|
+
payload: response,
|
|
193
|
+
version: this.hashPayload(response),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
case "business:profile": {
|
|
197
|
+
const response = await services_1.businessService.fetchBusinessById(businessId);
|
|
198
|
+
return {
|
|
199
|
+
payload: response,
|
|
200
|
+
version: this.hashPayload(response),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
case "operating-hours": {
|
|
204
|
+
const response = await services_1.businessService.fetchOperatingHours(businessId);
|
|
205
|
+
return {
|
|
206
|
+
payload: response,
|
|
207
|
+
version: this.hashPayload(response),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
default:
|
|
211
|
+
throw new Error(`No fallback fetch available for ${key}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Download every image referenced by the given cached reference keys into
|
|
216
|
+
* durable local storage, then prune orphaned files. Covers menu item photos
|
|
217
|
+
* and the business logo/cover. Idempotent — already-cached images are
|
|
218
|
+
* skipped. Errors never propagate: a failed download must not fail the sync.
|
|
219
|
+
*/
|
|
220
|
+
async cacheReferenceImages(db, businessId, keys) {
|
|
221
|
+
try {
|
|
222
|
+
const urls = [];
|
|
223
|
+
if (keys.includes("menu:items")) {
|
|
224
|
+
const cached = await (0, operations_1.getReferenceData)(db, businessId, "menu:items");
|
|
225
|
+
const items = cached?.payload?.data ?? [];
|
|
226
|
+
urls.push(...items.map((item) => item.image_url));
|
|
227
|
+
}
|
|
228
|
+
if (keys.includes("business:profile")) {
|
|
229
|
+
const cached = await (0, operations_1.getReferenceData)(db, businessId, "business:profile");
|
|
230
|
+
const business = cached?.payload?.data;
|
|
231
|
+
if (business) {
|
|
232
|
+
urls.push(business.logo_url, business.cover_image_url);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (urls.length === 0)
|
|
236
|
+
return;
|
|
237
|
+
const cachedImages = await (0, image_cache_1.cacheImageBatch)(urls, {
|
|
238
|
+
skipIfCached: true,
|
|
239
|
+
});
|
|
240
|
+
sync_logger_1.syncLogger.debug("ReferenceSync", "Reference images cached", {
|
|
241
|
+
businessId,
|
|
242
|
+
keys,
|
|
243
|
+
count: cachedImages.length,
|
|
244
|
+
});
|
|
245
|
+
await (0, image_cache_1.pruneImageCache)(urls);
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
sync_logger_1.syncLogger.warn("ReferenceSync", "Failed to cache reference images", {
|
|
249
|
+
error: String(error),
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
hashPayload(payload) {
|
|
254
|
+
// Simple deterministic hash for fallback versioning.
|
|
255
|
+
// Replace with a proper hash (e.g. SHA-256) when backend versioning is ready.
|
|
256
|
+
const json = JSON.stringify(payload);
|
|
257
|
+
let hash = 0;
|
|
258
|
+
for (let i = 0; i < json.length; i++) {
|
|
259
|
+
const char = json.charCodeAt(i);
|
|
260
|
+
hash = (hash << 5) - hash + char;
|
|
261
|
+
hash |= 0;
|
|
262
|
+
}
|
|
263
|
+
return `local-${Math.abs(hash).toString(16)}`;
|
|
264
|
+
}
|
|
265
|
+
async getCached(businessId, key) {
|
|
266
|
+
const db = await (0, connection_1.openSyncDatabase)();
|
|
267
|
+
return (0, operations_1.getReferenceData)(db, businessId, key);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exports.referenceSyncService = new ReferenceSyncService();
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for reference data synchronization.
|
|
3
|
+
*
|
|
4
|
+
* Reference data is data that changes infrequently and is required for
|
|
5
|
+
* day-to-day operation: menus, categories, products, modifiers, taxes,
|
|
6
|
+
* printers, kitchen stations, etc.
|
|
7
|
+
*
|
|
8
|
+
* It is synchronized separately from transactional data through the
|
|
9
|
+
* ReferenceSyncService.
|
|
10
|
+
*/
|
|
11
|
+
export type ReferenceDataKey = "menu:items" | "menu:categories" | "roles" | "members" | "tax-rules" | "business:profile" | "operating-hours" | "subscription:entitlements";
|
|
12
|
+
export interface ReferenceDataRecord<T = unknown> {
|
|
13
|
+
key: ReferenceDataKey;
|
|
14
|
+
version: string;
|
|
15
|
+
payload: T;
|
|
16
|
+
downloadedAt: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ReferenceVersionInfo {
|
|
19
|
+
key: ReferenceDataKey;
|
|
20
|
+
version: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ReferenceSyncOptions {
|
|
24
|
+
businessId: string;
|
|
25
|
+
keys?: ReferenceDataKey[];
|
|
26
|
+
/** If true, ignore local cache and fetch fresh data. */
|
|
27
|
+
force?: boolean;
|
|
28
|
+
/** If true, only sync keys that are missing or stale. */
|
|
29
|
+
incremental?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface ReferenceSyncResult {
|
|
32
|
+
synced: ReferenceDataKey[];
|
|
33
|
+
skipped: ReferenceDataKey[];
|
|
34
|
+
failed: ReferenceDataKey[];
|
|
35
|
+
errors: Record<ReferenceDataKey, string>;
|
|
36
|
+
}
|
|
37
|
+
export interface ReferenceChangeSet<T = unknown> {
|
|
38
|
+
key: ReferenceDataKey;
|
|
39
|
+
from_version: string;
|
|
40
|
+
to_version: string;
|
|
41
|
+
changes: T;
|
|
42
|
+
}
|
|
43
|
+
export interface ReferenceSyncStatus {
|
|
44
|
+
lastSyncAt: string | null;
|
|
45
|
+
isSyncing: boolean;
|
|
46
|
+
error: string | null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Types for reference data synchronization.
|
|
4
|
+
*
|
|
5
|
+
* Reference data is data that changes infrequently and is required for
|
|
6
|
+
* day-to-day operation: menus, categories, products, modifiers, taxes,
|
|
7
|
+
* printers, kitchen stations, etc.
|
|
8
|
+
*
|
|
9
|
+
* It is synchronized separately from transactional data through the
|
|
10
|
+
* ReferenceSyncService.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Order Repository.
|
|
3
|
+
*
|
|
4
|
+
* The interface between application hooks and the Sync Engine.
|
|
5
|
+
* Converts domain inputs into commands and returns the resulting events.
|
|
6
|
+
*
|
|
7
|
+
* In Phase 1, commands execute immediately through the REST transport.
|
|
8
|
+
* Future phases will queue commands when offline.
|
|
9
|
+
*/
|
|
10
|
+
import type { POSOrderCreateRequest } from "../types/pos.types";
|
|
11
|
+
import type { SellerOrder } from "../types/api/seller.api";
|
|
12
|
+
export interface CreateOrderInput {
|
|
13
|
+
businessId: string;
|
|
14
|
+
data: POSOrderCreateRequest;
|
|
15
|
+
/** Client-generated entity ID for offline-created orders. */
|
|
16
|
+
entityId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AssignRiderInput {
|
|
19
|
+
orderId: string;
|
|
20
|
+
riderId: string;
|
|
21
|
+
}
|
|
22
|
+
export interface OrderActionInput {
|
|
23
|
+
orderId: string;
|
|
24
|
+
businessId: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const orderRepository: {
|
|
27
|
+
createOrder(input: CreateOrderInput): Promise<SellerOrder>;
|
|
28
|
+
acceptOrder(input: OrderActionInput): Promise<SellerOrder>;
|
|
29
|
+
rejectOrder(input: OrderActionInput): Promise<SellerOrder>;
|
|
30
|
+
startPreparingOrder(input: OrderActionInput): Promise<SellerOrder>;
|
|
31
|
+
markOrderReady(input: OrderActionInput): Promise<SellerOrder>;
|
|
32
|
+
completePickup(input: OrderActionInput): Promise<SellerOrder>;
|
|
33
|
+
cancelOrder(input: OrderActionInput): Promise<SellerOrder>;
|
|
34
|
+
confirmHandover(input: {
|
|
35
|
+
deliveryId: string;
|
|
36
|
+
businessId: string;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
id: string;
|
|
39
|
+
status: string;
|
|
40
|
+
}>;
|
|
41
|
+
assignRider(input: AssignRiderInput & {
|
|
42
|
+
businessId: string;
|
|
43
|
+
}): Promise<unknown>;
|
|
44
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Order Repository.
|
|
4
|
+
*
|
|
5
|
+
* The interface between application hooks and the Sync Engine.
|
|
6
|
+
* Converts domain inputs into commands and returns the resulting events.
|
|
7
|
+
*
|
|
8
|
+
* In Phase 1, commands execute immediately through the REST transport.
|
|
9
|
+
* Future phases will queue commands when offline.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.orderRepository = void 0;
|
|
13
|
+
const engine_1 = require("../sync/engine");
|
|
14
|
+
const command_builder_1 = require("../sync/command-builder");
|
|
15
|
+
async function executeCommand(command) {
|
|
16
|
+
console.debug("[OrderRepository] Dispatching command", JSON.stringify({
|
|
17
|
+
command_id: command.command_id,
|
|
18
|
+
command_type: command.command_type,
|
|
19
|
+
business_id: command.business_id,
|
|
20
|
+
entity_id: command.entity_id,
|
|
21
|
+
payload_keys: Object.keys(command.payload ?? {}),
|
|
22
|
+
timestamp: new Date().toISOString(),
|
|
23
|
+
}));
|
|
24
|
+
const events = await engine_1.syncEngine.execute(command);
|
|
25
|
+
const event = events[0];
|
|
26
|
+
console.debug("[OrderRepository] Command result", JSON.stringify({
|
|
27
|
+
command_id: command.command_id,
|
|
28
|
+
command_type: command.command_type,
|
|
29
|
+
event_count: events.length,
|
|
30
|
+
event_type: event?.event_type,
|
|
31
|
+
order_number: event?.payload?.order_number ?? null,
|
|
32
|
+
status: event?.payload?.status ?? null,
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
}));
|
|
35
|
+
if (!event) {
|
|
36
|
+
throw new Error("OrderRepository: command returned no events");
|
|
37
|
+
}
|
|
38
|
+
return event.payload;
|
|
39
|
+
}
|
|
40
|
+
exports.orderRepository = {
|
|
41
|
+
async createOrder(input) {
|
|
42
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
43
|
+
commandType: "order:create",
|
|
44
|
+
businessId: input.businessId,
|
|
45
|
+
entityId: input.entityId,
|
|
46
|
+
payload: input.data,
|
|
47
|
+
});
|
|
48
|
+
return executeCommand(command);
|
|
49
|
+
},
|
|
50
|
+
async acceptOrder(input) {
|
|
51
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
52
|
+
commandType: "order:accept",
|
|
53
|
+
businessId: input.businessId,
|
|
54
|
+
entityId: input.orderId,
|
|
55
|
+
payload: {},
|
|
56
|
+
});
|
|
57
|
+
return executeCommand(command);
|
|
58
|
+
},
|
|
59
|
+
async rejectOrder(input) {
|
|
60
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
61
|
+
commandType: "order:reject",
|
|
62
|
+
businessId: input.businessId,
|
|
63
|
+
entityId: input.orderId,
|
|
64
|
+
payload: {},
|
|
65
|
+
});
|
|
66
|
+
return executeCommand(command);
|
|
67
|
+
},
|
|
68
|
+
async startPreparingOrder(input) {
|
|
69
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
70
|
+
commandType: "order:start_preparing",
|
|
71
|
+
businessId: input.businessId,
|
|
72
|
+
entityId: input.orderId,
|
|
73
|
+
payload: {},
|
|
74
|
+
});
|
|
75
|
+
return executeCommand(command);
|
|
76
|
+
},
|
|
77
|
+
async markOrderReady(input) {
|
|
78
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
79
|
+
commandType: "order:mark_ready",
|
|
80
|
+
businessId: input.businessId,
|
|
81
|
+
entityId: input.orderId,
|
|
82
|
+
payload: {},
|
|
83
|
+
});
|
|
84
|
+
return executeCommand(command);
|
|
85
|
+
},
|
|
86
|
+
async completePickup(input) {
|
|
87
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
88
|
+
commandType: "order:complete_pickup",
|
|
89
|
+
businessId: input.businessId,
|
|
90
|
+
entityId: input.orderId,
|
|
91
|
+
payload: {},
|
|
92
|
+
});
|
|
93
|
+
return executeCommand(command);
|
|
94
|
+
},
|
|
95
|
+
async cancelOrder(input) {
|
|
96
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
97
|
+
commandType: "order:cancel",
|
|
98
|
+
businessId: input.businessId,
|
|
99
|
+
entityId: input.orderId,
|
|
100
|
+
payload: {},
|
|
101
|
+
});
|
|
102
|
+
return executeCommand(command);
|
|
103
|
+
},
|
|
104
|
+
async confirmHandover(input) {
|
|
105
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
106
|
+
commandType: "delivery:confirm_handover",
|
|
107
|
+
businessId: input.businessId,
|
|
108
|
+
entityId: input.deliveryId,
|
|
109
|
+
payload: {},
|
|
110
|
+
});
|
|
111
|
+
const events = await engine_1.syncEngine.execute(command);
|
|
112
|
+
const event = events[0];
|
|
113
|
+
if (!event) {
|
|
114
|
+
throw new Error("OrderRepository: confirmHandover returned no events");
|
|
115
|
+
}
|
|
116
|
+
return event.payload;
|
|
117
|
+
},
|
|
118
|
+
async assignRider(input) {
|
|
119
|
+
const command = (0, command_builder_1.buildCommand)({
|
|
120
|
+
commandType: "delivery:assign",
|
|
121
|
+
businessId: input.businessId,
|
|
122
|
+
entityId: input.orderId,
|
|
123
|
+
payload: { rider_id: input.riderId },
|
|
124
|
+
});
|
|
125
|
+
const events = await engine_1.syncEngine.execute(command);
|
|
126
|
+
const event = events[0];
|
|
127
|
+
if (!event) {
|
|
128
|
+
throw new Error("OrderRepository: assignRider returned no events");
|
|
129
|
+
}
|
|
130
|
+
return event.payload;
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POS Repository.
|
|
3
|
+
*
|
|
4
|
+
* Domain-specific operations for the POS terminal. Builds on OrderRepository
|
|
5
|
+
* for commands and keeps read/query operations behind a repository interface.
|
|
6
|
+
*/
|
|
7
|
+
import type { POSOrderCreateRequest, POSOrderPreviewRequest, POSOrderPreviewResponse } from "../types/pos.types";
|
|
8
|
+
import type { SellerOrder } from "../types/api/seller.api";
|
|
9
|
+
import type { SellerOrdersResponse } from "../types/api/seller.api";
|
|
10
|
+
export interface POSOrderPreviewInput {
|
|
11
|
+
businessId: string;
|
|
12
|
+
items: POSOrderPreviewRequest["items"];
|
|
13
|
+
orderType: POSOrderPreviewRequest["order_type"];
|
|
14
|
+
customerName?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const posRepository: {
|
|
17
|
+
createOrder(businessId: string, data: POSOrderCreateRequest, entityId?: string): Promise<SellerOrder>;
|
|
18
|
+
fetchPreview(input: POSOrderPreviewInput): Promise<POSOrderPreviewResponse>;
|
|
19
|
+
fetchOrders(businessId: string, params?: Record<string, string>): Promise<SellerOrdersResponse>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* POS Repository.
|
|
4
|
+
*
|
|
5
|
+
* Domain-specific operations for the POS terminal. Builds on OrderRepository
|
|
6
|
+
* for commands and keeps read/query operations behind a repository interface.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.posRepository = void 0;
|
|
10
|
+
const services_1 = require("../adapters/services");
|
|
11
|
+
const order_repository_1 = require("./order.repository");
|
|
12
|
+
exports.posRepository = {
|
|
13
|
+
async createOrder(businessId, data, entityId) {
|
|
14
|
+
return order_repository_1.orderRepository.createOrder({ businessId, data, entityId });
|
|
15
|
+
},
|
|
16
|
+
async fetchPreview(input) {
|
|
17
|
+
const res = await services_1.posService.fetchPOSOrderPreview(input.businessId, {
|
|
18
|
+
items: input.items,
|
|
19
|
+
order_type: input.orderType,
|
|
20
|
+
customer_name: input.customerName,
|
|
21
|
+
});
|
|
22
|
+
return res;
|
|
23
|
+
},
|
|
24
|
+
async fetchOrders(businessId, params) {
|
|
25
|
+
const res = await services_1.posService.fetchBusinessOrders(businessId, params);
|
|
26
|
+
return res;
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command builder helpers.
|
|
3
|
+
*
|
|
4
|
+
* Centralizes creation of Command objects with consistent metadata
|
|
5
|
+
* (command_id, user_id, device_id, timestamps).
|
|
6
|
+
*/
|
|
7
|
+
import type { Command } from "./types";
|
|
8
|
+
import { getAppInstanceId } from "../adapters/devices";
|
|
9
|
+
export interface CommandBuilderOptions<T> {
|
|
10
|
+
commandType: string;
|
|
11
|
+
commandVersion?: string;
|
|
12
|
+
businessId: string;
|
|
13
|
+
entityId?: string;
|
|
14
|
+
payload: T;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildCommand<T>(options: CommandBuilderOptions<T>): Command<T>;
|
|
17
|
+
export { getAppInstanceId };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Command builder helpers.
|
|
4
|
+
*
|
|
5
|
+
* Centralizes creation of Command objects with consistent metadata
|
|
6
|
+
* (command_id, user_id, device_id, timestamps).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getAppInstanceId = void 0;
|
|
10
|
+
exports.buildCommand = buildCommand;
|
|
11
|
+
const auth_1 = require("../adapters/auth");
|
|
12
|
+
const devices_1 = require("../adapters/devices");
|
|
13
|
+
Object.defineProperty(exports, "getAppInstanceId", { enumerable: true, get: function () { return devices_1.getAppInstanceId; } });
|
|
14
|
+
const uuid_1 = require("../utils/uuid");
|
|
15
|
+
function buildCommand(options) {
|
|
16
|
+
const user = auth_1.useAuthStore.getState().user;
|
|
17
|
+
const userId = user?.id ?? "anonymous";
|
|
18
|
+
const deviceId = (0, devices_1.getAppInstanceId)();
|
|
19
|
+
return {
|
|
20
|
+
command_id: (0, uuid_1.generateCommandId)(),
|
|
21
|
+
command_type: options.commandType,
|
|
22
|
+
command_version: options.commandVersion ?? "1.0",
|
|
23
|
+
business_id: options.businessId,
|
|
24
|
+
entity_id: options.entityId,
|
|
25
|
+
user_id: userId,
|
|
26
|
+
device_id: deviceId,
|
|
27
|
+
timestamp: new Date().toISOString(),
|
|
28
|
+
payload: options.payload,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Queue Service.
|
|
3
|
+
*
|
|
4
|
+
* Durable SQLite outbox for commands that cannot be executed immediately.
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* - Persist commands when the cloud and Local Node are both unavailable.
|
|
7
|
+
* - Generate local order numbers for offline `order:create` commands.
|
|
8
|
+
* - Replay queued commands when a transport becomes available.
|
|
9
|
+
* - Provide optimistic domain events so the UI can render pending orders.
|
|
10
|
+
*/
|
|
11
|
+
import type { Command, DomainEvent, PersistedCommand } from "./types";
|
|
12
|
+
import type { SyncEngine } from "./engine";
|
|
13
|
+
declare class CommandQueueService {
|
|
14
|
+
private replaying;
|
|
15
|
+
private syncEngine;
|
|
16
|
+
setSyncEngine(engine: SyncEngine): void;
|
|
17
|
+
/**
|
|
18
|
+
* Enqueue a command because no transport is currently available.
|
|
19
|
+
* Returns an optimistic domain event so the caller can update the UI.
|
|
20
|
+
*/
|
|
21
|
+
enqueue(command: Command): Promise<DomainEvent>;
|
|
22
|
+
/**
|
|
23
|
+
* Try to execute a single pending command through the current transport.
|
|
24
|
+
*/
|
|
25
|
+
processCommand(command: PersistedCommand): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Replay all pending commands in order. Stops on the first failure to keep
|
|
28
|
+
* ordering consistent; the next replay will retry from the failed command.
|
|
29
|
+
*/
|
|
30
|
+
replay(): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Cancel a queued command that has not yet been transmitted.
|
|
33
|
+
*/
|
|
34
|
+
cancel(commandId: string): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Return the number of commands currently in the outbox.
|
|
37
|
+
*/
|
|
38
|
+
getPendingCount(): Promise<number>;
|
|
39
|
+
}
|
|
40
|
+
export declare const commandQueueService: CommandQueueService;
|
|
41
|
+
export {};
|