@peabody-soft/mbs-model-ts 1.4.0 → 1.6.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 (91) hide show
  1. package/dist/Address.js +8 -11
  2. package/dist/Attachment.js +7 -8
  3. package/dist/AttachmentBaseUrl.js +10 -10
  4. package/dist/AuthTokens.js +12 -7
  5. package/dist/Business.js +19 -19
  6. package/dist/BusinessIdentifier.js +2 -2
  7. package/dist/BusinessLink.js +16 -16
  8. package/dist/BusinessProfile.js +4 -4
  9. package/dist/CartEditor.d.ts +10 -5
  10. package/dist/CartEditor.js +258 -180
  11. package/dist/Catalog.js +31 -29
  12. package/dist/Constants.js +10 -10
  13. package/dist/ContainerNames.js +10 -11
  14. package/dist/Entity.js +11 -9
  15. package/dist/Enums.d.ts +1 -1
  16. package/dist/Enums.js +4 -3
  17. package/dist/Exceptions.js +2 -2
  18. package/dist/GeneralSettings.d.ts +4 -2
  19. package/dist/GeneralSettings.js +30 -18
  20. package/dist/HttpStatusCodes.js +10 -11
  21. package/dist/Ids.d.ts +2 -0
  22. package/dist/Ids.js +28 -24
  23. package/dist/Item.js +6 -7
  24. package/dist/LatLong.js +3 -3
  25. package/dist/LiveData.js +6 -6
  26. package/dist/Measures.js +2 -2
  27. package/dist/Message.js +6 -5
  28. package/dist/MockDatabase.js +2 -2
  29. package/dist/Model.d.ts +5 -0
  30. package/dist/Model.js +400 -254
  31. package/dist/Order.js +20 -17
  32. package/dist/Package.js +7 -8
  33. package/dist/PackageIdentifier.js +2 -2
  34. package/dist/PaymentMethodDetail.js +4 -4
  35. package/dist/Photo.js +6 -7
  36. package/dist/Price.js +9 -6
  37. package/dist/PriceList.js +2 -2
  38. package/dist/Product.d.ts +5 -3
  39. package/dist/Product.js +26 -19
  40. package/dist/ProductCategory.js +3 -4
  41. package/dist/Profile.d.ts +1 -0
  42. package/dist/Profile.js +94 -8
  43. package/dist/ProtoMap.d.ts +18 -8
  44. package/dist/ProtoMap.js +451 -331
  45. package/dist/Routes.js +3 -3
  46. package/dist/ServiceExceptions.js +11 -12
  47. package/dist/ServiceProxy.js +19 -23
  48. package/dist/ShoppingList.d.ts +30 -1
  49. package/dist/ShoppingList.js +339 -47
  50. package/dist/SyncCache.d.ts +12 -0
  51. package/dist/SyncCache.js +136 -56
  52. package/dist/SyncKeys.js +8 -8
  53. package/dist/SyncState.js +5 -5
  54. package/dist/Tax.d.ts +12 -4
  55. package/dist/Tax.js +37 -13
  56. package/dist/TaxHelper.d.ts +12 -0
  57. package/dist/TaxHelper.js +85 -0
  58. package/dist/TaxType.d.ts +7 -0
  59. package/dist/TaxType.js +31 -0
  60. package/dist/UserProfile.js +4 -4
  61. package/dist/WorkingDays.js +15 -15
  62. package/dist/generated/AttachmentType.d.ts +19 -1
  63. package/dist/generated/AttachmentType.js +18 -0
  64. package/dist/generated/AuthTokens.d.ts +3 -1
  65. package/dist/generated/BusinessLink.d.ts +4 -1
  66. package/dist/generated/BusinessSyncInRequest.d.ts +11 -3
  67. package/dist/generated/BusinessSyncInResponse.d.ts +5 -4
  68. package/dist/generated/BusinessSyncOutRequest.d.ts +37 -10
  69. package/dist/generated/BusinessSyncOutRequest.js +42 -29
  70. package/dist/generated/BusinessSyncOutResponse.d.ts +14 -1
  71. package/dist/generated/Configuration.d.ts +2 -1
  72. package/dist/generated/CreateUserRequest.d.ts +8 -2
  73. package/dist/generated/Entity.d.ts +7 -3
  74. package/dist/generated/GeneralSettings.d.ts +14 -4
  75. package/dist/generated/GeneralSettings.js +9 -1
  76. package/dist/generated/Message.d.ts +4 -2
  77. package/dist/generated/Product.d.ts +10 -1
  78. package/dist/generated/Product.js +9 -1
  79. package/dist/generated/ProfileSyncOutRequest.d.ts +17 -1
  80. package/dist/generated/ProfileSyncOutResponse.d.ts +8 -3
  81. package/dist/generated/SalesOrderPayload.d.ts +7 -2
  82. package/dist/generated/ShoppingList.d.ts +6 -3
  83. package/dist/generated/ShoppingListItem.d.ts +21 -6
  84. package/dist/generated/Tax.d.ts +4 -4
  85. package/dist/generated/Tax.js +7 -8
  86. package/dist/generated/WorkingHoursRange.d.ts +2 -2
  87. package/dist/index.d.ts +2 -2
  88. package/dist/index.js +11 -10
  89. package/package.json +55 -55
  90. package/dist/generated/TaxType.d.ts +0 -25
  91. package/dist/generated/TaxType.js +0 -30
