@teincfood/core 0.7.8 → 0.8.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 (198) hide show
  1. package/README.md +26 -21
  2. package/dist/adapters/auth.d.ts +10 -0
  3. package/dist/adapters/auth.js +43 -0
  4. package/dist/adapters/devices.d.ts +3 -1
  5. package/dist/adapters/devices.js +187 -3
  6. package/dist/adapters/filesystem.d.ts +4 -0
  7. package/dist/adapters/kv.d.ts +10 -0
  8. package/dist/adapters/kv.js +23 -7
  9. package/dist/adapters/local-node.d.ts +4 -6
  10. package/dist/adapters/local-node.js +9 -32
  11. package/dist/adapters/notify.d.ts +12 -0
  12. package/dist/adapters/notify.js +33 -0
  13. package/dist/adapters/query-keys.d.ts +4 -0
  14. package/dist/adapters/query-keys.js +4 -0
  15. package/dist/adapters/services.d.ts +99 -15
  16. package/dist/adapters/services.js +244 -4
  17. package/dist/auth/session.d.ts +66 -0
  18. package/dist/auth/session.js +172 -0
  19. package/dist/catalog/hooks.d.ts +10 -0
  20. package/dist/catalog/hooks.js +82 -11
  21. package/dist/db/migrations.js +274 -0
  22. package/dist/db/operations.d.ts +53 -2
  23. package/dist/db/operations.js +177 -8
  24. package/dist/domain/business/index.d.ts +5 -0
  25. package/dist/domain/business/index.js +5 -0
  26. package/dist/domain/orders/lifecycle.d.ts +6 -0
  27. package/dist/domain/orders/lifecycle.js +6 -0
  28. package/dist/domain/orders/service.d.ts +24 -0
  29. package/dist/domain/orders/service.js +242 -0
  30. package/dist/domain/payments/index.d.ts +6 -0
  31. package/dist/domain/payments/index.js +5 -0
  32. package/dist/domain/reference/index.d.ts +8 -0
  33. package/dist/domain/reference/index.js +8 -0
  34. package/dist/e2e/harness.d.ts +1 -0
  35. package/dist/e2e/harness.js +61 -0
  36. package/dist/hooks/analytics.hooks.d.ts +25 -0
  37. package/dist/hooks/analytics.hooks.js +214 -0
  38. package/dist/hooks/business-hours.hooks.d.ts +8 -0
  39. package/dist/hooks/business-hours.hooks.js +45 -0
  40. package/dist/hooks/dashboard.hooks.d.ts +30 -0
  41. package/dist/hooks/dashboard.hooks.js +132 -0
  42. package/dist/hooks/delivery.hooks.d.ts +225 -0
  43. package/dist/hooks/delivery.hooks.js +80 -0
  44. package/dist/hooks/fleet.hooks.d.ts +37 -0
  45. package/dist/hooks/fleet.hooks.js +229 -0
  46. package/dist/hooks/image.hooks.d.ts +20 -0
  47. package/dist/hooks/image.hooks.js +62 -0
  48. package/dist/hooks/kiosk.hooks.d.ts +22 -5
  49. package/dist/hooks/kiosk.hooks.js +74 -5
  50. package/dist/hooks/members.hooks.d.ts +48 -0
  51. package/dist/hooks/members.hooks.js +302 -0
  52. package/dist/hooks/offline-orders.hooks.d.ts +9 -0
  53. package/dist/hooks/offline-orders.hooks.js +51 -0
  54. package/dist/hooks/orders.hooks.d.ts +19 -39
  55. package/dist/hooks/orders.hooks.js +126 -321
  56. package/dist/hooks/places.hooks.d.ts +21 -0
  57. package/dist/hooks/places.hooks.js +112 -0
  58. package/dist/hooks/realtime-stubs.d.ts +28 -0
  59. package/dist/hooks/realtime-stubs.js +85 -0
  60. package/dist/hooks/rider.hooks.d.ts +470 -0
  61. package/dist/hooks/rider.hooks.js +373 -0
  62. package/dist/hooks/use-infinite-list.d.ts +243 -0
  63. package/dist/hooks/use-infinite-list.js +58 -0
  64. package/dist/hooks/wallet.hooks.d.ts +219 -0
  65. package/dist/hooks/wallet.hooks.js +56 -0
  66. package/dist/image-cache/service.d.ts +8 -0
  67. package/dist/image-cache/service.js +15 -3
  68. package/dist/index.d.ts +60 -5
  69. package/dist/index.js +78 -5
  70. package/dist/local-node/adapters.d.ts +87 -0
  71. package/dist/local-node/adapters.js +4 -0
  72. package/dist/local-node/index.d.ts +5 -0
  73. package/dist/local-node/index.js +5 -0
  74. package/dist/local-node/security.d.ts +25 -0
  75. package/dist/local-node/security.js +149 -0
  76. package/dist/local-node/service.d.ts +83 -0
  77. package/dist/local-node/service.js +706 -0
  78. package/dist/local-node/store.d.ts +50 -0
  79. package/dist/local-node/store.js +284 -0
  80. package/dist/local-node/types.d.ts +92 -0
  81. package/dist/local-node/types.js +4 -0
  82. package/dist/printer/receipt.d.ts +4 -0
  83. package/dist/printer/receipt.js +75 -0
  84. package/dist/printer/service.js +1 -1
  85. package/dist/providers/query-client.d.ts +13 -0
  86. package/dist/providers/query-client.js +58 -0
  87. package/dist/realtime/events.service.d.ts +18 -0
  88. package/dist/realtime/events.service.js +76 -0
  89. package/dist/realtime/order-events.service.d.ts +6 -0
  90. package/dist/realtime/order-events.service.js +17 -0
  91. package/dist/realtime/phoenix-socket.d.ts +55 -0
  92. package/dist/realtime/phoenix-socket.js +269 -0
  93. package/dist/realtime/useEventsConnection.d.ts +1 -0
  94. package/dist/realtime/useEventsConnection.js +50 -0
  95. package/dist/reference/api.d.ts +2 -0
  96. package/dist/reference/api.js +2 -0
  97. package/dist/reference/capabilities.d.ts +12 -7
  98. package/dist/reference/capabilities.js +28 -13
  99. package/dist/reference/hooks.d.ts +34 -0
  100. package/dist/reference/hooks.js +124 -0
  101. package/dist/reference/menu-apply.d.ts +29 -0
  102. package/dist/reference/menu-apply.js +289 -0
  103. package/dist/reference/menu-record.d.ts +69 -0
  104. package/dist/reference/menu-record.js +163 -0
  105. package/dist/reference/operations.d.ts +3 -0
  106. package/dist/reference/operations.js +35 -1
  107. package/dist/reference/service.js +36 -9
  108. package/dist/reference/types.d.ts +1 -1
  109. package/dist/repositories/order.repository.d.ts +8 -0
  110. package/dist/repositories/order.repository.js +24 -2
  111. package/dist/repositories/pos.repository.d.ts +4 -0
  112. package/dist/repositories/pos.repository.js +4 -0
  113. package/dist/sync/backoff.d.ts +21 -0
  114. package/dist/sync/backoff.js +42 -0
  115. package/dist/sync/command-builder.js +10 -2
  116. package/dist/sync/command-queue.service.d.ts +12 -0
  117. package/dist/sync/command-queue.service.js +115 -18
  118. package/dist/sync/device-lease.service.d.ts +3 -3
  119. package/dist/sync/device-lease.service.js +80 -20
  120. package/dist/sync/engine.d.ts +3 -1
  121. package/dist/sync/engine.js +44 -4
  122. package/dist/sync/full-sync.d.ts +6 -0
  123. package/dist/sync/full-sync.js +72 -0
  124. package/dist/sync/health-probe.d.ts +24 -0
  125. package/dist/sync/health-probe.js +138 -0
  126. package/dist/sync/offline-capable.js +25 -0
  127. package/dist/sync/online-gates.d.ts +17 -0
  128. package/dist/sync/online-gates.js +55 -0
  129. package/dist/sync/order-number.d.ts +4 -4
  130. package/dist/sync/order-number.js +33 -13
  131. package/dist/sync/order-snapshots.service.d.ts +1 -0
  132. package/dist/sync/order-snapshots.service.js +47 -2
  133. package/dist/sync/pending-orders.service.d.ts +18 -0
  134. package/dist/sync/pending-orders.service.js +148 -3
  135. package/dist/sync/reconciliation/index.d.ts +5 -0
  136. package/dist/sync/reconciliation/index.js +5 -0
  137. package/dist/sync/snapshots/entity.d.ts +6 -0
  138. package/dist/sync/snapshots/entity.js +6 -0
  139. package/dist/sync/sync-jobs.service.d.ts +18 -0
  140. package/dist/sync/sync-jobs.service.js +539 -0
  141. package/dist/sync/transition-endpoints.d.ts +11 -0
  142. package/dist/sync/transition-endpoints.js +30 -0
  143. package/dist/sync/transport-local-node.js +3 -2
  144. package/dist/sync/transport-rest.d.ts +7 -0
  145. package/dist/sync/transport-rest.js +90 -25
  146. package/dist/sync/use-sync-reconciliation.js +58 -16
  147. package/dist/test-helpers/memory-driver.js +182 -4
  148. package/dist/test-helpers/seed-hours.d.ts +1 -0
  149. package/dist/test-helpers/seed-hours.js +23 -0
  150. package/dist/types/api/business.api.d.ts +42 -0
  151. package/dist/types/api/menu.api.d.ts +15 -0
  152. package/dist/types/api/seller.api.d.ts +20 -0
  153. package/dist/types/places.d.ts +19 -0
  154. package/dist/types/places.js +2 -0
  155. package/dist/types/rider.d.ts +67 -0
  156. package/dist/types/rider.js +2 -0
  157. package/dist/utils/attempt-stats.d.ts +23 -0
  158. package/dist/utils/attempt-stats.js +61 -0
  159. package/dist/utils/business-hours.d.ts +37 -0
  160. package/dist/utils/business-hours.js +96 -0
  161. package/dist/utils/debug-log.d.ts +23 -0
  162. package/dist/utils/debug-log.js +153 -0
  163. package/dist/utils/order-filter.d.ts +1 -1
  164. package/dist/utils/order-filter.js +1 -1
  165. package/dist/utils/order-transitions.d.ts +30 -0
  166. package/dist/utils/order-transitions.js +185 -0
  167. package/dist/utils/sync-logger.js +5 -4
  168. package/dist/validation/offline.d.ts +46 -0
  169. package/dist/validation/offline.js +79 -0
  170. package/package.json +6 -1
  171. package/dist/pricing/engine.test.d.ts +0 -1
  172. package/dist/pricing/engine.test.js +0 -142
  173. package/dist/reference/lww.test.d.ts +0 -1
  174. package/dist/reference/lww.test.js +0 -23
  175. package/dist/reference/operations.test.d.ts +0 -1
  176. package/dist/reference/operations.test.js +0 -66
  177. package/dist/standalone.integration.test.d.ts +0 -1
  178. package/dist/standalone.integration.test.js +0 -235
  179. package/dist/sync/device-lease.test.d.ts +0 -1
  180. package/dist/sync/device-lease.test.js +0 -38
  181. package/dist/sync/offline-integration.test.d.ts +0 -1
  182. package/dist/sync/offline-integration.test.js +0 -52
  183. package/dist/sync/order-number.test.d.ts +0 -1
  184. package/dist/sync/order-number.test.js +0 -51
  185. package/dist/sync/perms.test.d.ts +0 -1
  186. package/dist/sync/perms.test.js +0 -15
  187. package/dist/utils/currency.test.d.ts +0 -1
  188. package/dist/utils/currency.test.js +0 -17
  189. package/dist/utils/hlc.test.d.ts +0 -1
  190. package/dist/utils/hlc.test.js +0 -29
  191. package/dist/utils/phone.test.d.ts +0 -1
  192. package/dist/utils/phone.test.js +0 -19
  193. package/dist/utils/responsive.d.ts +0 -13
  194. package/dist/utils/responsive.js +0 -18
  195. package/dist/utils/uuid.test.d.ts +0 -1
  196. package/dist/utils/uuid.test.js +0 -13
  197. package/dist/utils/validation.test.d.ts +0 -1
  198. package/dist/utils/validation.test.js +0 -23
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Local Node store — platform-agnostic, core-owned.
3
+ * Mesh mode: no elected node, every isLocalNodeEnabled device is both publisher and scanner.
4
+ */
5
+ export interface DiscoveredNode {
6
+ deviceId: string;
7
+ name: string;
8
+ host: string;
9
+ port: number;
10
+ deviceName?: string;
11
+ lastSeenAt: string;
12
+ }
13
+ export interface ConnectedClient {
14
+ deviceId: string;
15
+ deviceName?: string;
16
+ host?: string;
17
+ connectedAt: string;
18
+ }
19
+ interface LocalNodeState {
20
+ isLocalNodeEnabled: boolean;
21
+ localNodePort: number;
22
+ discoveredNodes: DiscoveredNode[];
23
+ connectedClients: ConnectedClient[];
24
+ }
25
+ type Listener = (state: LocalNodeState, prev: LocalNodeState) => void;
26
+ declare class LocalNodeStore {
27
+ private state;
28
+ private listeners;
29
+ constructor();
30
+ private hydrate;
31
+ rehydrate(): Promise<void>;
32
+ private persist;
33
+ private set;
34
+ getState(): LocalNodeState & {
35
+ setIsLocalNodeEnabled: (v: boolean) => void;
36
+ setLocalNodePort: (p: number) => void;
37
+ addDiscoveredNode: (n: DiscoveredNode) => void;
38
+ removeDiscoveredNode: (id: string) => void;
39
+ clearDiscoveredNodes: () => void;
40
+ addConnectedClient: (c: ConnectedClient) => void;
41
+ removeConnectedClient: (id: string) => void;
42
+ clearConnectedClients: () => void;
43
+ };
44
+ subscribe(listener: Listener): () => void;
45
+ _reset(): void;
46
+ }
47
+ export declare const localNodeStore: LocalNodeStore;
48
+ export declare function useLocalNodeStore(): ReturnType<LocalNodeStore["getState"]>;
49
+ export declare function useLocalNodeStore<T>(selector: (s: ReturnType<LocalNodeStore["getState"]>) => T): T;
50
+ export {};
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Local Node store — platform-agnostic, core-owned.
3
+ * Mesh mode: no elected node, every isLocalNodeEnabled device is both publisher and scanner.
4
+ */
5
+ import { getKVDriver } from "../adapters/kv";
6
+ const DEFAULT_PORT = 44621;
7
+ const KV_KEY = "teinc_local_node";
8
+ class LocalNodeStore {
9
+ constructor() {
10
+ this.state = {
11
+ isLocalNodeEnabled: false,
12
+ localNodePort: DEFAULT_PORT,
13
+ discoveredNodes: [],
14
+ connectedClients: [],
15
+ };
16
+ this.listeners = new Set();
17
+ this.hydrate();
18
+ }
19
+ hydrate() {
20
+ try {
21
+ const ls = typeof localStorage !== "undefined" ? localStorage.getItem(KV_KEY) : null;
22
+ if (ls) {
23
+ const parsed = JSON.parse(ls);
24
+ this.state = { ...this.state, ...parsed, discoveredNodes: [], connectedClients: [] };
25
+ return;
26
+ }
27
+ const old = typeof localStorage !== "undefined" ? localStorage.getItem("local_node_data") : null;
28
+ if (old) {
29
+ try {
30
+ const parsedOld = JSON.parse(old);
31
+ const p = parsedOld.state ?? parsedOld;
32
+ if (p && typeof p.isLocalNodeEnabled === "boolean") {
33
+ this.state = { ...this.state, ...p, discoveredNodes: [], connectedClients: [] };
34
+ return;
35
+ }
36
+ }
37
+ catch { }
38
+ }
39
+ }
40
+ catch { }
41
+ try {
42
+ const kv = getKVDriver();
43
+ const raw = kv?.getItem(KV_KEY);
44
+ if (raw instanceof Promise) {
45
+ raw.then((v) => {
46
+ if (!v) {
47
+ const kv2 = getKVDriver();
48
+ const rawOld = kv2?.getItem("local_node_data");
49
+ if (rawOld instanceof Promise)
50
+ rawOld.then((o) => { if (!o)
51
+ return; try {
52
+ const po = JSON.parse(o);
53
+ const p = po.state ?? po;
54
+ this.state = { ...this.state, ...p, discoveredNodes: [], connectedClients: [] };
55
+ const prev = { ...this.state };
56
+ for (const l of this.listeners)
57
+ try {
58
+ l(this.getState(), prev);
59
+ }
60
+ catch { }
61
+ }
62
+ catch { } }).catch(() => { });
63
+ else if (rawOld)
64
+ try {
65
+ const po = JSON.parse(rawOld);
66
+ const p = po.state ?? po;
67
+ this.state = { ...this.state, ...p, discoveredNodes: [], connectedClients: [] };
68
+ const prev2 = { ...this.state };
69
+ for (const l of this.listeners)
70
+ try {
71
+ l(this.getState(), prev2);
72
+ }
73
+ catch { }
74
+ }
75
+ catch { }
76
+ return;
77
+ }
78
+ try {
79
+ const parsed = JSON.parse(v);
80
+ const prev = { ...this.state, discoveredNodes: [...this.state.discoveredNodes], connectedClients: [...this.state.connectedClients] };
81
+ this.state = { ...this.state, ...parsed, discoveredNodes: [], connectedClients: [] };
82
+ for (const l of this.listeners)
83
+ try {
84
+ l(this.getState(), prev);
85
+ }
86
+ catch { }
87
+ }
88
+ catch { }
89
+ }).catch(() => { });
90
+ }
91
+ else if (raw) {
92
+ const parsed = JSON.parse(raw);
93
+ this.state = { ...this.state, ...parsed, discoveredNodes: [], connectedClients: [] };
94
+ }
95
+ }
96
+ catch { }
97
+ }
98
+ async rehydrate() {
99
+ try {
100
+ const kv = getKVDriver();
101
+ const raw = await kv.getItem(KV_KEY);
102
+ let parsed = null;
103
+ if (raw) {
104
+ try {
105
+ parsed = JSON.parse(raw);
106
+ }
107
+ catch { }
108
+ }
109
+ else {
110
+ try {
111
+ const ls = typeof localStorage !== "undefined" ? localStorage.getItem(KV_KEY) : null;
112
+ if (ls)
113
+ parsed = JSON.parse(ls);
114
+ }
115
+ catch { }
116
+ }
117
+ if (!parsed) {
118
+ try {
119
+ const oldRaw = await kv.getItem("local_node_data");
120
+ if (oldRaw) {
121
+ const po = JSON.parse(oldRaw);
122
+ parsed = po.state ?? po;
123
+ }
124
+ }
125
+ catch { }
126
+ }
127
+ if (!parsed)
128
+ return;
129
+ const prev = { ...this.state, discoveredNodes: [...this.state.discoveredNodes], connectedClients: [...this.state.connectedClients] };
130
+ const next = {};
131
+ if (typeof parsed.isLocalNodeEnabled === "boolean")
132
+ next.isLocalNodeEnabled = parsed.isLocalNodeEnabled;
133
+ if (typeof parsed.localNodePort === "number")
134
+ next.localNodePort = parsed.localNodePort;
135
+ const changed = next.isLocalNodeEnabled !== undefined && next.isLocalNodeEnabled !== this.state.isLocalNodeEnabled;
136
+ this.state = { ...this.state, ...next, discoveredNodes: [], connectedClients: [] };
137
+ if (changed) {
138
+ for (const l of this.listeners)
139
+ try {
140
+ l(this.getState(), prev);
141
+ }
142
+ catch { }
143
+ }
144
+ }
145
+ catch { }
146
+ }
147
+ persist() {
148
+ try {
149
+ const kv = getKVDriver();
150
+ if (!kv)
151
+ return;
152
+ const toSave = {
153
+ isLocalNodeEnabled: this.state.isLocalNodeEnabled,
154
+ localNodePort: this.state.localNodePort,
155
+ };
156
+ const raw = JSON.stringify(toSave);
157
+ kv.setItem(KV_KEY, raw);
158
+ try {
159
+ if (typeof localStorage !== "undefined")
160
+ localStorage.setItem(KV_KEY, raw);
161
+ }
162
+ catch { }
163
+ }
164
+ catch { }
165
+ }
166
+ set(partial) {
167
+ const prev = { ...this.state, discoveredNodes: [...this.state.discoveredNodes], connectedClients: [...this.state.connectedClients] };
168
+ this.state = { ...this.state, ...partial };
169
+ this.persist();
170
+ for (const l of this.listeners) {
171
+ try {
172
+ l(this.getState(), prev);
173
+ }
174
+ catch { }
175
+ }
176
+ }
177
+ getState() {
178
+ const self = this;
179
+ return {
180
+ ...this.state,
181
+ setIsLocalNodeEnabled: (enabled) => self.set({ isLocalNodeEnabled: enabled }),
182
+ setLocalNodePort: (port) => self.set({ localNodePort: port }),
183
+ addDiscoveredNode: (node) => {
184
+ const prev = { ...self.state, discoveredNodes: [...self.state.discoveredNodes] };
185
+ // Dedup by deviceId (same peer re-announced) OR by host:port
186
+ // (TCP sweep's synthetic "lan-X.X.X.X" vs mDNS real UUID for same device).
187
+ const existingById = self.state.discoveredNodes.find((n) => n.deviceId === node.deviceId);
188
+ const existingByAddr = !existingById && node.host && node.port
189
+ ? self.state.discoveredNodes.find((n) => n.host === node.host && n.port === node.port)
190
+ : undefined;
191
+ const existing = existingById ?? existingByAddr;
192
+ if (existing) {
193
+ // Prefer the real UUID entry over synthetic "lan-" prefix entries
194
+ const keep = (existing.deviceId.startsWith("lan-") && !node.deviceId.startsWith("lan-"))
195
+ ? node : existing;
196
+ self.state = {
197
+ ...self.state,
198
+ discoveredNodes: self.state.discoveredNodes.map((n) => (n.deviceId === keep.deviceId || n.host === keep.host && n.port === keep.port)
199
+ ? { ...n, ...keep, lastSeenAt: new Date().toISOString() }
200
+ : n),
201
+ };
202
+ }
203
+ else {
204
+ self.state = { ...self.state, discoveredNodes: [...self.state.discoveredNodes, { ...node, lastSeenAt: new Date().toISOString() }] };
205
+ }
206
+ for (const l of self.listeners) {
207
+ try {
208
+ l(self.getState(), prev);
209
+ }
210
+ catch { }
211
+ }
212
+ },
213
+ removeDiscoveredNode: (deviceId) => {
214
+ const prev = { ...self.state, discoveredNodes: [...self.state.discoveredNodes] };
215
+ self.state = { ...self.state, discoveredNodes: self.state.discoveredNodes.filter((n) => n.deviceId !== deviceId) };
216
+ for (const l of self.listeners) {
217
+ try {
218
+ l(self.getState(), prev);
219
+ }
220
+ catch { }
221
+ }
222
+ },
223
+ clearDiscoveredNodes: () => self.set({ discoveredNodes: [] }),
224
+ addConnectedClient: (client) => {
225
+ const prev = { ...self.state, connectedClients: [...self.state.connectedClients] };
226
+ const existing = self.state.connectedClients.find((c) => c.deviceId === client.deviceId);
227
+ if (existing) {
228
+ self.state = {
229
+ ...self.state,
230
+ connectedClients: self.state.connectedClients.map((c) => (c.deviceId === client.deviceId ? { ...c, ...client } : c)),
231
+ };
232
+ }
233
+ else {
234
+ self.state = { ...self.state, connectedClients: [...self.state.connectedClients, client] };
235
+ }
236
+ for (const l of self.listeners) {
237
+ try {
238
+ l(self.getState(), prev);
239
+ }
240
+ catch { }
241
+ }
242
+ },
243
+ removeConnectedClient: (deviceId) => {
244
+ const prev = { ...self.state, connectedClients: [...self.state.connectedClients] };
245
+ self.state = { ...self.state, connectedClients: self.state.connectedClients.filter((c) => c.deviceId !== deviceId) };
246
+ for (const l of self.listeners) {
247
+ try {
248
+ l(self.getState(), prev);
249
+ }
250
+ catch { }
251
+ }
252
+ },
253
+ clearConnectedClients: () => self.set({ connectedClients: [] }),
254
+ };
255
+ }
256
+ subscribe(listener) {
257
+ this.listeners.add(listener);
258
+ return () => this.listeners.delete(listener);
259
+ }
260
+ _reset() {
261
+ this.state = {
262
+ isLocalNodeEnabled: false,
263
+ localNodePort: DEFAULT_PORT,
264
+ discoveredNodes: [],
265
+ connectedClients: [],
266
+ };
267
+ this.listeners.clear();
268
+ }
269
+ }
270
+ export const localNodeStore = new LocalNodeStore();
271
+ import * as React from "react";
272
+ export function useLocalNodeStore(selector) {
273
+ const [state, setState] = React.useState(() => localNodeStore.getState());
274
+ React.useEffect(() => {
275
+ const unsub = localNodeStore.subscribe((next) => setState(next));
276
+ setState(localNodeStore.getState());
277
+ return unsub;
278
+ }, []);
279
+ if (selector)
280
+ return selector(state);
281
+ return state;
282
+ }
283
+ useLocalNodeStore.getState = () => localNodeStore.getState();
284
+ useLocalNodeStore.subscribe = (fn) => localNodeStore.subscribe(fn);
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Types for the Local Node / multi-terminal LAN layer.
3
+ */
4
+ import type { Command, DomainEvent } from "../sync/types";
5
+ import type { ReferenceDataKey, ReferenceDataRecord } from "../reference/types";
6
+ export type LocalNodeMessageType = "command" | "command_response" | "event" | "relay_event" | "ping" | "pong" | "hello" | "challenge" | "auth" | "auth_ok" | "auth_fail" | "register" | "register_ack" | "reference_fetch" | "reference_response";
7
+ export interface LocalNodeMessage {
8
+ type: LocalNodeMessageType;
9
+ id: string;
10
+ payload: unknown;
11
+ }
12
+ export interface LocalNodeCommandMessage extends LocalNodeMessage {
13
+ type: "command";
14
+ payload: Command;
15
+ }
16
+ export interface LocalNodeCommandResponseMessage extends LocalNodeMessage {
17
+ type: "command_response";
18
+ payload: DomainEvent[];
19
+ }
20
+ export interface LocalNodeEventMessage extends LocalNodeMessage {
21
+ type: "event";
22
+ payload: DomainEvent;
23
+ }
24
+ export interface LocalNodeRelayEventMessage extends LocalNodeMessage {
25
+ type: "relay_event";
26
+ payload: DomainEvent;
27
+ }
28
+ export interface LocalNodeHelloMessage extends LocalNodeMessage {
29
+ type: "hello";
30
+ payload: {
31
+ deviceId: string;
32
+ deviceName?: string;
33
+ businessId?: string;
34
+ };
35
+ }
36
+ export interface LocalNodeChallengeMessage extends LocalNodeMessage {
37
+ type: "challenge";
38
+ payload: {
39
+ nonce: string;
40
+ };
41
+ }
42
+ export interface LocalNodeAuthMessage extends LocalNodeMessage {
43
+ type: "auth";
44
+ payload: {
45
+ nonce: string;
46
+ proof: string;
47
+ };
48
+ }
49
+ export interface LocalNodeAuthOkMessage extends LocalNodeMessage {
50
+ type: "auth_ok";
51
+ payload: Record<string, never>;
52
+ }
53
+ export interface LocalNodeAuthFailMessage extends LocalNodeMessage {
54
+ type: "auth_fail";
55
+ payload: {
56
+ reason?: string;
57
+ };
58
+ }
59
+ export interface LocalNodeRegisterMessage extends LocalNodeMessage {
60
+ type: "register";
61
+ payload: {
62
+ deviceId: string;
63
+ deviceName?: string;
64
+ businessId?: string;
65
+ connectedAt: string;
66
+ };
67
+ }
68
+ export interface LocalNodeRegisterAckMessage extends LocalNodeMessage {
69
+ type: "register_ack";
70
+ payload: Record<string, never>;
71
+ }
72
+ export interface LocalNodeReferenceFetchMessage extends LocalNodeMessage {
73
+ type: "reference_fetch";
74
+ payload: {
75
+ businessId: string;
76
+ keys: ReferenceDataKey[];
77
+ };
78
+ }
79
+ export interface LocalNodeReferenceResponseMessage extends LocalNodeMessage {
80
+ type: "reference_response";
81
+ payload: {
82
+ data: Partial<Record<ReferenceDataKey, ReferenceDataRecord | null>>;
83
+ };
84
+ }
85
+ export type LocalNodeConnectionState = "disconnected" | "connecting" | "connected";
86
+ export interface LocalNodeInfo {
87
+ deviceId: string;
88
+ host: string;
89
+ port: number;
90
+ businessId?: string;
91
+ deviceName?: string;
92
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Types for the Local Node / multi-terminal LAN layer.
3
+ */
4
+ export {};
@@ -9,4 +9,8 @@
9
9
  import type { SellerOrder } from "../types/api/seller.api";
10
10
  export declare function calloutNumber(order: SellerOrder): string;
11
11
  export declare function generateEscpos(order: SellerOrder): string;
12
+ export declare function generateHtmlAsync(order: SellerOrder, opts?: {
13
+ logoDataUri?: string | null;
14
+ useCache?: boolean;
15
+ }): Promise<string>;
12
16
  export declare function generateHtml(order: SellerOrder, logoDataUri?: string | null): string;
@@ -6,6 +6,9 @@
6
6
  * Backend source: lib/teinc_food_backend/orders/receipt.ex
7
7
  * Keep this file in sync with backend when template changes.
8
8
  */
9
+ import { openSyncDatabase } from "../db/connection";
10
+ import { getReferenceData } from "../reference/operations";
11
+ import { cacheRemoteImage, resolveImageSource } from "../image-cache/service";
9
12
  const LINE_WIDTH = 32;
10
13
  const ESC_INIT = "\x1B\x40";
11
14
  const ESC_ALIGN_LEFT = "\x1B\x61\x00";
@@ -116,6 +119,31 @@ function businessAddress(business) {
116
119
  return b.address;
117
120
  return null;
118
121
  }
122
+ function businessLogoUrl(order) {
123
+ const b = order.business;
124
+ if (!b)
125
+ return null;
126
+ return (b.logo_url ?? b.logoUrl ?? null);
127
+ }
128
+ /** Fetch remote URL (or tfimg://) and return data URI, or null on failure. Uses Web fetch; works for http(s) and for cached tfimg via Rust protocol. */
129
+ async function fetchAsDataUri(url) {
130
+ try {
131
+ const res = await fetch(url);
132
+ if (!res.ok)
133
+ return null;
134
+ const blob = await res.blob();
135
+ const reader = await new Promise((resolve, reject) => {
136
+ const fr = new FileReader();
137
+ fr.onload = () => resolve(fr.result);
138
+ fr.onerror = () => reject(new Error("FileReader failed"));
139
+ fr.readAsDataURL(blob);
140
+ });
141
+ return reader;
142
+ }
143
+ catch {
144
+ return null;
145
+ }
146
+ }
119
147
  // ── ESC/POS ────────────────────────────────────────────────────────────
120
148
  function doubleLineEscpos() { return "═".repeat(LINE_WIDTH) + LF; }
121
149
  function singleLineEscpos() { return "─".repeat(LINE_WIDTH); }
@@ -222,6 +250,53 @@ export function generateEscpos(order) {
222
250
  return parts.filter((p) => p != null).join("");
223
251
  }
224
252
  // ── HTML ───────────────────────────────────────────────────────────────
253
+ export async function generateHtmlAsync(order, opts) {
254
+ const explicit = opts?.logoDataUri;
255
+ if (explicit)
256
+ return generateHtml(order, explicit);
257
+ const logoUrl = businessLogoUrl(order);
258
+ if (!logoUrl) {
259
+ try {
260
+ const bId = order.business_id;
261
+ if (bId) {
262
+ const db = await openSyncDatabase();
263
+ const ref = await getReferenceData(db, bId, "business:profile");
264
+ const refLogo = ref?.payload?.data?.logo_url;
265
+ if (refLogo) {
266
+ const dataUri = await fetchAsDataUri(refLogo);
267
+ if (dataUri)
268
+ return generateHtml(order, dataUri);
269
+ try {
270
+ await cacheRemoteImage(refLogo, { skipIfCached: true });
271
+ const resolved = await resolveImageSource(refLogo);
272
+ const src = resolved?.uri ?? refLogo;
273
+ const dataUri2 = await fetchAsDataUri(src);
274
+ if (dataUri2)
275
+ return generateHtml(order, dataUri2);
276
+ if (src !== refLogo)
277
+ return generateHtml(order, src);
278
+ }
279
+ catch { }
280
+ return generateHtml(order, refLogo);
281
+ }
282
+ }
283
+ }
284
+ catch { }
285
+ return generateHtml(order, null);
286
+ }
287
+ try {
288
+ await cacheRemoteImage(logoUrl, { skipIfCached: true });
289
+ const resolved = await resolveImageSource(logoUrl);
290
+ const src = resolved?.uri ?? logoUrl;
291
+ const dataUri = await fetchAsDataUri(src);
292
+ if (dataUri)
293
+ return generateHtml(order, dataUri);
294
+ if (src !== logoUrl)
295
+ return generateHtml(order, src);
296
+ }
297
+ catch { }
298
+ return generateHtml(order, logoUrl);
299
+ }
225
300
  export function generateHtml(order, logoDataUri) {
226
301
  const business = order.business;
227
302
  const items = order.items || [];
@@ -7,6 +7,7 @@ import { getHttpClient } from "../adapters/http";
7
7
  import { getTcpAdapter, getPrintFileAdapter } from "../adapters/print";
8
8
  import { getPrinterState } from "./store";
9
9
  import { generateEscpos, generateHtml } from "./receipt";
10
+ import { API_BASE_URL } from "../utils/constants";
10
11
  async function fetchReceipt(orderId, format = "html") {
11
12
  const http = getHttpClient();
12
13
  // http adapter wraps axios; fallback to fetch if not set
@@ -21,7 +22,6 @@ async function fetchReceipt(orderId, format = "html") {
21
22
  return String(data);
22
23
  }
23
24
  // Fallback fetch using API_BASE_URL already injected
24
- const { API_BASE_URL } = await import("../utils/constants");
25
25
  const url = `${API_BASE_URL}/orders/${orderId}/receipt?format=${format}`;
26
26
  const resp = await fetch(url, { headers: { Accept: format === "html" ? "text/html" : "text/plain" } });
27
27
  if (!resp.ok) {
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared QueryClient factory — single source for query defaults + cache
3
+ * persistence. Apps inject only platform specifics: the error toast and the
4
+ * storage backend (MMKV on mobile, Tauri store on desktop).
5
+ */
6
+ import { QueryClient } from "@tanstack/react-query";
7
+ export interface QueryPersistStorage {
8
+ getItem: (key: string) => string | null | undefined | Promise<string | null | undefined>;
9
+ setItem: (key: string, value: string) => void | Promise<void>;
10
+ removeItem: (key: string) => void | Promise<void>;
11
+ }
12
+ export declare function createAppQueryClient(onMutationError: (message: string) => void): QueryClient;
13
+ export declare function createQueryPersister(storage: QueryPersistStorage, key?: string, throttleTime?: number): import("@tanstack/query-persist-client-core").Persister;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Shared QueryClient factory — single source for query defaults + cache
3
+ * persistence. Apps inject only platform specifics: the error toast and the
4
+ * storage backend (MMKV on mobile, Tauri store on desktop).
5
+ */
6
+ import { MutationCache, QueryClient } from "@tanstack/react-query";
7
+ import { createAsyncStoragePersister } from "@tanstack/query-async-storage-persister";
8
+ import { getConnectivityState } from "../adapters/connectivity";
9
+ export function createAppQueryClient(onMutationError) {
10
+ return new QueryClient({
11
+ mutationCache: new MutationCache({
12
+ onError: (error) => {
13
+ try {
14
+ onMutationError(error instanceof Error ? error.message : "Something went wrong");
15
+ }
16
+ catch { }
17
+ },
18
+ }),
19
+ defaultOptions: {
20
+ queries: {
21
+ // Offline-first: when the cloud is known unreachable (health probe
22
+ // failed / backend down), fail fast with zero retries — retrying a
23
+ // dead backend only multiplies error spam. Transient failures while
24
+ // reachable keep the standard 2 retries.
25
+ retry: (failureCount, error) => {
26
+ try {
27
+ if (getConnectivityState().isCloudReachable === "unreachable")
28
+ return false;
29
+ }
30
+ catch { }
31
+ // Never retry explicit offline short-circuits (axios guard / adapters).
32
+ const msg = error instanceof Error ? error.message : String(error ?? "");
33
+ if (msg.startsWith("Offline") || error?.code === "OFFLINE")
34
+ return false;
35
+ return failureCount < 2;
36
+ },
37
+ refetchOnWindowFocus: true,
38
+ refetchOnReconnect: true,
39
+ staleTime: 30 * 1000,
40
+ gcTime: 5 * 60 * 1000,
41
+ },
42
+ mutations: {
43
+ retry: 1,
44
+ },
45
+ },
46
+ });
47
+ }
48
+ export function createQueryPersister(storage, key = "teincfood-query-cache", throttleTime = 1000) {
49
+ return createAsyncStoragePersister({
50
+ storage: {
51
+ getItem: (k) => storage.getItem(k),
52
+ setItem: (k, v) => storage.setItem(k, v),
53
+ removeItem: (k) => storage.removeItem(k),
54
+ },
55
+ key,
56
+ throttleTime,
57
+ });
58
+ }
@@ -0,0 +1,18 @@
1
+ type EventCallback = (event: Record<string, unknown>) => void;
2
+ declare class EventsService {
3
+ private refCount;
4
+ private topic;
5
+ private joined;
6
+ private lastSequence;
7
+ private activeBusinessId;
8
+ private listeners;
9
+ private wildcardListeners;
10
+ private frameHandlerUnsub;
11
+ connect(userId: string, businessId?: string | null): () => void;
12
+ on(eventType: string, callback: EventCallback): () => void;
13
+ onAny(callback: EventCallback): () => void;
14
+ isConnected(): boolean;
15
+ resetSequence(): void;
16
+ }
17
+ export declare const eventsService: EventsService;
18
+ export {};