@tallyui/connector-vendure 1.0.0 → 2.0.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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +77 -25
- package/dist/index.js +558 -143
- package/package.json +12 -3
- package/src/auth.ts +74 -0
- package/src/global-settings.ts +18 -0
- package/src/index.ts +67 -44
- package/src/reconcile/ids.ts +38 -0
- package/src/reconcile/prices.ts +58 -0
- package/src/reconcile/stock.ts +48 -0
- package/src/replication/products.ts +136 -62
- package/src/replication/variant-feed.ts +108 -0
- package/src/schemas/products.ts +17 -1
- package/src/store-settings.ts +78 -0
- package/src/sync/products.ts +9 -8
- package/src/traits/product.ts +115 -23
- package/src/__tests__/product-traits.test.ts +0 -248
- package/src/replication/products.test.ts +0 -204
package/dist/index.js
CHANGED
|
@@ -1,6 +1,79 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { combinePullAdapters, createReconcileFeed } from "@tallyui/core";
|
|
3
|
+
|
|
4
|
+
// src/auth.ts
|
|
5
|
+
import { SignInError } from "@tallyui/core";
|
|
6
|
+
var LOGIN = `mutation Login($email: String!, $password: String!) {
|
|
7
|
+
login(username: $email, password: $password) {
|
|
8
|
+
__typename
|
|
9
|
+
... on CurrentUser { id }
|
|
10
|
+
... on ErrorResult { errorCode message }
|
|
11
|
+
}
|
|
12
|
+
}`;
|
|
13
|
+
var vendureSignIn = async (baseUrl, { email, password }, init = {}) => {
|
|
14
|
+
const doFetch = init.fetch ?? fetch;
|
|
15
|
+
let res;
|
|
16
|
+
try {
|
|
17
|
+
res = await doFetch(`${baseUrl}/admin-api`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
signal: init.signal,
|
|
21
|
+
body: JSON.stringify({ query: LOGIN, variables: { email, password } })
|
|
22
|
+
});
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error?.name === "AbortError" || init.signal?.aborted) throw error;
|
|
25
|
+
throw new SignInError("failed", `Could not reach Vendure at ${baseUrl}: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
let body;
|
|
28
|
+
try {
|
|
29
|
+
body = await res.json();
|
|
30
|
+
} catch {
|
|
31
|
+
throw new SignInError("server_error", `Vendure sent an unreadable response (HTTP ${res.status})`, res.status);
|
|
32
|
+
}
|
|
33
|
+
if (!res.ok) throw new SignInError("server_error", body?.errors?.[0]?.message ?? `Vendure sign-in failed (HTTP ${res.status})`, res.status);
|
|
34
|
+
const login = body?.data?.login;
|
|
35
|
+
if (body?.errors?.length || !login) {
|
|
36
|
+
throw new SignInError("server_error", body?.errors?.[0]?.message ?? `Vendure sign-in failed (HTTP ${res.status})`, res.status);
|
|
37
|
+
}
|
|
38
|
+
if (login.errorCode === "INVALID_CREDENTIALS_ERROR") throw new SignInError("invalid_credentials", login.message ?? "Invalid email or password");
|
|
39
|
+
if (login.errorCode === "NATIVE_AUTH_STRATEGY_ERROR") throw new SignInError("unsupported", login.message ?? "Native email and password sign-in is disabled on this Vendure server");
|
|
40
|
+
if (login.__typename !== "CurrentUser") throw new SignInError("server_error", login.message ?? `Vendure sign-in failed (${login.__typename})`, res.status);
|
|
41
|
+
const token = res.headers.get("vendure-auth-token");
|
|
42
|
+
if (!token) {
|
|
43
|
+
throw new SignInError("unsupported", "Vendure signed in but sent no 'vendure-auth-token' header. Add 'bearer' to the server's authOptions.tokenMethod. This connector reads the default header name, so a server that renames authOptions.authTokenHeaderKey also gives this error.");
|
|
44
|
+
}
|
|
45
|
+
return { token, expiresAt: void 0 };
|
|
46
|
+
};
|
|
47
|
+
var warnedAuthToken = false;
|
|
48
|
+
var vendureAuth = {
|
|
49
|
+
type: "Vendure Admin API",
|
|
50
|
+
fields: [
|
|
51
|
+
{ key: "url", label: "Backend URL", type: "url", placeholder: "https://my-vendure-server.com", required: true },
|
|
52
|
+
{ key: "email", label: "Email", type: "text", required: true },
|
|
53
|
+
{ key: "password", label: "Password", type: "password", required: true },
|
|
54
|
+
{ key: "channel_token", label: "Channel token (optional)", type: "text" }
|
|
55
|
+
],
|
|
56
|
+
// An API key (Vendure 3.6+) wins over a signed-in session token; channel_token selects a non-default channel.
|
|
57
|
+
// auth_token is the deprecated name for token, kept so existing apps keep working.
|
|
58
|
+
getHeaders: (credentials) => {
|
|
59
|
+
if (credentials.token === void 0 && credentials.auth_token !== void 0 && !warnedAuthToken) {
|
|
60
|
+
warnedAuthToken = true;
|
|
61
|
+
console.warn("@tallyui/connector-vendure: the 'auth_token' credential is deprecated; store the credential as 'token' instead.");
|
|
62
|
+
}
|
|
63
|
+
const token = credentials.token ?? credentials.auth_token;
|
|
64
|
+
return {
|
|
65
|
+
...credentials.api_key ? { "vendure-api-key": credentials.api_key } : token ? { Authorization: `Bearer ${token}` } : {},
|
|
66
|
+
...credentials.channel_token ? { "vendure-token": credentials.channel_token } : {}
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
signIn: vendureSignIn
|
|
70
|
+
};
|
|
71
|
+
|
|
1
72
|
// src/schemas/products.ts
|
|
2
73
|
var vendureProductSchema = {
|
|
3
|
-
|
|
74
|
+
// 1: declares variant trackInventory/outOfStockThreshold/useGlobalOutOfStockThreshold/enabled
|
|
75
|
+
// (backlog 28). A bump drops and resyncs (ConnectorSchemas, ADR-060 amendment 9).
|
|
76
|
+
version: 1,
|
|
4
77
|
primaryKey: "id",
|
|
5
78
|
type: "object",
|
|
6
79
|
properties: {
|
|
@@ -68,7 +141,21 @@ var vendureProductSchema = {
|
|
|
68
141
|
currencyCode: { type: "string" },
|
|
69
142
|
stockLevel: { type: "string" },
|
|
70
143
|
stockOnHand: { type: "number" },
|
|
144
|
+
stockLevels: {
|
|
145
|
+
type: "array",
|
|
146
|
+
items: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
stockLocationId: { type: "string" },
|
|
150
|
+
stockOnHand: { type: "number" },
|
|
151
|
+
stockAllocated: { type: "number" }
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
71
155
|
trackInventory: { type: "string" },
|
|
156
|
+
outOfStockThreshold: { type: "number" },
|
|
157
|
+
useGlobalOutOfStockThreshold: { type: "boolean" },
|
|
158
|
+
enabled: { type: "boolean" },
|
|
72
159
|
featuredAsset: {
|
|
73
160
|
type: ["object", "null"],
|
|
74
161
|
properties: {
|
|
@@ -98,53 +185,100 @@ var vendureProductSchema = {
|
|
|
98
185
|
};
|
|
99
186
|
|
|
100
187
|
// src/traits/product.ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
function productStock(variants2) {
|
|
189
|
+
if (!variants2.length) return { status: "unknown" };
|
|
190
|
+
const status = variants2.some((stock) => stock.status === "in_stock") ? "in_stock" : variants2.some((stock) => stock.status === "backorder") ? "backorder" : variants2.every((stock) => stock.status === "out_of_stock") ? "out_of_stock" : "unknown";
|
|
191
|
+
const quantity = variants2.every((stock) => stock.quantity != null) ? variants2.reduce((sum, stock) => sum + stock.quantity, 0) : void 0;
|
|
192
|
+
return quantity === void 0 ? { status } : { status, quantity };
|
|
193
|
+
}
|
|
194
|
+
function variantStock(variant, stockLocationId, globalTrackInventory, globalOutOfStockThreshold) {
|
|
195
|
+
if (!variant) return { status: "unknown" };
|
|
196
|
+
const tracked = variant.trackInventory === "TRUE" || variant.trackInventory !== "FALSE" && globalTrackInventory;
|
|
197
|
+
if (!tracked) return { status: "in_stock" };
|
|
198
|
+
const threshold = variant.useGlobalOutOfStockThreshold !== false ? globalOutOfStockThreshold : variant.outOfStockThreshold ?? 0;
|
|
199
|
+
let onHandLessAllocated;
|
|
200
|
+
if (variant.stockLevels != null) {
|
|
201
|
+
onHandLessAllocated = variant.stockLevels.filter((level) => stockLocationId == null || String(level.stockLocationId) === stockLocationId).reduce((sum, level) => sum + level.stockOnHand - level.stockAllocated, 0);
|
|
202
|
+
} else if (variant.stockOnHand != null) {
|
|
203
|
+
onHandLessAllocated = variant.stockOnHand;
|
|
204
|
+
}
|
|
205
|
+
if (onHandLessAllocated != null) {
|
|
206
|
+
const saleable = onHandLessAllocated - threshold;
|
|
207
|
+
const status = saleable <= 0 ? "out_of_stock" : onHandLessAllocated > 0 ? "in_stock" : "backorder";
|
|
208
|
+
return { status, quantity: Math.max(saleable, 0) };
|
|
209
|
+
}
|
|
210
|
+
if (variant.stockLevel === "IN_STOCK" || variant.stockLevel === "LOW_STOCK") return { status: "in_stock" };
|
|
211
|
+
if (variant.stockLevel === "OUT_OF_STOCK") return { status: "out_of_stock" };
|
|
212
|
+
return { status: "unknown" };
|
|
213
|
+
}
|
|
214
|
+
function liveVariants(doc) {
|
|
215
|
+
return (doc.variants ?? []).filter((variant) => variant.enabled !== false);
|
|
216
|
+
}
|
|
217
|
+
function createVendureProductTraits(barcodeField, stockLocationId, pricesIncludeTax = false, globalTrackInventory = true, globalOutOfStockThreshold = 0) {
|
|
218
|
+
const priceField = pricesIncludeTax ? "priceWithTax" : "price";
|
|
219
|
+
const traits = {
|
|
220
|
+
getId: (doc) => String(doc.id),
|
|
221
|
+
getName: (doc) => doc.name ?? "",
|
|
222
|
+
getSku: (doc) => doc.variants?.[0]?.sku || void 0,
|
|
223
|
+
getPrices: (doc, context) => {
|
|
224
|
+
const variant = liveVariants(doc)[0];
|
|
225
|
+
const amount = variant?.[priceField];
|
|
226
|
+
if (amount == null) return [];
|
|
227
|
+
const currency = String(variant.currencyCode ?? context?.currency ?? "XXX").toUpperCase();
|
|
228
|
+
return [{ amount, currency, kind: "base" }];
|
|
229
|
+
},
|
|
230
|
+
getVariants: (doc, context) => liveVariants(doc).map((variant) => ({
|
|
231
|
+
id: String(variant.id),
|
|
232
|
+
title: variant.name || void 0,
|
|
233
|
+
sku: variant.sku || void 0,
|
|
234
|
+
barcode: barcodeField ? variant.customFields?.[barcodeField] || void 0 : void 0,
|
|
235
|
+
prices: traits.getPrices({ variants: [variant] }, context),
|
|
236
|
+
stock: traits.getStock({ variants: [variant] })
|
|
237
|
+
})),
|
|
238
|
+
getStock: (doc) => productStock(liveVariants(doc).map((variant) => variantStock(variant, stockLocationId, globalTrackInventory, globalOutOfStockThreshold))),
|
|
239
|
+
getPrice: (doc) => {
|
|
240
|
+
const amount = liveVariants(doc)[0]?.[priceField];
|
|
241
|
+
if (amount == null) return void 0;
|
|
242
|
+
return (amount / 100).toFixed(2);
|
|
243
|
+
},
|
|
244
|
+
getRegularPrice: (doc) => {
|
|
245
|
+
const amount = liveVariants(doc)[0]?.[priceField];
|
|
246
|
+
if (amount == null) return void 0;
|
|
247
|
+
return (amount / 100).toFixed(2);
|
|
248
|
+
},
|
|
249
|
+
getSalePrice: () => {
|
|
250
|
+
return void 0;
|
|
251
|
+
},
|
|
252
|
+
isOnSale: () => {
|
|
253
|
+
return false;
|
|
254
|
+
},
|
|
255
|
+
getImageUrl: (doc) => doc.featuredAsset?.preview || doc.assets?.[0]?.preview || void 0,
|
|
256
|
+
getImageUrls: (doc) => (doc.assets ?? []).map((a) => a.preview).filter(Boolean),
|
|
257
|
+
getDescription: (doc) => doc.description || void 0,
|
|
258
|
+
// Aggregated across every variant, like getStock (backlog 28), not variant 0.
|
|
259
|
+
getStockStatus: (doc) => {
|
|
260
|
+
const status = traits.getStock(doc).status;
|
|
261
|
+
if (status === "in_stock" || status === "backorder") return "instock";
|
|
262
|
+
if (status === "out_of_stock") return "outofstock";
|
|
263
|
+
return "unknown";
|
|
264
|
+
},
|
|
265
|
+
getStockQuantity: (doc) => traits.getStock(doc).quantity ?? null,
|
|
266
|
+
hasVariants: (doc) => (doc.variants?.length ?? 0) > 1,
|
|
267
|
+
// A product with no variants at all keeps the product-level-only answer.
|
|
268
|
+
isSellable: (doc) => doc.enabled !== false && ((doc.variants?.length ?? 0) === 0 || liveVariants(doc).length > 0),
|
|
269
|
+
getVariantCount: (doc) => doc.variants ? liveVariants(doc).length : 1,
|
|
270
|
+
getType: (doc) => {
|
|
271
|
+
if ((doc.variants?.length ?? 0) > 1) return "variable";
|
|
272
|
+
return "simple";
|
|
273
|
+
},
|
|
274
|
+
getBarcode: (doc) => {
|
|
275
|
+
return barcodeField ? doc.variants?.[0]?.customFields?.[barcodeField] || void 0 : void 0;
|
|
276
|
+
},
|
|
277
|
+
getCategoryNames: (doc) => (doc.collections ?? []).map((c) => c.name).filter(Boolean)
|
|
278
|
+
};
|
|
279
|
+
return traits;
|
|
280
|
+
}
|
|
281
|
+
var vendureProductTraits = createVendureProductTraits();
|
|
148
282
|
|
|
149
283
|
// src/sync/products.ts
|
|
150
284
|
var PRODUCT_LIST_QUERY = `
|
|
@@ -158,7 +292,7 @@ var PRODUCT_LIST_QUERY = `
|
|
|
158
292
|
}
|
|
159
293
|
}
|
|
160
294
|
`;
|
|
161
|
-
var PRODUCT_DETAIL_QUERY = `
|
|
295
|
+
var PRODUCT_DETAIL_QUERY = (barcodeField) => `
|
|
162
296
|
query GetProduct($id: ID!) {
|
|
163
297
|
product(id: $id) {
|
|
164
298
|
id
|
|
@@ -179,17 +313,16 @@ var PRODUCT_DETAIL_QUERY = `
|
|
|
179
313
|
price
|
|
180
314
|
priceWithTax
|
|
181
315
|
currencyCode
|
|
182
|
-
|
|
183
|
-
stockOnHand
|
|
316
|
+
stockLevels { stockLocationId stockOnHand stockAllocated }
|
|
184
317
|
trackInventory
|
|
185
318
|
featuredAsset { id preview }
|
|
186
319
|
options { id name code }
|
|
187
|
-
customFields
|
|
320
|
+
${barcodeField ? `customFields { ${barcodeField} }` : ""}
|
|
188
321
|
}
|
|
189
322
|
}
|
|
190
323
|
}
|
|
191
324
|
`;
|
|
192
|
-
var
|
|
325
|
+
var createVendureProductSync = (barcodeField) => ({
|
|
193
326
|
fetchAllIds: async (context) => {
|
|
194
327
|
const entries = [];
|
|
195
328
|
const take = 100;
|
|
@@ -215,7 +348,7 @@ var vendureProductSync = {
|
|
|
215
348
|
fetchByIds: async (ids, context) => {
|
|
216
349
|
const products = [];
|
|
217
350
|
for (const id of ids) {
|
|
218
|
-
const res = await gql(context, PRODUCT_DETAIL_QUERY, { id });
|
|
351
|
+
const res = await gql(context, PRODUCT_DETAIL_QUERY(barcodeField), { id });
|
|
219
352
|
if (res.data?.product) {
|
|
220
353
|
products.push(res.data.product);
|
|
221
354
|
}
|
|
@@ -241,7 +374,7 @@ var vendureProductSync = {
|
|
|
241
374
|
if (!data) break;
|
|
242
375
|
totalItems = data.totalItems;
|
|
243
376
|
for (const item of data.items ?? []) {
|
|
244
|
-
const detail = await gql(context, PRODUCT_DETAIL_QUERY, { id: item.id });
|
|
377
|
+
const detail = await gql(context, PRODUCT_DETAIL_QUERY(barcodeField), { id: item.id });
|
|
245
378
|
if (detail.data?.product) {
|
|
246
379
|
products.push(detail.data.product);
|
|
247
380
|
}
|
|
@@ -250,15 +383,16 @@ var vendureProductSync = {
|
|
|
250
383
|
}
|
|
251
384
|
return products;
|
|
252
385
|
}
|
|
253
|
-
};
|
|
254
|
-
|
|
386
|
+
});
|
|
387
|
+
var vendureProductSync = createVendureProductSync();
|
|
388
|
+
async function gql(context, query2, variables) {
|
|
255
389
|
const res = await fetch(`${context.baseUrl}/admin-api`, {
|
|
256
390
|
method: "POST",
|
|
257
391
|
headers: {
|
|
258
392
|
"Content-Type": "application/json",
|
|
259
393
|
...context.headers
|
|
260
394
|
},
|
|
261
|
-
body: JSON.stringify({ query, variables }),
|
|
395
|
+
body: JSON.stringify({ query: query2, variables }),
|
|
262
396
|
signal: context.signal
|
|
263
397
|
});
|
|
264
398
|
if (!res.ok) throw new Error(`Vendure API error: ${res.status}`);
|
|
@@ -266,7 +400,8 @@ async function gql(context, query, variables) {
|
|
|
266
400
|
}
|
|
267
401
|
|
|
268
402
|
// src/replication/products.ts
|
|
269
|
-
|
|
403
|
+
import { compareIds } from "@tallyui/core";
|
|
404
|
+
var PRODUCT_LIST_QUERY2 = (barcodeField) => `
|
|
270
405
|
query GetProducts($options: ProductListOptions) {
|
|
271
406
|
products(options: $options) {
|
|
272
407
|
items {
|
|
@@ -288,136 +423,416 @@ var PRODUCT_LIST_QUERY2 = `
|
|
|
288
423
|
price
|
|
289
424
|
priceWithTax
|
|
290
425
|
currencyCode
|
|
291
|
-
|
|
292
|
-
stockOnHand
|
|
426
|
+
stockLevels { stockLocationId stockOnHand stockAllocated }
|
|
293
427
|
trackInventory
|
|
428
|
+
outOfStockThreshold
|
|
429
|
+
useGlobalOutOfStockThreshold
|
|
430
|
+
enabled
|
|
294
431
|
featuredAsset { id preview }
|
|
295
432
|
options { id name code }
|
|
296
|
-
customFields
|
|
433
|
+
${barcodeField ? `customFields { ${barcodeField} }` : ""}
|
|
297
434
|
}
|
|
298
435
|
}
|
|
299
436
|
totalItems
|
|
300
437
|
}
|
|
301
438
|
}
|
|
302
439
|
`;
|
|
303
|
-
var
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
id
|
|
307
|
-
|
|
440
|
+
var PRODUCT_MARK_QUERY = `
|
|
441
|
+
query GetProductsMark($options: ProductListOptions) {
|
|
442
|
+
products(options: $options) {
|
|
443
|
+
items { id updatedAt }
|
|
444
|
+
totalItems
|
|
308
445
|
}
|
|
309
446
|
}
|
|
310
447
|
`;
|
|
311
|
-
async function gql2(context,
|
|
448
|
+
async function gql2(context, query2, variables) {
|
|
312
449
|
const res = await fetch(`${context.baseUrl}/admin-api`, {
|
|
313
450
|
method: "POST",
|
|
314
451
|
headers: {
|
|
315
452
|
"Content-Type": "application/json",
|
|
316
453
|
...context.headers
|
|
317
454
|
},
|
|
318
|
-
body: JSON.stringify({ query, variables }),
|
|
455
|
+
body: JSON.stringify({ query: query2, variables }),
|
|
319
456
|
signal: context.signal
|
|
320
457
|
});
|
|
321
|
-
if (!res.ok)
|
|
322
|
-
|
|
458
|
+
if (!res.ok) {
|
|
459
|
+
const body2 = await res.json().catch(() => void 0);
|
|
460
|
+
const message = body2?.errors?.[0]?.message;
|
|
461
|
+
throw new Error(`Vendure API error: ${res.status}${message ? `: ${message}` : ""}`);
|
|
462
|
+
}
|
|
463
|
+
const body = await res.json();
|
|
464
|
+
if (body.errors?.length) throw new Error(`Vendure GraphQL error: ${body.errors[0].message}`);
|
|
465
|
+
return body;
|
|
466
|
+
}
|
|
467
|
+
async function probeUpdatedAtSkew(passHighWater, updatedAtSkewMs, fetchTotal, remark) {
|
|
468
|
+
for (const overlap of [1, 0]) {
|
|
469
|
+
if (overlap === 0 && updatedAtSkewMs > 0) continue;
|
|
470
|
+
const after = (mark) => new Date(Date.parse(mark) + (overlap === 0 ? 1 : -1) - updatedAtSkewMs).toISOString();
|
|
471
|
+
let total = await fetchTotal(after(passHighWater));
|
|
472
|
+
if (overlap === 1 && total === 0 && remark) {
|
|
473
|
+
const remarked = await remark();
|
|
474
|
+
if (remarked) {
|
|
475
|
+
passHighWater = remarked;
|
|
476
|
+
total = await fetchTotal(after(passHighWater));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (overlap === 1 && total === 0) {
|
|
480
|
+
throw new Error("Vendure updatedAt filters miss changes: run Vendure with TZ=UTC or set updatedAtSkewMs to at least the magnitude of the server UTC offset in milliseconds.");
|
|
481
|
+
}
|
|
482
|
+
if (overlap === 0 && total > 0) {
|
|
483
|
+
console.warn("Vendure updatedAt filters over-fetch because the server is not in UTC.");
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return passHighWater;
|
|
487
|
+
}
|
|
488
|
+
function toProductDocument(p) {
|
|
489
|
+
const doc = { _deleted: false };
|
|
490
|
+
for (const field of Object.keys(vendureProductSchema.properties)) {
|
|
491
|
+
if (p[field] !== void 0) doc[field] = p[field];
|
|
492
|
+
}
|
|
493
|
+
if (Array.isArray(doc.variants)) {
|
|
494
|
+
doc.variants = [...doc.variants].sort((a, b) => compareIds(a.id, b.id));
|
|
495
|
+
}
|
|
496
|
+
return doc;
|
|
323
497
|
}
|
|
324
|
-
var
|
|
498
|
+
var createVendureProductReplication = (barcodeField, updatedAtSkewMs = 0) => ({
|
|
325
499
|
pull: {
|
|
326
500
|
async handler(lastCheckpoint, batchSize, context) {
|
|
501
|
+
if (batchSize > 1e3) throw new Error("Vendure Admin API take must not exceed 1000");
|
|
502
|
+
if (lastCheckpoint?.skip && lastCheckpoint.passHighWater === void 0 && lastCheckpoint.passTotal === void 0) {
|
|
503
|
+
lastCheckpoint = { skip: 0, updatedAt: lastCheckpoint.updatedAt ?? "" };
|
|
504
|
+
}
|
|
505
|
+
let passHighWater = lastCheckpoint?.passHighWater ?? lastCheckpoint?.updatedAt ?? "";
|
|
506
|
+
if (!lastCheckpoint?.skip) {
|
|
507
|
+
const fetchMark = async () => {
|
|
508
|
+
const head = await gql2(context, PRODUCT_MARK_QUERY, { options: { take: 1, sort: { updatedAt: "DESC" } } });
|
|
509
|
+
return head.data?.products?.items?.[0]?.updatedAt;
|
|
510
|
+
};
|
|
511
|
+
const mark = await fetchMark();
|
|
512
|
+
passHighWater = mark ?? lastCheckpoint?.updatedAt ?? "";
|
|
513
|
+
if (mark === void 0 || lastCheckpoint?.updatedAt && passHighWater === lastCheckpoint.updatedAt) {
|
|
514
|
+
return { documents: [], checkpoint: lastCheckpoint ?? { skip: 0, updatedAt: "" } };
|
|
515
|
+
}
|
|
516
|
+
passHighWater = await probeUpdatedAtSkew(passHighWater, updatedAtSkewMs, async (after) => {
|
|
517
|
+
const probe = await gql2(context, PRODUCT_MARK_QUERY, {
|
|
518
|
+
options: { take: 1, sort: { updatedAt: "DESC" }, filter: { updatedAt: { after } } }
|
|
519
|
+
});
|
|
520
|
+
return probe.data.products.totalItems;
|
|
521
|
+
}, fetchMark);
|
|
522
|
+
}
|
|
327
523
|
const options = {
|
|
328
524
|
take: batchSize,
|
|
329
|
-
skip: lastCheckpoint?.skip ?? 0
|
|
525
|
+
skip: lastCheckpoint?.skip ?? 0,
|
|
526
|
+
sort: { id: "ASC" }
|
|
330
527
|
};
|
|
331
528
|
if (lastCheckpoint?.updatedAt) {
|
|
332
529
|
options.filter = {
|
|
333
|
-
|
|
530
|
+
// Vendure after is strict; overlap by 1 ms to include timestamp ties.
|
|
531
|
+
updatedAt: { after: new Date(Date.parse(lastCheckpoint.updatedAt) - 1 - updatedAtSkewMs).toISOString() }
|
|
334
532
|
};
|
|
335
533
|
}
|
|
336
|
-
const res = await gql2(context, PRODUCT_LIST_QUERY2, { options });
|
|
337
|
-
|
|
338
|
-
|
|
534
|
+
const res = await gql2(context, PRODUCT_LIST_QUERY2(barcodeField), { options });
|
|
535
|
+
let data = res.data?.products;
|
|
536
|
+
let passTotal = options.skip === 0 ? data.totalItems : lastCheckpoint?.passTotal ?? data.totalItems;
|
|
537
|
+
if (options.skip > 0 && data.totalItems < passTotal) {
|
|
538
|
+
options.skip = 0;
|
|
539
|
+
const restarted = await gql2(context, PRODUCT_LIST_QUERY2(barcodeField), { options });
|
|
540
|
+
data = restarted.data?.products;
|
|
541
|
+
passTotal = data.totalItems;
|
|
339
542
|
}
|
|
340
|
-
const data = res.data?.products;
|
|
341
543
|
const products = data?.items ?? [];
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
544
|
+
if (!products.length && options.skip > 0) {
|
|
545
|
+
return createVendureProductReplication(barcodeField, updatedAtSkewMs).pull.handler(
|
|
546
|
+
{ skip: 0, updatedAt: lastCheckpoint?.updatedAt ?? "" },
|
|
547
|
+
batchSize,
|
|
548
|
+
context
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
const documents = products.map(toProductDocument);
|
|
552
|
+
const checkpoint = options.skip + products.length >= data.totalItems ? { skip: 0, updatedAt: passHighWater, passHighWater: void 0, passTotal: void 0 } : {
|
|
553
|
+
skip: options.skip + products.length,
|
|
554
|
+
updatedAt: lastCheckpoint?.updatedAt ?? "",
|
|
555
|
+
passHighWater,
|
|
556
|
+
passTotal
|
|
557
|
+
};
|
|
348
558
|
return { documents, checkpoint };
|
|
349
559
|
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
var vendureProductReplication = createVendureProductReplication();
|
|
563
|
+
|
|
564
|
+
// src/replication/variant-feed.ts
|
|
565
|
+
var MAX_TAKE = 1e3;
|
|
566
|
+
var variants = (context, fields, options) => gql2(context, `
|
|
567
|
+
query GetVariants($options: ProductVariantListOptions) {
|
|
568
|
+
productVariants(options: $options) { items { ${fields} } totalItems }
|
|
569
|
+
}
|
|
570
|
+
`, { options }).then((res) => res.data.productVariants);
|
|
571
|
+
var newest = (context) => variants(context, "id updatedAt", { take: 1, sort: { updatedAt: "DESC" } });
|
|
572
|
+
var createVendureVariantFeedReplication = (barcodeField, updatedAtSkewMs = 0, variantPageSize = MAX_TAKE) => ({
|
|
573
|
+
pull: {
|
|
574
|
+
// The next call's unchanged-high-water check then ends at one read, unless
|
|
575
|
+
// a variant changed since. The skew probe runs when a pass starts from it.
|
|
576
|
+
async seedCheckpoint(context) {
|
|
577
|
+
const head = await newest(context);
|
|
578
|
+
return { skip: 0, updatedAt: head.items[0]?.updatedAt ?? "", passHighWater: void 0, passTotal: void 0 };
|
|
579
|
+
},
|
|
580
|
+
async handler(lastCheckpoint, batchSize, context) {
|
|
581
|
+
if (batchSize > MAX_TAKE || variantPageSize > MAX_TAKE) throw new Error("Vendure Admin API take must not exceed 1000");
|
|
582
|
+
let passHighWater = lastCheckpoint?.passHighWater ?? lastCheckpoint?.updatedAt ?? "";
|
|
583
|
+
let skip = lastCheckpoint?.skip ?? 0;
|
|
584
|
+
let passTotal = lastCheckpoint?.passTotal;
|
|
585
|
+
if (!skip) {
|
|
586
|
+
const head = await newest(context);
|
|
587
|
+
passHighWater = head.items[0]?.updatedAt ?? lastCheckpoint?.updatedAt ?? "";
|
|
588
|
+
if (!head.items.length || lastCheckpoint?.updatedAt && passHighWater === lastCheckpoint.updatedAt) {
|
|
589
|
+
return { documents: [], checkpoint: lastCheckpoint ?? { skip: 0, updatedAt: "" } };
|
|
590
|
+
}
|
|
591
|
+
await probeUpdatedAtSkew(passHighWater, updatedAtSkewMs, async (after) => {
|
|
592
|
+
const probe = await variants(context, "id updatedAt", { take: 1, sort: { updatedAt: "DESC" }, filter: { updatedAt: { after } } });
|
|
593
|
+
return probe.totalItems;
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
const filter = lastCheckpoint?.updatedAt ? { updatedAt: { after: new Date(Date.parse(lastCheckpoint.updatedAt) - 1 - updatedAtSkewMs).toISOString() } } : void 0;
|
|
597
|
+
const page = (from) => variants(context, "id productId", { take: variantPageSize, skip: from, sort: { id: "ASC" }, filter });
|
|
598
|
+
const seen = /* @__PURE__ */ new Set();
|
|
599
|
+
const documents = [];
|
|
600
|
+
let complete = false;
|
|
601
|
+
while (!complete && documents.length < batchSize) {
|
|
602
|
+
let data = await page(skip);
|
|
603
|
+
if (skip > 0 && (!data.items.length || data.totalItems < (passTotal ?? data.totalItems))) {
|
|
604
|
+
skip = 0;
|
|
605
|
+
data = await page(0);
|
|
606
|
+
}
|
|
607
|
+
if (skip === 0) passTotal = data.totalItems;
|
|
608
|
+
const parentIds = [...new Set(data.items.map((v) => String(v.productId)))].filter((id) => !seen.has(id));
|
|
609
|
+
parentIds.forEach((id) => seen.add(id));
|
|
610
|
+
if (parentIds.length) {
|
|
611
|
+
const res = await gql2(context, PRODUCT_LIST_QUERY2(barcodeField), {
|
|
612
|
+
options: { take: parentIds.length, filter: { id: { in: parentIds } } }
|
|
359
613
|
});
|
|
360
|
-
|
|
361
|
-
if (row.assumedMasterState) {
|
|
362
|
-
conflicts.push({ ...row.assumedMasterState, _deleted: false });
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
} catch {
|
|
366
|
-
if (row.assumedMasterState) {
|
|
367
|
-
conflicts.push({ ...row.assumedMasterState, _deleted: false });
|
|
368
|
-
}
|
|
614
|
+
documents.push(...(res.data?.products?.items ?? []).map(toProductDocument));
|
|
369
615
|
}
|
|
616
|
+
skip += data.items.length;
|
|
617
|
+
complete = skip >= data.totalItems || !data.items.length;
|
|
618
|
+
}
|
|
619
|
+
const checkpoint = complete ? { skip: 0, updatedAt: passHighWater, passHighWater: void 0, passTotal: void 0 } : { skip, updatedAt: lastCheckpoint?.updatedAt ?? "", passHighWater, passTotal };
|
|
620
|
+
const moved = ["skip", "updatedAt", "passHighWater", "passTotal"].some((key) => checkpoint[key] !== lastCheckpoint?.[key]);
|
|
621
|
+
if (!documents.length && moved) {
|
|
622
|
+
const res = await gql2(context, PRODUCT_LIST_QUERY2(barcodeField), { options: { take: 1 } });
|
|
623
|
+
documents.push(...(res.data?.products?.items ?? []).map(toProductDocument));
|
|
370
624
|
}
|
|
371
|
-
return
|
|
625
|
+
return { documents, checkpoint };
|
|
372
626
|
}
|
|
373
627
|
}
|
|
374
|
-
};
|
|
628
|
+
});
|
|
375
629
|
|
|
376
|
-
// src/
|
|
377
|
-
var
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
type: "password",
|
|
396
|
-
placeholder: "vendure-auth-token from login",
|
|
397
|
-
required: true
|
|
398
|
-
}
|
|
399
|
-
],
|
|
400
|
-
getHeaders: (credentials) => ({
|
|
401
|
-
Authorization: `Bearer ${credentials.auth_token}`
|
|
402
|
-
})
|
|
403
|
-
},
|
|
404
|
-
schemas: {
|
|
405
|
-
products: vendureProductSchema
|
|
406
|
-
},
|
|
407
|
-
traits: {
|
|
408
|
-
product: vendureProductTraits
|
|
409
|
-
},
|
|
410
|
-
sync: {
|
|
411
|
-
products: vendureProductSync
|
|
630
|
+
// src/reconcile/stock.ts
|
|
631
|
+
var PAGE_SIZE = 1e3;
|
|
632
|
+
var VARIANT_STOCK_QUERY = `
|
|
633
|
+
query VariantStock($options: ProductVariantListOptions) {
|
|
634
|
+
productVariants(options: $options) {
|
|
635
|
+
items { id stockLevels { stockLocationId stockOnHand stockAllocated } }
|
|
636
|
+
totalItems
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
`;
|
|
640
|
+
var levelsKey = (levels = []) => levels.map((l) => JSON.stringify([l.stockLocationId, l.stockOnHand, l.stockAllocated])).sort().join();
|
|
641
|
+
var vendureStockReconcile = {
|
|
642
|
+
async *fetchPages(context) {
|
|
643
|
+
for (let skip = 0, total = 1; skip < total; skip += PAGE_SIZE) {
|
|
644
|
+
const body = await gql2(context, VARIANT_STOCK_QUERY, { options: { take: PAGE_SIZE, skip, sort: { id: "ASC" } } });
|
|
645
|
+
const { items, totalItems } = body.data.productVariants;
|
|
646
|
+
total = totalItems;
|
|
647
|
+
yield new Map(items.map((v) => [v.id, v.stockLevels]));
|
|
648
|
+
}
|
|
412
649
|
},
|
|
413
|
-
|
|
414
|
-
|
|
650
|
+
overlay(doc, stock) {
|
|
651
|
+
const variants2 = doc.variants ?? [];
|
|
652
|
+
let changed = false;
|
|
653
|
+
const next = variants2.map((v) => {
|
|
654
|
+
const levels = stock.get(v.id);
|
|
655
|
+
if (!levels || levelsKey(levels) === levelsKey(v.stockLevels)) return v;
|
|
656
|
+
changed = true;
|
|
657
|
+
return { ...v, stockLevels: levels };
|
|
658
|
+
});
|
|
659
|
+
return changed ? { variants: next } : void 0;
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/reconcile/ids.ts
|
|
664
|
+
var PAGE_SIZE2 = 1e3;
|
|
665
|
+
var ID_LIST_QUERY = `
|
|
666
|
+
query GetProductIds($options: ProductListOptions) {
|
|
667
|
+
products(options: $options) { items { id variants { id } } totalItems }
|
|
668
|
+
}
|
|
669
|
+
`;
|
|
670
|
+
async function* fetchPages(context) {
|
|
671
|
+
for (let skip = 0, total = 1; skip < total; skip += PAGE_SIZE2) {
|
|
672
|
+
const res = await gql2(context, ID_LIST_QUERY, { options: { take: PAGE_SIZE2, skip, sort: { id: "ASC" } } });
|
|
673
|
+
const { items, totalItems } = res.data.products;
|
|
674
|
+
total = totalItems;
|
|
675
|
+
yield items.map((p) => ({ id: String(p.id), variantIds: (p.variants ?? []).map((v) => String(v.id)) }));
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function variantIds(doc) {
|
|
679
|
+
return (doc.variants ?? []).map((v) => String(v.id));
|
|
680
|
+
}
|
|
681
|
+
function createFetchByIds(barcodeField) {
|
|
682
|
+
return async function fetchByIds(ids, context) {
|
|
683
|
+
if (!ids.length) return [];
|
|
684
|
+
const res = await gql2(context, PRODUCT_LIST_QUERY2(barcodeField), { options: { take: ids.length, filter: { id: { in: ids } } } });
|
|
685
|
+
return (res.data?.products?.items ?? []).map(toProductDocument);
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/reconcile/prices.ts
|
|
690
|
+
var PAGE_SIZE3 = 1e3;
|
|
691
|
+
var VENDURE_PRICE_RECONCILE_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
692
|
+
var PRICE_LIST_QUERY = `
|
|
693
|
+
query GetVariantPrices($options: ProductVariantListOptions) {
|
|
694
|
+
productVariants(options: $options) { items { id productId price priceWithTax currencyCode } totalItems }
|
|
695
|
+
}
|
|
696
|
+
`;
|
|
697
|
+
var fingerprintOf = (entries) => [...entries].sort().join("|");
|
|
698
|
+
async function* fetchPages2(context) {
|
|
699
|
+
const byProduct = /* @__PURE__ */ new Map();
|
|
700
|
+
for (let skip = 0, total = 1; skip < total; skip += PAGE_SIZE3) {
|
|
701
|
+
const res = await gql2(context, PRICE_LIST_QUERY, { options: { take: PAGE_SIZE3, skip, sort: { id: "ASC" } } });
|
|
702
|
+
const { items, totalItems } = res.data.productVariants;
|
|
703
|
+
total = totalItems;
|
|
704
|
+
const touched = /* @__PURE__ */ new Set();
|
|
705
|
+
for (const variant of items) {
|
|
706
|
+
const productId = String(variant.productId);
|
|
707
|
+
touched.add(productId);
|
|
708
|
+
const entries = byProduct.get(productId) ?? [];
|
|
709
|
+
byProduct.set(productId, entries);
|
|
710
|
+
entries.push(`${variant.id}:${variant.currencyCode}:${variant.price}:${variant.priceWithTax}`);
|
|
711
|
+
}
|
|
712
|
+
yield new Map([...touched].map((id) => [id, fingerprintOf(byProduct.get(id) ?? [])]));
|
|
415
713
|
}
|
|
714
|
+
}
|
|
715
|
+
function fingerprint(doc) {
|
|
716
|
+
const entries = (doc.variants ?? []).map(
|
|
717
|
+
(v) => `${v.id}:${v.currencyCode}:${v.price}:${v.priceWithTax}`
|
|
718
|
+
);
|
|
719
|
+
return fingerprintOf(entries);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/store-settings.ts
|
|
723
|
+
import { StoreSettingsError } from "@tallyui/core";
|
|
724
|
+
var CHANNEL_AND_CATEGORIES_QUERY = `
|
|
725
|
+
query StoreSettingsChannel {
|
|
726
|
+
activeChannel { defaultCurrencyCode pricesIncludeTax defaultTaxZone { id } }
|
|
727
|
+
taxCategories { items { id isDefault } }
|
|
728
|
+
}
|
|
729
|
+
`;
|
|
730
|
+
var TAX_RATES_QUERY = `
|
|
731
|
+
query StoreSettingsTaxRates($zoneId: String!) {
|
|
732
|
+
taxRates(options: { take: 1000, filter: { zoneId: { eq: $zoneId } } }) {
|
|
733
|
+
items { enabled value category { id } customerGroup { id } }
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
`;
|
|
737
|
+
async function query(context, source, variables) {
|
|
738
|
+
try {
|
|
739
|
+
return await gql2(context, source, variables);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
throw new StoreSettingsError("failed", error instanceof Error ? error.message : String(error));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
var vendureStoreSettings = async (context, _choice) => {
|
|
745
|
+
const head = await query(context, CHANNEL_AND_CATEGORIES_QUERY);
|
|
746
|
+
const channel = head.data?.activeChannel;
|
|
747
|
+
if (!channel) throw new StoreSettingsError("failed", "Vendure returned no active channel");
|
|
748
|
+
const categories = head.data?.taxCategories?.items ?? [];
|
|
749
|
+
let rateItems = [];
|
|
750
|
+
if (channel.defaultTaxZone) {
|
|
751
|
+
const ratesBody = await query(context, TAX_RATES_QUERY, { zoneId: channel.defaultTaxZone.id });
|
|
752
|
+
rateItems = ratesBody.data?.taxRates?.items ?? [];
|
|
753
|
+
}
|
|
754
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
755
|
+
for (const rate of rateItems) {
|
|
756
|
+
if (!rate.enabled || rate.customerGroup || !rate.category) continue;
|
|
757
|
+
byCategory.set(rate.category.id, Math.round(rate.value * 1e4));
|
|
758
|
+
}
|
|
759
|
+
const defaultCategory = categories.find((c) => c.isDefault) ?? categories[0];
|
|
760
|
+
const taxRatesPpm = {
|
|
761
|
+
default: defaultCategory ? byCategory.get(defaultCategory.id) ?? 0 : 0
|
|
762
|
+
};
|
|
763
|
+
for (const [categoryId, ppm] of byCategory) taxRatesPpm[categoryId] = ppm;
|
|
764
|
+
return {
|
|
765
|
+
currency: channel.defaultCurrencyCode,
|
|
766
|
+
pricesIncludeTax: channel.pricesIncludeTax,
|
|
767
|
+
taxRatesPpm
|
|
768
|
+
};
|
|
769
|
+
};
|
|
770
|
+
|
|
771
|
+
// src/global-settings.ts
|
|
772
|
+
var GLOBAL_SETTINGS_QUERY = `
|
|
773
|
+
query GlobalStockSettings {
|
|
774
|
+
globalSettings { trackInventory outOfStockThreshold }
|
|
775
|
+
}
|
|
776
|
+
`;
|
|
777
|
+
async function vendureGlobalStockSettings(context) {
|
|
778
|
+
const res = await gql2(context, GLOBAL_SETTINGS_QUERY);
|
|
779
|
+
return res.data.globalSettings;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// src/index.ts
|
|
783
|
+
var createVendureConnector = (options = {}) => {
|
|
784
|
+
const idFeed = createReconcileFeed({ fetchByIds: createFetchByIds(options.barcodeField) });
|
|
785
|
+
return {
|
|
786
|
+
id: "vendure",
|
|
787
|
+
name: "Vendure",
|
|
788
|
+
description: "Connect to Vendure backends via the Admin GraphQL API",
|
|
789
|
+
icon: void 0,
|
|
790
|
+
auth: vendureAuth,
|
|
791
|
+
schemas: {
|
|
792
|
+
products: vendureProductSchema
|
|
793
|
+
},
|
|
794
|
+
traits: {
|
|
795
|
+
product: createVendureProductTraits(
|
|
796
|
+
options.barcodeField,
|
|
797
|
+
options.stockLocationId,
|
|
798
|
+
options.pricesIncludeTax,
|
|
799
|
+
options.globalTrackInventory,
|
|
800
|
+
options.globalOutOfStockThreshold
|
|
801
|
+
)
|
|
802
|
+
},
|
|
803
|
+
sync: {
|
|
804
|
+
products: createVendureProductSync(options.barcodeField)
|
|
805
|
+
},
|
|
806
|
+
replication: {
|
|
807
|
+
// One replication per collection: the product, variant and id-reconcile feeds
|
|
808
|
+
// share it (ADR-060). reconcile is last so its fetch wins duplicates.
|
|
809
|
+
products: combinePullAdapters({
|
|
810
|
+
products: createVendureProductReplication(options.barcodeField, options.updatedAtSkewMs),
|
|
811
|
+
variants: createVendureVariantFeedReplication(options.barcodeField, options.updatedAtSkewMs),
|
|
812
|
+
reconcile: idFeed.adapter
|
|
813
|
+
}, { legacyKey: "products" })
|
|
814
|
+
},
|
|
815
|
+
reconcile: {
|
|
816
|
+
stock: vendureStockReconcile,
|
|
817
|
+
ids: { fetchPages, variantIds, enqueue: idFeed.enqueue },
|
|
818
|
+
prices: { fetchPages: fetchPages2, fingerprint, enqueue: idFeed.enqueue }
|
|
819
|
+
},
|
|
820
|
+
storeSettings: vendureStoreSettings
|
|
821
|
+
};
|
|
416
822
|
};
|
|
823
|
+
var vendureConnector = createVendureConnector();
|
|
417
824
|
export {
|
|
825
|
+
createVendureConnector,
|
|
826
|
+
createVendureProductReplication,
|
|
827
|
+
createVendureVariantFeedReplication,
|
|
828
|
+
vendureAuth,
|
|
418
829
|
vendureConnector,
|
|
830
|
+
vendureGlobalStockSettings,
|
|
419
831
|
vendureProductReplication,
|
|
420
832
|
vendureProductSchema,
|
|
421
833
|
vendureProductSync,
|
|
422
|
-
vendureProductTraits
|
|
834
|
+
vendureProductTraits,
|
|
835
|
+
vendureSignIn,
|
|
836
|
+
vendureStockReconcile,
|
|
837
|
+
vendureStoreSettings
|
|
423
838
|
};
|