@tallyui/connector-woocommerce 3.0.0-next.2 → 3.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/README.md CHANGED
@@ -36,7 +36,7 @@ The incremental pull can miss an edit: a `modified_after` that over-excludes (in
36
36
  ## Errors
37
37
 
38
38
  - `ConnectorUnauthorizedError`, from `@tallyui/core` and re-exported here: the store refused the request (401: the token is missing, expired or revoked; 403: signed in but not allowed), or `WooMissingTokenError` (a subclass) was thrown for a missing token. `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).
39
- - `WooPluginUpdateRequiredError` (`code: 'unsupported_store'`, `fixedBy: 'store'`): the store answered a `jwt_auth_*` 403, which WCPOS before 1.10.8 sends for a valid token (wcpos#1863). The store owner must update WCPOS to `minVersion` (`1.10.8`); the pull retries on the store schedule and the till stays signed in.
39
+ - `WooTokenRefusedError` (`code: 'store_misconfigured'`, `fixedBy: 'store'`, `fix`, and the store's own `storeCode`): the store answered a `jwt_auth_*` 403, the JWT Authentication plugin refusing the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token (wcpos#1863), and later versions can still send it when the token resolves to another user, so the error names no version. The store owner checks the JWT Authentication plugin's settings, or the till is paired again; the pull retries on the store schedule and the till stays signed in.
40
40
  - `WooDateFilterError` (`code: 'unsupported_store'`): the store returned a product outside the requested date window, so it does not honour `modified_after` (WooCommerce before 5.8, or a proxy that drops the parameter). An app should show its own words for the `code`; the message, `This store needs WooCommerce 5.8 or later to sync products.`, is a plain fallback. The diagnostics are in `productId`, `bound` (the `modified_after` sent) and `received`. Today the pull retries a failed request every 5 s (RxDB's `retryTime`), one mark request each time. Stopping on errors that cannot recover is shared replication work.
41
41
  - `WooTillUpdateRequiredError` (`code: 'till_update_required'`, `fixedBy: 'till'`): the store answered 426, normally WCPOS's protocol gate (`serverCode: 'wcpos_update_required'`), so this till must be updated; the pull pauses until `resume()`.
42
42
  - `WooMissingUuidError`: a product came back without a `uuid`, so the store is not running the WCPOS Free plugin 1.10.0 or later, or the connector is not reaching it through `wcpos/v2`.
package/dist/index.d.ts CHANGED
@@ -69,14 +69,19 @@ declare class WooDateFilterError extends Error {
69
69
  readonly minVersion = "5.8";
70
70
  constructor(productId: number | undefined, bound: string, received: string | undefined);
71
71
  }
72
- /** A stale JWT plugin 403 on WCPOS 1.10.0–1.10.7 (wcpos/woocommerce-pos#1863; fixed by 7243675c9). */
73
- declare class WooPluginUpdateRequiredError extends Error {
72
+ /**
73
+ * A `jwt_auth_*` 403: the store's JWT Authentication plugin refused the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token
74
+ * (wcpos/woocommerce-pos#1863); 1.10.8 and later can still send it when the token resolves to another user. So the
75
+ * message names no version: a version hint belongs only where a response itself carries the WCPOS version (#360).
76
+ */
77
+ declare class WooTokenRefusedError extends Error {
74
78
  readonly storeCode: string;
75
79
  name: string;
76
- readonly code: "unsupported_store";
80
+ readonly code: "store_misconfigured";
81
+ /** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
77
82
  readonly fixedBy: "store";
78
- readonly software = "WCPOS";
79
- readonly minVersion = "1.10.8";
83
+ /** The store-side remedy, for `SyncStatus`'s detail. */
84
+ readonly fix = "check the JWT Authentication plugin's settings";
80
85
  constructor(storeCode: string);
81
86
  }
82
87
  /**
@@ -134,4 +139,4 @@ declare function createWooCommerceConnector(): TallyConnector;
134
139
  */
135
140
  declare const woocommerceConnector: TallyConnector;
136
141
 
137
- export { WooDateFilterError, WooMissingTokenError, WooMissingUuidError, WooPluginUpdateRequiredError, WooTillUpdateRequiredError, createWooCommerceConnector, wcposClientPart, wooCatalogueReconcile, wooProductReplication, wooProductSchema, wooProductSync, wooProductTraits, wooReconcileFingerprint, woocommerceConnector };
142
+ export { WooDateFilterError, WooMissingTokenError, WooMissingUuidError, WooTillUpdateRequiredError, WooTokenRefusedError, createWooCommerceConnector, wcposClientPart, wooCatalogueReconcile, wooProductReplication, wooProductSchema, wooProductSync, wooProductTraits, wooReconcileFingerprint, woocommerceConnector };
package/dist/index.js CHANGED
@@ -260,7 +260,7 @@ async function checkResponse(response) {
260
260
  if (response.ok) return;
261
261
  if (response.status === 401 || response.status === 403) {
262
262
  const body = response.status === 403 ? await response.json().catch(() => void 0) : void 0;
263
- if (typeof body?.code === "string" && body.code.startsWith("jwt_auth_")) throw new WooPluginUpdateRequiredError(body.code);
263
+ if (typeof body?.code === "string" && body.code.startsWith("jwt_auth_")) throw new WooTokenRefusedError(body.code);
264
264
  throw new ConnectorUnauthorizedError(`WooCommerce API error: ${response.status}`, response.status);
265
265
  }
266
266
  if (response.status === 426) {
@@ -315,16 +315,17 @@ var WooDateFilterError = class extends Error {
315
315
  /** The first WooCommerce version that applies `modified_after` (5.8). */
316
316
  minVersion = "5.8";
317
317
  };
318
- var WooPluginUpdateRequiredError = class extends Error {
318
+ var WooTokenRefusedError = class extends Error {
319
319
  constructor(storeCode) {
320
- super("This store needs WCPOS 1.10.8 or later: the JWT Authentication plugin blocks the till on older versions.");
320
+ super("The store refused the sign-in token (403). Ask the store owner to check the JWT Authentication plugin's settings, or pair the till again.");
321
321
  this.storeCode = storeCode;
322
322
  }
323
- name = "WooPluginUpdateRequiredError";
324
- code = "unsupported_store";
323
+ name = "WooTokenRefusedError";
324
+ code = "store_misconfigured";
325
+ /** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
325
326
  fixedBy = "store";
326
- software = "WCPOS";
327
- minVersion = "1.10.8";
327
+ /** The store-side remedy, for `SyncStatus`'s detail. */
328
+ fix = "check the JWT Authentication plugin's settings";
328
329
  };
329
330
  var wooProductReplication = {
330
331
  pull: {
@@ -438,6 +439,7 @@ function wooCatalogueReconcile(feed) {
438
439
  return;
439
440
  }
440
441
  if (!failed) console.warn("WooCommerce catalogue reconcile: the fast path answered with something that is not a list; listing page by page.");
442
+ yield { entries: [], cursor: from };
441
443
  }
442
444
  for (let page = from; ; page++) {
443
445
  const params = new URLSearchParams({
@@ -461,8 +463,8 @@ function wooCatalogueReconcile(feed) {
461
463
  if (!ids.length) return [];
462
464
  const params = new URLSearchParams({ include: ids.join(","), per_page: String(PAGE_SIZE), status: "any", _fields: "id,uuid,status" });
463
465
  const rows = await get(`/products?${params}`, context);
464
- const live = new Set(rows.filter((row) => row.status === "publish").map((row) => row.id));
465
- return locals.filter((doc) => Number.isInteger(doc.id) && !live.has(doc.id)).map((doc) => doc.uuid);
466
+ const live = new Map(rows.filter((row) => row.status === "publish").map((row) => [row.id, typeof row.uuid === "string" && row.uuid.length > 0 ? row.uuid : void 0]));
467
+ return locals.filter((doc) => Number.isInteger(doc.id) && (!live.has(doc.id) || typeof live.get(doc.id) === "string" && live.get(doc.id) !== doc.uuid)).map((doc) => doc.uuid);
466
468
  },
467
469
  // An entry with no numeric id, locally or in the listing, is dropped: the store cannot be asked about it, so
468
470
  // the feed would find nothing and tombstone it without proof. (The schema requires only uuid.)
@@ -493,7 +495,7 @@ function createWooReconcileFeed() {
493
495
  }
494
496
 
495
497
  // package.json
496
- var version = "3.0.0-next.2";
498
+ var version = "3.0.0";
497
499
 
498
500
  // src/index.ts
499
501
  import { ConnectorUnauthorizedError as ConnectorUnauthorizedError3 } from "@tallyui/core";
@@ -575,8 +577,8 @@ export {
575
577
  WooDateFilterError,
576
578
  WooMissingTokenError,
577
579
  WooMissingUuidError,
578
- WooPluginUpdateRequiredError,
579
580
  WooTillUpdateRequiredError,
581
+ WooTokenRefusedError,
580
582
  createWooCommerceConnector,
581
583
  wcposClientPart,
582
584
  wooCatalogueReconcile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tallyui/connector-woocommerce",
3
- "version": "3.0.0-next.2",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "WooCommerce connector for Tally UI",
6
6
  "main": "./dist/index.js",
@@ -26,10 +26,10 @@
26
26
  "directory": "connectors/woocommerce"
27
27
  },
28
28
  "peerDependencies": {
29
- "@tallyui/core": "^3.0.0-next.2"
29
+ "@tallyui/core": "^3.0.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@tallyui/database": "3.0.0-next.2",
32
+ "@tallyui/database": "3.0.0",
33
33
  "rxdb": "17.5.0"
34
34
  },
35
35
  "scripts": {
package/src/index.ts CHANGED
@@ -119,5 +119,5 @@ export { ConnectorUnauthorizedError } from '@tallyui/core';
119
119
  export { wooProductSchema } from './schemas/products';
120
120
  export { wooProductTraits } from './traits/product';
121
121
  export { wooProductSync } from './sync/products';
122
- export { wooProductReplication, WooDateFilterError, WooPluginUpdateRequiredError, WooMissingUuidError, WooTillUpdateRequiredError } from './replication/products';
122
+ export { wooProductReplication, WooDateFilterError, WooTokenRefusedError, WooMissingUuidError, WooTillUpdateRequiredError } from './replication/products';
123
123
  export { wooCatalogueReconcile, wooReconcileFingerprint } from './reconcile/catalogue';
@@ -32,7 +32,7 @@ async function get(path: string, context: SyncContext): Promise<any> {
32
32
  * `capabilities` (a missing field is false). Never a version number, and never a failed attempt.
33
33
  * A failed status read is "no capability known"; only a 401 or 403 would make the paged listing fail the same way.
34
34
  * Only a till-class error (401 `unauthorized`, 426 `till_update_required`) or an abort stops the pass.
35
- * A 403 `forbidden` is store-class: the catalogue reconcile skips this check and tries again at the next hourly check.
35
+ * A 403 `forbidden` is store-class: the whole pass is logged `skipped` and runs again at the next check (hourly by default).
36
36
  */
37
37
  export async function wooHasIdFastPath(context: SyncContext): Promise<boolean> {
38
38
  let status: any;
@@ -86,6 +86,8 @@ export function wooCatalogueReconcile(feed: Pick<ReconcileFeed<any>, 'enqueue'>)
86
86
  return;
87
87
  }
88
88
  if (!failed) console.warn('WooCommerce catalogue reconcile: the fast path answered with something that is not a list; listing page by page.');
89
+ // An empty page, so the fallback's first request takes its own budget slot, as the status read does.
90
+ yield { entries: [], cursor: from };
89
91
  }
90
92
  // status=publish: a product that is not published is absent, so it becomes a deletion candidate and
91
93
  // confirmGone decides; drafts cost no daily refetch.
@@ -106,9 +108,11 @@ export function wooCatalogueReconcile(feed: Pick<ReconcileFeed<any>, 'enqueue'>)
106
108
  // An empty include= would list the whole store; a local without a numeric id has no proof and is kept.
107
109
  if (!ids.length) return [];
108
110
  const params = new URLSearchParams({ include: ids.join(','), per_page: String(PAGE_SIZE), status: 'any', _fields: 'id,uuid,status' });
109
- const rows: Array<{ id: number; status: string }> = await get(`/products?${params}`, context);
110
- const live = new Set(rows.filter((row) => row.status === 'publish').map((row) => row.id));
111
- return locals.filter((doc) => Number.isInteger(doc.id) && !live.has(doc.id)).map((doc) => doc.uuid);
111
+ const rows: Array<{ id: number; uuid: string; status: string }> = await get(`/products?${params}`, context);
112
+ const live = new Map(rows.filter((row) => row.status === 'publish').map((row) => [row.id, typeof row.uuid === 'string' && row.uuid.length > 0 ? row.uuid : undefined]));
113
+ // A live id backs only the store's uuid; other local copies are gone (#369).
114
+ // A row without a uuid can't prove the local copy stale, so a live id keeps it (#375 review).
115
+ return locals.filter((doc) => Number.isInteger(doc.id) && (!live.has(doc.id) || (typeof live.get(doc.id) === 'string' && live.get(doc.id) !== doc.uuid))).map((doc) => doc.uuid);
112
116
  },
113
117
  // An entry with no numeric id, locally or in the listing, is dropped: the store cannot be asked about it, so
114
118
  // the feed would find nothing and tombstone it without proof. (The schema requires only uuid.)
@@ -21,9 +21,9 @@ const MAX_STORE_MESSAGE = 200;
21
21
  export async function checkResponse(response: Response) {
22
22
  if (response.ok) return;
23
23
  if (response.status === 401 || response.status === 403) {
24
- // Older WCPOS versions leak the JWT plugin's stale 403 despite a valid token.
24
+ // A jwt_auth_* 403 is the store's JWT layer refusing the token, on any WCPOS version (#360).
25
25
  const body = response.status === 403 ? await response.json().catch(() => undefined) : undefined;
26
- if (typeof body?.code === 'string' && body.code.startsWith('jwt_auth_')) throw new WooPluginUpdateRequiredError(body.code);
26
+ if (typeof body?.code === 'string' && body.code.startsWith('jwt_auth_')) throw new WooTokenRefusedError(body.code);
27
27
  throw new ConnectorUnauthorizedError(`WooCommerce API error: ${response.status}`, response.status as 401 | 403);
28
28
  }
29
29
  if (response.status === 426) {
@@ -90,16 +90,21 @@ export class WooDateFilterError extends Error {
90
90
  }
91
91
  }
92
92
 
93
- /** A stale JWT plugin 403 on WCPOS 1.10.0–1.10.7 (wcpos/woocommerce-pos#1863; fixed by 7243675c9). */
94
- export class WooPluginUpdateRequiredError extends Error {
95
- name = 'WooPluginUpdateRequiredError';
96
- readonly code = 'unsupported_store' as const;
93
+ /**
94
+ * A `jwt_auth_*` 403: the store's JWT Authentication plugin refused the till's token. WCPOS 1.10.0–1.10.7 sent it for a valid token
95
+ * (wcpos/woocommerce-pos#1863); 1.10.8 and later can still send it when the token resolves to another user. So the
96
+ * message names no version: a version hint belongs only where a response itself carries the WCPOS version (#360).
97
+ */
98
+ export class WooTokenRefusedError extends Error {
99
+ name = 'WooTokenRefusedError';
100
+ readonly code = 'store_misconfigured' as const;
101
+ /** The store owner fixes it on the store, so the pull waits the store delay and the till stays signed in (`errorKind`). */
97
102
  readonly fixedBy = 'store' as const;
98
- readonly software = 'WCPOS';
99
- readonly minVersion = '1.10.8';
103
+ /** The store-side remedy, for `SyncStatus`'s detail. */
104
+ readonly fix = "check the JWT Authentication plugin's settings";
100
105
 
101
106
  constructor(readonly storeCode: string) {
102
- super('This store needs WCPOS 1.10.8 or later: the JWT Authentication plugin blocks the till on older versions.');
107
+ super("The store refused the sign-in token (403). Ask the store owner to check the JWT Authentication plugin's settings, or pair the till again.");
103
108
  }
104
109
  }
105
110