@teincfood/core 0.10.22 → 0.10.24

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Local Orders Service — SQLite is real DB (like backend Orders).
3
- * Device-partitioned by UUID PK + DD-RRR-CCC per business.
3
+ * Device-partitioned by UUID PK + DD-NNNN-MMM per business.
4
4
  * POS orders start in the business's configured flow (staged → accepted,
5
5
  * direct → delivered); online payment requires connectivity.
6
6
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Local Orders Service — SQLite is real DB (like backend Orders).
3
- * Device-partitioned by UUID PK + DD-RRR-CCC per business.
3
+ * Device-partitioned by UUID PK + DD-NNNN-MMM per business.
4
4
  * POS orders start in the business's configured flow (staged → accepted,
5
5
  * direct → delivered); online payment requires connectivity.
6
6
  */
@@ -43,6 +43,8 @@ export interface LocalNodeClient {
43
43
  contentType?: string | null;
44
44
  error?: string | null;
45
45
  }>;
46
+ /** Mirror locally cached image bytes to a connected node. */
47
+ putAsset?(businessId: string, url: string, base64: string): Promise<void>;
46
48
  publishEvent?(event: DomainEvent): Promise<void> | void;
47
49
  }
48
50
  export interface TransportAdapter {
@@ -24,6 +24,11 @@ declare class LocalNodeServiceCore {
24
24
  init(deps: LocalNodeDeps): void;
25
25
  private requestReferenceFromPeers;
26
26
  private requestAssetFromPeers;
27
+ /**
28
+ * Push locally cached image bytes to every connected node so terminals that
29
+ * cannot reach S3 (dark LAN) can fetch the same offline image.
30
+ */
31
+ putAsset(businessId: string, url: string, base64: string): Promise<boolean>;
27
32
  /**
28
33
  * Catch a newly connected peer up on orders created while the mesh was not
29
34
  * connected (e.g. a sale rung up before Local Node was enabled).
@@ -101,6 +101,30 @@ class LocalNodeServiceCore {
101
101
  }
102
102
  return null;
103
103
  }
104
+ /**
105
+ * Push locally cached image bytes to every connected node so terminals that
106
+ * cannot reach S3 (dark LAN) can fetch the same offline image.
107
+ */
108
+ async putAsset(businessId, url, base64) {
109
+ if (!businessId || !url || !base64)
110
+ return false;
111
+ let sent = false;
112
+ for (const peer of this.peers.values()) {
113
+ if (!peer.isConnected() || !peer.putAsset)
114
+ continue;
115
+ try {
116
+ await peer.putAsset(businessId, url, base64);
117
+ sent = true;
118
+ }
119
+ catch (error) {
120
+ syncLogger.debug("LocalNodeService", "asset put failed", {
121
+ url,
122
+ error: String(error),
123
+ });
124
+ }
125
+ }
126
+ return sent;
127
+ }
104
128
  async publishOrderCatchup(peerDeviceId) {
105
129
  if (!this.businessId)
106
130
  return;
@@ -181,7 +181,7 @@ export function calloutNumber(order) {
181
181
  const num = order.order_number || "";
182
182
  if (!num)
183
183
  return "----";
184
- // Strip non-digits first: local DD-RRR-CCC numbers end in "-CCC" and
184
+ // Strip non-digits first: local DD-NNNN-MMM numbers end in "-MMM" and
185
185
  // parseInt("-005") would give -5 (the old "-5" bug).
186
186
  const digits = num.slice(-4).replace(/\D/g, "");
187
187
  if (!digits)
@@ -7,7 +7,7 @@ import { generateEscpos, generateHtml } from "./receipt";
7
7
  import type { SellerOrder } from "../types/api/seller.api";
8
8
  declare function fetchReceipt(orderId: string, format?: "html" | "escpos"): Promise<string>;
9
9
  /**
10
- * Generate receipt locally without network — used for offline queued DD-RRR-CCC orders.
10
+ * Generate receipt locally without network — used for offline queued DD-NNNN-MMM orders.
11
11
  * Pass a base64 logo raster (see resolveLogoRaster) to include the thermal header logo.
12
12
  */
13
13
  export declare function generateLocalReceipt(order: SellerOrder, format?: "html" | "escpos", logoDataUri?: string | null, logoRasterB64?: string | null): string;
@@ -31,7 +31,7 @@ async function fetchReceipt(orderId, format = "html") {
31
31
  return resp.text();
32
32
  }
33
33
  /**
34
- * Generate receipt locally without network — used for offline queued DD-RRR-CCC orders.
34
+ * Generate receipt locally without network — used for offline queued DD-NNNN-MMM orders.
35
35
  * Pass a base64 logo raster (see resolveLogoRaster) to include the thermal header logo.
36
36
  */
37
37
  export function generateLocalReceipt(order, format = "escpos", logoDataUri, logoRasterB64) {
@@ -62,7 +62,7 @@ export async function printOrderReceipt(order, opts) {
62
62
  let html = null;
63
63
  if (isTcp) {
64
64
  try {
65
- // Prefer backend escpos when we have a real UUID (not DD-RRR-CCC)
65
+ // Prefer backend escpos when we have a real UUID (not a local order number)
66
66
  const isLocal = /^[0-9]{2}-[0-9]{3,4}-[0-9]{3}$/.test(order.order_number) || /^[0-9A-F-]{36}$/.test(order.id) === false && order.id === order.order_number;
67
67
  if (!isLocal || order.id) {
68
68
  try {
@@ -28,7 +28,7 @@ export declare const orderRepository: {
28
28
  * Single create path: build an `order:create` command and route it through
29
29
  * the SyncEngine (cloud → LAN node → queue). Every path returns an
30
30
  * order-shaped payload: cloud/LAN execute the real create, queue returns
31
- * the optimistic order (same UUID + DD-RRR-CCC, replayed idempotently).
31
+ * the optimistic order (same UUID + DD-NNNN-MMM, replayed idempotently).
32
32
  * Direct `createLocalOrder` bypasses sync — never call it from UI flows.
33
33
  */
34
34
  createOrder(input: CreateOrderInput): Promise<SellerOrder>;
@@ -42,7 +42,7 @@ export const orderRepository = {
42
42
  * Single create path: build an `order:create` command and route it through
43
43
  * the SyncEngine (cloud → LAN node → queue). Every path returns an
44
44
  * order-shaped payload: cloud/LAN execute the real create, queue returns
45
- * the optimistic order (same UUID + DD-RRR-CCC, replayed idempotently).
45
+ * the optimistic order (same UUID + DD-NNNN-MMM, replayed idempotently).
46
46
  * Direct `createLocalOrder` bypasses sync — never call it from UI flows.
47
47
  */
48
48
  async createOrder(input) {
@@ -194,7 +194,7 @@ function injectLocalOrderNumber(command) {
194
194
  const payload = command.payload;
195
195
  if (payload.order_number)
196
196
  return command;
197
- // 0.7.0: business-scoped DD-RRR-CCC; requires leased device_number
197
+ // Business-scoped DD-NNNN-MMM; requires a leased device_number
198
198
  const orderNumber = generateOrderNumberForBusiness(command.business_id);
199
199
  return {
200
200
  ...command,
@@ -19,6 +19,10 @@ import { getConnectivityState } from "../adapters/connectivity";
19
19
  import { CAPABILITY_BY_COMMAND } from "../reference/capabilities";
20
20
  import { openSyncDatabase } from "../db/connection";
21
21
  import { getReferenceData } from "../reference/operations";
22
+ import { commandQueueService } from "./command-queue.service";
23
+ import { applyMenuCommandLocal } from "../reference/menu-apply";
24
+ import { emitReferenceChanged } from "../reference/events";
25
+ import { isOnlineCapable } from "./online-gates";
22
26
  export async function runOfflineCapableMutation(options) {
23
27
  // Per-command capability guard before even attempting direct call
24
28
  const required = CAPABILITY_BY_COMMAND[options.commandType];
@@ -26,10 +30,10 @@ export async function runOfflineCapableMutation(options) {
26
30
  // Online-required gate first: these commands can never succeed offline —
27
31
  // throw loud immediately instead of queuing what no transport can fulfill.
28
32
  assertOnlineFor(options.commandType, options.payload);
29
- // Local-first for engine-routed domains (menu:*): commit locally via the
30
- // engine and resolve immediately. Network is sync transport only — never a
31
- // mutation delay. The engine cloud path carries the same 2.5s cap and
32
- // returns the backend response when online.
33
+ // Local-first for engine-routed domains (menu:*): persist to SQLite and the
34
+ // durable outbox immediately, update the local reference cache, and let
35
+ // replay upload to the cloud/LAN in the background. A slow or absent
36
+ // network must never hold the form open.
33
37
  if (options.commandType.startsWith("menu:")) {
34
38
  const command = buildCommand({
35
39
  commandType: options.commandType,
@@ -37,8 +41,26 @@ export async function runOfflineCapableMutation(options) {
37
41
  entityId: options.entityId,
38
42
  payload: options.payload,
39
43
  });
40
- const events = await syncEngine.execute(command);
41
- return events[0]?.payload;
44
+ try {
45
+ const { keys } = await applyMenuCommandLocal(options.businessId, command);
46
+ if (keys.length)
47
+ emitReferenceChanged(options.businessId, keys);
48
+ }
49
+ catch (error) {
50
+ syncLogger.debug("OfflineCapable", "menu local apply failed", {
51
+ error: String(error),
52
+ });
53
+ }
54
+ const event = await commandQueueService.enqueue(command);
55
+ if (isOnlineCapable()) {
56
+ // Background only: save must return now.
57
+ void syncEngine.replayQueuedCommands().catch((error) => {
58
+ syncLogger.debug("OfflineCapable", "menu background replay failed", {
59
+ error: String(error),
60
+ });
61
+ });
62
+ }
63
+ return event.payload;
42
64
  }
43
65
  try {
44
66
  const db = await openSyncDatabase();
@@ -25,7 +25,7 @@ export interface StartupReadiness {
25
25
  ready: boolean;
26
26
  /** Required keys with no cached payload. */
27
27
  missingKeys: ReferenceDataKey[];
28
- /** True when no DD-RRR-CCC device number is available (needs one online lease). */
28
+ /** True when no DD-NNNN-MMM device number is available (needs one online lease). */
29
29
  needsLease: boolean;
30
30
  /**
31
31
  * True when an entitlement envelope is absent or does not evaluate as
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teincfood/core",
3
- "version": "0.10.22",
3
+ "version": "0.10.24",
4
4
  "description": "TeincFood shared offline-first core — types, sync engine, local DB, reference data, and repositories for mobile + desktop",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",