@teincfood/core 0.10.26 → 0.10.27
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/catalog/hooks.d.ts +8 -7
- package/dist/catalog/hooks.js +56 -50
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sync/offline-capable.d.ts +21 -0
- package/dist/sync/offline-capable.js +81 -33
- package/package.json +1 -1
package/dist/catalog/hooks.d.ts
CHANGED
|
@@ -17,19 +17,20 @@ export declare function useMenuItemsQuery(filters?: {
|
|
|
17
17
|
data: unknown[] | undefined;
|
|
18
18
|
};
|
|
19
19
|
export declare function useMenuCategoriesQuery(): ReturnType<typeof useQuery>;
|
|
20
|
-
export declare function useCreateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
21
|
-
export declare function useUpdateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
20
|
+
export declare function useCreateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, Record<string, unknown>, unknown>;
|
|
21
|
+
export declare function useUpdateMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, {
|
|
22
22
|
id: string;
|
|
23
23
|
payload: Record<string, unknown>;
|
|
24
24
|
}, unknown>;
|
|
25
|
-
export declare function useDeleteMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
26
|
-
export declare function useToggleItemAvailabilityMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
25
|
+
export declare function useDeleteMenuItemMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, string, unknown>;
|
|
26
|
+
export declare function useToggleItemAvailabilityMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, {
|
|
27
27
|
id: string;
|
|
28
28
|
available: boolean;
|
|
29
|
+
cacheRecord?: unknown;
|
|
29
30
|
}, unknown>;
|
|
30
|
-
export declare function useCreateMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
31
|
-
export declare function useUpdateMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
31
|
+
export declare function useCreateMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, Record<string, unknown>, unknown>;
|
|
32
|
+
export declare function useUpdateMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, {
|
|
32
33
|
id: string;
|
|
33
34
|
payload: Record<string, unknown>;
|
|
34
35
|
}, unknown>;
|
|
35
|
-
export declare function useDeleteMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<unknown
|
|
36
|
+
export declare function useDeleteMenuCategoryMutation(): import("@tanstack/react-query").UseMutationResult<import("..").DomainEvent<unknown>, Error, string, unknown>;
|
package/dist/catalog/hooks.js
CHANGED
|
@@ -11,8 +11,7 @@ import { menuService } from "../adapters/services";
|
|
|
11
11
|
import { getReferenceData } from "../reference/operations";
|
|
12
12
|
import { openSyncDatabase } from "../db/connection";
|
|
13
13
|
import { referenceSyncService } from "../reference/service";
|
|
14
|
-
import {
|
|
15
|
-
import { runOfflineCapableMutation } from "../sync/offline-capable";
|
|
14
|
+
import { commitMenuMutationLocal } from "../sync/offline-capable";
|
|
16
15
|
import { useIsEffectivelyOnline } from "../hooks/use-infinite-list";
|
|
17
16
|
export const CATALOG_KEYS = {
|
|
18
17
|
all: ["menu"],
|
|
@@ -134,21 +133,24 @@ export function useMenuCategoriesQuery() {
|
|
|
134
133
|
error: hasCache ? null : networkQuery.error,
|
|
135
134
|
};
|
|
136
135
|
}
|
|
137
|
-
// Mutations —
|
|
136
|
+
// Mutations — local-first.
|
|
137
|
+
//
|
|
138
|
+
// The only work on the mutation path is the local SQLite commit. Reference
|
|
139
|
+
// fan-out and cloud/LAN replay are scheduled by commitMenuMutationLocal AFTER
|
|
140
|
+
// the outbox row exists, so a slow or absent network can never delay a save.
|
|
141
|
+
// Invalidating the cached query key makes the committed row render immediately.
|
|
138
142
|
export function useCreateMenuItemMutation() {
|
|
139
143
|
const businessId = useBusinessId();
|
|
140
144
|
const qc = useQueryClient();
|
|
141
145
|
return useMutation({
|
|
142
|
-
mutationFn: (payload) => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
},
|
|
146
|
+
mutationFn: (payload) => commitMenuMutationLocal({
|
|
147
|
+
commandType: "menu:create_item",
|
|
148
|
+
businessId: businessId,
|
|
149
|
+
entityId: payload.id,
|
|
150
|
+
payload,
|
|
151
|
+
}),
|
|
148
152
|
onSuccess: () => {
|
|
149
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
150
|
-
if (businessId)
|
|
151
|
-
emitReferenceChanged(businessId, ["menu:items"]);
|
|
153
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
152
154
|
},
|
|
153
155
|
});
|
|
154
156
|
}
|
|
@@ -156,15 +158,14 @@ export function useUpdateMenuItemMutation() {
|
|
|
156
158
|
const businessId = useBusinessId();
|
|
157
159
|
const qc = useQueryClient();
|
|
158
160
|
return useMutation({
|
|
159
|
-
mutationFn: ({ id, payload }) => {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
161
|
+
mutationFn: ({ id, payload }) => commitMenuMutationLocal({
|
|
162
|
+
commandType: "menu:update_item",
|
|
163
|
+
businessId: businessId,
|
|
164
|
+
entityId: id,
|
|
165
|
+
payload,
|
|
166
|
+
}),
|
|
164
167
|
onSuccess: () => {
|
|
165
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
166
|
-
if (businessId)
|
|
167
|
-
emitReferenceChanged(businessId, ["menu:items"]);
|
|
168
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
168
169
|
},
|
|
169
170
|
});
|
|
170
171
|
}
|
|
@@ -172,11 +173,14 @@ export function useDeleteMenuItemMutation() {
|
|
|
172
173
|
const businessId = useBusinessId();
|
|
173
174
|
const qc = useQueryClient();
|
|
174
175
|
return useMutation({
|
|
175
|
-
mutationFn: (id) =>
|
|
176
|
+
mutationFn: (id) => commitMenuMutationLocal({
|
|
177
|
+
commandType: "menu:delete_item",
|
|
178
|
+
businessId: businessId,
|
|
179
|
+
entityId: id,
|
|
180
|
+
payload: { id },
|
|
181
|
+
}),
|
|
176
182
|
onSuccess: () => {
|
|
177
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
178
|
-
if (businessId)
|
|
179
|
-
emitReferenceChanged(businessId, ["menu:items"]);
|
|
183
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
180
184
|
},
|
|
181
185
|
});
|
|
182
186
|
}
|
|
@@ -184,13 +188,14 @@ export function useToggleItemAvailabilityMutation() {
|
|
|
184
188
|
const businessId = useBusinessId();
|
|
185
189
|
const qc = useQueryClient();
|
|
186
190
|
return useMutation({
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
191
|
+
mutationFn: ({ id, available, cacheRecord }) => commitMenuMutationLocal({
|
|
192
|
+
commandType: "menu:update_item",
|
|
193
|
+
businessId: businessId,
|
|
194
|
+
entityId: id,
|
|
195
|
+
payload: cacheRecord ? { id, available, cacheRecord } : { id, available },
|
|
196
|
+
}),
|
|
190
197
|
onSuccess: () => {
|
|
191
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
192
|
-
if (businessId)
|
|
193
|
-
emitReferenceChanged(businessId, ["menu:items"]);
|
|
198
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.items(businessId) });
|
|
194
199
|
},
|
|
195
200
|
});
|
|
196
201
|
}
|
|
@@ -198,15 +203,14 @@ export function useCreateMenuCategoryMutation() {
|
|
|
198
203
|
const businessId = useBusinessId();
|
|
199
204
|
const qc = useQueryClient();
|
|
200
205
|
return useMutation({
|
|
201
|
-
mutationFn: (payload) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
+
mutationFn: (payload) => commitMenuMutationLocal({
|
|
207
|
+
commandType: "menu:create_category",
|
|
208
|
+
businessId: businessId,
|
|
209
|
+
entityId: payload.id,
|
|
210
|
+
payload,
|
|
211
|
+
}),
|
|
206
212
|
onSuccess: () => {
|
|
207
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
208
|
-
if (businessId)
|
|
209
|
-
emitReferenceChanged(businessId, ["menu:categories"]);
|
|
213
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
210
214
|
},
|
|
211
215
|
});
|
|
212
216
|
}
|
|
@@ -214,15 +218,14 @@ export function useUpdateMenuCategoryMutation() {
|
|
|
214
218
|
const businessId = useBusinessId();
|
|
215
219
|
const qc = useQueryClient();
|
|
216
220
|
return useMutation({
|
|
217
|
-
mutationFn: ({ id, payload }) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
mutationFn: ({ id, payload }) => commitMenuMutationLocal({
|
|
222
|
+
commandType: "menu:update_category",
|
|
223
|
+
businessId: businessId,
|
|
224
|
+
entityId: id,
|
|
225
|
+
payload: { ...payload, id },
|
|
226
|
+
}),
|
|
222
227
|
onSuccess: () => {
|
|
223
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
224
|
-
if (businessId)
|
|
225
|
-
emitReferenceChanged(businessId, ["menu:categories"]);
|
|
228
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
226
229
|
},
|
|
227
230
|
});
|
|
228
231
|
}
|
|
@@ -230,11 +233,14 @@ export function useDeleteMenuCategoryMutation() {
|
|
|
230
233
|
const businessId = useBusinessId();
|
|
231
234
|
const qc = useQueryClient();
|
|
232
235
|
return useMutation({
|
|
233
|
-
mutationFn: (id) =>
|
|
236
|
+
mutationFn: (id) => commitMenuMutationLocal({
|
|
237
|
+
commandType: "menu:delete_category",
|
|
238
|
+
businessId: businessId,
|
|
239
|
+
entityId: id,
|
|
240
|
+
payload: { id },
|
|
241
|
+
}),
|
|
234
242
|
onSuccess: () => {
|
|
235
|
-
qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
236
|
-
if (businessId)
|
|
237
|
-
emitReferenceChanged(businessId, ["menu:categories"]);
|
|
243
|
+
void qc.invalidateQueries({ queryKey: CATALOG_KEYS.categories(businessId) });
|
|
238
244
|
},
|
|
239
245
|
});
|
|
240
246
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -73,8 +73,8 @@ export { localNodeTransport } from "./sync/transport-local-node";
|
|
|
73
73
|
export * from "./sync/transport-local-node";
|
|
74
74
|
export * from "./sync/order-snapshots.service";
|
|
75
75
|
export { hydratePendingOrders, applyPendingOrderEvent, applyOrderStateChangeEvent } from "./sync/pending-orders.service";
|
|
76
|
-
export { runOfflineCapableMutation } from "./sync/offline-capable";
|
|
77
|
-
export type { OfflineCapableOptions } from "./sync/offline-capable";
|
|
76
|
+
export { commitMenuMutationLocal, runOfflineCapableMutation } from "./sync/offline-capable";
|
|
77
|
+
export type { LocalMenuMutationOptions, OfflineCapableOptions } from "./sync/offline-capable";
|
|
78
78
|
export { orderRepository } from "./repositories/order.repository";
|
|
79
79
|
export type { CreateOrderInput, AssignRiderInput, OrderActionInput } from "./repositories/order.repository";
|
|
80
80
|
export { posRepository } from "./repositories/pos.repository";
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ export { localNodeTransport } from "./sync/transport-local-node";
|
|
|
75
75
|
export * from "./sync/transport-local-node";
|
|
76
76
|
export * from "./sync/order-snapshots.service";
|
|
77
77
|
export { hydratePendingOrders, applyPendingOrderEvent, applyOrderStateChangeEvent } from "./sync/pending-orders.service";
|
|
78
|
-
export { runOfflineCapableMutation } from "./sync/offline-capable";
|
|
78
|
+
export { commitMenuMutationLocal, runOfflineCapableMutation } from "./sync/offline-capable";
|
|
79
79
|
// ── Repositories ──
|
|
80
80
|
export { orderRepository } from "./repositories/order.repository";
|
|
81
81
|
export { posRepository } from "./repositories/pos.repository";
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Keeps the online path and its result shape unchanged.
|
|
12
12
|
*/
|
|
13
|
+
import type { DomainEvent } from "./types";
|
|
13
14
|
export interface OfflineCapableOptions<TService, TCommandPayload> {
|
|
14
15
|
/** Direct REST call — used online. */
|
|
15
16
|
run: () => Promise<TService>;
|
|
@@ -19,4 +20,24 @@ export interface OfflineCapableOptions<TService, TCommandPayload> {
|
|
|
19
20
|
entityId?: string;
|
|
20
21
|
payload: TCommandPayload;
|
|
21
22
|
}
|
|
23
|
+
export interface LocalMenuMutationOptions<TCommandPayload> {
|
|
24
|
+
/** Must be a `menu:*` command. */
|
|
25
|
+
commandType: string;
|
|
26
|
+
businessId: string;
|
|
27
|
+
entityId?: string;
|
|
28
|
+
payload: TCommandPayload;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The ONLY menu write path.
|
|
32
|
+
*
|
|
33
|
+
* Local SQLite is the source of truth: capability/entitlement checks read the
|
|
34
|
+
* cached proof, the menu row is applied to the reference cache, and the
|
|
35
|
+
* command is inserted into the durable outbox. All of that completes before
|
|
36
|
+
* this promise resolves. No HTTP, connectivity probe, image upload, reference
|
|
37
|
+
* pull, LAN call, or replay is allowed here.
|
|
38
|
+
*
|
|
39
|
+
* Consumers must treat the returned event as the committed local state and
|
|
40
|
+
* close the form immediately. Sync is a separate background worker.
|
|
41
|
+
*/
|
|
42
|
+
export declare function commitMenuMutationLocal<TCommandPayload>(options: LocalMenuMutationOptions<TCommandPayload>): Promise<DomainEvent>;
|
|
22
43
|
export declare function runOfflineCapableMutation<TService, TCommandPayload>(options: OfflineCapableOptions<TService, TCommandPayload>): Promise<TService>;
|
|
@@ -46,49 +46,97 @@ async function assertCachedCapability(businessId, required) {
|
|
|
46
46
|
throw e;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
businessId: options.businessId,
|
|
68
|
-
entityId: options.entityId,
|
|
69
|
-
payload: options.payload,
|
|
70
|
-
});
|
|
71
|
-
try {
|
|
72
|
-
const { keys } = await applyMenuCommandLocal(options.businessId, command);
|
|
73
|
-
if (keys.length)
|
|
74
|
-
emitReferenceChanged(options.businessId, keys);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
syncLogger.debug("OfflineCapable", "menu local apply failed", {
|
|
78
|
-
error: String(error),
|
|
79
|
-
});
|
|
49
|
+
/**
|
|
50
|
+
* Publish fan-out only after the local transaction has committed.
|
|
51
|
+
*
|
|
52
|
+
* This is deliberately scheduled off the mutation's promise: reference sync
|
|
53
|
+
* and replay are downstream of SQLite, never a prerequisite for it. If either
|
|
54
|
+
* fails, the local row and outbox stay intact for the next retry.
|
|
55
|
+
*/
|
|
56
|
+
function scheduleMenuPostCommit(businessId, keys) {
|
|
57
|
+
setTimeout(() => {
|
|
58
|
+
if (keys.length > 0) {
|
|
59
|
+
try {
|
|
60
|
+
emitReferenceChanged(businessId, keys);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
syncLogger.debug("OfflineCapable", "menu reference fan-out failed", {
|
|
64
|
+
error: String(error),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
80
67
|
}
|
|
81
|
-
const event = await commandQueueService.enqueue(command);
|
|
82
68
|
if (isOnlineCapable()) {
|
|
83
|
-
// Background only: save must return now.
|
|
84
69
|
void syncEngine.replayQueuedCommands().catch((error) => {
|
|
85
70
|
syncLogger.debug("OfflineCapable", "menu background replay failed", {
|
|
86
71
|
error: String(error),
|
|
87
72
|
});
|
|
88
73
|
});
|
|
89
74
|
}
|
|
75
|
+
}, 0);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The ONLY menu write path.
|
|
79
|
+
*
|
|
80
|
+
* Local SQLite is the source of truth: capability/entitlement checks read the
|
|
81
|
+
* cached proof, the menu row is applied to the reference cache, and the
|
|
82
|
+
* command is inserted into the durable outbox. All of that completes before
|
|
83
|
+
* this promise resolves. No HTTP, connectivity probe, image upload, reference
|
|
84
|
+
* pull, LAN call, or replay is allowed here.
|
|
85
|
+
*
|
|
86
|
+
* Consumers must treat the returned event as the committed local state and
|
|
87
|
+
* close the form immediately. Sync is a separate background worker.
|
|
88
|
+
*/
|
|
89
|
+
export async function commitMenuMutationLocal(options) {
|
|
90
|
+
if (!options.commandType.startsWith("menu:")) {
|
|
91
|
+
throw new Error(`commitMenuMutationLocal only accepts menu:* commands, got ${options.commandType}`);
|
|
92
|
+
}
|
|
93
|
+
// Cached permission proof — local reads only. The backend remains the final
|
|
94
|
+
// authority when the queued command replays.
|
|
95
|
+
const required = CAPABILITY_BY_COMMAND[options.commandType];
|
|
96
|
+
if (required)
|
|
97
|
+
await assertCachedCapability(options.businessId, required);
|
|
98
|
+
await assertEntitledForCommand(options.businessId, options.commandType);
|
|
99
|
+
const command = buildCommand({
|
|
100
|
+
commandType: options.commandType,
|
|
101
|
+
businessId: options.businessId,
|
|
102
|
+
entityId: options.entityId,
|
|
103
|
+
payload: options.payload,
|
|
104
|
+
});
|
|
105
|
+
// 1) local read model, 2) durable outbox. Both are local writes.
|
|
106
|
+
let keys = [];
|
|
107
|
+
try {
|
|
108
|
+
const applied = await applyMenuCommandLocal(options.businessId, command);
|
|
109
|
+
keys = applied.keys;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
// The outbox row is still authoritative and will re-apply on replay.
|
|
113
|
+
syncLogger.debug("OfflineCapable", "menu local apply failed", {
|
|
114
|
+
error: String(error),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const event = await commandQueueService.enqueue(command);
|
|
118
|
+
// 3) only now may the rest of the system hear about it.
|
|
119
|
+
scheduleMenuPostCommit(options.businessId, keys);
|
|
120
|
+
return event;
|
|
121
|
+
}
|
|
122
|
+
export async function runOfflineCapableMutation(options) {
|
|
123
|
+
// Menu writes have a dedicated local-first API. Keep this branch for legacy
|
|
124
|
+
// callers (e.g. image upload follow-ups) but route it through the same
|
|
125
|
+
// commit-only implementation.
|
|
126
|
+
if (options.commandType.startsWith("menu:")) {
|
|
127
|
+
const event = await commitMenuMutationLocal({
|
|
128
|
+
commandType: options.commandType,
|
|
129
|
+
businessId: options.businessId,
|
|
130
|
+
entityId: options.entityId,
|
|
131
|
+
payload: options.payload,
|
|
132
|
+
});
|
|
90
133
|
return event.payload;
|
|
91
134
|
}
|
|
135
|
+
// Online-required gate first: these commands can never succeed offline —
|
|
136
|
+
// throw loud immediately instead of queuing what no transport can fulfill.
|
|
137
|
+
assertOnlineFor(options.commandType, options.payload);
|
|
138
|
+
// Per-command capability guard before touching local state or the network.
|
|
139
|
+
const required = CAPABILITY_BY_COMMAND[options.commandType];
|
|
92
140
|
if (required)
|
|
93
141
|
await assertCachedCapability(options.businessId, required);
|
|
94
142
|
try {
|
package/package.json
CHANGED