@teincfood/core 0.9.2 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/sync/command-queue.service.js +9 -0
- package/dist/sync/engine.js +14 -1
- package/dist/sync/sync-guard.d.ts +4 -1
- package/dist/sync/sync-guard.js +15 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -141,7 +141,7 @@ export { getSyncJobsPendingCount, debugDumpSyncJobs, drainPending } from "./sync
|
|
|
141
141
|
export { performFullSync } from "./sync/full-sync";
|
|
142
142
|
export { stopHealthProbe } from "./sync/health-probe";
|
|
143
143
|
export { notifyAuthChanged, subscribeAuth } from "./adapters/auth";
|
|
144
|
-
export { isAuthenticated, hasActiveBusiness, canBusinessSync } from "./sync/sync-guard";
|
|
144
|
+
export { isAuthenticated, hasActiveBusiness, canBusinessSync, setOfflineSyncEnabled, isOfflineSyncEnabled } from "./sync/sync-guard";
|
|
145
145
|
export { ACCESS_EXPIRED_CODE, SESSION_DEAD_CODES, SKIP_AUTH_RETRY, extractAuthFailure, isAccessExpired, isSessionDead, hasAuthRetrySkip, refreshSession, withAuthRetry, } from "./auth/session";
|
|
146
146
|
export type { AuthFailure, SessionTokens } from "./auth/session";
|
|
147
147
|
export { getCachedImagePath, ensureImageCacheDir, getCachedImageInfo, cacheRemoteImage, cacheImageBatch, clearImageCache, pruneImageCache, resolveImageSource, stableCacheUrl } from "./image-cache/service";
|
package/dist/index.js
CHANGED
|
@@ -156,7 +156,7 @@ export { getSyncJobsPendingCount, debugDumpSyncJobs, drainPending } from "./sync
|
|
|
156
156
|
export { performFullSync } from "./sync/full-sync";
|
|
157
157
|
export { stopHealthProbe } from "./sync/health-probe";
|
|
158
158
|
export { notifyAuthChanged, subscribeAuth } from "./adapters/auth";
|
|
159
|
-
export { isAuthenticated, hasActiveBusiness, canBusinessSync } from "./sync/sync-guard";
|
|
159
|
+
export { isAuthenticated, hasActiveBusiness, canBusinessSync, setOfflineSyncEnabled, isOfflineSyncEnabled } from "./sync/sync-guard";
|
|
160
160
|
// ── Session lifecycle (access-expired → refresh → refresh-expired → logout)
|
|
161
161
|
export { ACCESS_EXPIRED_CODE, SESSION_DEAD_CODES, SKIP_AUTH_RETRY, extractAuthFailure, isAccessExpired, isSessionDead, hasAuthRetrySkip, refreshSession, withAuthRetry, } from "./auth/session";
|
|
162
162
|
// ── Image cache (durable) ──
|
|
@@ -13,6 +13,7 @@ import { insertCommand, getPendingCommands, markCommandInFlight, updateCommandSt
|
|
|
13
13
|
import { syncLogger } from "../utils/sync-logger";
|
|
14
14
|
import { recordAttempt } from "../utils/attempt-stats";
|
|
15
15
|
import { TransitionError, isReplayDeferred, TRANSITION_ERROR_CODES } from "./transition-errors";
|
|
16
|
+
import { assertBusinessOpenForCreate } from "../utils/business-hours";
|
|
16
17
|
import { extractApiErrorCode } from "../utils/error";
|
|
17
18
|
import { generateEventId } from "../utils/uuid";
|
|
18
19
|
import { generateLocalCalloutNumber, generateOrderNumberForBusiness, generateOrderNumber, } from "./order-number";
|
|
@@ -203,6 +204,14 @@ class CommandQueueService {
|
|
|
203
204
|
* Returns an optimistic domain event so the caller can update the UI.
|
|
204
205
|
*/
|
|
205
206
|
async enqueue(command) {
|
|
207
|
+
// Queue-time hours gate: a closed business can never legally sync an
|
|
208
|
+
// order:create, so fail fast with BusinessHoursError instead of queuing
|
|
209
|
+
// an optimistic order that would sit pending forever. Other command
|
|
210
|
+
// types (transitions, payments, delivery) are unaffected so in-progress
|
|
211
|
+
// orders can still be completed while closed.
|
|
212
|
+
if (command.command_type === "order:create") {
|
|
213
|
+
await assertBusinessOpenForCreate(command.business_id);
|
|
214
|
+
}
|
|
206
215
|
const db = getSyncDatabase();
|
|
207
216
|
const preparedCommand = injectLocalOrderNumber(command);
|
|
208
217
|
await insertCommand(db, { command: preparedCommand, status: "pending" });
|
package/dist/sync/engine.js
CHANGED
|
@@ -149,6 +149,20 @@ export class SyncEngine {
|
|
|
149
149
|
return events;
|
|
150
150
|
}
|
|
151
151
|
catch (error) {
|
|
152
|
+
// Definitive hours-guard rejection (thrown by createLocalOrder before
|
|
153
|
+
// any HTTP): the business is closed. Surface it immediately — do not
|
|
154
|
+
// mark cloud unreachable and do not fall back to the outbox, which
|
|
155
|
+
// would either re-throw from the enqueue gate or mask a real decision
|
|
156
|
+
// as a connectivity problem.
|
|
157
|
+
const code = errorCodeOf(error);
|
|
158
|
+
if (code === "BUSINESS_CLOSED" || code === "HOURS_NOT_SYNCED") {
|
|
159
|
+
syncLogger.warn("SyncEngine", "Command blocked: business closed", {
|
|
160
|
+
command_type: command.command_type,
|
|
161
|
+
command_id: command.command_id,
|
|
162
|
+
code,
|
|
163
|
+
});
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
152
166
|
// Definitive server rejection (4xx): the backend answered with a real
|
|
153
167
|
// decision (e.g. INVALID_TRANSITION, PAYMENT_REQUIRED, FORBIDDEN).
|
|
154
168
|
// Surface it instead of masking it as a connectivity problem and
|
|
@@ -159,7 +173,6 @@ export class SyncEngine {
|
|
|
159
173
|
? error.status
|
|
160
174
|
: undefined;
|
|
161
175
|
if (rejectionStatus !== undefined && rejectionStatus >= 400 && rejectionStatus < 500) {
|
|
162
|
-
const code = errorCodeOf(error);
|
|
163
176
|
syncLogger.warn("SyncEngine", "Cloud command rejected by server", {
|
|
164
177
|
command_type: command.command_type,
|
|
165
178
|
command_id: command.command_id,
|
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* the user has logged in and a business is selected. User-scoped queries
|
|
8
8
|
* (invitations, fleet) gate on auth only.
|
|
9
9
|
*/
|
|
10
|
+
export declare function setOfflineSyncEnabled(enabled: boolean): void;
|
|
11
|
+
export declare function isOfflineSyncEnabled(): boolean;
|
|
10
12
|
export declare function isAuthenticated(): boolean;
|
|
11
13
|
export declare function hasActiveBusiness(): boolean;
|
|
12
14
|
/**
|
|
13
|
-
* Business sync may run only when
|
|
15
|
+
* Business sync may run only when offline sync is enabled for the role,
|
|
16
|
+
* authenticated and a business is selected.
|
|
14
17
|
* Pass an explicit `businessId` to test that specific id; otherwise reads
|
|
15
18
|
* the active business store.
|
|
16
19
|
*/
|
package/dist/sync/sync-guard.js
CHANGED
|
@@ -9,6 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { getAuthToken } from "../adapters/auth";
|
|
11
11
|
import { getActiveBusiness } from "../adapters/business";
|
|
12
|
+
// Offline machinery (reference sync, drains, pulls, leases) runs for the
|
|
13
|
+
// seller role only. Fleet managers and riders are fully online: the app
|
|
14
|
+
// disables this flag on role change, which funnels through canBusinessSync
|
|
15
|
+
// and stops every business sync path at once.
|
|
16
|
+
let offlineSyncEnabled = true;
|
|
17
|
+
export function setOfflineSyncEnabled(enabled) {
|
|
18
|
+
offlineSyncEnabled = enabled;
|
|
19
|
+
}
|
|
20
|
+
export function isOfflineSyncEnabled() {
|
|
21
|
+
return offlineSyncEnabled;
|
|
22
|
+
}
|
|
12
23
|
export function isAuthenticated() {
|
|
13
24
|
return !!getAuthToken();
|
|
14
25
|
}
|
|
@@ -16,11 +27,14 @@ export function hasActiveBusiness() {
|
|
|
16
27
|
return !!getActiveBusiness()?.id;
|
|
17
28
|
}
|
|
18
29
|
/**
|
|
19
|
-
* Business sync may run only when
|
|
30
|
+
* Business sync may run only when offline sync is enabled for the role,
|
|
31
|
+
* authenticated and a business is selected.
|
|
20
32
|
* Pass an explicit `businessId` to test that specific id; otherwise reads
|
|
21
33
|
* the active business store.
|
|
22
34
|
*/
|
|
23
35
|
export function canBusinessSync(businessId) {
|
|
36
|
+
if (!offlineSyncEnabled)
|
|
37
|
+
return false;
|
|
24
38
|
const bid = businessId ?? getActiveBusiness()?.id;
|
|
25
39
|
return !!bid && !!getAuthToken();
|
|
26
40
|
}
|
package/package.json
CHANGED