@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.
Files changed (120) hide show
  1. package/README.md +44 -0
  2. package/dist/adapters/auth.d.ts +13 -0
  3. package/dist/adapters/auth.js +14 -0
  4. package/dist/adapters/business.d.ts +13 -0
  5. package/dist/adapters/business.js +14 -0
  6. package/dist/adapters/connectivity.d.ts +17 -0
  7. package/dist/adapters/connectivity.js +38 -0
  8. package/dist/adapters/devices.d.ts +5 -0
  9. package/dist/adapters/devices.js +14 -0
  10. package/dist/adapters/http.d.ts +30 -0
  11. package/dist/adapters/http.js +37 -0
  12. package/dist/adapters/image-cache.d.ts +8 -0
  13. package/dist/adapters/image-cache.js +12 -0
  14. package/dist/adapters/kv-types.d.ts +5 -0
  15. package/dist/adapters/kv-types.js +2 -0
  16. package/dist/adapters/kv.d.ts +20 -0
  17. package/dist/adapters/kv.js +81 -0
  18. package/dist/adapters/local-node.d.ts +34 -0
  19. package/dist/adapters/local-node.js +40 -0
  20. package/dist/adapters/pending-orders.d.ts +17 -0
  21. package/dist/adapters/pending-orders.js +31 -0
  22. package/dist/adapters/query-keys.d.ts +17 -0
  23. package/dist/adapters/query-keys.js +20 -0
  24. package/dist/adapters/services.d.ts +62 -0
  25. package/dist/adapters/services.js +95 -0
  26. package/dist/adapters/sqlite.d.ts +20 -0
  27. package/dist/adapters/sqlite.js +23 -0
  28. package/dist/db/connection.d.ts +8 -0
  29. package/dist/db/connection.js +87 -0
  30. package/dist/db/migrations.d.ts +9 -0
  31. package/dist/db/migrations.js +106 -0
  32. package/dist/db/operations.d.ts +45 -0
  33. package/dist/db/operations.js +192 -0
  34. package/dist/index.d.ts +50 -0
  35. package/dist/index.js +118 -0
  36. package/dist/reference/api.d.ts +10 -0
  37. package/dist/reference/api.js +24 -0
  38. package/dist/reference/events.d.ts +54 -0
  39. package/dist/reference/events.js +144 -0
  40. package/dist/reference/operations.d.ts +11 -0
  41. package/dist/reference/operations.js +46 -0
  42. package/dist/reference/service.d.ts +27 -0
  43. package/dist/reference/service.js +270 -0
  44. package/dist/reference/types.d.ts +47 -0
  45. package/dist/reference/types.js +12 -0
  46. package/dist/repositories/order.repository.d.ts +44 -0
  47. package/dist/repositories/order.repository.js +132 -0
  48. package/dist/repositories/pos.repository.d.ts +20 -0
  49. package/dist/repositories/pos.repository.js +28 -0
  50. package/dist/sync/command-builder.d.ts +17 -0
  51. package/dist/sync/command-builder.js +30 -0
  52. package/dist/sync/command-queue.service.d.ts +41 -0
  53. package/dist/sync/command-queue.service.js +253 -0
  54. package/dist/sync/engine.d.ts +53 -0
  55. package/dist/sync/engine.js +216 -0
  56. package/dist/sync/order-number.d.ts +20 -0
  57. package/dist/sync/order-number.js +58 -0
  58. package/dist/sync/order-snapshots.service.d.ts +46 -0
  59. package/dist/sync/order-snapshots.service.js +151 -0
  60. package/dist/sync/pending-orders.service.d.ts +45 -0
  61. package/dist/sync/pending-orders.service.js +163 -0
  62. package/dist/sync/transport-local-node.d.ts +14 -0
  63. package/dist/sync/transport-local-node.js +67 -0
  64. package/dist/sync/transport-rest.d.ts +17 -0
  65. package/dist/sync/transport-rest.js +112 -0
  66. package/dist/sync/types.d.ts +102 -0
  67. package/dist/sync/types.js +13 -0
  68. package/dist/sync/use-sync-reconciliation.d.ts +10 -0
  69. package/dist/sync/use-sync-reconciliation.js +106 -0
  70. package/dist/types/api/auth.api.d.ts +61 -0
  71. package/dist/types/api/auth.api.js +3 -0
  72. package/dist/types/api/business.api.d.ts +137 -0
  73. package/dist/types/api/business.api.js +3 -0
  74. package/dist/types/api/conversation.api.d.ts +46 -0
  75. package/dist/types/api/conversation.api.js +2 -0
  76. package/dist/types/api/error.api.d.ts +8 -0
  77. package/dist/types/api/error.api.js +2 -0
  78. package/dist/types/api/fleet.api.d.ts +98 -0
  79. package/dist/types/api/fleet.api.js +3 -0
  80. package/dist/types/api/index.d.ts +11 -0
  81. package/dist/types/api/index.js +27 -0
  82. package/dist/types/api/kiosk.api.d.ts +4 -0
  83. package/dist/types/api/kiosk.api.js +2 -0
  84. package/dist/types/api/menu.api.d.ts +90 -0
  85. package/dist/types/api/menu.api.js +2 -0
  86. package/dist/types/api/pagination.d.ts +10 -0
  87. package/dist/types/api/pagination.js +2 -0
  88. package/dist/types/api/rider.api.d.ts +179 -0
  89. package/dist/types/api/rider.api.js +2 -0
  90. package/dist/types/api/seller.api.d.ts +164 -0
  91. package/dist/types/api/seller.api.js +2 -0
  92. package/dist/types/api/wallet.api.d.ts +27 -0
  93. package/dist/types/api/wallet.api.js +2 -0
  94. package/dist/types/pos.types.d.ts +33 -0
  95. package/dist/types/pos.types.js +5 -0
  96. package/dist/utils/constants.d.ts +85 -0
  97. package/dist/utils/constants.js +72 -0
  98. package/dist/utils/copyToClipBoard.d.ts +5 -0
  99. package/dist/utils/copyToClipBoard.js +23 -0
  100. package/dist/utils/currency.d.ts +9 -0
  101. package/dist/utils/currency.js +37 -0
  102. package/dist/utils/error.d.ts +4 -0
  103. package/dist/utils/error.js +73 -0
  104. package/dist/utils/formatCurrency.d.ts +4 -0
  105. package/dist/utils/formatCurrency.js +16 -0
  106. package/dist/utils/formatDate.d.ts +8 -0
  107. package/dist/utils/formatDate.js +42 -0
  108. package/dist/utils/phone.d.ts +10 -0
  109. package/dist/utils/phone.js +71 -0
  110. package/dist/utils/responsive.d.ts +13 -0
  111. package/dist/utils/responsive.js +21 -0
  112. package/dist/utils/storage.d.ts +15 -0
  113. package/dist/utils/storage.js +60 -0
  114. package/dist/utils/sync-logger.d.ts +22 -0
  115. package/dist/utils/sync-logger.js +79 -0
  116. package/dist/utils/uuid.d.ts +7 -0
  117. package/dist/utils/uuid.js +47 -0
  118. package/dist/utils/validation.d.ts +31 -0
  119. package/dist/utils/validation.js +170 -0
  120. package/package.json +49 -0
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * Service stubs for pos/seller/kiosk/menu/business/roles.
4
+ * Host apps can replace via setServiceImpls; core defaults to no-op that throws helpful error.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.useLocationStore = exports.rolesService = exports.businessService = exports.menuService = exports.kioskService = exports.sellerService = exports.posService = void 0;
8
+ exports.getRegionConfig = getRegionConfig;
9
+ exports.formatCurrencyByRegion = formatCurrencyByRegion;
10
+ function notImpl(name) {
11
+ throw new Error(`Service ${name} not injected. Wire via adapters/services.ts or setHttpClient.`);
12
+ }
13
+ // pos.service
14
+ exports.posService = {
15
+ createPOSOrder: async (_businessId, _payload) => {
16
+ notImpl("posService.createPOSOrder");
17
+ },
18
+ fetchPOSOrderPreview: async (_businessId, _payload) => {
19
+ notImpl("posService.fetchPOSOrderPreview");
20
+ },
21
+ fetchBusinessOrders: async (_businessId, _params) => {
22
+ notImpl("posService.fetchBusinessOrders");
23
+ },
24
+ };
25
+ // seller.service
26
+ exports.sellerService = {
27
+ acceptOrder: async (_orderId) => {
28
+ notImpl("sellerService.acceptOrder");
29
+ },
30
+ rejectOrder: async (_orderId) => {
31
+ notImpl("sellerService.rejectOrder");
32
+ },
33
+ startPreparingOrder: async (_orderId) => {
34
+ notImpl("sellerService.startPreparingOrder");
35
+ },
36
+ markOrderReady: async (_orderId) => {
37
+ notImpl("sellerService.markOrderReady");
38
+ },
39
+ completeOrderPickup: async (_orderId) => {
40
+ notImpl("sellerService.completeOrderPickup");
41
+ },
42
+ cancelOrder: async (_orderId) => {
43
+ notImpl("sellerService.cancelOrder");
44
+ },
45
+ confirmHandover: async (_deliveryId) => {
46
+ notImpl("sellerService.confirmHandover");
47
+ },
48
+ };
49
+ // kiosk.service
50
+ exports.kioskService = {
51
+ assignKioskOrderToRider: async (_orderId, _riderId) => {
52
+ notImpl("kioskService.assignKioskOrderToRider");
53
+ },
54
+ };
55
+ // menu.service etc. for reference fallback
56
+ exports.menuService = {
57
+ fetchBusinessMenuItems: async (_businessId, _page) => {
58
+ notImpl("menuService.fetchBusinessMenuItems");
59
+ },
60
+ fetchBusinessCategories: async (_businessId) => {
61
+ notImpl("menuService.fetchBusinessCategories");
62
+ },
63
+ };
64
+ exports.businessService = {
65
+ fetchBusinessMembers: async (_businessId) => {
66
+ notImpl("businessService.fetchBusinessMembers");
67
+ },
68
+ fetchBusinessTaxRules: async (_businessId) => {
69
+ notImpl("businessService.fetchBusinessTaxRules");
70
+ },
71
+ fetchBusinessById: async (_businessId) => {
72
+ notImpl("businessService.fetchBusinessById");
73
+ },
74
+ fetchOperatingHours: async (_businessId) => {
75
+ notImpl("businessService.fetchOperatingHours");
76
+ },
77
+ };
78
+ exports.rolesService = {
79
+ fetchBusinessRoles: async () => {
80
+ notImpl("rolesService.fetchBusinessRoles");
81
+ },
82
+ };
83
+ // region pricing + location stubs for currency utils
84
+ function getRegionConfig(_countryCode) {
85
+ return { currencyCode: "GHS" };
86
+ }
87
+ function formatCurrencyByRegion(amountMinor, _config) {
88
+ return `GH₵${(amountMinor / 100).toFixed(2)}`;
89
+ }
90
+ exports.useLocationStore = {
91
+ getState: () => ({ currentLocation: null }),
92
+ // hook form: selector
93
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
94
+ subscribe: (_fn) => () => { },
95
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * SQLite driver adapter — decouples core from expo-sqlite.
3
+ * Host apps inject a concrete driver via setSqliteDriver().
4
+ */
5
+ export interface SQLiteRunResult {
6
+ lastInsertRowId: number;
7
+ changes: number;
8
+ }
9
+ export interface SqliteDriver {
10
+ execAsync(sql: string, ...args: unknown[]): Promise<void>;
11
+ runAsync(sql: string, ...args: unknown[]): Promise<SQLiteRunResult>;
12
+ getFirstAsync<T>(sql: string, ...args: unknown[]): Promise<T | null>;
13
+ getAllAsync<T>(sql: string, ...args: unknown[]): Promise<T[]>;
14
+ withTransactionAsync?(fn: () => Promise<void>): Promise<void>;
15
+ closeAsync?(): Promise<void>;
16
+ }
17
+ export type SQLiteDatabase = SqliteDriver;
18
+ export declare function setSqliteDriver(d: SqliteDriver): void;
19
+ export declare function getSqliteDriver(): SqliteDriver;
20
+ export declare function hasSqliteDriver(): boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite driver adapter — decouples core from expo-sqlite.
4
+ * Host apps inject a concrete driver via setSqliteDriver().
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.setSqliteDriver = setSqliteDriver;
8
+ exports.getSqliteDriver = getSqliteDriver;
9
+ exports.hasSqliteDriver = hasSqliteDriver;
10
+ let driver = null;
11
+ function setSqliteDriver(d) {
12
+ driver = d;
13
+ }
14
+ function getSqliteDriver() {
15
+ if (!driver) {
16
+ throw new Error("SQLite driver not injected. Call setSqliteDriver(driver) before openSyncDatabase(). " +
17
+ "Provide an object implementing SqliteDriver (see src/adapters/sqlite.ts).");
18
+ }
19
+ return driver;
20
+ }
21
+ function hasSqliteDriver() {
22
+ return driver !== null;
23
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * SQLite connection for the offline-first command outbox and event journal.
3
+ * Delegates to injected SqliteDriver (see src/adapters/sqlite.ts).
4
+ */
5
+ import { type SQLiteDatabase } from "../adapters/sqlite";
6
+ export declare function openSyncDatabase(): Promise<SQLiteDatabase>;
7
+ export declare function getSyncDatabase(): SQLiteDatabase;
8
+ export declare function closeSyncDatabase(): Promise<void>;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite connection for the offline-first command outbox and event journal.
4
+ * Delegates to injected SqliteDriver (see src/adapters/sqlite.ts).
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.openSyncDatabase = openSyncDatabase;
8
+ exports.getSyncDatabase = getSyncDatabase;
9
+ exports.closeSyncDatabase = closeSyncDatabase;
10
+ const sync_logger_1 = require("../utils/sync-logger");
11
+ const migrations_1 = require("./migrations");
12
+ const sqlite_1 = require("../adapters/sqlite");
13
+ const DB_NAME = "teincfood_sync.db";
14
+ let dbInstance = null;
15
+ let initPromise = null;
16
+ async function openSyncDatabase() {
17
+ if (dbInstance)
18
+ return dbInstance;
19
+ if (initPromise)
20
+ return initPromise;
21
+ if (!(0, sqlite_1.hasSqliteDriver)()) {
22
+ throw new Error("SQLite driver not injected. Call setSqliteDriver(driver) before openSyncDatabase().");
23
+ }
24
+ const driver = (0, sqlite_1.getSqliteDriver)();
25
+ initPromise = (async () => {
26
+ await runMigrations(driver);
27
+ dbInstance = driver;
28
+ sync_logger_1.syncLogger.info("SQLite", "Database opened and migrated", {
29
+ name: DB_NAME,
30
+ });
31
+ return driver;
32
+ })();
33
+ initPromise = initPromise.catch((error) => {
34
+ initPromise = null;
35
+ sync_logger_1.syncLogger.error("SQLite", "Failed to open sync database", {
36
+ error: String(error),
37
+ });
38
+ throw error;
39
+ });
40
+ return initPromise;
41
+ }
42
+ function getSyncDatabase() {
43
+ if (!dbInstance) {
44
+ // If driver injected but not yet opened, return driver directly to avoid "not initialized"
45
+ // during early replay (cold start). Migration will run on openSyncDatabase.
46
+ if ((0, sqlite_1.hasSqliteDriver)()) {
47
+ return (0, sqlite_1.getSqliteDriver)();
48
+ }
49
+ throw new Error("Sync database not initialized. Call openSyncDatabase() first.");
50
+ }
51
+ return dbInstance;
52
+ }
53
+ async function runMigrations(db) {
54
+ const doMigrate = async () => {
55
+ await db.execAsync(`
56
+ CREATE TABLE IF NOT EXISTS migrations (
57
+ version INTEGER PRIMARY KEY NOT NULL,
58
+ applied_at TEXT NOT NULL
59
+ );
60
+ `);
61
+ for (const migration of migrations_1.MIGRATIONS) {
62
+ const row = await db.getFirstAsync("SELECT version FROM migrations WHERE version = ?;", migration.version);
63
+ if (row)
64
+ continue;
65
+ await migration.up(db);
66
+ await db.runAsync("INSERT INTO migrations (version, applied_at) VALUES (?, ?);", migration.version, new Date().toISOString());
67
+ sync_logger_1.syncLogger.info("SQLite", "Applied migration", {
68
+ version: migration.version,
69
+ });
70
+ }
71
+ };
72
+ if (db.withTransactionAsync) {
73
+ await db.withTransactionAsync(doMigrate);
74
+ }
75
+ else {
76
+ await doMigrate();
77
+ }
78
+ }
79
+ async function closeSyncDatabase() {
80
+ if (dbInstance) {
81
+ if (dbInstance.closeAsync)
82
+ await dbInstance.closeAsync();
83
+ dbInstance = null;
84
+ initPromise = null;
85
+ sync_logger_1.syncLogger.info("SQLite", "Database closed");
86
+ }
87
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * SQLite migrations for the offline-first command outbox and event journal.
3
+ */
4
+ import type { SQLiteDatabase } from "../adapters/sqlite";
5
+ export interface Migration {
6
+ version: number;
7
+ up: (db: SQLiteDatabase) => Promise<void>;
8
+ }
9
+ export declare const MIGRATIONS: Migration[];
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /**
3
+ * SQLite migrations for the offline-first command outbox and event journal.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MIGRATIONS = void 0;
7
+ exports.MIGRATIONS = [
8
+ {
9
+ version: 1,
10
+ up: async (db) => {
11
+ await db.execAsync(`
12
+ CREATE TABLE IF NOT EXISTS commands (
13
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14
+ command_id TEXT NOT NULL UNIQUE,
15
+ command_type TEXT NOT NULL,
16
+ command_version TEXT NOT NULL,
17
+ business_id TEXT NOT NULL,
18
+ entity_id TEXT,
19
+ user_id TEXT NOT NULL,
20
+ device_id TEXT NOT NULL,
21
+ payload TEXT NOT NULL,
22
+ status TEXT NOT NULL DEFAULT 'pending',
23
+ error TEXT,
24
+ retry_count INTEGER NOT NULL DEFAULT 0,
25
+ last_attempted_at TEXT,
26
+ created_at TEXT NOT NULL,
27
+ updated_at TEXT NOT NULL
28
+ );
29
+
30
+ CREATE INDEX IF NOT EXISTS idx_commands_status ON commands(status);
31
+ CREATE INDEX IF NOT EXISTS idx_commands_business ON commands(business_id);
32
+ CREATE INDEX IF NOT EXISTS idx_commands_entity ON commands(entity_id);
33
+ CREATE INDEX IF NOT EXISTS idx_commands_created_at ON commands(created_at);
34
+ `);
35
+ await db.execAsync(`
36
+ CREATE TABLE IF NOT EXISTS events (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ event_id TEXT NOT NULL UNIQUE,
39
+ event_type TEXT NOT NULL,
40
+ event_version TEXT NOT NULL,
41
+ entity_id TEXT NOT NULL,
42
+ user_id TEXT NOT NULL,
43
+ device_id TEXT NOT NULL,
44
+ payload TEXT NOT NULL,
45
+ origin TEXT NOT NULL,
46
+ synced_to_cloud INTEGER NOT NULL DEFAULT 0,
47
+ timestamp TEXT NOT NULL,
48
+ persisted_at TEXT NOT NULL
49
+ );
50
+
51
+ CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type);
52
+ CREATE INDEX IF NOT EXISTS idx_events_entity ON events(entity_id);
53
+ CREATE INDEX IF NOT EXISTS idx_events_synced ON events(synced_to_cloud);
54
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
55
+ CREATE INDEX IF NOT EXISTS idx_events_origin ON events(origin);
56
+ `);
57
+ await db.execAsync(`
58
+ CREATE TABLE IF NOT EXISTS entity_snapshots (
59
+ entity_type TEXT NOT NULL,
60
+ entity_id TEXT NOT NULL,
61
+ last_event_id TEXT NOT NULL,
62
+ last_event_timestamp TEXT NOT NULL,
63
+ payload TEXT NOT NULL,
64
+ PRIMARY KEY (entity_type, entity_id)
65
+ );
66
+
67
+ CREATE INDEX IF NOT EXISTS idx_snapshots_entity_id ON entity_snapshots(entity_id);
68
+ `);
69
+ await db.execAsync(`
70
+ CREATE TABLE IF NOT EXISTS reference_data (
71
+ key TEXT NOT NULL PRIMARY KEY,
72
+ version TEXT NOT NULL,
73
+ payload TEXT NOT NULL,
74
+ downloaded_at TEXT NOT NULL
75
+ );
76
+ `);
77
+ },
78
+ },
79
+ {
80
+ version: 2,
81
+ up: async (db) => {
82
+ await db.execAsync(`
83
+ ALTER TABLE entity_snapshots ADD COLUMN business_id TEXT;
84
+ CREATE INDEX IF NOT EXISTS idx_snapshots_business ON entity_snapshots(business_id, entity_type);
85
+
86
+ CREATE TABLE reference_data_new (
87
+ business_id TEXT NOT NULL,
88
+ key TEXT NOT NULL,
89
+ version TEXT NOT NULL,
90
+ payload TEXT NOT NULL,
91
+ downloaded_at TEXT NOT NULL,
92
+ PRIMARY KEY (business_id, key)
93
+ );
94
+ `);
95
+ await db.execAsync(`
96
+ INSERT INTO reference_data_new (business_id, key, version, payload, downloaded_at)
97
+ SELECT '', key, version, payload, downloaded_at FROM reference_data;
98
+ `);
99
+ await db.execAsync(`
100
+ DROP TABLE reference_data;
101
+ ALTER TABLE reference_data_new RENAME TO reference_data;
102
+ CREATE INDEX IF NOT EXISTS idx_reference_data_business ON reference_data(business_id);
103
+ `);
104
+ },
105
+ },
106
+ ];
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Low-level SQLite operations for the command outbox and event journal.
3
+ */
4
+ import type { SQLiteDatabase } from "../adapters/sqlite";
5
+ import type { Command, DomainEvent, PersistedCommand } from "../sync/types";
6
+ export interface InsertCommandInput {
7
+ command: Command;
8
+ status?: PersistedCommand["status"];
9
+ }
10
+ export declare function insertCommand(db: SQLiteDatabase, input: InsertCommandInput): Promise<PersistedCommand>;
11
+ export declare function getPendingCommands(db: SQLiteDatabase, limit?: number): Promise<PersistedCommand[]>;
12
+ export declare function updateCommandStatus(db: SQLiteDatabase, commandId: string, status: PersistedCommand["status"], error?: string): Promise<void>;
13
+ export declare function markCommandInFlight(db: SQLiteDatabase, commandId: string): Promise<void>;
14
+ export declare function deleteCommand(db: SQLiteDatabase, commandId: string): Promise<void>;
15
+ export declare function clearCompletedCommands(db: SQLiteDatabase, olderThanHours?: number): Promise<void>;
16
+ export interface InsertEventInput {
17
+ event: DomainEvent;
18
+ origin: "local" | "cloud" | "lan";
19
+ syncedToCloud?: boolean;
20
+ }
21
+ export declare function insertEvent(db: SQLiteDatabase, input: InsertEventInput): Promise<void>;
22
+ export declare function getEventsForEntity(db: SQLiteDatabase, entityId: string, limit?: number): Promise<DomainEvent[]>;
23
+ export declare function getUnsyncedEvents(db: SQLiteDatabase, origin?: "local" | "lan", limit?: number): Promise<DomainEvent[]>;
24
+ export declare function markEventsSynced(db: SQLiteDatabase, eventIds: string[]): Promise<void>;
25
+ export interface EntitySnapshot<T = unknown> {
26
+ entity_type: string;
27
+ entity_id: string;
28
+ business_id: string | null;
29
+ last_event_id: string;
30
+ last_event_timestamp: string;
31
+ payload: T;
32
+ }
33
+ export interface UpsertEntitySnapshotInput<T = unknown> {
34
+ entityType: string;
35
+ entityId: string;
36
+ businessId?: string | null;
37
+ lastEventId: string;
38
+ lastEventTimestamp: string;
39
+ payload: T;
40
+ }
41
+ export declare function upsertEntitySnapshot<T>(db: SQLiteDatabase, input: UpsertEntitySnapshotInput<T>): Promise<void>;
42
+ export declare function getEntitySnapshot<T>(db: SQLiteDatabase, entityType: string, entityId: string): Promise<EntitySnapshot<T> | null>;
43
+ export declare function getEntitySnapshotsByType<T>(db: SQLiteDatabase, entityType: string): Promise<EntitySnapshot<T>[]>;
44
+ export declare function deleteEntitySnapshot(db: SQLiteDatabase, entityType: string, entityId: string): Promise<void>;
45
+ export declare function pruneEntitySnapshots(db: SQLiteDatabase, entityType: string, cutoffIso: string): Promise<void>;
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /**
3
+ * Low-level SQLite operations for the command outbox and event journal.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.insertCommand = insertCommand;
7
+ exports.getPendingCommands = getPendingCommands;
8
+ exports.updateCommandStatus = updateCommandStatus;
9
+ exports.markCommandInFlight = markCommandInFlight;
10
+ exports.deleteCommand = deleteCommand;
11
+ exports.clearCompletedCommands = clearCompletedCommands;
12
+ exports.insertEvent = insertEvent;
13
+ exports.getEventsForEntity = getEventsForEntity;
14
+ exports.getUnsyncedEvents = getUnsyncedEvents;
15
+ exports.markEventsSynced = markEventsSynced;
16
+ exports.upsertEntitySnapshot = upsertEntitySnapshot;
17
+ exports.getEntitySnapshot = getEntitySnapshot;
18
+ exports.getEntitySnapshotsByType = getEntitySnapshotsByType;
19
+ exports.deleteEntitySnapshot = deleteEntitySnapshot;
20
+ exports.pruneEntitySnapshots = pruneEntitySnapshots;
21
+ const sync_logger_1 = require("../utils/sync-logger");
22
+ async function insertCommand(db, input) {
23
+ const { command, status = "pending" } = input;
24
+ const now = new Date().toISOString();
25
+ const payload = JSON.stringify(command.payload);
26
+ const result = await db.runAsync(`
27
+ INSERT INTO commands (
28
+ command_id, command_type, command_version, business_id, entity_id,
29
+ user_id, device_id, payload, status, retry_count, last_attempted_at,
30
+ created_at, updated_at
31
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
32
+ ON CONFLICT(command_id) DO UPDATE SET
33
+ status = excluded.status,
34
+ payload = excluded.payload,
35
+ updated_at = excluded.updated_at;
36
+ `, command.command_id, command.command_type, command.command_version, command.business_id, command.entity_id ?? null, command.user_id, command.device_id, payload, status, 0, null, now, now);
37
+ const row = await db.getFirstAsync("SELECT * FROM commands WHERE id = ?;", result.lastInsertRowId);
38
+ if (!row) {
39
+ throw new Error(`Failed to insert command ${command.command_id}`);
40
+ }
41
+ sync_logger_1.syncLogger.command(command, "queued");
42
+ return rowToPersistedCommand(row);
43
+ }
44
+ async function getPendingCommands(db, limit = 100) {
45
+ const rows = await db.getAllAsync(`
46
+ SELECT * FROM commands
47
+ WHERE status IN ('pending', 'failed')
48
+ ORDER BY created_at ASC
49
+ LIMIT ?;
50
+ `, limit);
51
+ return rows.map(rowToPersistedCommand);
52
+ }
53
+ async function updateCommandStatus(db, commandId, status, error) {
54
+ const now = new Date().toISOString();
55
+ if (status === "failed") {
56
+ await db.runAsync(`
57
+ UPDATE commands
58
+ SET status = ?, error = ?, retry_count = retry_count + 1,
59
+ last_attempted_at = ?, updated_at = ?
60
+ WHERE command_id = ?;
61
+ `, status, error ?? null, now, now, commandId);
62
+ }
63
+ else if (status === "completed") {
64
+ await db.runAsync("UPDATE commands SET status = ?, error = NULL, updated_at = ? WHERE command_id = ?;", status, now, commandId);
65
+ }
66
+ else {
67
+ await db.runAsync("UPDATE commands SET status = ?, error = ?, updated_at = ? WHERE command_id = ?;", status, error ?? null, now, commandId);
68
+ }
69
+ sync_logger_1.syncLogger.debug("CommandOutbox", "Status updated", { commandId, status });
70
+ }
71
+ async function markCommandInFlight(db, commandId) {
72
+ const now = new Date().toISOString();
73
+ await db.runAsync(`
74
+ UPDATE commands
75
+ SET status = 'in_flight', last_attempted_at = ?, updated_at = ?
76
+ WHERE command_id = ?;
77
+ `, now, now, commandId);
78
+ }
79
+ async function deleteCommand(db, commandId) {
80
+ await db.runAsync("DELETE FROM commands WHERE command_id = ?;", commandId);
81
+ }
82
+ async function clearCompletedCommands(db, olderThanHours = 24) {
83
+ const cutoff = new Date(Date.now() - olderThanHours * 60 * 60 * 1000).toISOString();
84
+ await db.runAsync("DELETE FROM commands WHERE status = 'completed' AND updated_at < ?;", cutoff);
85
+ }
86
+ async function insertEvent(db, input) {
87
+ const { event, origin, syncedToCloud = false } = input;
88
+ await db.runAsync(`
89
+ INSERT INTO events (
90
+ event_id, event_type, event_version, entity_id, user_id, device_id,
91
+ payload, origin, synced_to_cloud, timestamp, persisted_at
92
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
93
+ ON CONFLICT(event_id) DO NOTHING;
94
+ `, event.event_id, event.event_type, event.event_version, event.entity_id, event.user_id, event.device_id, JSON.stringify(event.payload), origin, syncedToCloud ? 1 : 0, event.timestamp, new Date().toISOString());
95
+ sync_logger_1.syncLogger.event(event, "persisted", { origin, syncedToCloud });
96
+ }
97
+ async function getEventsForEntity(db, entityId, limit = 100) {
98
+ const rows = await db.getAllAsync(`
99
+ SELECT * FROM events
100
+ WHERE entity_id = ?
101
+ ORDER BY timestamp ASC
102
+ LIMIT ?;
103
+ `, entityId, limit);
104
+ return rows.map(rowToDomainEvent);
105
+ }
106
+ async function getUnsyncedEvents(db, origin = "local", limit = 100) {
107
+ const rows = await db.getAllAsync(`
108
+ SELECT * FROM events
109
+ WHERE origin = ? AND synced_to_cloud = 0
110
+ ORDER BY timestamp ASC
111
+ LIMIT ?;
112
+ `, origin, limit);
113
+ return rows.map(rowToDomainEvent);
114
+ }
115
+ async function markEventsSynced(db, eventIds) {
116
+ if (eventIds.length === 0)
117
+ return;
118
+ const placeholders = eventIds.map(() => "?").join(",");
119
+ await db.runAsync(`UPDATE events SET synced_to_cloud = 1 WHERE event_id IN (${placeholders});`, ...eventIds);
120
+ }
121
+ function rowToEntitySnapshot(row) {
122
+ return {
123
+ entity_type: row.entity_type,
124
+ entity_id: row.entity_id,
125
+ business_id: row.business_id,
126
+ last_event_id: row.last_event_id,
127
+ last_event_timestamp: row.last_event_timestamp,
128
+ payload: JSON.parse(row.payload),
129
+ };
130
+ }
131
+ async function upsertEntitySnapshot(db, input) {
132
+ const { entityType, entityId, businessId = null, lastEventId, lastEventTimestamp, payload, } = input;
133
+ const existing = await db.getFirstAsync("SELECT last_event_timestamp FROM entity_snapshots WHERE entity_type = ? AND entity_id = ?;", entityType, entityId);
134
+ if (existing && existing.last_event_timestamp > lastEventTimestamp) {
135
+ return;
136
+ }
137
+ await db.runAsync(`
138
+ INSERT INTO entity_snapshots (entity_type, entity_id, business_id, last_event_id, last_event_timestamp, payload)
139
+ VALUES (?, ?, ?, ?, ?, ?)
140
+ ON CONFLICT(entity_type, entity_id) DO UPDATE SET
141
+ business_id = excluded.business_id,
142
+ last_event_id = excluded.last_event_id,
143
+ last_event_timestamp = excluded.last_event_timestamp,
144
+ payload = excluded.payload;
145
+ `, entityType, entityId, businessId, lastEventId, lastEventTimestamp, JSON.stringify(payload));
146
+ }
147
+ async function getEntitySnapshot(db, entityType, entityId) {
148
+ const row = await db.getFirstAsync("SELECT * FROM entity_snapshots WHERE entity_type = ? AND entity_id = ?;", entityType, entityId);
149
+ return row ? rowToEntitySnapshot(row) : null;
150
+ }
151
+ async function getEntitySnapshotsByType(db, entityType) {
152
+ const rows = await db.getAllAsync("SELECT * FROM entity_snapshots WHERE entity_type = ? ORDER BY last_event_timestamp DESC;", entityType);
153
+ return rows.map(rowToEntitySnapshot);
154
+ }
155
+ async function deleteEntitySnapshot(db, entityType, entityId) {
156
+ await db.runAsync("DELETE FROM entity_snapshots WHERE entity_type = ? AND entity_id = ?;", entityType, entityId);
157
+ }
158
+ async function pruneEntitySnapshots(db, entityType, cutoffIso) {
159
+ await db.runAsync("DELETE FROM entity_snapshots WHERE entity_type = ? AND last_event_timestamp < ?;", entityType, cutoffIso);
160
+ }
161
+ function rowToPersistedCommand(row) {
162
+ return {
163
+ id: row.id,
164
+ command_id: row.command_id,
165
+ command_type: row.command_type,
166
+ command_version: row.command_version,
167
+ business_id: row.business_id,
168
+ entity_id: row.entity_id ?? undefined,
169
+ user_id: row.user_id,
170
+ device_id: row.device_id,
171
+ payload: JSON.parse(row.payload),
172
+ status: row.status,
173
+ error: row.error,
174
+ retry_count: row.retry_count,
175
+ last_attempted_at: row.last_attempted_at ?? undefined,
176
+ timestamp: row.timestamp,
177
+ created_at: row.created_at,
178
+ updated_at: row.updated_at,
179
+ };
180
+ }
181
+ function rowToDomainEvent(row) {
182
+ return {
183
+ event_id: row.event_id,
184
+ event_type: row.event_type,
185
+ event_version: row.event_version,
186
+ entity_id: row.entity_id,
187
+ user_id: row.user_id,
188
+ device_id: row.device_id,
189
+ payload: JSON.parse(row.payload),
190
+ timestamp: row.timestamp,
191
+ };
192
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @teincfood/core — shared offline-first backbone
3
+ *
4
+ * Entry point. Re-exports types, sync engine, DB, reference service, repos and adapters.
5
+ * Host apps call `createTeincCore` once at bootstrap to wire platform adapters.
6
+ */
7
+ export * from "./types/api";
8
+ export * from "./types/pos.types";
9
+ export * from "./utils/constants";
10
+ export * from "./utils/currency";
11
+ export * from "./utils/error";
12
+ export * from "./utils/uuid";
13
+ export * from "./utils/sync-logger";
14
+ export * from "./db/migrations";
15
+ export * as dbOps from "./db/operations";
16
+ export { openSyncDatabase, getSyncDatabase } from "./db/connection";
17
+ export * from "./sync/types";
18
+ export { SyncEngine } from "./sync/engine";
19
+ export { commandQueueService } from "./sync/command-queue.service";
20
+ export { buildCommand } from "./sync/command-builder";
21
+ export { generateLocalOrderNumber, generateLocalCalloutNumber, isLocalOrderNumber } from "./sync/order-number";
22
+ export { orderSnapshotsService } from "./sync/order-snapshots.service";
23
+ export * from "./sync/pending-orders.service";
24
+ export { orderRepository } from "./repositories/order.repository";
25
+ export { posRepository } from "./repositories/pos.repository";
26
+ export * from "./reference/types";
27
+ export { referenceSyncService } from "./reference/service";
28
+ export * from "./adapters/sqlite";
29
+ export * from "./adapters/kv";
30
+ export * from "./adapters/connectivity";
31
+ export * from "./adapters/http";
32
+ export * from "./adapters/local-node";
33
+ import { type SqliteDriver } from "./adapters/sqlite";
34
+ import { type KVDriver } from "./adapters/kv";
35
+ import { type HttpClient } from "./adapters/http";
36
+ import { SyncEngine } from "./sync/engine";
37
+ export interface CoreConfig {
38
+ sqliteDriver?: SqliteDriver;
39
+ kvDriver?: KVDriver;
40
+ httpClient?: HttpClient;
41
+ /** Optional initial connectivity override */
42
+ initialConnectivity?: {
43
+ isOnline?: boolean;
44
+ isCloudReachable?: "reachable" | "unreachable" | "unknown";
45
+ };
46
+ }
47
+ export declare function createTeincCore(config?: CoreConfig): {
48
+ engine: SyncEngine;
49
+ };
50
+ export declare function getCoreEngine(): SyncEngine;