@peabody-soft/mbs-model-ts 1.6.0 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ProtoMap.js CHANGED
@@ -98,12 +98,12 @@ export function getProfileResponseEntities(response) {
98
98
  result.push(toWireEntity(business.entity, proto_Business.toBinary(business)));
99
99
  return result;
100
100
  }
101
- // One blob per entity, tombstones included and as sent, plus the attachment base url in a blob of its
102
- // own (it belongs to the business, not to an entity).
101
+ // One blob per entity, tombstones included and as sent, plus a blob for the attachment base url,
102
+ // which belongs to the business and has no entity.
103
103
  export function businessResponseToCacheWrite(response) {
104
104
  const write = toCacheWrite(getBusinessResponseEntities(response));
105
- // Its eTag is its expiry, which is also its cursor: a client sends back the expiry it holds and
106
- // gets a later url only if one exists.
105
+ // The url's eTag is its expiry, which is also its cursor: a client sends back the expiry it holds
106
+ // and gets a later url only if one exists.
107
107
  const attachmentBaseUrl = response.attachmentBaseUrl;
108
108
  if (attachmentBaseUrl !== undefined)
109
109
  write.blobs.push({
@@ -122,9 +122,10 @@ export function profileResponseToCacheWrite(response) {
122
122
  export function getBusinessResponseAttachmentBaseUrlExpiry(response) {
123
123
  return response?.attachmentBaseUrl?.expiry ?? 0n;
124
124
  }
125
- // One order as its own blob, for a caller holding a message rather than a response. Only the payload
126
- // says what a message is, so a non-order is refused here instead of stored where a read would not
127
- // find it. heldETag is the eTag the container holds the order at, or null - see messageToBlob.
125
+ // One order as a blob, for a caller holding a message rather than a response. Only the payload says
126
+ // whether a message is an order, so a message without a sales order throws here instead of being
127
+ // stored where a read would not find it. heldETag is the eTag the container holds the order at, or
128
+ // null - see messageToBlob.
128
129
  export function orderToBlob(order, heldETag) {
129
130
  if (order.payload.oneofKind !== "salesOrder")
130
131
  throw new InvalidArgumentException("That message carries no sales order, so it is not an order");
@@ -134,8 +135,8 @@ export function orderToBlob(order, heldETag) {
134
135
  export function isPartyPreferencesMessage(message) {
135
136
  return message.payload.oneofKind === "partyPreferences";
136
137
  }
137
- // When a message was last written. It compares two copies of one thing held at different ids, which
138
- // have no shared version.
138
+ // When a message was last written. Use it to order two copies of one thing held at different ids,
139
+ // which have no shared version.
139
140
  export function getMessageUpdatedAt(message) {
140
141
  return getEntity(message.entity).updatedAt;
141
142
  }
@@ -149,8 +150,8 @@ export function partyPreferencesToBlob(partyPreferences, heldETag) {
149
150
  return messageToBlob(partyPreferences, heldETag);
150
151
  }
151
152
  // Rebuilds the response a business container's blobs make up, as the call returned it. Null when they
152
- // hold nothing yet. The entity type in each blob id sorts them, so no type tag is stored. An unknown
153
- // type is skipped, so a newer server cannot break an older client.
153
+ // hold nothing yet. The entity type is part of each blob id and picks the array a blob goes in, so no
154
+ // type tag is stored. An unknown type is skipped, so a newer server cannot break an older client.
154
155
  export function businessResponseFromBlobs(blobs) {
155
156
  const response = proto_BusinessSyncOutResponse.create({});
156
157
  let hasContent = false;
@@ -260,9 +261,9 @@ export function orderMessagesFromBlobs(blobs) {
260
261
  return ordersAmong(messagesFromBlobs(blobs, false));
261
262
  }
262
263
  // The orders the business has completed, by fulfilling or cancelling. The merchant app completes an
263
- // order by deleting its message, so these are tombstones, but a server sends them whole because a
264
- // customer is shown the order afterwards. A tombstone with no order (left by an older server that
265
- // sent only the header) is not one.
264
+ // order by deleting its message, so these are tombstones. The server sends them whole because the
265
+ // customer is shown the order afterwards. A tombstone without an order (from an older server that
266
+ // sent only the header) is skipped.
266
267
  export function deletedOrderMessagesFromBlobs(blobs) {
267
268
  return ordersAmong(messagesFromBlobs(blobs, true));
268
269
  }
@@ -270,12 +271,13 @@ function ordersAmong(messages) {
270
271
  return messages.filter((message) => message.payload.oneofKind === "salesOrder");
271
272
  }
272
273
  // The party preferences message among these messages, or null. A customer has one per business, so
273
- // two means two callers' messages share a container. The greater update number wins for want of
274
- // anything better, since nothing in a message says which caller it was sent to. See THE MESSAGES OF
275
- // A BUSINESS ARE NOT PUBLIC.
274
+ // two mean that two callers' messages share a container. The one with the greater update number is
275
+ // returned, an arbitrary choice since nothing in a message says which caller it was sent to.
276
+ // See THE MESSAGES OF A BUSINESS ARE NOT PUBLIC.
276
277
  // Split from partyPreferencesMessageFromBlobs so a caller already holding the messages (such as
277
278
  // Model.businessSyncIn folding the anonymous container in) need not read the blobs again.
278
- // buildBusiness uses it too, so the cart shown, amended and carried over a sign in is one message.
279
+ // buildBusiness uses it too, so the cart that is shown, amended and carried over at sign in always
280
+ // comes from the same message.
279
281
  export function partyPreferencesMessageFromMessages(messages) {
280
282
  let result = null;
281
283
  for (const message of messages) {
@@ -297,9 +299,9 @@ export function partyPreferencesMessageFromBlobs(blobs) {
297
299
  // #endregion
298
300
  // #region Requests and credentials
299
301
  // The request for everything changed since these cursors. A bigint business is the Business entity
300
- // id, which a caller that has read it before has. A string is its url friendly businessId, which is
301
- // all a first read has. attachmentBaseUrlExpiry is not a cursor but the expiry of the url already
302
- // held, sent back unchanged (zero when none).
302
+ // id, which a caller that has read the business before holds. A string is its url friendly
303
+ // businessId, the only identifier a first read has. attachmentBaseUrlExpiry is not one of the
304
+ // cursors. It is the expiry of the url already held, sent back unchanged (zero when none).
303
305
  export function buildBusinessSyncOutRequest(business, syncInState, attachmentBaseUrlExpiry) {
304
306
  return createBusinessSyncOutRequest(typeof business === "bigint"
305
307
  ? { oneofKind: "businessEntityId", businessEntityId: business }
@@ -333,9 +335,9 @@ export function cursorsToBlobs(cursors, movedKeys) {
333
335
  return blobs;
334
336
  }
335
337
  // The cursors a container holds, from the blobs at the cursor ids of keys (in key order, null where
336
- // none). A container never read, or filled by a version that kept no cursors, gets zeros and asks for
337
- // everything again. That costs bandwidth only, since the store keeps a blob only when its eTag is
338
- // greater.
338
+ // none). A container never read, or filled by a version that kept no cursors, gets zeros, so the
339
+ // next read fetches everything again. That costs only bandwidth, since the store keeps a blob only
340
+ // when its eTag is greater.
339
341
  export function cursorsFromBlobs(keys, blobs) {
340
342
  const cursors = new SyncState(keys);
341
343
  for (const [index, key] of keys.entries()) {
@@ -345,15 +347,15 @@ export function cursorsFromBlobs(keys, blobs) {
345
347
  }
346
348
  return cursors;
347
349
  }
348
- // Raises the cursors to what a page of a business read brought, tombstones included, and answers the
349
- // keys that moved. Only a read moves them. What this client pushed does not.
350
+ // Raises the cursors to what a page of a business read brought, tombstones included, and returns the
351
+ // keys that moved. Only a read moves them. A push does not.
350
352
  export function raiseBusinessCursors(cursors, response) {
351
353
  return raiseCursorsFrom(cursors, getBusinessResponseEntities(response));
352
354
  }
353
- // The request that pushes what this customer changed. A storefront may push only messages, and one
355
+ // The request that pushes what this customer changed. A storefront may push only messages. One
354
356
  // request is one batch, so an order and the cart it emptied go together and a customer is never left
355
- // holding the order and a still full cart. messageUpdateNumber is how far reads of this container
356
- // have got with messages - see SyncCache.readMessageUpdateNumber.
357
+ // with the order and a still full cart. messageUpdateNumber is how far reads of this container have
358
+ // got with messages - see SyncCache.readMessageUpdateNumber.
357
359
  export function buildBusinessSyncInRequest(messageUpdateNumber, messages) {
358
360
  return proto_BusinessSyncInRequest.create({
359
361
  messageUpdateNumber: messageUpdateNumber,
@@ -366,10 +368,10 @@ export function getSyncInResponseMessages(response) {
366
368
  return response.messages;
367
369
  }
368
370
  // The request for everything changed in the caller's profile since these cursors. It names no
369
- // business, since a profile sync is not scoped to one and has no sync in. joinBusinessId tells the
370
- // server to make the caller a customer of that business, by entity id, while answering.
371
- // Constants.ENTITY_ID_NULL asks for no join and is the default, since an ordinary read joins
372
- // nothing. A join belongs to one call, so nextProfileSyncOutRequest leaves it off later pages.
371
+ // business, since a profile sync is not scoped to one and has no sync in. joinBusinessId is the
372
+ // entity id of a business to make the caller a customer of while the server answers.
373
+ // Constants.ENTITY_ID_NULL means no join and is the default, since an ordinary read joins nothing.
374
+ // A join belongs to one call, so nextProfileSyncOutRequest leaves it off later pages.
373
375
  export function buildProfileSyncOutRequest(syncInState, joinBusinessId = BigInt(proto_Constants.ENTITY_ID_NULL)) {
374
376
  return proto_ProfileSyncOutRequest.create({
375
377
  userUpdateNumber: syncInState.getUpdateNumber(proto_Constants.USER_ENTITY_TYPE_ID),
@@ -382,8 +384,8 @@ export function buildProfileSyncOutRequest(syncInState, joinBusinessId = BigInt(
382
384
  export function raiseProfileCursors(cursors, response) {
383
385
  return raiseCursorsFrom(cursors, getProfileResponseEntities(response));
384
386
  }
385
- // The user a sign up asks the server to create, as CreateUserRequest.user. It has no entity yet (the
386
- // server assigns id, createdAt and updatedAt), so none of EntityHeader goes out.
387
+ // The user a sign up sends as CreateUserRequest.user, for the server to create. It has no entity yet
388
+ // (the server assigns id, createdAt and updatedAt), so none of EntityHeader is sent.
387
389
  export function buildUser(userProfile) {
388
390
  return proto_User.create({
389
391
  firstName: userProfile.firstName,
@@ -393,18 +395,18 @@ export function buildUser(userProfile) {
393
395
  preferredLanguageCode: stringToProto(userProfile.preferredLanguageCode),
394
396
  });
395
397
  }
396
- // The user Service.updateUser overwrites this customer's profile with: the fields of buildUser plus
397
- // the entity id, which the call matches the caller by. The rest of EntityHeader is not read on that
398
- // call, so it is not sent.
398
+ // The user that Service.updateUser writes over this customer's profile: the fields of buildUser plus
399
+ // the entity id, which the server matches to the caller. The rest of EntityHeader is not read on
400
+ // that call, so it is not sent.
399
401
  export function buildUpdateUser(userProfile) {
400
402
  return proto_User.create({
401
403
  ...buildUser(userProfile),
402
404
  entity: proto_Entity.create({ id: userProfile.id }),
403
405
  });
404
406
  }
405
- // The two things a join must be answered with, checked separately so a caller can report which one a
406
- // server left out - see Model.requireJoinedBusiness. They take the id, since both requests that
407
- // carry a joinBusinessId need them.
407
+ // The two things a response to a join must hold, checked separately so a caller can report which one
408
+ // is missing - see Model.requireJoinedBusiness. Both take the id, since both requests that carry a
409
+ // joinBusinessId need them.
408
410
  // The membership itself: a live BusinessLink naming that business. A tombstoned link does not count
409
411
  // (see OBJECT MODEL), nor does a link to another business. Its linkToken is the only copy of that
410
412
  // credential a profile response carries.
@@ -424,7 +426,7 @@ export function hasJoinedBusinessLink(joinBusinessId, response) {
424
426
  export function hasJoinedBusiness(joinBusinessId, response) {
425
427
  if (joinBusinessId === BigInt(proto_Constants.ENTITY_ID_NULL))
426
428
  return false;
427
- // A tombstone is not the business, which is what the join is answered with
429
+ // A tombstoned business does not count
428
430
  for (const business of response.businesses)
429
431
  if (getEntity(business.entity).id === joinBusinessId &&
430
432
  !isDeletedEntity(getEntity(business.entity)))
@@ -432,8 +434,8 @@ export function hasJoinedBusiness(joinBusinessId, response) {
432
434
  return false;
433
435
  }
434
436
  // The country of the business a response holds, ISO 3166-1 numeric, or undefined when it holds none.
435
- // A tombstone has no country, and a bare header carries zero, which must not be taken for one: a
436
- // cart's taxes are raw codes that mean something only within a country.
437
+ // A tombstone has no country, and a bare header carries zero. Zero must not be taken for a country,
438
+ // because a cart's taxes are raw codes that mean something only within a country.
437
439
  export function getBusinessResponseCountryCode(response) {
438
440
  const business = response?.business;
439
441
  if (business === undefined || isDeletedEntity(getEntity(business.entity)))
@@ -441,8 +443,8 @@ export function getBusinessResponseCountryCode(response) {
441
443
  return business.countryCode;
442
444
  }
443
445
  // The entity id of the business a response holds, or null when it holds none or only a tombstone.
444
- // Once a business has been read, later requests name it by this id: it is stable, and the url
445
- // friendly id is only how it was first found.
446
+ // Once a business has been read, later requests name it by this id, which is stable. The url
447
+ // friendly id is used only to find the business the first time.
446
448
  export function getBusinessResponseBusinessEntityId(response) {
447
449
  const entity = response?.business?.entity;
448
450
  if (entity === undefined || isDeletedEntity(entity))
@@ -465,9 +467,9 @@ export function isBusinessResponseOnlineShoppingOn(response) {
465
467
  return false;
466
468
  return getGeneralSettingsOf(response)?.onlineShoppingEnabled ?? false;
467
469
  }
468
- // The general settings a response holds, as on the wire, or undefined when none: the one public
469
- // configuration that carries them, unless it is deleted. The business built from them and whether
470
- // shopping is on both read from here, so they cannot be about different settings.
470
+ // The general settings a response holds, as on the wire, or undefined when none. They come from the
471
+ // one public configuration that carries them, unless it is deleted. buildBusiness and
472
+ // isBusinessResponseOnlineShoppingOn both read them here, so both use the same settings.
471
473
  function getGeneralSettingsOf(response) {
472
474
  let settings;
473
475
  for (const configuration of response.configurations) {
@@ -480,7 +482,7 @@ function getGeneralSettingsOf(response) {
480
482
  return settings;
481
483
  }
482
484
  // The credentials of one call, as on the wire. An absent token is left off the message, since the
483
- // protocol tells absent from empty.
485
+ // protocol distinguishes absent from empty.
484
486
  export function buildAuthTokens(authTokens) {
485
487
  const result = proto_AuthTokens.create({
486
488
  authenticationMethod: authTokens.authenticationMethod,
@@ -503,10 +505,10 @@ export function authTokensFromProto(authTokens) {
503
505
  export function buildAuthTokensBytes(authTokens) {
504
506
  return proto_AuthTokens.toBinary(authTokens);
505
507
  }
506
- // How a container keeps the credentials it earned: the two tokens this server issued. The eTag is
507
- // null because a reissued token is not a later version of the old one, and greater-wins would refuse
508
- // it. The authentication token is never stored: it belongs to the identity provider, so a copy would
509
- // be staler than the one in memory and would outlive the sign out.
508
+ // The credentials as a container stores them: the two tokens this server issued, and the
509
+ // authentication method. The eTag is null because a reissued token is not a later version of the old
510
+ // one, and greater-wins would refuse it. The authentication token is never stored: it belongs to the
511
+ // identity provider, so a copy would be staler than the one in memory and would outlive the sign out.
510
512
  export function authTokensToBlob(authTokens) {
511
513
  const stored = proto_AuthTokens.create({
512
514
  authenticationMethod: authTokens.authenticationMethod,
@@ -542,13 +544,13 @@ export function refreshAuthTokens(authTokens, issued) {
542
544
  result.linkToken = issued.linkToken;
543
545
  return result;
544
546
  }
545
- // The request for the page after this response, or null when it was the last. A full page was cut
546
- // short, so the cursors are raised from what arrived and the call repeats. A full page that raised no
547
- // cursor means the server broke the ascending order it promises. The loop is unbounded, so it stops
548
- // there.
547
+ // The request for the page after this response, or null when it was the last. A full page may have
548
+ // been cut short, so the cursors are raised from what arrived and the call repeats. A full page that
549
+ // raised no cursor means the server broke the ascending order it promises. The loop has no other
550
+ // bound, so it ends there.
549
551
  export function nextBusinessSyncOutRequest(request, response) {
550
552
  // A short page means the server sent everything. Counting first keeps the ordinary case to nine
551
- // length reads.
553
+ // field reads.
552
554
  if (businessResponseEntityCount(response) < proto_Constants.SYNC_OUT_LIMIT)
553
555
  return null;
554
556
  const cursors = businessSyncOutCursors(request);
@@ -577,12 +579,12 @@ export function nextBusinessSyncOutRequest(request, response) {
577
579
  raise(getItem(price.item).entity);
578
580
  if (!haveCursorsRisen)
579
581
  return null;
580
- // A page that renewed the url means a later one is now held; otherwise what was sent still stands
582
+ // Use the expiry of a renewed url if this page carried one, else the expiry already sent
581
583
  const renewedExpiry = getBusinessResponseAttachmentBaseUrlExpiry(response);
582
584
  return createBusinessSyncOutRequest(request.business, cursors, renewedExpiry === 0n ? request.attachmentBaseUrlExpiry : renewedExpiry);
583
585
  }
584
586
  // The same for a profile read. A joinBusinessId on the first request is left off the later ones,
585
- // since the call that asked for the join does it once.
587
+ // since the first call makes the join.
586
588
  export function nextProfileSyncOutRequest(request, response) {
587
589
  if (profileResponseEntityCount(response) < proto_Constants.SYNC_OUT_LIMIT)
588
590
  return null;
@@ -601,8 +603,8 @@ export function nextProfileSyncOutRequest(request, response) {
601
603
  return null;
602
604
  return buildProfileSyncOutRequest(cursors);
603
605
  }
604
- // How many entities a response carried, without encoding any. A paging loop asks this of every page,
605
- // and getBusinessResponseEntities would encode each entity to answer it.
606
+ // How many entities a response carried, counted without encoding any. A paging loop needs the count
607
+ // for every page, and getBusinessResponseEntities would encode each entity to produce it.
606
608
  function businessResponseEntityCount(response) {
607
609
  return ((response.business === undefined ? 0 : 1) +
608
610
  response.attachments.length +
@@ -619,7 +621,7 @@ function profileResponseEntityCount(response) {
619
621
  response.businessLinks.length +
620
622
  response.businesses.length);
621
623
  }
622
- // Raises the cursors the entities of one page advance and answers the keys that moved, one per table
624
+ // Raises the cursors the entities of one page advance and returns the keys that moved, each once
623
625
  // however many entities raised it. A tombstone counts like any entity, as in raiseCursor.
624
626
  function raiseCursorsFrom(cursors, entities) {
625
627
  const movedKeys = new Set();
@@ -628,8 +630,8 @@ function raiseCursorsFrom(cursors, entities) {
628
630
  movedKeys.add(SyncKeys.keyOf(entity.entityTypeId));
629
631
  return movedKeys;
630
632
  }
631
- // Raises the cursor one entity advances and answers whether it moved. A tombstone counts, or the
632
- // server would send it again for ever.
633
+ // Raises the cursor one entity advances and returns whether it moved. A tombstone counts, because
634
+ // otherwise the server would send it again forever.
633
635
  function raiseCursor(cursors, entity) {
634
636
  if (entity === undefined)
635
637
  return false;
@@ -690,9 +692,9 @@ export function buildBusiness(response) {
690
692
  updatableBusiness.settings = generalSettingsFromProto(generalSettings, countryCode);
691
693
  // The photos of the store itself
692
694
  updatableBusiness.photos = takePhotos(attachmentsByOwner, business.id, attachmentBaseUrl);
693
- // Prices, by the package each applies to. Only the MeraBills Online list is this storefront's. A
695
+ // Prices, by the package each applies to. This storefront uses only the MeraBills Online list. A
694
696
  // business prices a package in several lists, and a price from another list is not what it sells at
695
- // here, so it is skipped. A package has one price in that list; if a second arrives, the first is
697
+ // here, so it is skipped. A package has one price in that list. If a second arrives, the first is
696
698
  // kept.
697
699
  const pricesByPackage = new Map();
698
700
  for (const priceProto of response.prices) {
@@ -751,9 +753,9 @@ export function buildBusiness(response) {
751
753
  UpdatableEntity.setParent(productCategory, business);
752
754
  productCategories.push(productCategory);
753
755
  }
754
- // The one list a storefront sells from is MeraBills Online, picked by id since the same sync can
755
- // carry others. The catalog must answer with the list the prices above came from, or a cart would
756
- // name one they are not in.
756
+ // A storefront sells from the MeraBills Online list only, picked by id since the same sync can
757
+ // carry others. The catalog must hold the list the prices above came from. Otherwise a cart would
758
+ // name a list that does not contain them.
757
759
  let priceList = null;
758
760
  for (const priceListProto of response.priceLists) {
759
761
  if (isDeletedItem(priceListProto.item))
@@ -767,25 +769,25 @@ export function buildBusiness(response) {
767
769
  updatableBusiness.catalog = new Catalog(productCategories, products, priceList);
768
770
  // The orders of this customer, and their party preferences (the cart and the fulfillment mode to
769
771
  // default the next order to). A message of any other payload type is dropped. The business completes
770
- // an order by deleting its message, and the server sends that message whole, so the orders are the
771
- // open and the completed ones, told apart by Order.isDeleted. A deleted message with no order (from
772
- // an older server that sent only the header) is not an order.
772
+ // an order by deleting its message, and the server sends that message whole, so the orders include
773
+ // the open and the completed ones. Order.isDeleted tells them apart. A deleted message without an
774
+ // order (from an older server that sent only the header) is skipped.
773
775
  const orders = [];
774
776
  for (const message of response.messages) {
775
777
  // Party preferences are one message out of possibly several, so they are settled below
776
778
  if (message.payload.oneofKind === "salesOrder")
777
779
  orders.push(orderFromProto(message, message.payload.salesOrder, countryCode));
778
780
  }
779
- // A customer has one party preferences message, so two means two callers' messages share a
780
- // container. Take the one partyPreferencesMessageFromMessages picks, which is also what an amended or
781
- // resent cart is read from. A rule of its own here would show one cart and amend another. The newer
782
- // one wins even when it is the emptied one, and the older one's list is not shown.
781
+ // A customer has one party preferences message, so two mean two callers' messages share a
782
+ // container. Use the one partyPreferencesMessageFromMessages picks, which is also the one an amended
783
+ // or resent cart is read from. A separate rule here would show one cart and amend another. The newer
784
+ // message wins even when its cart is empty, and the older one's list is not shown.
783
785
  const partyPreferences = partyPreferencesMessageFromMessages(response.messages);
784
786
  let cart = null;
785
787
  let defaultFulfillmentMode = proto_FulfillmentMode.DELIVERY;
786
788
  if (partyPreferences !== null && partyPreferences.payload.oneofKind === "partyPreferences") {
787
789
  // An emptied cart is a message whose payload has no shopping list, which is what placing an order
788
- // leaves. It answers null, the same as never having had a cart.
790
+ // leaves. The cart is null then, the same as for a customer who never had one.
789
791
  cart = cartShoppingListFromProto(partyPreferences.payload.partyPreferences, countryCode);
790
792
  // Taken as the message has it. DELIVERY comes only from the fallback above, even when the message
791
793
  // holds UNSPECIFIED_FULFILLMENT_MODE.
@@ -821,16 +823,16 @@ export function buildProfile(profileId, response) {
821
823
  return null;
822
824
  return new Profile(profileId, user, businessLinks, businesses);
823
825
  }
824
- // One order on its own, for a caller holding a message rather than a whole response. The country of
825
- // the business it was placed with is the caller's to give, since a tax travels as a raw code.
826
+ // One order on its own, for a caller holding a message rather than a whole response. The caller
827
+ // passes the country of the business the order was placed with, since a tax travels as a raw code.
826
828
  export function buildOrder(message, countryCode) {
827
829
  if (message.payload.oneofKind !== "salesOrder")
828
830
  return null;
829
831
  return orderFromProto(message, message.payload.salesOrder, countryCode);
830
832
  }
831
- // The shopping list a cart message carries, empty when the cart was emptied. Business.cart answers
832
- // null for that (see buildBusiness), but the callers here, createOrder and the visitor cart carried
833
- // over on sign in, need an actual list. countryCode is the cart's business, as for buildOrder.
833
+ // The shopping list a cart message carries, empty when the cart was emptied. Business.cart is null
834
+ // then (see buildBusiness), but the callers here, createOrder and the visitor cart carried over on
835
+ // sign in, need a list. countryCode is the country of the cart's business, as for buildOrder.
834
836
  export function buildShoppingList(message, countryCode) {
835
837
  if (message.payload.oneofKind !== "partyPreferences")
836
838
  throw new InvalidArgumentException("That message carries no party preferences, so it is not a cart");
@@ -840,28 +842,29 @@ export function buildShoppingList(message, countryCode) {
840
842
  // #region What a customer pushes
841
843
  //
842
844
  // A storefront writes one entity type, a Message carrying the cart or an order placed from it. Two
843
- // rules hold for both, so a pushed message is storable here as well as on the server. The id is the
844
- // client's: a sync in answers with an update number and no id, so a message the server named could
845
- // not be found here again. updatedAt is the client's too and the server keeps it, so the copy stored
846
- // here has the same eTag as the one a later read brings back.
845
+ // rules hold for both, so a pushed message is storable here as well as on the server. The client
846
+ // chooses the id: a sync in returns an update number and no id, so a message the server named could
847
+ // not be found here again. The client also sets updatedAt and the server keeps it, so the copy
848
+ // stored here has the same eTag as the one a later read brings back.
847
849
  // This customer's party preferences as a message to push, carrying the cart and optionally the
848
850
  // fulfillment mode. An existing message is amended, since its payload can carry fields a storefront
849
851
  // cannot read, and dropping them would overwrite that data. Its id is kept, as the server holds the
850
852
  // preferences under it. The derived id is used only when there is nothing to amend.
851
- // A list with no items is pushed as no list, as Java serializeShoppingList answers null below one
852
- // item. Emptying a cart and never having had one are one state on the wire.
853
- // fulfillmentMode null leaves the existing value alone, the ordinary case: a storefront treats it as
854
- // the merchant app's data. createOrder sets it to the mode of the order that just emptied the cart.
853
+ // A list with no items is pushed as no list, as Java serializeShoppingList returns null below one
854
+ // item. An emptied cart and a cart never created look the same on the wire.
855
+ // fulfillmentMode null keeps the existing value, the ordinary case: a storefront treats that field
856
+ // as the merchant app's data and leaves it alone. createOrder sets it to the mode of the order that
857
+ // emptied the cart.
855
858
  export function buildPartyPreferencesMessage(messageId, existing, senderId, shoppingList, fulfillmentMode, now) {
856
859
  const existingEntity = existing === null ? null : getEntity(existing.entity);
857
860
  const preferences = existing !== null && existing.payload.oneofKind === "partyPreferences"
858
861
  ? existing.payload.partyPreferences
859
862
  : proto_PartyPreferencesPayload.create();
860
- // Later than the copy it replaces, whatever this client's clock reads. An amendment written without
861
- // the server is stored at the update number of the copy it amends, so updatedAt decides. A client
862
- // clock behind that copy (Constants.MAX_CLOCK_SKEW says it happens) would have its own store refuse
863
- // the edit and leave the customer looking at a stale cart. One millisecond past the copy is enough,
864
- // and the server keeps what is sent, so both sides agree.
863
+ // Later than the copy it replaces, even if this client's clock is behind it. An amendment written
864
+ // without the server is stored at the update number of the copy it amends, so updatedAt decides. A
865
+ // client clock behind that copy (clocks do differ, see Constants.MAX_CLOCK_SKEW) would make its own
866
+ // store refuse the edit and leave the customer looking at a stale cart. One millisecond past the
867
+ // copy is enough, and the server keeps what is sent, so both sides agree.
865
868
  const updatedAt = existingEntity === null || existingEntity.updatedAt < now ? now : existingEntity.updatedAt + 1n;
866
869
  const partyPreferences = proto_PartyPreferencesPayload.create({
867
870
  ...preferences,
@@ -877,12 +880,12 @@ export function buildPartyPreferencesMessage(messageId, existing, senderId, shop
877
880
  payload: { oneofKind: "partyPreferences", partyPreferences: partyPreferences },
878
881
  });
879
882
  }
880
- // One order as a message to push. Its shopping list goes out final whatever the list says, as Java
881
- // SalesOrderViewModel.saveChanges does before creating an order: the customer has finished choosing,
882
- // and only a final list tracks a later change as a change instead of silently altering what was
883
- // ordered. The two totals are all that remains of an order completed by an older merchant app, which
884
- // moved the list out of the order. They are filled from the list being sent, not from the counts it
885
- // carries, which a caller could set to anything.
883
+ // One order as a message to push. Its shopping list is sent as final regardless of the list's
884
+ // isFinal, as Java SalesOrderViewModel.saveChanges does before creating an order: the customer has
885
+ // finished choosing, and only a final list records a later edit as a change instead of altering what
886
+ // was ordered. The two totals are all that remains of an order completed by an older merchant app,
887
+ // which moved the list out of the order. They are filled from the list being sent, not from the
888
+ // counts it carries, which a caller could set to anything.
886
889
  export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMode, deliveryAddressId, deliveryAddress, note, creationDate) {
887
890
  return proto_Message.create({
888
891
  entity: pushedEntity(orderId, creationDate, creationDate),
@@ -906,30 +909,30 @@ export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMo
906
909
  });
907
910
  }
908
911
  // The messages of a sync in response as a cache write, tombstones included like any other entity
909
- // array. This stores a push's own messages under the update numbers the server just assigned, with no
912
+ // array. This stores a push's own messages under the update numbers the server assigned, with no
910
913
  // separate stamping step, since the response carries full entities.
911
914
  export function messagesToCacheWrite(messages) {
912
915
  return toCacheWrite(messages.map((message) => toWireEntity(message.entity, proto_Message.toBinary(message))));
913
916
  }
914
- // The Entity of something this client is pushing. The update number goes out as zero: the server
915
- // replaces a caller's own with the one it assigns, so there is nothing to send. A message not pushed
916
- // yet is stored holding zero, and messageToBlob versions it from the copy it amends. updateInfo is
917
- // zero too, as it would be the id of the store that made the change and the server knows no store of
918
- // this client. It needs none: a read sends these messages back (the server leaves no one's changes
919
- // out of a storefront's read), and the eTag stored here is the one the server kept, so the read
920
- // changes nothing.
917
+ // The Entity of something this client is pushing. The update number goes out as zero, since the
918
+ // server replaces a caller's own with the one it assigns. A message not pushed yet is stored holding
919
+ // zero, and messageToBlob versions it from the copy it amends. updateInfo is zero too: it would be
920
+ // the id of the store that made the change, and the server knows no store of this client. This
921
+ // client does not need it: a read sends these messages back (the server leaves no one's changes out
922
+ // of a storefront's read), and the eTag stored here is the one the server kept, so storing what the
923
+ // read brings back changes nothing.
921
924
  function pushedEntity(id, createdAt, updatedAt) {
922
925
  return proto_Entity.create({ id: id, createdAt: createdAt, updatedAt: updatedAt });
923
926
  }
924
927
  // Whether shoppingList differs from what stored holds, so updateCart can skip a push. Compares raw
925
- // lists, since a message's updatedAt differs on every call. That is sound because the server echoes
926
- // a pushed list back with the items it was sent, so stored is what was last sent.
927
- // The server echoes the items in package id order, and the list's own order is each item's
928
- // shoppingListOrder, compared with the item. So both lists are put in package id order first, or a
929
- // cart not picked in that order would differ from its echo and be pushed again, re-stamping
930
- // updatedAt for every other device.
931
- // True also when stored was never pushed (updateNumber 0, left by updateCartLocalOnly), so an
932
- // unsent local edit still goes out.
928
+ // lists, since a message's updatedAt differs on every call. That works because the server echoes a
929
+ // pushed list back with the items it was sent, so stored is what was last sent.
930
+ // The server echoes the items in package id order, and the list's own order is kept in each item's
931
+ // shoppingListOrder, which is compared with the item. So both lists are put in package id order
932
+ // first. Otherwise a cart not picked in that order would differ from its echo and be pushed again,
933
+ // which re-stamps updatedAt for every other device.
934
+ // Also true when stored was never pushed (updateNumber 0, left by updateCartLocalOnly), so an
935
+ // unsent local edit is still pushed.
933
936
  export function hasShoppingListChanged(stored, shoppingList) {
934
937
  if (stored === null)
935
938
  return pushedShoppingList(shoppingList, false) !== undefined;
@@ -937,7 +940,7 @@ export function hasShoppingListChanged(stored, shoppingList) {
937
940
  return true;
938
941
  return !proto_ShoppingList.equals(inPackageIdOrder(getCartShoppingList(stored)), inPackageIdOrder(pushedShoppingList(shoppingList, false)));
939
942
  }
940
- // The same list with its items by package id, the order in which the server sends them
943
+ // The same list with its items sorted by package id, the order the server sends them in
941
944
  function inPackageIdOrder(list) {
942
945
  if (list === undefined)
943
946
  return undefined;
@@ -954,7 +957,7 @@ function getCartShoppingList(message) {
954
957
  ? message.payload.partyPreferences.shoppingList
955
958
  : undefined;
956
959
  }
957
- // A shopping list on its way out, or absent when it holds nothing - see buildPartyPreferencesMessage.
960
+ // A shopping list to push, or absent when it holds nothing - see buildPartyPreferencesMessage.
958
961
  // Emptiness is judged by the items sent, not the counts, for the reason given at shoppingListToProto.
959
962
  function pushedShoppingList(shoppingList, isFinal) {
960
963
  if (shoppingList === null)
@@ -963,9 +966,9 @@ function pushedShoppingList(shoppingList, isFinal) {
963
966
  return undefined;
964
967
  return shoppingListToProto(shoppingList, isFinal);
965
968
  }
966
- // The counts come from the arrays, not the list. A read takes them from the wire because the
967
- // protocol sends a count even where it omits the items, but a push always sends the items. Taking the
968
- // counts from the arrays means a pushed list cannot disagree with its contents.
969
+ // The counts are the lengths of the item arrays, not the list's own counts. A read takes them from
970
+ // the wire because the protocol sends a count even where it omits the items, but a push always sends
971
+ // the items. Counting the arrays means a pushed list cannot disagree with its contents.
969
972
  function shoppingListToProto(shoppingList, isFinal) {
970
973
  return proto_ShoppingList.create({
971
974
  isFinal: isFinal,
@@ -981,8 +984,8 @@ function shoppingListToProto(shoppingList, isFinal) {
981
984
  deletedItems: shoppingList.deletedItems.map(shoppingListItemToProto),
982
985
  });
983
986
  }
984
- // The mirror of shoppingListItemFromProto. Unscaled model values go back scaled, and nothing derived
985
- // is sent: amount, totalTax and finalPrice are computed from these fields on both sides.
987
+ // The mirror of shoppingListItemFromProto. Unscaled model values are scaled again, and nothing
988
+ // derived is sent: amount, totalTax and finalPrice are computed from these fields on both sides.
986
989
  function shoppingListItemToProto(item) {
987
990
  return proto_ShoppingListItem.create({
988
991
  packageId: item.packageId,
@@ -1005,11 +1008,11 @@ function taxToProto(tax) {
1005
1008
  // #endregion
1006
1009
  // #region Entity and item bookkeeping
1007
1010
  // One pass over a response's entities, each becoming a blob at its own id, so one read of a container
1008
- // is enough to sort it by entity type. Every blob is versioned by entityETag, tombstones too. A
1009
- // delete is a write of its own, so a tombstone has a greater update number than any copy of what it
1010
- // deleted and replaces it. An older copy sent again is lesser and refused, and a later write of the
1011
- // same id is greater and kept. The arrays are mutable so that businessResponseToCacheWrite can add
1012
- // the attachment base url.
1011
+ // is enough to sort it by entity type. Every blob is versioned by entityETag, tombstones too. The
1012
+ // server gives a delete an update number like any other write, so a tombstone has a greater update
1013
+ // number than any copy of what it deleted and replaces it. An older copy sent again is lesser and
1014
+ // refused, and a later write of the same id is greater and kept. The arrays are mutable so that
1015
+ // businessResponseToCacheWrite can add the attachment base url.
1013
1016
  function toCacheWrite(entities) {
1014
1017
  const blobs = [];
1015
1018
  for (const entity of entities)
@@ -1021,11 +1024,12 @@ function toCacheWrite(entities) {
1021
1024
  return { blobs: blobs, deletedIds: [] };
1022
1025
  }
1023
1026
  // What a store versions an entity's blob by: the update number, with updatedAt below it, in one
1024
- // bigint so the greater wins as the store says. The server assigns an update number to every write,
1025
- // deletes included, so it rises with every change whatever any clock reads, and decides between
1026
- // different versions. updatedAt decides between copies of one update number. Only a copy this client
1027
- // wrote itself, at the update number of the one it amended, shares one. The value is wider than 64
1028
- // bits, which a bigint holds exactly and a store must compare as one.
1027
+ // bigint so that the store's greater-wins rule orders versions correctly. The server assigns an
1028
+ // update number to every write, deletes included, so it rises with every change regardless of any
1029
+ // clock, and decides between different versions. updatedAt decides between copies of one update
1030
+ // number. Only a copy this client wrote itself, at the update number of the one it amended, shares
1031
+ // one. The value is wider than 64 bits. A bigint holds it exactly, and a store must compare it as a
1032
+ // bigint.
1029
1033
  export function entityETag(updateNumber, updatedAt) {
1030
1034
  if (updateNumber < 0n || updatedAt < 0n)
1031
1035
  throw new InvalidArgumentException("An update number and an updatedAt are never negative");
@@ -1033,10 +1037,10 @@ export function entityETag(updateNumber, updatedAt) {
1033
1037
  }
1034
1038
  // A message this client wrote has no update number until a server assigns one, and has none in the
1035
1039
  // store while unpushed (see pushedEntity). It is versioned at the update number of the copy it
1036
- // amends, which heldETag carries, so by its updatedAt it is the later copy of that version. The
1037
- // server sending the amended copy again does not replace it, but a later server version with a
1038
- // greater update number does. A message that amends nothing is at zero. A message with an update
1039
- // number of its own is versioned by it, as any entity is.
1040
+ // amends, which heldETag carries, so its updatedAt makes it the later copy of that version. If the
1041
+ // server sends the amended copy again, that copy does not replace it, but a later server version
1042
+ // with a greater update number does. A message that amends nothing is at zero. A message that has an
1043
+ // update number is versioned by it, as any entity is.
1040
1044
  function messageToBlob(message, heldETag) {
1041
1045
  const entity = getEntity(message.entity);
1042
1046
  const updateNumber = entity.updateNumber !== 0n
@@ -1050,9 +1054,9 @@ function messageToBlob(message, heldETag) {
1050
1054
  bytes: proto_Message.toBinary(message),
1051
1055
  };
1052
1056
  }
1053
- // The messages a container holds that are deleted (isDeleted) or live. Only the update number says
1054
- // which, never what a message carries. A deleted message is a completed order when it carries one,
1055
- // and is never a customer's cart. Each as stored.
1057
+ // The messages a container holds that are deleted (isDeleted true) or live (false). A message is
1058
+ // deleted according to its update number alone, regardless of its payload. A deleted message is a
1059
+ // completed order when it carries one, and is never a customer's cart. Each as stored.
1056
1060
  function messagesFromBlobs(blobs, isDeleted = false) {
1057
1061
  const result = [];
1058
1062
  for (const blob of blobs) {
@@ -1111,12 +1115,12 @@ function getItemId(item) {
1111
1115
  }
1112
1116
  // #endregion
1113
1117
  // #region Values
1114
- // An absent optional string is null, because Java tells absent from empty
1118
+ // An absent optional string is null, because Java distinguishes absent from empty
1115
1119
  function stringFromProto(value) {
1116
1120
  return value === undefined ? null : value;
1117
1121
  }
1118
- // The way back. An empty string goes out absent too: the two mean the same, and sending one could
1119
- // bring the field back as the other.
1122
+ // The reverse of stringFromProto. An empty string goes out absent too: the two mean the same, and
1123
+ // sending one could bring the field back as the other.
1120
1124
  function stringToProto(value) {
1121
1125
  return value === null || value.length === 0 ? undefined : value;
1122
1126
  }
@@ -1126,9 +1130,9 @@ function attachmentBaseUrlFromProto(urlAndToken) {
1126
1130
  function addressFromProto(address) {
1127
1131
  return new Address(address.id, address.addressType, stringFromProto(address.unitNumber), stringFromProto(address.community), stringFromProto(address.line2), stringFromProto(address.city), stringFromProto(address.state), stringFromProto(address.pinCode), address.countryCode);
1128
1132
  }
1129
- // The way back, for a delivery address sent directly instead of by id - see buildOrderMessage. id is
1130
- // whatever Address carries, though it means nothing outside the list it was read from, and this
1131
- // address is in none.
1133
+ // The reverse of addressFromProto, for a delivery address sent directly instead of by id - see
1134
+ // buildOrderMessage. id is copied from Address, though it means nothing outside the list the address
1135
+ // was read from, and this address is in none.
1132
1136
  function addressToProto(address) {
1133
1137
  return proto_Address.create({
1134
1138
  id: address.id,
@@ -1142,7 +1146,7 @@ function addressToProto(address) {
1142
1146
  countryCode: address.countryCode,
1143
1147
  });
1144
1148
  }
1145
- // The way back, for the addresses of a user sent to the server.
1149
+ // The reverse of addressListFromProto, for the addresses of a user sent to the server.
1146
1150
  function addressListToProto(addressList) {
1147
1151
  return proto_AddressList.create({
1148
1152
  defaultAddressId: addressList.defaultAddressId,
@@ -1154,8 +1158,8 @@ function addressListFromProto(addressList) {
1154
1158
  return AddressList.EMPTY;
1155
1159
  return new AddressList(addressList.defaultAddressId, Object.freeze(addressList.addresses.map(addressFromProto)));
1156
1160
  }
1157
- // The wire carries a tax's raw code, meaningful only within a country, so every caller names that of
1158
- // the business the tax is on.
1161
+ // The wire carries a tax's raw code, meaningful only within a country, so every caller passes the
1162
+ // country of the business the tax is on.
1159
1163
  function taxFromProto(tax, countryCode) {
1160
1164
  return Tax.fromScaledValues(countryCode, tax.taxCode, tax.rate);
1161
1165
  }
@@ -1263,8 +1267,8 @@ function orderFromProto(message, salesOrder, countryCode) {
1263
1267
  return Order.fromScaledValues(toEntityHeader(entity), message.senderId, message.status, stringFromProto(message.senderName), stringFromProto(message.senderPhone), salesOrder.status, salesOrder.fulfillmentMode, shoppingListFromProto(salesOrder.shoppingList, countryCode), salesOrder.shoppingListTotal, salesOrder.shoppingListCount, salesOrder.deliveryAddressId, salesOrder.deliveryAddress === undefined ? null : addressFromProto(salesOrder.deliveryAddress), salesOrder.creationDate, stringFromProto(salesOrder.note), salesOrder.paymentReceived);
1264
1268
  }
1265
1269
  // The one part of a cart message a customer is shown. Null when the cart holds nothing, so a cart
1266
- // emptied by placing an order and one never created give the same answer. The rest of the message is
1267
- // the server's or read elsewhere: who sent it, how far the business has read it, and the fulfillment
1270
+ // emptied by placing an order and one never created both give null. The rest of the message is set by
1271
+ // the server or read elsewhere: who sent it, how far the business has read it, and the fulfillment
1268
1272
  // mode, which buildBusiness reads as defaultFulfillmentMode.
1269
1273
  function cartShoppingListFromProto(partyPreferences, countryCode) {
1270
1274
  const shoppingList = partyPreferences.shoppingList;
@@ -1283,6 +1287,6 @@ function takePhotos(attachmentsByOwner, owningEntityId, attachmentBaseUrl) {
1283
1287
  // #endregion
1284
1288
  // The width of the updatedAt an eTag carries below its update number. An int64 is never wider.
1285
1289
  const UPDATED_AT_BITS = 64n;
1286
- // What a blob that is only its eTag holds
1290
+ // The bytes of a blob that exists only for its eTag
1287
1291
  const NO_BYTES = new Uint8Array(0);
1288
1292
  const NO_PHOTOS = Object.freeze([]);