@tallyui/connector-vendure 3.0.0-next.0 → 3.0.0-next.1
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/README.md +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -5
- package/package.json +4 -4
- package/src/capabilities.ts +6 -5
- package/src/replication/products.ts +1 -1
- package/src/session-probe.ts +2 -1
- package/src/store-settings.ts +11 -3
- package/src/sync/products.ts +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
## Errors
|
|
4
4
|
|
|
5
|
-
Requests with expired or rejected credentials reject with `ConnectorUnauthorizedError
|
|
5
|
+
Requests with expired or rejected credentials reject with `ConnectorUnauthorizedError`, defined in `@tallyui/core` and re-exported by this connector. `error.status` is `401` (`code: 'unauthorized'`, sign in again) or `403` (`code: 'forbidden'`, signed in but not allowed: the pull retries on the store schedule; don't sign out); a signed-out Vendure session is always `401`.
|
|
6
6
|
|
|
7
7
|
Sign-in itself rejects with `SignInError`.
|
package/dist/index.d.ts
CHANGED
|
@@ -85,7 +85,7 @@ declare const vendureStockReconcile: StockReconcileAdapter;
|
|
|
85
85
|
/**
|
|
86
86
|
* Reads the active channel's currency and tax-inclusivity (ADR-049), and the
|
|
87
87
|
* default tax zone's enabled, non-customer-group rates keyed by tax category
|
|
88
|
-
* id, in integer ppm rounded once. Read-only (ADR-048). Ignores `choice`:
|
|
88
|
+
* id, in integer ppm rounded once, with their rate names for grouping. Read-only (ADR-048). Ignores `choice`:
|
|
89
89
|
* Vendure's channel is already chosen by the `vendure-token` header sent
|
|
90
90
|
* with every request, so there is no `pricingContext` either.
|
|
91
91
|
*/
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ async function signedOut(context) {
|
|
|
29
29
|
}
|
|
30
30
|
async function forbiddenError(context, forbidden) {
|
|
31
31
|
const out = await signedOut(context);
|
|
32
|
-
if (out === true) return new ConnectorUnauthorizedError(`Vendure GraphQL error: ${forbidden[0].message}
|
|
32
|
+
if (out === true) return new ConnectorUnauthorizedError(`Vendure GraphQL error: ${forbidden[0].message}`, 401);
|
|
33
33
|
const what = forbidden.map(({ message, path }) => path?.length ? `${path.join(".")}: ${message}` : message).join("; ");
|
|
34
34
|
if (out === false) return new Error(`Vendure GraphQL error: FORBIDDEN: the store refused this request although the session is signed in (a permission is missing): ${what}`);
|
|
35
35
|
return new Error(`Vendure GraphQL error: FORBIDDEN: ${what} (the session check failed, so this may be transient)`);
|
|
@@ -47,7 +47,7 @@ async function readVendureCapabilities(baseUrl, headers, init = {}) {
|
|
|
47
47
|
}
|
|
48
48
|
if (res.status === 401 || res.status === 403) {
|
|
49
49
|
const out = await signedOut({ connectorId: "vendure", baseUrl, headers, signal: init.signal });
|
|
50
|
-
if (out === true) throw new ConnectorUnauthorizedError2(`Vendure refused ${CAPABILITIES_PATH}: the session is signed out`,
|
|
50
|
+
if (out === true) throw new ConnectorUnauthorizedError2(`Vendure refused ${CAPABILITIES_PATH}: the session is signed out`, 401);
|
|
51
51
|
if (out === false) throw new Error(`Vendure refused ${CAPABILITIES_PATH} although the session is signed in: the CreateOrder permission is missing (HTTP ${res.status})`);
|
|
52
52
|
return void 0;
|
|
53
53
|
}
|
|
@@ -57,7 +57,7 @@ async function readVendureCapabilities(baseUrl, headers, init = {}) {
|
|
|
57
57
|
try {
|
|
58
58
|
body = await res.json();
|
|
59
59
|
} catch {
|
|
60
|
-
return
|
|
60
|
+
return void 0;
|
|
61
61
|
}
|
|
62
62
|
return parseInfoCapabilities(body, (reason) => console.warn(`@tallyui/connector-vendure: ${reason}`));
|
|
63
63
|
}
|
|
@@ -824,7 +824,7 @@ var CHANNEL_AND_CATEGORIES_QUERY = `
|
|
|
824
824
|
var TAX_RATES_QUERY = `
|
|
825
825
|
query StoreSettingsTaxRates($zoneId: String!) {
|
|
826
826
|
taxRates(options: { take: 1000, filter: { zoneId: { eq: $zoneId } } }) {
|
|
827
|
-
items { enabled value category { id } customerGroup { id } }
|
|
827
|
+
items { name enabled value category { id } customerGroup { id } }
|
|
828
828
|
}
|
|
829
829
|
}
|
|
830
830
|
`;
|
|
@@ -846,11 +846,18 @@ var vendureStoreSettings = async (context, _choice) => {
|
|
|
846
846
|
rateItems = ratesBody.data?.taxRates?.items ?? [];
|
|
847
847
|
}
|
|
848
848
|
const byCategory = /* @__PURE__ */ new Map();
|
|
849
|
+
const namesByCategory = /* @__PURE__ */ new Map();
|
|
849
850
|
for (const rate of rateItems) {
|
|
850
851
|
if (!rate.enabled || rate.customerGroup || !rate.category) continue;
|
|
851
852
|
byCategory.set(rate.category.id, Math.round(rate.value * 1e4));
|
|
853
|
+
if (typeof rate.name === "string" && rate.name) namesByCategory.set(rate.category.id, rate.name);
|
|
854
|
+
else namesByCategory.delete(rate.category.id);
|
|
852
855
|
}
|
|
853
856
|
const defaultCategory = categories.find((c) => c.isDefault) ?? categories[0];
|
|
857
|
+
const taxRateCodes = {};
|
|
858
|
+
const defaultName = defaultCategory && namesByCategory.get(defaultCategory.id);
|
|
859
|
+
if (defaultName) taxRateCodes.default = defaultName;
|
|
860
|
+
for (const [categoryId, name] of namesByCategory) taxRateCodes[categoryId] = name;
|
|
854
861
|
const taxRatesPpm = {
|
|
855
862
|
default: defaultCategory ? byCategory.get(defaultCategory.id) ?? 0 : 0
|
|
856
863
|
};
|
|
@@ -859,7 +866,8 @@ var vendureStoreSettings = async (context, _choice) => {
|
|
|
859
866
|
return {
|
|
860
867
|
currency: channel.defaultCurrencyCode,
|
|
861
868
|
pricesIncludeTax: channel.pricesIncludeTax,
|
|
862
|
-
taxRatesPpm
|
|
869
|
+
taxRatesPpm,
|
|
870
|
+
...Object.keys(taxRateCodes).length > 0 && { taxRateCodes }
|
|
863
871
|
};
|
|
864
872
|
};
|
|
865
873
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/connector-vendure",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vendure connector for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"directory": "connectors/vendure"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@tallyui/core": "^3.0.0-next.
|
|
29
|
+
"@tallyui/core": "^3.0.0-next.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@tallyui/database": "3.0.0-next.
|
|
33
|
-
"@tallyui/pos": "3.0.0-next.
|
|
32
|
+
"@tallyui/database": "3.0.0-next.1",
|
|
33
|
+
"@tallyui/pos": "3.0.0-next.1",
|
|
34
34
|
"rxdb": "17.5.0"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
package/src/capabilities.ts
CHANGED
|
@@ -7,9 +7,9 @@ export const CAPABILITIES_PATH = '/tally/v1/info';
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Reads the store's contract capabilities (ADR-062) and `taxRounding` (#287) with the
|
|
10
|
-
* connector's own headers. A
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* connector's own headers. A 404 means an old plugin and gives the default; a network failure,
|
|
11
|
+
* non-2xx other than 404/401/403, non-JSON 2xx or malformed `taxRounding` is unknown and gives
|
|
12
|
+
* `undefined`. The route answers a dead session and a missing
|
|
13
13
|
* `CreateOrder` permission alike with 403, so a 401/403 asks who is signed in (#279): nobody
|
|
14
14
|
* throws `ConnectorUnauthorizedError`, an administrator throws a plain Error naming the
|
|
15
15
|
* permission, and a failed check is unknown.
|
|
@@ -28,7 +28,8 @@ export async function readVendureCapabilities(
|
|
|
28
28
|
}
|
|
29
29
|
if (res.status === 401 || res.status === 403) {
|
|
30
30
|
const out = await signedOut({ connectorId: 'vendure', baseUrl, headers, signal: init.signal });
|
|
31
|
-
|
|
31
|
+
// The probe confirms sign-out; the route's 403 also covers missing permissions.
|
|
32
|
+
if (out === true) throw new ConnectorUnauthorizedError(`Vendure refused ${CAPABILITIES_PATH}: the session is signed out`, 401);
|
|
32
33
|
if (out === false) throw new Error(`Vendure refused ${CAPABILITIES_PATH} although the session is signed in: the CreateOrder permission is missing (HTTP ${res.status})`);
|
|
33
34
|
return undefined;
|
|
34
35
|
}
|
|
@@ -38,7 +39,7 @@ export async function readVendureCapabilities(
|
|
|
38
39
|
try {
|
|
39
40
|
body = await res.json();
|
|
40
41
|
} catch {
|
|
41
|
-
return
|
|
42
|
+
return undefined;
|
|
42
43
|
}
|
|
43
44
|
return parseInfoCapabilities(body, (reason) => console.warn(`@tallyui/connector-vendure: ${reason}`));
|
|
44
45
|
}
|
|
@@ -81,7 +81,7 @@ export async function gql(
|
|
|
81
81
|
if (!res.ok) {
|
|
82
82
|
const body = await res.json().catch(() => undefined) as GqlBody | undefined;
|
|
83
83
|
const message = body?.errors?.[0]?.message;
|
|
84
|
-
if (res.status === 401 || res.status === 403) throw new ConnectorUnauthorizedError(`Vendure API error: ${res.status}${message ? `: ${message}` : ''}`, res.status);
|
|
84
|
+
if (res.status === 401 || res.status === 403) throw new ConnectorUnauthorizedError(`Vendure API error: ${res.status}${message ? `: ${message}` : ''}`, res.status as 401 | 403);
|
|
85
85
|
throw new Error(`Vendure API error: ${res.status}${message ? `: ${message}` : ''}`);
|
|
86
86
|
}
|
|
87
87
|
const body = await res.json() as GqlBody;
|
package/src/session-probe.ts
CHANGED
|
@@ -32,7 +32,8 @@ export async function signedOut(context: SyncContext): Promise<boolean | undefin
|
|
|
32
32
|
*/
|
|
33
33
|
export async function forbiddenError(context: SyncContext, forbidden: Array<{ message?: string; path?: ReadonlyArray<string | number> }>): Promise<Error> {
|
|
34
34
|
const out = await signedOut(context);
|
|
35
|
-
|
|
35
|
+
// The probe confirmed a signed-out session even though Vendure answered FORBIDDEN.
|
|
36
|
+
if (out === true) return new ConnectorUnauthorizedError(`Vendure GraphQL error: ${forbidden[0].message}`, 401);
|
|
36
37
|
const what = forbidden.map(({ message, path }) => (path?.length ? `${path.join('.')}: ${message}` : message)).join('; ');
|
|
37
38
|
if (out === false) return new Error(`Vendure GraphQL error: FORBIDDEN: the store refused this request although the session is signed in (a permission is missing): ${what}`);
|
|
38
39
|
return new Error(`Vendure GraphQL error: FORBIDDEN: ${what} (the session check failed, so this may be transient)`);
|
package/src/store-settings.ts
CHANGED
|
@@ -13,14 +13,14 @@ const CHANNEL_AND_CATEGORIES_QUERY = `
|
|
|
13
13
|
const TAX_RATES_QUERY = `
|
|
14
14
|
query StoreSettingsTaxRates($zoneId: String!) {
|
|
15
15
|
taxRates(options: { take: 1000, filter: { zoneId: { eq: $zoneId } } }) {
|
|
16
|
-
items { enabled value category { id } customerGroup { id } }
|
|
16
|
+
items { name enabled value category { id } customerGroup { id } }
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
`;
|
|
20
20
|
|
|
21
21
|
type ActiveChannel = { defaultCurrencyCode: string; pricesIncludeTax: boolean; defaultTaxZone: { id: string } | null };
|
|
22
22
|
type TaxCategory = { id: string; isDefault: boolean };
|
|
23
|
-
type TaxRateItem = { enabled: boolean; value: number; category: { id: string } | null; customerGroup: { id: string } | null };
|
|
23
|
+
type TaxRateItem = { name?: string; enabled: boolean; value: number; category: { id: string } | null; customerGroup: { id: string } | null };
|
|
24
24
|
|
|
25
25
|
/** `gql` throws a plain `Error` for a non-OK response or a GraphQL `errors` body; both become `StoreSettingsError('failed')` here. */
|
|
26
26
|
async function query(context: SyncContext, source: string, variables?: Record<string, unknown>): Promise<any> {
|
|
@@ -34,7 +34,7 @@ async function query(context: SyncContext, source: string, variables?: Record<st
|
|
|
34
34
|
/**
|
|
35
35
|
* Reads the active channel's currency and tax-inclusivity (ADR-049), and the
|
|
36
36
|
* default tax zone's enabled, non-customer-group rates keyed by tax category
|
|
37
|
-
* id, in integer ppm rounded once. Read-only (ADR-048). Ignores `choice`:
|
|
37
|
+
* id, in integer ppm rounded once, with their rate names for grouping. Read-only (ADR-048). Ignores `choice`:
|
|
38
38
|
* Vendure's channel is already chosen by the `vendure-token` header sent
|
|
39
39
|
* with every request, so there is no `pricingContext` either.
|
|
40
40
|
*/
|
|
@@ -51,10 +51,13 @@ export const vendureStoreSettings = async (context: SyncContext, _choice?: Store
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const byCategory = new Map<string, number>();
|
|
54
|
+
const namesByCategory = new Map<string, string>();
|
|
54
55
|
for (const rate of rateItems) {
|
|
55
56
|
if (!rate.enabled || rate.customerGroup || !rate.category) continue;
|
|
56
57
|
// Rounded once, here, at the connector's edge (a backend decimal percentage -> integer ppm).
|
|
57
58
|
byCategory.set(rate.category.id, Math.round(rate.value * 10_000));
|
|
59
|
+
if (typeof rate.name === 'string' && rate.name) namesByCategory.set(rate.category.id, rate.name);
|
|
60
|
+
else namesByCategory.delete(rate.category.id);
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
// Vendure's own fallback for a variant created without a category
|
|
@@ -65,6 +68,10 @@ export const vendureStoreSettings = async (context: SyncContext, _choice?: Store
|
|
|
65
68
|
// at all), `default` is 0 — that is what Vendure itself charges in that
|
|
66
69
|
// case, never a silent guess.
|
|
67
70
|
const defaultCategory = categories.find((c) => c.isDefault) ?? categories[0];
|
|
71
|
+
const taxRateCodes: Record<string, string> = {};
|
|
72
|
+
const defaultName = defaultCategory && namesByCategory.get(defaultCategory.id);
|
|
73
|
+
if (defaultName) taxRateCodes.default = defaultName;
|
|
74
|
+
for (const [categoryId, name] of namesByCategory) taxRateCodes[categoryId] = name;
|
|
68
75
|
const taxRatesPpm: { default: number; [taxClass: string]: number } = {
|
|
69
76
|
default: defaultCategory ? byCategory.get(defaultCategory.id) ?? 0 : 0,
|
|
70
77
|
};
|
|
@@ -77,5 +84,6 @@ export const vendureStoreSettings = async (context: SyncContext, _choice?: Store
|
|
|
77
84
|
currency: channel.defaultCurrencyCode,
|
|
78
85
|
pricesIncludeTax: channel.pricesIncludeTax,
|
|
79
86
|
taxRatesPpm,
|
|
87
|
+
...(Object.keys(taxRateCodes).length > 0 && { taxRateCodes }),
|
|
80
88
|
};
|
|
81
89
|
};
|
package/src/sync/products.ts
CHANGED
|
@@ -152,7 +152,7 @@ async function gql(
|
|
|
152
152
|
signal: context.signal,
|
|
153
153
|
});
|
|
154
154
|
|
|
155
|
-
if (res.status === 401 || res.status === 403) throw new ConnectorUnauthorizedError(`Vendure API error: ${res.status}`, res.status);
|
|
155
|
+
if (res.status === 401 || res.status === 403) throw new ConnectorUnauthorizedError(`Vendure API error: ${res.status}`, res.status as 401 | 403);
|
|
156
156
|
if (!res.ok) throw new Error(`Vendure API error: ${res.status}`);
|
|
157
157
|
const body: { data?: any; errors?: Array<{ message?: string; path?: Array<string | number>; extensions?: { code?: string } }> } = await res.json();
|
|
158
158
|
const forbidden = body.errors?.filter((error) => error.extensions?.code === 'FORBIDDEN') ?? [];
|