@peabody-soft/mbs-model-ts 1.7.0 → 1.8.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.
Files changed (64) hide show
  1. package/dist/Attachment.js +2 -2
  2. package/dist/AttachmentBaseUrl.js +4 -4
  3. package/dist/AuthTokens.js +7 -7
  4. package/dist/Business.js +11 -10
  5. package/dist/BusinessIdentifier.js +3 -2
  6. package/dist/BusinessLink.js +3 -2
  7. package/dist/CartEditor.js +89 -81
  8. package/dist/Catalog.d.ts +19 -7
  9. package/dist/Catalog.js +121 -41
  10. package/dist/Constants.d.ts +1 -0
  11. package/dist/Constants.js +7 -5
  12. package/dist/ContainerNames.js +6 -6
  13. package/dist/Entity.js +4 -3
  14. package/dist/Enums.d.ts +1 -0
  15. package/dist/Enums.js +3 -2
  16. package/dist/Exceptions.js +2 -2
  17. package/dist/GeneralSettings.d.ts +5 -4
  18. package/dist/GeneralSettings.js +17 -15
  19. package/dist/HttpStatusCodes.js +7 -7
  20. package/dist/Ids.js +4 -5
  21. package/dist/Item.js +8 -5
  22. package/dist/LiveData.js +2 -2
  23. package/dist/Measures.js +3 -2
  24. package/dist/Message.js +1 -1
  25. package/dist/MockDatabase.js +6 -5
  26. package/dist/Model.js +166 -162
  27. package/dist/Order.js +10 -12
  28. package/dist/Package.d.ts +6 -2
  29. package/dist/Package.js +10 -5
  30. package/dist/PackageIdentifier.js +2 -2
  31. package/dist/Price.js +6 -5
  32. package/dist/PriceList.d.ts +13 -0
  33. package/dist/PriceList.js +34 -2
  34. package/dist/Product.d.ts +6 -2
  35. package/dist/Product.js +15 -10
  36. package/dist/Profile.js +15 -14
  37. package/dist/ProtoMap.js +189 -206
  38. package/dist/ServiceExceptions.js +6 -6
  39. package/dist/ServiceProxy.js +15 -14
  40. package/dist/ShoppingList.js +102 -100
  41. package/dist/SyncCache.js +76 -68
  42. package/dist/SyncKeys.js +5 -5
  43. package/dist/SyncState.js +14 -6
  44. package/dist/Tax.js +11 -10
  45. package/dist/TaxHelper.js +9 -8
  46. package/dist/TaxType.js +7 -6
  47. package/dist/UserProfile.js +7 -7
  48. package/dist/WorkingDays.js +21 -18
  49. package/dist/generated/BusinessSyncOutResponse.d.ts +4 -1
  50. package/dist/generated/Constants.d.ts +0 -4
  51. package/dist/generated/Constants.js +0 -4
  52. package/dist/generated/GeneralSettings.d.ts +5 -4
  53. package/dist/generated/GeneralSettings.js +8 -6
  54. package/dist/generated/Item.d.ts +4 -3
  55. package/dist/generated/Message.d.ts +5 -0
  56. package/dist/generated/OnlineStoreTheme.d.ts +33 -0
  57. package/dist/generated/OnlineStoreTheme.js +38 -0
  58. package/dist/generated/PriceList.d.ts +3 -2
  59. package/dist/generated/ProfileSyncOutRequest.d.ts +16 -15
  60. package/dist/generated/ProfileSyncOutResponse.d.ts +4 -6
  61. package/dist/generated/SalesOrderPayload.d.ts +6 -3
  62. package/dist/index.d.ts +4 -0
  63. package/dist/index.js +11 -8
  64. package/package.json +2 -2
package/dist/Catalog.js CHANGED
@@ -1,51 +1,70 @@
1
1
  import { Constants as proto_Constants } from "./generated/Constants";
2
- import { EntityHeader } from "./Entity";
2
+ import { DEFAULT_SALE_PRICE_LIST_ID } from "./Constants";
3
+ import { EntityHeader, UpdatableEntity } from "./Entity";
3
4
  import ProductCategory from "./ProductCategory";
4
5
  // The catalog of a business: its categories and the products in them. Not an entity and not on the