package/dist/Order.js CHANGED
@@ -2,12 +2,14 @@ import { MessageType as proto_MessageType } from "./generated/MessageType";
2
2
  import { amountFromScaledValue } from "./Constants";
3
3
  import Message from "./Message";
4
4
  // An order a customer has placed: a message carrying a sales order payload.
5
- // deliveryAddressId is an id into the customer's address list, not an address; deliveryAddress is
6
- // a full address sent directly instead, and takes precedence over deliveryAddressId when both are
7
- // present. paymentReceived is set only by an operator of the business, never by this library.
5
+ // The business completes an order (fulfilling or cancelling it) by deleting its message, so
6
+ // isDeleted is true of a completed order. That message is still the whole order: status and what
7
+ // the customer ordered are there to show, as for an open one. status says how far the business
8
+ // got with it.
9
+ // deliveryAddressId is an id into the customer's address list. deliveryAddress is a full address
10
+ // sent directly, and takes precedence over deliveryAddressId when both are present.
8
11
  export default class Order extends Message {
9
- // Only ProtoMap calls this: it is the one place that sees a total still multiplied by
10
- // Constants.AMOUNT_FACTOR.
12
+ // For ProtoMap, which sees a total still multiplied by Constants.AMOUNT_FACTOR.
11
13
  static fromScaledValues(header, senderId, taskStatus, senderName, senderPhone, status, fulfillmentMode, shoppingList, scaledShoppingListTotal, shoppingListCount, deliveryAddressId, deliveryAddress, creationDate, note, scaledPaymentReceived) {
12
14
  return new Order(header, senderId, taskStatus, senderName, senderPhone, status, fulfillmentMode, shoppingList, amountFromScaledValue(scaledShoppingListTotal), shoppingListCount, deliveryAddressId, deliveryAddress, creationDate, note, scaledPaymentReceived === undefined ? null : amountFromScaledValue(scaledPaymentReceived));
13
15
  }
@@ -24,34 +26,35 @@ export default class Order extends Message {
24
26
  this._paymentReceived = _paymentReceived;
25
27
  this._creationDate = Number(creationDate);
26
28
  }
27
- // How far the business has got with fulfilling this order, as opposed to taskStatus, which is
28
- // how far it has got with reading the message
29
+ // How far the business has got with fulfilling this order. taskStatus is how far it has got with
30
+ // reading the message.
29
31
  get status() {
30
32
  return this._status;
31
33
  }
32
34
  get fulfillmentMode() {
33
35
  return this._fulfillmentMode;
34
36
  }
35
- // What was ordered. A fulfilled order can have had its list cleared, in which case the totals
36
- // below are all that is left of it.
37
+ // What was ordered. An order completed before the business kept the list with it (an older
38
+ // merchant app moved the list into a record of the sale, which a customer never sees) holds
39
+ // ShoppingList.EMPTY, which has no lines. The totals below are then all that is left.
37
40
  get shoppingList() {
38
41
  return this._shoppingList;
39
42
  }
40
- // The authoritative total to display; survives the shopping list being cleared, unlike a total
41
- // read off the list itself.
43
+ // The authoritative total to display. Unlike a total read off the list, it survives the order
44
+ // having no list.
42
45
  get shoppingListTotal() {
43
46
  return this._shoppingListTotal;
44
47
  }
45
48
  get shoppingListCount() {
46
49
  return this._shoppingListCount;
47
50
  }
48
- // Names an address in the address list of the customer, and Constants.ENTITY_ID_NULL when the
49
- // order has no delivery address - which is normal for one to be picked up
51
+ // Names an address in the customer's address list. Constants.ENTITY_ID_NULL when the order has no
52
+ // delivery address, as is normal for a pickup.
50
53
  get deliveryAddressId() {
51
54
  return this._deliveryAddressId;
52
55
  }
53
- // A full address sent directly rather than by id, and null when none was. Takes precedence over
54
- // deliveryAddressId for display when it is not null - see the comment on this class.
56
+ // A full address sent directly rather than by id. Null when none was. Takes precedence over
57
+ // deliveryAddressId when not null.
55
58
  get deliveryAddress() {
56
59
  return this._deliveryAddress;
57
60
  }
@@ -62,8 +65,8 @@ export default class Order extends Message {
62
65
  get note() {
63
66
  return this._note;
64
67
  }
65
- // What the business has recorded as received against this order so far, and null when it has
66
- // recorded nothing - only a business operator sets this, never this library.
68
+ // What the business has recorded as received against this order so far. Null when nothing is
69
+ // recorded. Only a business operator sets it.
67
70
  get paymentReceived() {
68
71
  return this._paymentReceived;
69
72
  }
package/dist/Package.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import Item from "./Item";
2
- // A package, or variant, of a product - a 500 gram bottle of mango pickle, for example - and the
3
- // thing a customer actually buys. Built complete, with photos and price already resolved, so everything here is readonly.
2
+ // A package, or variant, of a product (a 500 gram bottle of mango pickle, say) and the thing a
3
+ // customer buys. Built complete, with photos and price resolved, so everything here is readonly.
4
4
  export default class Package extends Item {
5
5
  constructor(header, name, description, productId, isDisabled, _isSoldLoose, _itemCode, _identifiers, _dimensions, _weight, _photos, _price) {
6
6
  super(header, name, description, productId, isDisabled);
@@ -37,15 +37,14 @@ export default class Package extends Item {
37
37
  get photos() {
38
38
  return this._photos;
39
39
  }
40
- // What this package costs; null when the business has not priced it, in which case it cannot be
41
- // bought.
40
+ // What this package costs, with the taxes of its product included (Product.taxes). Null when the
41
+ // business has not priced it, so it cannot be bought.
42
42
  get price() {
43
43
  return this._price;
44
44
  }
45
- // True when this package is enabled and has a price that is itself enabled. isDisabled is
46
- // checked directly rather than trusted to have been filtered out: a product-level filter does
47
- // not, by itself, guarantee that every package - or every price - of an otherwise visible
48
- // product is itself enabled.
45
+ // True when this package is enabled and has an enabled price. Checked here because a
46
+ // product-level filter does not guarantee that every package, or every price, of an otherwise
47
+ // visible product is enabled.
49
48
  get isBuyable() {
50
49
  return !this.isDisabled && this._price !== null && !this._price.isDisabled;
51
50
  }
@@ -1,5 +1,5 @@
1
- // An identifier of a package - a GTIN, for example - and the kind of identifier it is. Java keys
2
- // these by string id, not ordinal, so map through PackageIdentifierType.proto's comments, never the enum ordinal.
1
+ // An identifier of a package, such as a GTIN, and its kind. Java keys kinds by string id, so map
2
+ // through the comments in PackageIdentifierType.proto, never the enum ordinal.
3
3
  export default class PackageIdentifier {
4
4
  constructor(_type, _value) {
5
5
  this._type = _type;
@@ -1,8 +1,8 @@
1
- // A payment method a business accepts, and what a customer needs in order to use it - the UPI id
2
- // of the business, for example.
1
+ // A payment method a business accepts, and what a customer needs to use it (the UPI id of the
2
+ // business, say).
3
3
  //
4
- // The data is null rather than empty when the business has given none, which is normal for a
5
- // method that needs nothing, such as cash.
4
+ // The data is null, not empty, when the business has given none. That is normal for a method that
5
+ // needs nothing, such as cash.
6
6
  export default class PaymentMethodDetail {
7
7
  constructor(_method, _data) {
8
8
  this._method = _method;
package/dist/Photo.js CHANGED
@@ -1,9 +1,8 @@
1
- // A photo of a business, product or package: which one it belongs to, and where to fetch it from.
2
- // All that's exposed of an Attachment - nothing else on it is useful to a UI.
1
+ // A photo of a business, product or package: its attachment type and where to fetch it from.
2
+ // All that a UI needs of an Attachment.
3
3
  export default class Photo {
4
- // The base url is the one in effect when this photo was built, not looked up at read time - a
5
- // photo is rebuilt by the next sync rather than edited in place, so there is nothing to keep in
6
- // sync with a later renewal.
4
+ // The base url is the one in effect when this photo was built. A photo is rebuilt by the next
5
+ // sync, not edited in place, so a later renewal needs no handling.
7
6
  constructor(_attachment, _attachmentBaseUrl) {
8
7
  this._attachment = _attachment;
9
8
  this._attachmentBaseUrl = _attachmentBaseUrl;
@@ -12,8 +11,8 @@ export default class Photo {
12
11
  get attachmentType() {
13
12
  return this._attachment.attachmentType;
14
13
  }
15
- // Where to fetch this photo from, or null if there's nothing to fetch (deleted attachment, or the
16
- // owning business's base url hadn't arrived when this photo was built). May be expired; cached
14
+ // Where to fetch this photo from. Null if there is nothing to fetch (deleted attachment, or the
15
+ // owning business's base url had not arrived when this photo was built). May be expired. Cached
17
16
  // once built.
18
17
  get url() {
19
18
  if (this._url !== null)
package/dist/Price.js CHANGED
@@ -2,9 +2,12 @@ import { amountFromScaledValue } from "./Constants";
2
2
  import Item from "./Item";
3
3
  // What one package costs: the regular price and the offer price. Exposed unscaled as numbers; they
4
4
  // travel multiplied by Constants.AMOUNT_FACTOR and stay well below 2^53, so nothing is lost.
5
+ //
6
+ // BOTH INCLUDE TAXES: the taxes of the package's product (Product.taxes) are in the price, not
7
+ // added to it, so this is what a customer pays and what a catalog shows. A shopping list stores a
8
+ // price without them, so a cart takes the taxes out (CartEditor).
5
9
  export default class Price extends Item {
6
- // Only ProtoMap calls this: it is the one place that sees prices still multiplied by
7
- // Constants.AMOUNT_FACTOR.
10
+ // For ProtoMap, which sees prices still multiplied by Constants.AMOUNT_FACTOR.
8
11
  static fromScaledValues(header, name, description, priceListId, isDisabled, packageId, scaledRegularPrice, scaledOfferPrice) {
9
12
  return new Price(header, name, description, priceListId, isDisabled, packageId, amountFromScaledValue(scaledRegularPrice), amountFromScaledValue(scaledOfferPrice));
10
13
  }
@@ -30,8 +33,8 @@ export default class Price extends Item {
30
33
  get offerPrice() {
31
34
  return this._offerPrice;
32
35
  }
33
- // What a customer actually pays: the offer price when there is one, else the regular price.
34
- // Computed here (not sent), matching the "on offer" definition from Price.proto.
36
+ // What a customer pays, taxes included: the offer price when there is one, else the regular
37
+ // price. Computed here, not sent, using the "on offer" definition from Price.proto.
35
38
  get finalPrice() {
36
39
  return this.isOnOffer ? this._offerPrice : this._regularPrice;
37
40
  }
@@ -42,8 +45,8 @@ export default class Price extends Item {
42
45
  get discount() {
43
46
  return this._regularPrice - this.finalPrice;
44
47
  }
45
- // Discount as an exact, unrounded percentage of the regular price - rounding is left to the UI.
46
- // Zero when the regular price is zero.
48
+ // Discount as an unrounded percentage of the regular price. Rounding is left to the UI. Zero
49
+ // when the regular price is zero.
47
50
  get discountPercent() {
48
51
  if (this._regularPrice <= 0)
49
52
  return 0;
package/dist/PriceList.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Item from "./Item";
2
- // A named list of prices. A storefront sells from exactly one, so no package ever has two prices
3
- // to choose between; it's carried (not just a bare id) because a client naming a cart needs it.
2
+ // A named list of prices. A storefront sells from one, so no package has two prices to choose
3
+ // between. Carried as an object, not a bare id, because a client naming a cart needs it.
4
4
  export default class PriceList extends Item {
5
5
  }
package/dist/Product.d.ts CHANGED
@@ -14,20 +14,22 @@ export default class Product extends Item {
14
14
  private readonly _manufacturerAddress;
15
15
  private readonly _taxes;
16
16
  private readonly _features;
17
+ private readonly _isForOfflineSaleOnly;
17
18
  private readonly _photos;
18
- private readonly _packages;
19
- constructor(header: EntityHeader, name: string, description: string | null, productCategoryId: bigint, isDisabled: boolean, _productType: proto_ProductType, _brand: string | null, _isManufacturedByBusiness: boolean | null, _manufacturer: string | null, _manufacturerAddress: Address | null, _taxes: ReadonlyArray<Tax>, _features: ReadonlyArray<string>, _photos: ReadonlyArray<Photo>, _packages: ReadonlyArray<Package>);
19
+ constructor(header: EntityHeader, name: string, description: string | null, productCategoryId: bigint, isDisabled: boolean, _productType: proto_ProductType, _brand: string | null, _isManufacturedByBusiness: boolean, _manufacturer: string | null, _manufacturerAddress: Address | null, _taxes: ReadonlyArray<Tax>, _features: ReadonlyArray<string>, _isForOfflineSaleOnly: boolean, _photos: ReadonlyArray<Photo>, packages: ReadonlyArray<Package>);
20
20
  get productCategoryId(): bigint;
21
21
  get productType(): proto_ProductType;
22
22
  get brand(): string | null;
23
- get isManufacturedByBusiness(): boolean | null;
23
+ get isManufacturedByBusiness(): boolean;
24
24
  get manufacturer(): string | null;
25
25
  get manufacturerAddress(): Address | null;
26
26
  get taxes(): ReadonlyArray<Tax>;
27
27
  get features(): ReadonlyArray<string>;
28
+ get isForOfflineSaleOnly(): boolean;
28
29
  get photos(): ReadonlyArray<Photo>;
29
30
  get packages(): ReadonlyArray<Package>;
30
31
  get lowestPrice(): Price | null;
31
32
  get isOnOffer(): boolean;
32
33
  get isBuyable(): boolean;
34
+ private readonly _packages;
33
35
  }
package/dist/Product.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import Item from "./Item";
2
2
  // A product in the catalog: goods, a service, a raw material or a fixed asset. Built complete
3
- // (photos/packages resolved first) so everything here is readonly except category (via parentItemId).
3
+ // (photos and packages resolved first), so everything here is readonly.
4
4
  export default class Product extends Item {
5
- constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _photos, _packages) {
5
+ constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _isForOfflineSaleOnly, _photos, packages) {
6
6
  super(header, name, description, productCategoryId, isDisabled);
7
7
  this._productType = _productType;
8
8
  this._brand = _brand;
@@ -11,8 +11,10 @@ export default class Product extends Item {
11
11
  this._manufacturerAddress = _manufacturerAddress;
12
12
  this._taxes = _taxes;
13
13
  this._features = _features;
14
+ this._isForOfflineSaleOnly = _isForOfflineSaleOnly;
14
15
  this._photos = _photos;
15
- this._packages = _packages;
16
+ // A package that cannot be bought is not kept, whoever builds the product and whatever they pass in
17
+ this._packages = Object.freeze(packages.filter((pkg) => pkg.isBuyable));
16
18
  }
17
19
  // The category this product is in, or Constants.ENTITY_ID_NULL when it is in none
18
20
  get productCategoryId() {
@@ -24,8 +26,8 @@ export default class Product extends Item {
24
26
  get brand() {
25
27
  return this._brand;
26
28
  }
27
- // Null when the business hasn't said either way - the three states are why this isn't a plain
28
- // boolean.
29
+ // False only when the business says it does not make this product itself. Absent on the wire
30
+ // means true (Product.proto).
29
31
  get isManufacturedByBusiness() {
30
32
  return this._isManufacturedByBusiness;
31
33
  }
@@ -35,6 +37,8 @@ export default class Product extends Item {
35
37
  get manufacturerAddress() {
36
38
  return this._manufacturerAddress;
37
39
  }
40
+ // The taxes included in the price of every package of this product (see Price), not added to it.
41
+ // A UI shows a price as is, and shows these only as part of what a bill's total includes.
38
42
  get taxes() {
39
43
  return this._taxes;
40
44
  }
@@ -42,22 +46,29 @@ export default class Product extends Item {
42
46
  get features() {
43
47
  return this._features;
44
48
  }
49
+ // True when the business sells this product only offline. The server sends such a product like
50
+ // any other, flagged, and this model must not offer it to a customer (see isBuyable). A catalog
51
+ // never holds one, so only a hand-built product can report true.
52
+ get isForOfflineSaleOnly() {
53
+ return this._isForOfflineSaleOnly;
54
+ }
45
55
  get photos() {
46
56
  return this._photos;
47
57
  }
48
- // Every variant of this product, in the order they arrived - ordering them for display is a UI
49
- // decision, see Catalog.comparePackages. A product with no packages cannot be bought.
58
+ // Every buyable variant of this product, in the order they arrived. Ordering for display is a UI
59
+ // decision, see Catalog.comparePackages. A product with no buyable variant cannot be bought.
50
60
  get packages() {
51
61
  return this._packages;
52
62
  }
53
- // The cheapest final price across variants (what a listing shows as "from"), or null if none has
54
- // a price. Compares final price, not regular, since that's what a customer would actually pay.
63
+ // The cheapest final price across variants (what a listing shows as "from"), or null if there
64
+ // are no variants. Compares final price, since that is what a customer pays. Every variant is
65
+ // taxed at this product's rates, so prices with taxes order the variants as prices without would.
55
66
  get lowestPrice() {
56
67
  let result = null;
57
68
  for (const pkg of this._packages) {
58
69
  const price = pkg.price;
59
70
  if (price === null)
60
- continue; // This variant cannot be bought
71
+ continue; // Not so of a buyable variant, though the type does not say
61
72
  if (result === null || price.finalPrice < result.finalPrice)
62
73
  result = price;
63
74
  }
@@ -70,15 +81,11 @@ export default class Product extends Item {
70
81
  return true;
71
82
  return false;
72
83
  }
73
- // True when this product itself is enabled and at least one variant is buyable. The server
74
- // still filters out draft and non-public products before they ever arrive, but isDisabled can
75
- // change after an item exists, so it is checked here rather than trusted to have been filtered.
84
+ // True when this product is enabled, is not sold only offline, and has at least one variant (it
85
+ // keeps only buyable ones). The server filters out draft and non-public products before they
86
+ // arrive. isDisabled can change after an item exists and offline-only products are sent flagged,
87
+ // so both are checked here.
76
88
  get isBuyable() {
77
- if (this.isDisabled)
78
- return false;
79
- for (const pkg of this._packages)
80
- if (pkg.isBuyable)
81
- return true;
82
- return false;
89
+ return !this.isDisabled && !this._isForOfflineSaleOnly && this._packages.length > 0;
83
90
  }
84
91
  }
@@ -1,8 +1,7 @@
1
1
  import Item from "./Item";
2
- // A category that groups products, real or the dummy one a Catalog builds to stand in for a
3
- // product with no category of its own. Products are set once by the Catalog that builds this, not
4
- // passed to the constructor - the full set isn't known until every product has been read. A
5
- // Catalog never builds one of these with an empty products array - see Catalog for why.
2
+ // A category that groups products, real or the dummy one a Catalog builds for products with no
3
+ // category. The Catalog sets the products once, after reading every product, so they are not
4
+ // constructor arguments. A Catalog never builds one with an empty products array (see Catalog).
6
5
  export default class ProductCategory extends Item {
7
6
  constructor() {
8
7
  super(...arguments);
package/dist/Profile.d.ts CHANGED
@@ -18,3 +18,4 @@ export default class Profile {
18
18
  private readonly _businessMap;
19
19
  private readonly _businessLinkMap;
20
20
  }
21
+ export declare function canonicalizeBusinessId(businessId: string): string;
package/dist/Profile.js CHANGED
@@ -6,8 +6,8 @@ export default class Profile {
6
6
  this._user = _user;
7
7
  this._businessMap = new Map();
8
8
  this._businessLinkMap = new Map();
9
- // A business is listed only when its link is still held; a revoked link is removed by the
10
- // sync but the business it named is not, so filtering here hides it without discarding it.
9
+ // A business is listed only when its link is still held. A revoked link is removed by the sync
10
+ // but its business is not, so filtering here hides the business without discarding it.
11
11
  const linkedBusinessIds = new Set();
12
12
  for (const businessLink of businessLinks)
13
13
  linkedBusinessIds.add(businessLink.businessId);
@@ -15,8 +15,16 @@ export default class Profile {
15
15
  this._businesses = Object.freeze(businesses.filter((business) => linkedBusinessIds.has(business.id)));
16
16
  for (const business of this._businesses)
17
17
  this._businessMap.set(business.id, business);
18
- for (const businessLink of this._businessLinks)
19
- this._businessLinkMap.set(businessLink.businessId, businessLink);
18
+ // One link is kept per business: the one that names this customer. A profile can hold several
19
+ // links to a business, and another user's link is not what this customer writes with. A link
20
+ // that does not name this customer never replaces one that does, whichever comes last. If none
21
+ // names them (no user is held yet), the last is kept.
22
+ const isOwn = (link) => this._user !== null && link.linkedEntityId === this._user.id;
23
+ for (const businessLink of this._businessLinks) {
24
+ const held = this._businessLinkMap.get(businessLink.businessId);
25
+ if (held === undefined || isOwn(businessLink) || !isOwn(held))
26
+ this._businessLinkMap.set(businessLink.businessId, businessLink);
27
+ }
20
28
  }
21
29
  // The id of the profile container this was built from, and what Model.signIn was given
22
30
  get profileId() {
@@ -37,15 +45,18 @@ export default class Profile {
37
45
  getBusiness(businessEntityId) {
38
46
  return this._businessMap.get(businessEntityId) ?? null;
39
47
  }
40
- // BusinessLink.businessId is an entity id, matching Business.entity.id and not the url friendly
41
- // businessId - this join spares a caller from having to remember which one it is holding
48
+ // Takes the entity id (BusinessLink.businessId, matching Business.entity.id), not the url friendly
49
+ // businessId. See getBusinessLinkFor.
42
50
  getBusinessLink(businessEntityId) {
43
51
  return this._businessLinkMap.get(businessEntityId) ?? null;
44
52
  }
45
- // The same, by the url friendly business id a storefront is reached by
53
+ // The same, by the url friendly business id a storefront is reached by, however it is written in
54
+ // the url. The server finds a business whatever the case, so this must too, or a customer who
55
+ // has joined would be asked to join again.
46
56
  getBusinessLinkFor(businessId) {
57
+ const wanted = canonicalizeBusinessId(businessId);
47
58
  for (const business of this._businesses)
48
- if (business.businessId === businessId)
59
+ if (canonicalizeBusinessId(business.businessId) === wanted)
49
60
  return this.getBusinessLink(business.id);
50
61
  return null;
51
62
  }
@@ -53,3 +64,78 @@ export default class Profile {
53
64
  return `Profile(profileId=${this._profileId}, user=${this._user?.formattedName ?? "none"}, businesses=${this._businesses.length})`;
54
65
  }
55
66
  }
67
+ // A business id in the canonical form the server finds it by (Java Business.canonicalizeBusinessId):
68
+ // spaces normalized as Java StringHelper.normalizeSpace does, then lower case. So a url typed in any
69
+ // case or spacing names one business. Inner spaces count, as an id can hold some (Java builds one
70
+ // from the display name, "Warm Loaf_1a2b3c4d", or takes what is typed).
71
+ // normalizeSpace is followed step for step, quirks included: a string with no whitespace at either
72
+ // end and none twice in a row is left alone, and a non-breaking space is not whitespace to it, so
73
+ // one between words stays unless something else in the string is normalized.
74
+ // Java refuses an id not four to sixty-four characters long. This is only a comparison and does
75
+ // not. The server answers a read of such an id with NotFoundException, as for a missing business.
76
+ export function canonicalizeBusinessId(businessId) {
77
+ return normalizeSpace(businessId).toLowerCase();
78
+ }
79
+ // Java StringHelper.normalizeSpace. Where Java answers null (a string of only whitespace), this answers an empty string.
80
+ function normalizeSpace(input) {
81
+ if (!needsNormalization(input))
82
+ return input;
83
+ let result = "";
84
+ let whitespaceCount = 0;
85
+ let isAtStart = true;
86
+ for (let index = 0; index < input.length; ++index) {
87
+ const unit = input.charCodeAt(index);
88
+ if (isWhitespace(unit)) {
89
+ if (whitespaceCount === 0 && !isAtStart)
90
+ result += " ";
91
+ ++whitespaceCount;
92
+ }
93
+ else {
94
+ isAtStart = false;
95
+ result += unit === NO_BREAK_SPACE ? " " : input.charAt(index);
96
+ whitespaceCount = 0;
97
+ }
98
+ }
99
+ if (isAtStart)
100
+ return "";
101
+ // Java String.trim removes every character up to a space from either end, unlike a script's trim, which removes
102
+ // Unicode spaces. The space standing for trailing whitespace goes with it (Java drops that one first, same result)
103
+ let first = 0;
104
+ let last = result.length;
105
+ while (first < last && result.charCodeAt(first) <= SPACE)
106
+ ++first;
107
+ while (last > first && result.charCodeAt(last - 1) <= SPACE)
108
+ --last;
109
+ return result.slice(first, last);
110
+ }
111
+ // Java StringHelper.needsNormalization: whitespace at either end, or two together between
112
+ function needsNormalization(input) {
113
+ if (input.length === 0)
114
+ return false;
115
+ if (isWhitespace(input.charCodeAt(0)) || isWhitespace(input.charCodeAt(input.length - 1)))
116
+ return true;
117
+ let wasWhitespace = false;
118
+ for (let index = 1; index < input.length - 1; ++index) {
119
+ const isNowWhitespace = isWhitespace(input.charCodeAt(index));
120
+ if (isNowWhitespace && wasWhitespace)
121
+ return true;
122
+ wasWhitespace = isNowWhitespace;
123
+ }
124
+ return false;
125
+ }
126
+ // Java Character.isWhitespace: the Unicode spaces that do not hold a place (not the no-break ones U+00A0, U+2007 and
127
+ // U+202F) and the control characters that are spaces. A regular expression's \s differs: it includes the no-break
128
+ // spaces and the byte order mark, and excludes U+001C to U+001F
129
+ function isWhitespace(unit) {
130
+ if (unit === SPACE)
131
+ return true;
132
+ if (unit >= 0x09 && unit <= 0x0d)
133
+ return true; // Tab, line feed, vertical tab, form feed and carriage return
134
+ if (unit >= 0x1c && unit <= 0x1f)
135
+ return true; // The four separators
136
+ if (unit >= 0x2000 && unit <= 0x200a)
137
+ return unit !== 0x2007;
138
+ return unit === 0x1680 || unit === 0x2028 || unit === 0x2029 || unit === 0x205f || unit === 0x3000;
139
+ }
140
+ const SPACE = 0x20;
141
+ const NO_BREAK_SPACE = 0xa0;
@@ -34,25 +34,35 @@ export declare function getProfileResponseEntities(response: proto_ProfileSyncOu
34
34
  export declare function businessResponseToCacheWrite(response: proto_BusinessSyncOutResponse): CacheWrite;
35
35
  export declare function profileResponseToCacheWrite(response: proto_ProfileSyncOutResponse): CacheWrite;
36
36
  export declare function getBusinessResponseAttachmentBaseUrlExpiry(response: proto_BusinessSyncOutResponse | null): bigint;
37
- export declare function orderToBlob(order: proto_Message): StoredBlob;
37
+ export declare function orderToBlob(order: proto_Message, heldETag: bigint | null): StoredBlob;
38
38
  export declare function isPartyPreferencesMessage(message: proto_Message): boolean;
39
39
  export declare function getMessageUpdatedAt(message: proto_Message): bigint;
40
- export declare function partyPreferencesToBlob(partyPreferences: proto_Message): StoredBlob;
40
+ export declare function getMessageId(message: proto_Message): bigint;
41
+ export declare function partyPreferencesToBlob(partyPreferences: proto_Message, heldETag: bigint | null): StoredBlob;
41
42
  export declare function businessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
42
43
  export declare function profileResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_ProfileSyncOutResponse | null;
44
+ export declare function deletedBusinessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
45
+ export declare function deletedProfileResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_ProfileSyncOutResponse | null;
43
46
  export declare function orderMessagesFromBlobs(blobs: ReadonlyArray<StoredBlob>): ReadonlyArray<proto_Message>;
47
+ export declare function deletedOrderMessagesFromBlobs(blobs: ReadonlyArray<StoredBlob>): ReadonlyArray<proto_Message>;
44
48
  export declare function partyPreferencesMessageFromMessages(messages: ReadonlyArray<proto_Message>): proto_Message | null;
45
49
  export declare function partyPreferencesMessageFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_Message | null;
46
- export declare function buildBusinessSyncOutRequest(businessId: string, syncInState: SyncState, attachmentBaseUrlExpiry: bigint): proto_BusinessSyncOutRequest;
47
- export declare function buildBusinessSyncOutRequestFromCache(businessId: string, cached: proto_BusinessSyncOutResponse | null): proto_BusinessSyncOutRequest;
50
+ export declare function buildBusinessSyncOutRequest(business: string | bigint, syncInState: SyncState, attachmentBaseUrlExpiry: bigint): proto_BusinessSyncOutRequest;
51
+ export declare function cursorsToBlobs(cursors: SyncState, movedKeys: ReadonlySet<number>): Array<StoredBlob>;
52
+ export declare function cursorsFromBlobs(keys: ReadonlyArray<number>, blobs: ReadonlyArray<StoredBlob | null>): SyncState;
53
+ export declare function raiseBusinessCursors(cursors: SyncState, response: proto_BusinessSyncOutResponse): ReadonlySet<number>;
48
54
  export declare function buildBusinessSyncInRequest(messageUpdateNumber: bigint, messages: ReadonlyArray<proto_Message>): proto_BusinessSyncInRequest;
49
55
  export declare function getSyncInResponseMessages(response: proto_BusinessSyncInResponse): ReadonlyArray<proto_Message>;
50
56
  export declare function buildProfileSyncOutRequest(syncInState: SyncState, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
51
- export declare function buildProfileSyncOutRequestFromCache(cached: proto_ProfileSyncOutResponse | null, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
57
+ export declare function raiseProfileCursors(cursors: SyncState, response: proto_ProfileSyncOutResponse): ReadonlySet<number>;
52
58
  export declare function buildUser(userProfile: UserProfile): proto_User;
53
59
  export declare function buildUpdateUser(userProfile: UserProfile): proto_User;
54
60
  export declare function hasJoinedBusinessLink(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
55
61
  export declare function hasJoinedBusiness(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
62
+ export declare function getBusinessResponseCountryCode(response: proto_BusinessSyncOutResponse | null): number | undefined;
63
+ export declare function getBusinessResponseBusinessEntityId(response: proto_BusinessSyncOutResponse | null): bigint | null;
64
+ export declare function getBusinessResponseBusinessId(response: proto_BusinessSyncOutResponse | null): string | null;
65
+ export declare function isBusinessResponseOnlineShoppingOn(response: proto_BusinessSyncOutResponse | null): boolean;
56
66
  export declare function buildAuthTokens(authTokens: AuthTokens): proto_AuthTokens;
57
67
  export declare function authTokensFromProto(authTokens: proto_AuthTokens): AuthTokens;
58
68
  export declare function buildAuthTokensBytes(authTokens: proto_AuthTokens): Uint8Array;
@@ -65,10 +75,10 @@ export declare function nextBusinessSyncOutRequest(request: proto_BusinessSyncOu
65
75
  export declare function nextProfileSyncOutRequest(request: proto_ProfileSyncOutRequest, response: proto_ProfileSyncOutResponse): proto_ProfileSyncOutRequest | null;
66
76
  export declare function buildBusiness(response: proto_BusinessSyncOutResponse): Business | null;
67
77
  export declare function buildProfile(profileId: string, response: proto_ProfileSyncOutResponse): Profile | null;
68
- export declare function buildOrder(message: proto_Message): Order | null;
69
- export declare function buildShoppingList(message: proto_Message): ShoppingList;
78
+ export declare function buildOrder(message: proto_Message, countryCode: number): Order | null;
79
+ export declare function buildShoppingList(message: proto_Message, countryCode: number): ShoppingList;
70
80
  export declare function buildPartyPreferencesMessage(messageId: bigint, existing: proto_Message | null, senderId: bigint, shoppingList: ShoppingList | null, fulfillmentMode: proto_FulfillmentMode | null, now: bigint): proto_Message;
71
81
  export declare function buildOrderMessage(orderId: bigint, senderId: bigint, shoppingList: ShoppingList, fulfillmentMode: proto_FulfillmentMode, deliveryAddressId: bigint, deliveryAddress: Address | null, note: string | null, creationDate: bigint): proto_Message;
72
82
  export declare function messagesToCacheWrite(messages: ReadonlyArray<proto_Message>): CacheWrite;
73
- export declare function highestCachedMessageUpdateNumber(blobs: ReadonlyArray<StoredBlob>): bigint;
74
83
  export declare function hasShoppingListChanged(stored: proto_Message | null, shoppingList: ShoppingList): boolean;
84
+ export declare function entityETag(updateNumber: bigint, updatedAt: bigint): bigint;