5
- // wire. The sync layer rebuilds it from the flat item entities of a sync response.
6
+ // wire. The sync layer builds it from the flat item entities of a sync response, as the merchant
7
+ // app's Catalog is built.
8
+ //
9
+ // The constructor links what it is given: a package to its product, a price to its price list and a
10
+ // price list to its parent. It does not depend on any price list being present. Packages are priced
11
+ // from currentSalePriceListId, so setting that id decides which packages and products are buyable.
6
12
  class Catalog {
7
- constructor(categories, products, _priceList) {
8
- this._priceList = _priceList;
13
+ constructor(categories, products, packages, priceLists, prices) {
14
+ // Every package that has a product, by that product's id, whether it can be bought or not
15
+ this._candidatePackages = new Map();
16
+ this._priceListMap = new Map();
17
+ this._currentSalePriceListId = DEFAULT_SALE_PRICE_LIST_ID;
18
+ // What a customer can reach, rebuilt by applySalePriceList
19
+ this._categories = Object.freeze([]);
20
+ this._products = Object.freeze([]);
9
21
  this._categoryMap = new Map();
10
22
  this._productMap = new Map();
11
23
  this._packageMap = new Map();
24
+ this._allCategories = categories;
25
+ this._allProducts = products;
12
26
  // A dummy category for products whose parentItemId is Constants.ENTITY_ID_NULL. Its id is also
13
27
  // ENTITY_ID_NULL, so getCategory(product.productCategoryId) resolves it like a real category.
14
- // It is left out below like any other category when it has no products.
15
- const uncategorized = new ProductCategory(new EntityHeader(BigInt(proto_Constants.ENTITY_ID_NULL), 0n, 0n, 0n, 0n), "", null, BigInt(proto_Constants.ENTITY_ID_NULL), false);
16
- // Only a buyable product is part of a catalog: enabled, not sold only offline, with at least one
17
- // buyable package. Any other is left out of products, getProduct and its categories, and its
18
- // packages are unreachable by id. Enforced here so no builder can expose a product that cannot
19
- // be bought.
20
- const buyableProducts = [];
21
- const productsByCategory = new Map();
22
- for (const product of products) {
23
- if (!product.isBuyable)
28
+ // It is left out like any other category when it has no buyable products.
29
+ this._uncategorized = new ProductCategory(new EntityHeader(BigInt(proto_Constants.ENTITY_ID_NULL), 0n, 0n, 0n, 0n), "", null, BigInt(proto_Constants.ENTITY_ID_NULL), false);
30
+ // A package with no product is dropped
31
+ const productsById = new Map();
32
+ for (const product of products)
33
+ productsById.set(product.id, product);
34
+ for (const pkg of packages) {
35
+ const product = productsById.get(pkg.productId);
36
+ if (product === undefined)
24
37
  continue;
25
- buyableProducts.push(product);
26
- this._productMap.set(product.id, product);
27
- const productsOfCategory = productsByCategory.get(product.productCategoryId);
28
- if (productsOfCategory === undefined)
29
- productsByCategory.set(product.productCategoryId, [product]);
38
+ UpdatableEntity.setParent(pkg, product);
39
+ const productPackages = this._candidatePackages.get(product.id);
40
+ if (productPackages === undefined)
41
+ this._candidatePackages.set(product.id, [pkg]);
30
42
  else
31
- productsOfCategory.push(product);
32
- for (const pkg of product.packages)
33
- this._packageMap.set(pkg.id, pkg);
43
+ productPackages.push(pkg);
34
44
  }
35
- this._products = Object.freeze(buyableProducts);
36
- // A category that no buyable product reached is left out rather than shown empty. This covers
37
- // the dummy too.
38
- const nonEmptyCategories = [];
39
- for (const category of [...categories, uncategorized]) {
40
- const categoryProducts = productsByCategory.get(category.id);
41
- if (categoryProducts === undefined)
45
+ // The first of two price lists with one id is kept. A price with no price list is dropped, and a
46
+ // list holds one price per package, the first to arrive.
47
+ for (const priceList of priceLists)
48
+ if (!this._priceListMap.has(priceList.id))
49
+ this._priceListMap.set(priceList.id, priceList);
50
+ const pricesByList = new Map();
51
+ for (const price of prices) {
52
+ if (!this._priceListMap.has(price.priceListId))
42
53
  continue;
43
- category.products = Object.freeze(categoryProducts);
44
- nonEmptyCategories.push(category);
54
+ const listPrices = pricesByList.get(price.priceListId);
55
+ if (listPrices === undefined)
56
+ pricesByList.set(price.priceListId, new Map([[price.packageId, price]]));
57
+ else if (!listPrices.has(price.packageId))
58
+ listPrices.set(price.packageId, price);
45
59
  }
46
- this._categories = Object.freeze(nonEmptyCategories);
47
- for (const category of this._categories)
48
- this._categoryMap.set(category.id, category);
60
+ // A parent that was not read, or a list that names itself, leaves the list without a parent
61
+ for (const priceList of this._priceListMap.values()) {
62
+ const updatable = priceList;
63
+ updatable.prices = pricesByList.get(priceList.id) ?? new Map();
64
+ const parent = this._priceListMap.get(priceList.parentPriceListId) ?? null;
65
+ updatable.parentPriceList = parent === priceList ? null : parent;
66
+ }
67
+ this.applySalePriceList();
49
68
  }
50
69
  // In no particular order - a UI orders these for display
51
70
  get categories() {
@@ -55,11 +74,22 @@ class Catalog {
55
74
  get products() {
56
75
  return this._products;
57
76
  }
58
- // The one price list a storefront sells from (MeraBills Online). Null until it arrives. Every
59
- // price in this catalog belongs to it, and prices in other lists are left out. A shopping list
60
- // names this list, or falls back to its id when it has none set.
77
+ // The id of the price list packages are priced from, DEFAULT_SALE_PRICE_LIST_ID until the sync
78
+ // layer sets it. Setting it prices every package from that list, or from its parent where the list
79
+ // has no price, and keeps the packages, products and categories that can then be bought. An id
80
+ // that names no list prices nothing, so the catalog holds nothing.
81
+ get currentSalePriceListId() {
82
+ return this._currentSalePriceListId;
83
+ }
84
+ set currentSalePriceListId(value) {
85
+ this._currentSalePriceListId = value;
86
+ this.applySalePriceList();
87
+ }
88
+ // The price list packages are priced from (MeraBills Online for a storefront). Null when the
89
+ // catalog has no list with the current id, as before the catalog has arrived. A shopping list
90
+ // built from this catalog carries its id, or MERABILLS_ONLINE_PRICE_LIST_ID while it is null.
61
91
  get priceList() {
62
- return this._priceList;
92
+ return this._priceListMap.get(this._currentSalePriceListId) ?? null;
63
93
  }
64
94
  get productCount() {
65
95
  return this._products.length;
@@ -75,7 +105,8 @@ class Catalog {
75
105
  return this._productMap.get(productId) ?? null;
76
106
  }
77
107
  // Null for the four reserved package ids a shopping list uses for its charge and summary lines
78
- // (see Ids.isReservedPackageId), and for a package whose product was deleted or is not buyable
108
+ // (see Ids.isReservedPackageId), and for a package that is not buyable or whose product is not
109
+ // buyable
79
110
  getPackage(packageId) {
80
111
  return this._packageMap.get(packageId) ?? null;
81
112
  }
@@ -90,7 +121,7 @@ class Catalog {
90
121
  return byPrice;
91
122
  return Catalog.compareIds(left, right);
92
123
  }
93
- // Display comparator for items generally: case insensitive, numeric, id-tiebroken
124
+ // Display comparator for items generally: case insensitive, numeric, ties broken by id
94
125
  static compareByName(left, right) {
95
126
  const byName = Catalog.compareNames(left.name, right.name);
96
127
  if (byName !== 0)
@@ -111,7 +142,56 @@ class Catalog {
111
142
  return -1;
112
143
  return left.offerPrice - right.offerPrice;
113
144
  }
145
+ // Prices the packages from the current sale price list, then rebuilds what a customer can reach.
146
+ // Only a buyable product is part of a catalog: enabled, not sold only offline, with at least one
147
+ // buyable package. Any other is left out of products, getProduct and its categories, and its
148
+ // packages are unreachable by id. A category with no buyable products is left out too, the dummy
149
+ // one included.
150
+ applySalePriceList() {
151
+ const salePriceList = this.priceList;
152
+ const buyableProducts = [];
153
+ const productMap = new Map();
154
+ const packageMap = new Map();
155
+ const productsByCategory = new Map();
156
+ for (const product of this._allProducts) {
157
+ const candidates = this._candidatePackages.get(product.id) ?? [];
158
+ for (const pkg of candidates) {
159
+ const price = salePriceList === null ? null : salePriceList.getPrice(pkg.id);
160
+ pkg.price = price;
161
+ if (price !== null)
162
+ UpdatableEntity.setParent(price, pkg);
163
+ }
164
+ // The product keeps only the buyable ones, and is judged by them
165
+ product.packages = candidates;
166
+ if (!product.isBuyable)
167
+ continue;
168
+ buyableProducts.push(product);
169
+ productMap.set(product.id, product);
170
+ const productsOfCategory = productsByCategory.get(product.productCategoryId);
171
+ if (productsOfCategory === undefined)
172
+ productsByCategory.set(product.productCategoryId, [product]);
173
+ else
174
+ productsOfCategory.push(product);
175
+ for (const pkg of product.packages)
176
+ packageMap.set(pkg.id, pkg);
177
+ }
178
+ const nonEmptyCategories = [];
179
+ const categoryMap = new Map();
180
+ for (const category of [...this._allCategories, this._uncategorized]) {
181
+ const categoryProducts = productsByCategory.get(category.id);
182
+ category.products = Object.freeze(categoryProducts ?? []);
183
+ if (categoryProducts === undefined)
184
+ continue;
185
+ nonEmptyCategories.push(category);
186
+ categoryMap.set(category.id, category);
187
+ }
188
+ this._products = Object.freeze(buyableProducts);
189
+ this._categories = Object.freeze(nonEmptyCategories);
190
+ this._productMap = productMap;
191
+ this._packageMap = packageMap;
192
+ this._categoryMap = categoryMap;
193
+ }
114
194
  }
115
195
  // A business whose catalog has not arrived yet
116
- Catalog.EMPTY = new Catalog(Object.freeze([]), Object.freeze([]), null);
196
+ Catalog.EMPTY = new Catalog([], [], [], [], []);
117
197
  export default Catalog;
@@ -1,5 +1,6 @@
1
1
  export { Constants } from "./generated/Constants";
2
2
  export declare function buildPriceListId(priceList: number): bigint;
3
+ export declare const DEFAULT_SALE_PRICE_LIST_ID: bigint;
3
4
  export declare const MERABILLS_ONLINE_PRICE_LIST_ID: bigint;
4
5
  export declare const MAX_SCALED_AMOUNT: bigint;
5
6
  export declare function amountFromScaledValue(scaledAmount: bigint): number;
package/dist/Constants.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Constants as proto_Constants } from "./generated/Constants";
2
2
  import Ids from "./Ids";
3
3
  // The protocol's limits and scaling factors, and the wire-to-UI value conversions.
4
- // Limits are re-exported so a Java constant has only one place to be wrong.
4
+ // Limits are re-exported from the generated code so each Java constant is defined in one place.
5
5
  export { Constants } from "./generated/Constants";
6
6
  // The id of a built-in price list, from the low part Constants carries for it:
7
7
  // DEFAULT_SALE_PRICE_LIST, DEFAULT_PURCHASE_PRICE_LIST, MERABILLS_ONLINE_PRICE_LIST,
@@ -11,12 +11,14 @@ export { Constants } from "./generated/Constants";
11
11
  export function buildPriceListId(priceList) {
12
12
  return Ids.buildId(proto_Constants.PRICE_LIST_ENTITY_TYPE_ID, BigInt(priceList));
13
13
  }
14
- // The only price list this library reads. A business prices a package in several lists (over the
15
- // counter, on a marketplace), and only this one is the storefront's. Prices in other lists are
16
- // skipped, see ProtoMap.buildBusiness.
14
+ // The list a catalog sells from until another is set, as in the merchant app's Catalog
15
+ export const DEFAULT_SALE_PRICE_LIST_ID = buildPriceListId(proto_Constants.DEFAULT_SALE_PRICE_LIST);
16
+ // The list a storefront sells from. A business prices a package in several lists (over the counter,
17
+ // on a marketplace), and a response can carry any of its public ones. ProtoMap.buildBusiness sets
18
+ // the catalog to sell from this one, and a package with no price in it is priced from its parent.
17
19
  export const MERABILLS_ONLINE_PRICE_LIST_ID = buildPriceListId(proto_Constants.MERABILLS_ONLINE_PRICE_LIST);
18
20
  // The largest amount the protocol can carry (Java Transaction.MAX_AMOUNT). Kept here because it does
19
- // not fit a protobuf enum value. Unscaled it is well below 2^53, so an amount is safe as a number.
21
+ // not fit a protobuf enum value. It is 2^44 - 1, well below 2^53, so an amount is safe as a number.
20
22
  export const MAX_SCALED_AMOUNT = 17592186044415n;
21
23
  // An amount, price or discount travels multiplied by Constants.AMOUNT_FACTOR, bounded by
22
24
  // MAX_SCALED_AMOUNT, so it's safe as a number even though it arrives as an int64.
@@ -9,12 +9,12 @@ export function forBusiness(nameSuffix) {
9
9
  export function forProfile(nameSuffix) {
10
10
  return `${VERSION_PREFIX}${PROFILE_PREFIX}${nameSuffix}`;
11
11
  }
12
- // Every name carries the layout version of its contents, so a change in how a container is stored
13
- // needs no migration: the new version uses names nothing has written, and the old containers stay
14
- // inert until deleted. Bump it whenever a stored blob changes meaning, since callers cannot be
15
- // relied on to clear a store and a half-understood container is worse than no cache.
12
+ // Every name carries the layout version of its contents, so changing how a container is stored
13
+ // needs no migration. The new version uses names nothing has written, and the old containers stay
14
+ // unused until deleted. Bump it whenever a stored blob changes meaning. Callers cannot be relied
15
+ // on to clear a store, and a container read with the wrong meaning is worse than no cache.
16
16
  const VERSION_PREFIX = "V1.";
17
- // Neither id space is this library's to constrain, so without a prefix a subject id equal to a
18
- // business id would put a profile and a catalog in one container
17
+ // This library does not control either id space. Without a prefix, a subject id equal to a
18
+ // business id would put a profile and a catalog in one container.
19
19
  const BUSINESS_PREFIX = "b.";
20
20
  const PROFILE_PREFIX = "p.";
package/dist/Entity.js CHANGED
@@ -35,8 +35,9 @@ class Entity {
35
35
  get updateType() {
36
36
  return Number(this._header.updateNumber & Entity.UPDATE_TYPE_MASK);
37
37
  }
38
- // A deleted entity is a tombstone: it carries what the entity was when deleted. A UI never reaches
39
- // one, except an order whose message is deleted, which is an order the business has completed.
38
+ // A deleted entity is a tombstone: it carries what the entity was when deleted. A UI is never
39
+ // given one, except an order whose message is deleted, which is an order the business has
40
+ // completed.
40
41
  get isDeleted() {
41
42
  return this.updateType === proto_Constants.UPDATE_TYPE_DELETED;
42
43
  }
@@ -52,7 +53,7 @@ class Entity {
52
53
  Entity.UPDATE_TYPE_MASK = BigInt(proto_Constants.UPDATE_TYPE_MASK);
53
54
  export default Entity;
54
55
  // The fields of the Entity message every entity carries. Passed as one value because they are all
55
- // bigint and could be transposed silently at a call site.
56
+ // bigint, so swapping two at a call site would not be a type error.
56
57
  export class EntityHeader {
57
58
  constructor(id, createdAt, updatedAt, updateNumber, updateInfo) {
58
59
  this.id = id;
package/dist/Enums.d.ts CHANGED
@@ -5,6 +5,7 @@ export { BusinessIdentifierType } from "./generated/BusinessIdentifierType";
5
5
  export { DimensionUnit } from "./generated/DimensionUnit";
6
6
  export { FulfillmentMode } from "./generated/FulfillmentMode";
7
7
  export { MessageType } from "./generated/MessageType";
8
+ export { OnlineStoreTheme } from "./generated/OnlineStoreTheme";
8
9
  export { OrderStatus } from "./generated/OrderStatus";
9
10
  export { PackageIdentifierType } from "./generated/PackageIdentifierType";
10
11
  export { PaymentMethod } from "./generated/PaymentMethod";
package/dist/Enums.js CHANGED
@@ -1,5 +1,5 @@
1
- // Every enum of the protocol, re-exported so their Java-defined numeric ids have only one place to
2
- // be wrong. The list also records which enums are part of the library's surface.
1
+ // Every enum of the protocol, re-exported so each Java-defined numeric id is defined in one place.
2
+ // The list also records which enums are part of the library's surface.
3
3
  // TaxType is the exception: the protocol does not carry it, so it is defined in TaxType.ts.
4
4
  export { AddressType } from "./generated/AddressType";
5
5
  export { AttachmentType } from "./generated/AttachmentType";
@@ -8,6 +8,7 @@ export { BusinessIdentifierType } from "./generated/BusinessIdentifierType";
8
8
  export { DimensionUnit } from "./generated/DimensionUnit";
9
9
  export { FulfillmentMode } from "./generated/FulfillmentMode";
10
10
  export { MessageType } from "./generated/MessageType";
11
+ export { OnlineStoreTheme } from "./generated/OnlineStoreTheme";
11
12
  export { OrderStatus } from "./generated/OrderStatus";
12
13
  export { PackageIdentifierType } from "./generated/PackageIdentifierType";
13
14
  export { PaymentMethod } from "./generated/PaymentMethod";
@@ -1,5 +1,5 @@
1
- // The two failures this library raises. Both are programming errors that a UI does not recover
2
- // from. A sync failure surfaces as a rejected promise.
1
+ // The two programming errors this library throws. A UI does not recover from either. A sync
2
+ // failure is a rejected promise (see ServiceExceptions.ts).
3
3
  // A caller passed something this library cannot work with
4
4
  export class InvalidArgumentException extends Error {
5
5
  }
@@ -1,4 +1,5 @@
1
1
  import { FulfillmentMode as proto_FulfillmentMode } from "./generated/FulfillmentMode";
2
+ import { OnlineStoreTheme as proto_OnlineStoreTheme } from "./generated/OnlineStoreTheme";
2
3
  import { AddressList } from "./Address";
3
4
  import BusinessIdentifier from "./BusinessIdentifier";
4
5
  import LatLong from "./LatLong";
@@ -22,10 +23,10 @@ export default class GeneralSettings {
22
23
  private readonly _primaryStoreLocation;
23
24
  private readonly _googleBusinessCategoryId;
24
25
  private readonly _googleBusinessCategoryName;
25
- private readonly _onlineStoreThemeId;
26
+ private readonly _onlineStoreTheme;
26
27
  private readonly _onlineShoppingEnabled;
27
- static fromScaledValues(paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, customerServicePhone: string | null, isWhatsAppSupported: boolean, isSmsSupported: boolean, businessAddresses: AddressList, businessIdentifiers: ReadonlyArray<BusinessIdentifier>, fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, scaledDeliveryCharge: bigint, deliveryChargeTaxes: ReadonlyArray<Tax>, scaledFreeDeliveryTotal: bigint, deliveryRadiusKilometres: number, storeTagline: string | null, workingDays: WorkingDays, primaryStoreLocation: LatLong | null, googleBusinessCategoryId: string | null, googleBusinessCategoryName: string | null, onlineStoreThemeId: string | null, onlineShoppingEnabled: boolean): GeneralSettings;
28
- constructor(_paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, _customerServicePhone: string | null, _isWhatsAppSupported: boolean, _isSmsSupported: boolean, _businessAddresses: AddressList, _businessIdentifiers: ReadonlyArray<BusinessIdentifier>, _fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, _deliveryCharge: number, _deliveryChargeTaxes: ReadonlyArray<Tax>, _freeDeliveryTotal: number, _deliveryRadiusKilometres: number, _storeTagline: string | null, _workingDays: WorkingDays, _primaryStoreLocation: LatLong | null, _googleBusinessCategoryId: string | null, _googleBusinessCategoryName: string | null, _onlineStoreThemeId: string | null, _onlineShoppingEnabled: boolean);
28
+ static fromScaledValues(paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, customerServicePhone: string | null, isWhatsAppSupported: boolean, isSmsSupported: boolean, businessAddresses: AddressList, businessIdentifiers: ReadonlyArray<BusinessIdentifier>, fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, scaledDeliveryCharge: bigint, deliveryChargeTaxes: ReadonlyArray<Tax>, scaledFreeDeliveryTotal: bigint, deliveryRadiusKilometres: number, storeTagline: string | null, workingDays: WorkingDays, primaryStoreLocation: LatLong | null, googleBusinessCategoryId: string | null, googleBusinessCategoryName: string | null, onlineStoreTheme: proto_OnlineStoreTheme | null, onlineShoppingEnabled: boolean): GeneralSettings;
29
+ constructor(_paymentMethodDetails: ReadonlyArray<PaymentMethodDetail>, _customerServicePhone: string | null, _isWhatsAppSupported: boolean, _isSmsSupported: boolean, _businessAddresses: AddressList, _businessIdentifiers: ReadonlyArray<BusinessIdentifier>, _fulfillmentModes: ReadonlyArray<proto_FulfillmentMode>, _deliveryCharge: number, _deliveryChargeTaxes: ReadonlyArray<Tax>, _freeDeliveryTotal: number, _deliveryRadiusKilometres: number, _storeTagline: string | null, _workingDays: WorkingDays, _primaryStoreLocation: LatLong | null, _googleBusinessCategoryId: string | null, _googleBusinessCategoryName: string | null, _onlineStoreTheme: proto_OnlineStoreTheme | null, _onlineShoppingEnabled: boolean);
29
30
  get paymentMethodDetails(): ReadonlyArray<PaymentMethodDetail>;
30
31
  get customerServicePhone(): string | null;
31
32
  get isWhatsAppSupported(): boolean;
@@ -42,7 +43,7 @@ export default class GeneralSettings {
42
43
  get primaryStoreLocation(): LatLong | null;
43
44
  get googleBusinessCategoryId(): string | null;
44
45
  get googleBusinessCategoryName(): string | null;
45
- get onlineStoreThemeId(): string | null;
46
+ get onlineStoreTheme(): proto_OnlineStoreTheme | null;
46
47
  get onlineShoppingEnabled(): boolean;
47
48
  static readonly EMPTY: GeneralSettings;
48
49
  }
@@ -7,10 +7,10 @@ import WorkingDays from "./WorkingDays";
7
7
  // merchant-only fields are absent.
8
8
  class GeneralSettings {
9
9
  // For ProtoMap, which sees amounts still multiplied by Constants.AMOUNT_FACTOR.
10
- static fromScaledValues(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, scaledDeliveryCharge, deliveryChargeTaxes, scaledFreeDeliveryTotal, deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreThemeId, onlineShoppingEnabled) {
11
- return new GeneralSettings(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, amountFromScaledValue(scaledDeliveryCharge), deliveryChargeTaxes, amountFromScaledValue(scaledFreeDeliveryTotal), deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreThemeId, onlineShoppingEnabled);
10
+ static fromScaledValues(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, scaledDeliveryCharge, deliveryChargeTaxes, scaledFreeDeliveryTotal, deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreTheme, onlineShoppingEnabled) {
11
+ return new GeneralSettings(paymentMethodDetails, customerServicePhone, isWhatsAppSupported, isSmsSupported, businessAddresses, businessIdentifiers, fulfillmentModes, amountFromScaledValue(scaledDeliveryCharge), deliveryChargeTaxes, amountFromScaledValue(scaledFreeDeliveryTotal), deliveryRadiusKilometres, storeTagline, workingDays, primaryStoreLocation, googleBusinessCategoryId, googleBusinessCategoryName, onlineStoreTheme, onlineShoppingEnabled);
12
12
  }
13
- constructor(_paymentMethodDetails, _customerServicePhone, _isWhatsAppSupported, _isSmsSupported, _businessAddresses, _businessIdentifiers, _fulfillmentModes, _deliveryCharge, _deliveryChargeTaxes, _freeDeliveryTotal, _deliveryRadiusKilometres, _storeTagline, _workingDays, _primaryStoreLocation, _googleBusinessCategoryId, _googleBusinessCategoryName, _onlineStoreThemeId, _onlineShoppingEnabled) {
13
+ constructor(_paymentMethodDetails, _customerServicePhone, _isWhatsAppSupported, _isSmsSupported, _businessAddresses, _businessIdentifiers, _fulfillmentModes, _deliveryCharge, _deliveryChargeTaxes, _freeDeliveryTotal, _deliveryRadiusKilometres, _storeTagline, _workingDays, _primaryStoreLocation, _googleBusinessCategoryId, _googleBusinessCategoryName, _onlineStoreTheme, _onlineShoppingEnabled) {
14
14
  this._paymentMethodDetails = _paymentMethodDetails;
15
15
  this._customerServicePhone = _customerServicePhone;
16
16
  this._isWhatsAppSupported = _isWhatsAppSupported;
@@ -27,7 +27,7 @@ class GeneralSettings {
27
27
  this._primaryStoreLocation = _primaryStoreLocation;
28
28
  this._googleBusinessCategoryId = _googleBusinessCategoryId;
29
29
  this._googleBusinessCategoryName = _googleBusinessCategoryName;
30
- this._onlineStoreThemeId = _onlineStoreThemeId;
30
+ this._onlineStoreTheme = _onlineStoreTheme;
31
31
  this._onlineShoppingEnabled = _onlineShoppingEnabled;
32
32
  }
33
33
  get paymentMethodDetails() {
@@ -49,7 +49,8 @@ class GeneralSettings {
49
49
  get businessIdentifiers() {
50
50
  return this._businessIdentifiers;
51
51
  }
52
- // The modes this business offers, as a list rather than Java's bit mask. Empty means none published, not none offered.
52
+ // The modes this business offers, as a list rather than Java's bit mask. An empty list means the
53
+ // business has published none, and CartEditor then offers no delivery.
53
54
  get fulfillmentModes() {
54
55
  return this._fulfillmentModes;
55
56
  }
@@ -92,16 +93,17 @@ class GeneralSettings {
92
93
  get googleBusinessCategoryName() {
93
94
  return this._googleBusinessCategoryName;
94
95
  }
95
- // Which of the storefront's own themes this business picked. What an id means is up to the
96
- // storefront, not this library. Null when none was picked.
97
- get onlineStoreThemeId() {
98
- return this._onlineStoreThemeId;
99
- }
100
- // Whether the business has turned online shopping on. The server does not refuse a business that
101
- // has not. Model does, with a NotFoundException, after reading everything it is sent, when the
102
- // settings it holds do not say so or it holds none (see Model.setBusinessId). Also false for
103
- // GeneralSettings.EMPTY. It is the only thing about what a business sells that closes it: one with
104
- // nothing a customer can buy is open, with an empty catalog.
96
+ // Which of the storefront's themes this business picked. Null when none was picked. A theme that a
97
+ // newer app added, which this version of the library has no entry for, is returned as it came: a
98
+ // number that is not a member of the enum. A caller that maps themes must have a fallback.
99
+ get onlineStoreTheme() {
100
+ return this._onlineStoreTheme;
101
+ }
102
+ // Whether the business has turned online shopping on. The server sends the business either way.
103
+ // Model throws a NotFoundException, after reading everything it was sent, when the settings it
104
+ // holds do not say shopping is on or it holds none (see Model.setBusinessId). Also false for
105
+ // GeneralSettings.EMPTY. A business with online shopping off is closed. A business with nothing
106
+ // to buy is open.
105
107
  get onlineShoppingEnabled() {
106
108
  return this._onlineShoppingEnabled;
107
109
  }
@@ -1,15 +1,15 @@
1
1
  import { BadRequestException, ConflictException, InternalServerException, MethodNotAllowedException, NotFoundException, ServiceException, ServiceUnavailableException, UnauthorizedException, UpgradeRequiredException, } from "./ServiceExceptions";
2
- // Which failure a status code stands for. The classes are in ServiceExceptions.ts. The split matches
3
- // Java's HttpStatusCodes and exception package, so the table of what each code means has one home
4
- // in each client.
2
+ // Maps a status code to its exception. The classes are in ServiceExceptions.ts. The split matches
3
+ // Java's HttpStatusCodes and exception package, so each client has one table of what each code
4
+ // means.
5
5
  // Everything from 300 up (Java's HttpURLConnection.HTTP_MULT_CHOICE). A redirect counts as a
6
- // failure because this client follows none, so being sent elsewhere means the call did not happen.
6
+ // failure because this client follows none, so the call did not happen.
7
7
  export function isError(httpStatusCode) {
8
8
  return httpStatusCode >= LOWEST_ERROR_STATUS_CODE;
9
9
  }
10
- // Raises the failure a status code stands for, and does nothing for a code that is not an error.
11
- // A code with no class of its own becomes a ServiceException carrying the code, so a new server
12
- // response cannot pass as a success.
10
+ // Throws the exception for a status code, and does nothing for a code that is not an error. A code
11
+ // with no class throws a ServiceException carrying the code, so an unlisted error code cannot pass
12
+ // as a success.
13
13
  export function throwOnError(httpStatusCode, message) {
14
14
  if (!isError(httpStatusCode))
15
15
  return;
package/dist/Ids.js CHANGED
@@ -22,8 +22,7 @@ class Ids {
22
22
  }
23
23
  // A fresh id for an entity this client creates (Java Entity.getRandomLongForId): random, never
24
24
  // in 0-255, which every entity type reserves. Only the low part is drawn, as buildId masks the
25
- // rest off. It uses the cryptographic generator because a Math.random id is guessable and a
26
- // message id can be handed to a caller.
25
+ // rest off. It uses the cryptographic generator because a Math.random id is guessable.
27
26
  static getNewId(entityTypeId) {
28
27
  for (;;) {
29
28
  const bytes = new Uint8Array(8);
@@ -55,9 +54,9 @@ class Ids {
55
54
  static toOrderNumberString(orderId) {
56
55
  return (orderId & Ids.UNSIGNED_32_BIT_MASK).toString(16).toUpperCase();
57
56
  }
58
- // Where a container keeps how far its reads of one entity type have got. The blob's eTag is the
59
- // cursor the next read sends, and the blob holds nothing else. The store keeps the greater eTag,
60
- // so a cursor never drops on a stale or repeated page or a write from another tab. Written in
57
+ // The blob in which a container keeps the sync cursor of one entity type. Its eTag is the cursor
58
+ // the next read sends, and the blob holds no other data. The store keeps the greater eTag, so a
59
+ // stale or repeated page, or a write from another tab, cannot lower the cursor. It is written in
61
60
  // the batch of the page that moved it, so it cannot get ahead of the entities it describes. key
62
61
  // is one of SyncKeys.
63
62
  static getSyncCursorBlobId(key) {
package/dist/Item.js CHANGED
@@ -2,7 +2,8 @@ import Entity from "./Entity";
2
2
  // Something a business buys or sells, or a category or price of one. Mirrors the protocol's Item, embedded in each of the five real types.
3
3
  // parentItemId is the wire parent (a product's category). Entity.parent is the sync layer's containment parent (the business).
4
4
  export default class Item extends Entity {
5
- // Public because ProductCategory and PriceList add nothing of their own. The class is still abstract.
5
+ // Public because ProductCategory and PriceList declare no constructor and inherit this one. The
6
+ // class is still abstract.
6
7
  constructor(header, _name, _description, _parentItemId, _isDisabled) {
7
8
  super(header);
8
9
  this._name = _name;
@@ -16,13 +17,15 @@ export default class Item extends Entity {
16
17
  get description() {
17
18
  return this._description;
18
19
  }
19
- // Constants.ENTITY_ID_NULL when this item has no parent (e.g. an uncategorized product, or a price list).
20
+ // Constants.ENTITY_ID_NULL when this item has no parent (e.g. an uncategorized product, or a price
21
+ // list with no parent list).
20
22
  get parentItemId() {
21
23
  return this._parentItemId;
22
24
  }
23
- // True when the business has disabled this item. Unlike isPublic and Product.isDraft, it can
24
- // change after an item exists, so it is sent rather than filtered out. A product-level check does
25
- // not guarantee that every package of an otherwise visible product is enabled.
25
+ // True when the business has disabled this item. The server filters out draft and non-public
26
+ // products, but this flag can change after an item exists, so a disabled item is sent flagged. A
27
+ // product-level check does not guarantee that every package of an otherwise visible product is
28
+ // enabled.
26
29
  get isDisabled() {
27
30
  return this._isDisabled;
28
31
  }
package/dist/LiveData.js CHANGED
@@ -29,7 +29,7 @@ class LiveData {
29
29
  // Returns the function that stops this observer observing. Observing twice with the same function only observes once.
30
30
  observe(observer, notifyImmediately = false) {
31
31
  if (!this._observers.has(observer)) {
32
- // NOT_NOTIFIED means the immediate notification below has something to deliver; otherwise a new observer only hears future changes (not sticky, unlike Android LiveData).
32
+ // NOT_NOTIFIED means the immediate notification below has something to deliver; otherwise a new observer receives only future changes (not sticky, unlike Android LiveData).
33
33
  this._observers.set(observer, notifyImmediately ? LiveData.NOT_NOTIFIED : this._version);
34
34
  }
35
35
  if (notifyImmediately)
@@ -54,7 +54,7 @@ class LiveData {
54
54
  if (this._isNotificationStale)
55
55
  break; // Start again with the newer value
56
56
  if (this._observers.get(observer) === this._version)
57
- continue; // Already told
57
+ continue; // Already notified
58
58
  this._observers.set(observer, this._version);
59
59
  observer(this._holder);
60
60
  }
package/dist/Measures.js CHANGED
@@ -1,5 +1,6 @@
1
- // How big a package is and how much it weighs, grouped since a UI showing one usually shows both. Neither is an entity; both are values on the Package row.
2
- // Units are the generated proto enums. A value displays in the unit the merchant entered it in, and nothing here converts between units.
1
+ // How big a package is and how much it weighs, in one file since a UI showing one usually shows both.
2
+ // Neither is an entity; both are values on Package.
3
+ // Units are the generated proto enums. A value is shown in the unit the merchant entered it in, and nothing here converts between units.
3
4
  export class Dimensions {
4
5
  constructor(_unit, _length, _width, _height) {
5
6
  this._unit = _unit;
package/dist/Message.js CHANGED
@@ -12,7 +12,7 @@ export default class Message extends Entity {
12
12
  }
13
13
  // The customer's party id at the business (BusinessLink.partyId), not the id of their User. A
14
14
  // business sync out sends a customer only messages from their own party. senderName and
15
- // senderPhone say who it was without resolving the party.
15
+ // senderPhone identify the sender without resolving the party.
16
16
  get senderId() {
17
17
  return this._senderId;
18
18
  }
@@ -1,5 +1,6 @@
1
- // A Database that holds everything in a Map, for tests and training mode to run against with no
2
- // network. Enforces the eTag rule (write only if the new eTag is greater), so a test proves the sync is idempotent.
1
+ // A Database that holds everything in a Map, for tests and training mode, which run with no
2
+ // network. It enforces the eTag rule (a write applies only if its eTag is greater), so a test can
3
+ // check that a sync is idempotent.
3
4
  export default class MockDatabase {
4
5
  constructor() {
5
6
  this._containers = new Map();
@@ -18,14 +19,14 @@ export default class MockDatabase {
18
19
  async deleteContainer(name) {
19
20
  this._containers.delete(name);
20
21
  }
21
- // For a test that wants to look at what was stored without going through a sync
22
+ // Lets a test inspect what was stored without going through a sync
22
23
  getContainerIfPresent(name) {
23
24
  return this._containers.get(name) ?? null;
24
25
  }
25
26
  }
26
27
  export class MockContainer {
27
28
  constructor() {
28
- // Set by a test just before the write it wants to fail
29
+ // A test sets this just before the write that should fail. That write clears it.
29
30
  this.failNextWrite = false;
30
31
  // How many times write has been called, so that a test can show a sync did not write per entity
31
32
  this.writeCount = 0;
@@ -56,7 +57,7 @@ export class MockContainer {
56
57
  this._blobs.delete(id);
57
58
  return appliedCount;
58
59
  }
59
- // Greater eTag wins; a blob with no eTag (or overwriting one with none) is always written.
60
+ // The greater eTag wins. A write always applies when the incoming blob or the stored one has no eTag.
60
61
  static isNewer(incoming, stored) {
61
62
  if (stored === null)
62
63
  return true;