@peabody-soft/mbs-model-ts 1.11.0 → 1.12.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.
@@ -5,9 +5,11 @@ import { BusinessSyncOutRequest as proto_BusinessSyncOutRequest } from "./genera
5
5
  import { BusinessSyncOutResponse as proto_BusinessSyncOutResponse } from "./generated/BusinessSyncOutResponse";
6
6
  import { FulfillmentMode as proto_FulfillmentMode } from "./generated/FulfillmentMode";
7
7
  import { Message as proto_Message } from "./generated/Message";
8
+ import { Party as proto_Party } from "./generated/Party";
8
9
  import { ProfileSyncOutRequest as proto_ProfileSyncOutRequest } from "./generated/ProfileSyncOutRequest";
9
10
  import { ProfileSyncOutResponse as proto_ProfileSyncOutResponse } from "./generated/ProfileSyncOutResponse";
10
11
  import { User as proto_User } from "./generated/User";
12
+ import { UserRequest as proto_UserRequest } from "./generated/UserRequest";
11
13
  import Address from "./Address";
12
14
  import AuthTokens from "./AuthTokens";
13
15
  import Business from "./Business";
@@ -34,11 +36,11 @@ export declare function getProfileResponseEntities(response: proto_ProfileSyncOu
34
36
  export declare function businessResponseToCacheWrite(response: proto_BusinessSyncOutResponse): CacheWrite;
35
37
  export declare function profileResponseToCacheWrite(response: proto_ProfileSyncOutResponse): CacheWrite;
36
38
  export declare function getBusinessResponseAttachmentBaseUrlExpiry(response: proto_BusinessSyncOutResponse | null): bigint;
37
- export declare function orderToBlob(order: proto_Message, heldETag: bigint | null): StoredBlob;
39
+ export declare function orderToBlob(order: proto_Message, storedETag: bigint | null): StoredBlob;
38
40
  export declare function isPartyPreferencesMessage(message: proto_Message): boolean;
39
41
  export declare function getMessageUpdatedAt(message: proto_Message): bigint;
40
42
  export declare function getMessageId(message: proto_Message): bigint;
41
- export declare function partyPreferencesToBlob(partyPreferences: proto_Message, heldETag: bigint | null): StoredBlob;
43
+ export declare function partyPreferencesToBlob(partyPreferences: proto_Message, storedETag: bigint | null): StoredBlob;
42
44
  export declare function businessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
43
45
  export declare function profileResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_ProfileSyncOutResponse | null;
44
46
  export declare function deletedBusinessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
@@ -51,12 +53,16 @@ export declare function buildBusinessSyncOutRequest(business: string | bigint, s
51
53
  export declare function cursorsToBlobs(cursors: SyncState, movedKeys: ReadonlySet<number>): Array<StoredBlob>;
52
54
  export declare function cursorsFromBlobs(keys: ReadonlyArray<number>, blobs: ReadonlyArray<StoredBlob | null>): SyncState;
53
55
  export declare function raiseBusinessCursors(cursors: SyncState, response: proto_BusinessSyncOutResponse): ReadonlySet<number>;
54
- export declare function buildBusinessSyncInRequest(messageUpdateNumber: bigint, messages: ReadonlyArray<proto_Message>): proto_BusinessSyncInRequest;
56
+ export declare function raiseMessageCursors(cursors: SyncState, messages: ReadonlyArray<proto_Message>): ReadonlySet<number>;
57
+ export declare function buildBusinessSyncInRequest(messageUpdateNumber: bigint, messages: ReadonlyArray<proto_Message>, party?: proto_Party | null): proto_BusinessSyncInRequest;
55
58
  export declare function getSyncInResponseMessages(response: proto_BusinessSyncInResponse): ReadonlyArray<proto_Message>;
59
+ export declare function isSyncInResponseFull(request: proto_BusinessSyncInRequest, response: proto_BusinessSyncInResponse): boolean;
56
60
  export declare function buildProfileSyncOutRequest(syncInState: SyncState, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
57
61
  export declare function raiseProfileCursors(cursors: SyncState, response: proto_ProfileSyncOutResponse): ReadonlySet<number>;
58
- export declare function buildUser(userProfile: UserProfile): proto_User;
62
+ export declare function buildUser(userProfile: UserProfile, userId: bigint): proto_User;
59
63
  export declare function buildUpdateUser(userProfile: UserProfile): proto_User;
64
+ export declare function buildUserRequest(user: proto_User, syncInState: SyncState, joinBusinessId?: bigint): proto_UserRequest;
65
+ export declare function getUserRequestSyncOutRequest(request: proto_UserRequest): proto_ProfileSyncOutRequest;
60
66
  export declare function hasJoinedBusinessLink(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
61
67
  export declare function hasJoinedBusiness(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
62
68
  export declare function getBusinessResponseCountryCode(response: proto_BusinessSyncOutResponse | null): number | undefined;
@@ -68,6 +74,8 @@ export declare function authTokensFromProto(authTokens: proto_AuthTokens): AuthT
68
74
  export declare function buildAuthTokensBytes(authTokens: proto_AuthTokens): Uint8Array;
69
75
  export declare function authTokensToBlob(authTokens: AuthTokens): StoredBlob;
70
76
  export declare function authTokensFromBytes(bytes: Uint8Array): AuthTokens;
77
+ export declare function userIdToBlob(userId: bigint): StoredBlob;
78
+ export declare function userIdFromBytes(bytes: Uint8Array): bigint;
71
79
  export declare function getResponseAuthTokens(response: proto_BusinessSyncOutResponse | proto_BusinessSyncInResponse | proto_ProfileSyncOutResponse): proto_AuthTokens | undefined;
72
80
  export declare function refreshAuthTokens(authTokens: proto_AuthTokens, issued: proto_AuthTokens | undefined): proto_AuthTokens;
73
81
  export declare function refreshAuthTokens(authTokens: proto_AuthTokens | null, issued: proto_AuthTokens | undefined): proto_AuthTokens | null;
@@ -79,6 +87,7 @@ export declare function buildOrder(message: proto_Message, countryCode: number):
79
87
  export declare function buildShoppingList(message: proto_Message, countryCode: number): ShoppingList;
80
88
  export declare function buildPartyPreferencesMessage(messageId: bigint, existing: proto_Message | null, senderId: bigint, shoppingList: ShoppingList | null, fulfillmentMode: proto_FulfillmentMode | null, now: bigint): proto_Message;
81
89
  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;
90
+ export declare function buildParty(user: UserProfile, deliveryAddress: Address | null): proto_Party;
82
91
  export declare function messagesToCacheWrite(messages: ReadonlyArray<proto_Message>): CacheWrite;
83
92
  export declare function hasShoppingListChanged(stored: proto_Message | null, shoppingList: ShoppingList): boolean;
84
93
  export declare function entityETag(updateNumber: bigint, updatedAt: bigint): bigint;
package/dist/ProtoMap.js CHANGED
@@ -16,6 +16,7 @@ import { MessageType as proto_MessageType } from "./generated/MessageType";
16
16
  import { OnlineStoreTheme as proto_OnlineStoreTheme } from "./generated/OnlineStoreTheme";
17
17
  import { OrderStatus as proto_OrderStatus } from "./generated/OrderStatus";
18
18
  import { Package as proto_Package } from "./generated/Package";
19
+ import { Party as proto_Party } from "./generated/Party";
19
20
  import { PartyPreferencesPayload as proto_PartyPreferencesPayload } from "./generated/PartyPreferencesPayload";
20
21
  import { Price as proto_Price } from "./generated/Price";
21
22
  import { PriceList as proto_PriceList } from "./generated/PriceList";
@@ -30,6 +31,7 @@ import { TaskStatus as proto_TaskStatus } from "./generated/TaskStatus";
30
31
  import { Tax as proto_Tax } from "./generated/Tax";
31
32
  import { UrlAndToken as proto_UrlAndToken } from "./generated/UrlAndToken";
32
33
  import { User as proto_User } from "./generated/User";
34
+ import { UserRequest as proto_UserRequest } from "./generated/UserRequest";
33
35
  import Address, { AddressList } from "./Address";
34
36
  import Attachment from "./Attachment";
35
37
  import AttachmentBaseUrl from "./AttachmentBaseUrl";
@@ -73,8 +75,7 @@ export function getBusinessResponseEntities(response) {
73
75
  result.push(toWireEntity(attachment.entity, proto_Attachment.toBinary(attachment)));
74
76
  for (const configuration of response.configurations)
75
77
  result.push(toWireEntity(configuration.entity, proto_Configuration.toBinary(configuration)));
76
- for (const message of response.messages)
77
- result.push(toWireEntity(message.entity, proto_Message.toBinary(message)));
78
+ result.push(...getMessageEntities(response.messages));
78
79
  for (const productCategory of response.productCategories)
79
80
  result.push(toItemWireEntity(productCategory.item, proto_ProductCategory.toBinary(productCategory)));
80
81
  for (const product of response.products)
@@ -125,18 +126,18 @@ export function getBusinessResponseAttachmentBaseUrlExpiry(response) {
125
126
  }
126
127
  // One order as a blob, for a caller holding a message rather than a response. Only the payload says
127
128
  // whether a message is an order, so a message without a sales order throws here instead of being
128
- // stored where a read would not find it. heldETag is the eTag the container holds the order at, or
129
+ // stored where a read would not find it. storedETag is the eTag the container holds the order at, or
129
130
  // null - see messageToBlob.
130
- export function orderToBlob(order, heldETag) {
131
+ export function orderToBlob(order, storedETag) {
131
132
  if (order.payload.oneofKind !== "salesOrder")
132
133
  throw new InvalidArgumentException("That message carries no sales order, so it is not an order");
133
- return messageToBlob(order, heldETag);
134
+ return messageToBlob(order, storedETag);
134
135
  }
135
136
  // Whether a message carries party preferences.
136
137
  export function isPartyPreferencesMessage(message) {
137
138
  return message.payload.oneofKind === "partyPreferences";
138
139
  }
139
- // When a message was last written. Use it to order two copies of one thing held at different ids,
140
+ // When a message was last written. Use it to order two copies of one thing stored at different ids,
140
141
  // which have no shared version.
141
142
  export function getMessageUpdatedAt(message) {
142
143
  return getEntity(message.entity).updatedAt;
@@ -144,11 +145,11 @@ export function getMessageUpdatedAt(message) {
144
145
  export function getMessageId(message) {
145
146
  return getEntity(message.entity).id;
146
147
  }
147
- // heldETag as for orderToBlob
148
- export function partyPreferencesToBlob(partyPreferences, heldETag) {
148
+ // storedETag as for orderToBlob
149
+ export function partyPreferencesToBlob(partyPreferences, storedETag) {
149
150
  if (partyPreferences.payload.oneofKind !== "partyPreferences")
150
151
  throw new InvalidArgumentException("That message carries no party preferences");
151
- return messageToBlob(partyPreferences, heldETag);
152
+ return messageToBlob(partyPreferences, storedETag);
152
153
  }
153
154
  // Rebuilds the response a business container's blobs make up, as the call returned it. Null when they
154
155
  // hold nothing yet. The entity type is part of each blob id and picks the array a blob goes in, so no
@@ -349,25 +350,43 @@ export function cursorsFromBlobs(keys, blobs) {
349
350
  return cursors;
350
351
  }
351
352
  // Raises the cursors to what a page of a business read brought, tombstones included, and returns the
352
- // keys that moved. Only a read moves them. A push does not.
353
+ // keys that moved. A page is a response to a read, or to a sync in, which carries the messages that
354
+ // are newer than the cursor, as a read does (see raiseMessageCursors).
353
355
  export function raiseBusinessCursors(cursors, response) {
354
356
  return raiseCursorsFrom(cursors, getBusinessResponseEntities(response));
355
357
  }
356
- // The request that pushes what this customer changed. A storefront may push only messages. One
357
- // request is one batch, so an order and the cart it emptied go together and a customer is never left
358
- // with the order and a still full cart. messageUpdateNumber is how far reads of this container have
359
- // got with messages - see SyncCache.readMessageUpdateNumber.
360
- export function buildBusinessSyncInRequest(messageUpdateNumber, messages) {
358
+ // The same for the messages of a sync in response, which is a page of the messages of a read.
359
+ export function raiseMessageCursors(cursors, messages) {
360
+ return raiseCursorsFrom(cursors, getMessageEntities(messages));
361
+ }
362
+ // The request that sends what this customer changed. A storefront may send only messages and,
363
+ // with an order, the customer as a party (see buildParty). One request is one batch, so an order and
364
+ // the cart it emptied go together and a customer is never left with the order and a still full
365
+ // cart. messageUpdateNumber is how far reads of this container have got with messages - see
366
+ // SyncCache.readMessageUpdateNumber.
367
+ export function buildBusinessSyncInRequest(messageUpdateNumber, messages, party = null) {
361
368
  return proto_BusinessSyncInRequest.create({
362
369
  messageUpdateNumber: messageUpdateNumber,
363
370
  messages: [...messages],
371
+ party: party ?? undefined,
364
372
  });
365
373
  }
366
- // The messages a sync in response carried: this push's own, with the update numbers the server
367
- // assigned. Those numbers are not how far a read has got.
374
+ // The messages a sync in response carried. The server makes a sync out of the customer's messages
375
+ // after it writes, so they are the stored messages that are newer than the cursor of the request,
376
+ // the ones that were sent among them, in the order of their update numbers and not in the order
377
+ // they were sent in. A message is found by its id.
368
378
  export function getSyncInResponseMessages(response) {
369
379
  return response.messages;
370
380
  }
381
+ // Whether a sync in response may have been cut short, and the rest of the messages is read as a
382
+ // read reads on. As for a page of a read (see nextBusinessSyncOutRequest), it is a full page that
383
+ // raised the cursor the request carried. A full page that raised none means the server broke the
384
+ // ascending order it promises, so reading ends there.
385
+ export function isSyncInResponseFull(request, response) {
386
+ if (response.messages.length < proto_Constants.SYNC_OUT_LIMIT)
387
+ return false;
388
+ return response.messages.some((message) => getEntity(message.entity).updateNumber > request.messageUpdateNumber);
389
+ }
371
390
  // The request for everything changed in the caller's profile since these cursors. It names no
372
391
  // business, since a profile sync is not scoped to one and has no sync in. joinBusinessId is the
373
392
  // entity id of a business to make the caller a customer of while the server answers.
@@ -385,10 +404,13 @@ export function buildProfileSyncOutRequest(syncInState, joinBusinessId = BigInt(
385
404
  export function raiseProfileCursors(cursors, response) {
386
405
  return raiseCursorsFrom(cursors, getProfileResponseEntities(response));
387
406
  }
388
- // The user a sign up sends as CreateUserRequest.user, for the server to create. It has no entity yet
389
- // (the server assigns id, createdAt and updatedAt), so none of EntityHeader is sent.
390
- export function buildUser(userProfile) {
407
+ // The user a call of Service.createUser or Service.updateUser sends, under the entity id userId. The
408
+ // rest of EntityHeader is not read by the server, so it is not sent. A sign up sends the id that
409
+ // Model.createUser made, which the server stores the new user under. An update sends the id of the
410
+ // customer's own user, which the server matches to the caller.
411
+ export function buildUser(userProfile, userId) {
391
412
  return proto_User.create({
413
+ entity: proto_Entity.create({ id: userId }),
392
414
  firstName: userProfile.firstName,
393
415
  lastName: stringToProto(userProfile.lastName),
394
416
  mobilePhoneNumber: userProfile.mobilePhoneNumber,
@@ -396,15 +418,25 @@ export function buildUser(userProfile) {
396
418
  preferredLanguageCode: stringToProto(userProfile.preferredLanguageCode),
397
419
  });
398
420
  }
399
- // The user that Service.updateUser writes over this customer's profile: the fields of buildUser plus
400
- // the entity id, which the server matches to the caller. The rest of EntityHeader is not read on
401
- // that call, so it is not sent.
421
+ // The user that Service.updateUser writes over this customer's profile: buildUser under the id of
422
+ // userProfile.
402
423
  export function buildUpdateUser(userProfile) {
403
- return proto_User.create({
404
- ...buildUser(userProfile),
405
- entity: proto_Entity.create({ id: userProfile.id }),
424
+ return buildUser(userProfile, userProfile.id);
425
+ }
426
+ // The request of Service.createUser and Service.updateUser: the user to write, and the profile read
427
+ // from these cursors that the response answers. joinBusinessId is the entity id of a business to
428
+ // join after the user is created, and only a sign up may name one (Constants.ENTITY_ID_NULL, the
429
+ // default, joins nothing).
430
+ export function buildUserRequest(user, syncInState, joinBusinessId = BigInt(proto_Constants.ENTITY_ID_NULL)) {
431
+ return proto_UserRequest.create({
432
+ user: user,
433
+ profileSyncOutRequest: buildProfileSyncOutRequest(syncInState, joinBusinessId),
406
434
  });
407
435
  }
436
+ // The profile read that a user request holds. Absent means every cursor at zero and no join.
437
+ export function getUserRequestSyncOutRequest(request) {
438
+ return request.profileSyncOutRequest ?? proto_ProfileSyncOutRequest.create({});
439
+ }
408
440
  // The two things a response to a join must hold, checked separately so a caller can report which one
409
441
  // is missing - see Model.requireJoinedBusiness. Both take the id, since both requests that carry a
410
442
  // joinBusinessId need them.
@@ -529,6 +561,16 @@ export function authTokensToBlob(authTokens) {
529
561
  export function authTokensFromBytes(bytes) {
530
562
  return authTokensFromProto(proto_AuthTokens.fromBinary(bytes));
531
563
  }
564
+ // The id that a sign up will create the user under, as a blob: the id as eight bytes, and a null
565
+ // eTag, since a new id replaces the old one.
566
+ export function userIdToBlob(userId) {
567
+ const bytes = new Uint8Array(USER_ID_BYTE_COUNT);
568
+ new DataView(bytes.buffer).setBigUint64(0, userId);
569
+ return { id: Ids.USER_ID_BLOB_ID, eTag: null, bytes: bytes };
570
+ }
571
+ export function userIdFromBytes(bytes) {
572
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getBigUint64(0);
573
+ }
532
574
  // #endregion
533
575
  // #region Paging
534
576
  // What the server issued while answering. All three response types carry it in the same field.
@@ -814,18 +856,20 @@ export function buildShoppingList(message, countryCode) {
814
856
  return (cartShoppingListFromProto(message.payload.partyPreferences, countryCode) ?? ShoppingList.EMPTY);
815
857
  }
816
858
  // #endregion
817
- // #region What a customer pushes
859
+ // #region What a customer sends
818
860
  //
819
861
  // A storefront writes one entity type, a Message carrying the cart or an order placed from it. Two
820
- // rules hold for both, so a pushed message is storable here as well as on the server. The client
821
- // chooses the id: a sync in returns an update number and no id, so a message the server named could
822
- // not be found here again. The client also sets updatedAt and the server keeps it, so the copy
823
- // stored here has the same eTag as the one a later read brings back.
824
- // This customer's party preferences as a message to push, carrying the cart and optionally the
825
- // fulfillment mode. An existing message is amended, since its payload can carry fields a storefront
826
- // cannot read, and dropping them would overwrite that data. Its id is kept, as the server holds the
827
- // preferences under it. The derived id is used only when there is nothing to amend.
828
- // A list with no items is pushed as no list. The merchant app stores its cart with no items too, and
862
+ // rules hold for both. The client chooses the id: a sync in returns an update number and no id, so
863
+ // a message the server named could not be found here again. The client also sets updatedAt. The
864
+ // server keeps it for a cart, and gives an order the oldest possible one. The copy stored here is
865
+ // the one in the response, so it has the same eTag as the copy a later read brings back.
866
+ // This customer's party preferences as a message to send, carrying the cart and optionally the
867
+ // fulfillment mode. The server stores a cart as a whole, so the existing message is amended: the
868
+ // fulfillment mode is sent back as it is, and a field left out would be removed from the stored cart.
869
+ // The message always has the id given, which a signed-in customer takes from their business link
870
+ // (Ids.getPartyPreferencesMessageId): the server refuses a cart with another id. Only a visitor,
871
+ // who has no link, passes the id of the cart already stored, so that an edit amends it.
872
+ // A list with no items is sent as no list. The merchant app stores its cart with no items too, and
829
873
  // both read as no cart (cartShoppingListFromProto), so an emptied cart, a cart with no items and a cart
830
874
  // never created are one state to a storefront.
831
875
  // fulfillmentMode null keeps the existing value, the ordinary case: a storefront treats that field
@@ -844,19 +888,23 @@ export function buildPartyPreferencesMessage(messageId, existing, senderId, shop
844
888
  const updatedAt = existingEntity === null || existingEntity.updatedAt < now ? now : existingEntity.updatedAt + 1n;
845
889
  const partyPreferences = proto_PartyPreferencesPayload.create({
846
890
  ...preferences,
847
- shoppingList: pushedShoppingList(shoppingList, false),
891
+ shoppingList: shoppingListToSend(shoppingList, false),
848
892
  });
849
893
  if (fulfillmentMode !== null)
850
894
  partyPreferences.fulfillmentMode = fulfillmentMode;
895
+ // The creation time is that of the message amended. A message under another id is not that one.
896
+ const isAmended = existingEntity !== null && existingEntity.id === messageId;
851
897
  return proto_Message.create({
852
- entity: pushedEntity(existingEntity?.id ?? messageId, existingEntity?.createdAt ?? updatedAt, updatedAt),
898
+ entity: entityToSend(messageId, isAmended ? existingEntity.createdAt : updatedAt, updatedAt),
853
899
  senderId: senderId,
854
900
  messageType: proto_MessageType.PARTY_PREFERENCES,
855
901
  status: proto_TaskStatus.NOT_YET_PROCESSED,
856
902
  payload: { oneofKind: "partyPreferences", partyPreferences: partyPreferences },
857
903
  });
858
904
  }
859
- // One order as a message to push. Its shopping list is sent as final regardless of the list's
905
+ // One order as a message to send. The server stores it only if no message has its id, with the
906
+ // oldest possible updatedAt, so sending an order again changes nothing. creationDate is the time of
907
+ // the order, in the message as createdAt and in the payload. Its shopping list is sent as final regardless of the list's
860
908
  // isFinal, as Java SalesOrderViewModel.saveChanges does before creating an order: the customer has
861
909
  // finished choosing, and only a final list records a later edit as a change instead of altering what
862
910
  // was ordered. The two totals are all that remains of an order completed by an older merchant app,
@@ -864,7 +912,7 @@ export function buildPartyPreferencesMessage(messageId, existing, senderId, shop
864
912
  // counts it carries, which a caller could set to anything.
865
913
  export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMode, deliveryAddressId, deliveryAddress, note, creationDate) {
866
914
  return proto_Message.create({
867
- entity: pushedEntity(orderId, creationDate, creationDate),
915
+ entity: entityToSend(orderId, creationDate, creationDate),
868
916
  senderId: senderId,
869
917
  messageType: proto_MessageType.SALES_ORDER,
870
918
  status: proto_TaskStatus.NOT_YET_PROCESSED,
@@ -873,7 +921,7 @@ export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMo
873
921
  salesOrder: proto_SalesOrderPayload.create({
874
922
  status: proto_OrderStatus.NOT_STARTED,
875
923
  fulfillmentMode: fulfillmentMode,
876
- shoppingList: pushedShoppingList(shoppingList, true),
924
+ shoppingList: shoppingListToSend(shoppingList, true),
877
925
  shoppingListTotal: amountToScaledValue(shoppingList.totalAmount),
878
926
  shoppingListCount: shoppingList.existingItems.length,
879
927
  deliveryAddressId: deliveryAddressId,
@@ -884,37 +932,55 @@ export function buildOrderMessage(orderId, senderId, shoppingList, fulfillmentMo
884
932
  },
885
933
  });
886
934
  }
935
+ // The customer as the party that an order is sent with, which the business needs to show the order
936
+ // and to record payments against it. The party carries no id: the server applies it to the party of
937
+ // the link. The name is the customer's formatted name, as Java User.getFormattedName makes it. It is
938
+ // at most 65 characters, and the server shortens it to a party's 32. The phone number is the
939
+ // customer's own, which the server requires to be the one that the authentication token verified.
940
+ // deliveryAddress is the address of an order that is delivered, or null for an order that is not, as
941
+ // the merchant app adds one to the party only for a delivery. It keeps its id, which is its id in
942
+ // the customer's list of addresses.
943
+ export function buildParty(user, deliveryAddress) {
944
+ return proto_Party.create({
945
+ name: user.formattedName,
946
+ mobilePhoneNumber: user.mobilePhoneNumber,
947
+ addresses: deliveryAddress === null ? [] : [addressToProto(deliveryAddress)],
948
+ });
949
+ }
887
950
  // The messages of a sync in response as a cache write, tombstones included like any other entity
888
- // array. This stores a push's own messages under the update numbers the server assigned, with no
889
- // separate stamping step, since the response carries full entities.
951
+ // array. They are stored under the update numbers the server stored them under, with no separate
952
+ // stamping step, since the response carries full entities.
890
953
  export function messagesToCacheWrite(messages) {
891
- return toCacheWrite(messages.map((message) => toWireEntity(message.entity, proto_Message.toBinary(message))));
954
+ return toCacheWrite(getMessageEntities(messages));
955
+ }
956
+ function getMessageEntities(messages) {
957
+ return messages.map((message) => toWireEntity(message.entity, proto_Message.toBinary(message)));
892
958
  }
893
- // The Entity of something this client is pushing. The update number goes out as zero, since the
894
- // server replaces a caller's own with the one it assigns. A message not pushed yet is stored holding
959
+ // The Entity of something this client is sending. The update number goes out as zero, since the
960
+ // server replaces a caller's own with the one it assigns. A message not sent yet is stored with
895
961
  // zero, and messageToBlob versions it from the copy it amends. updateInfo is zero too: it would be
896
962
  // the id of the store that made the change, and the server knows no store of this client. This
897
963
  // client does not need it: a read sends these messages back (the server leaves no one's changes out
898
964
  // of a storefront's read), and the eTag stored here is the one the server kept, so storing what the
899
965
  // read brings back changes nothing.
900
- function pushedEntity(id, createdAt, updatedAt) {
966
+ function entityToSend(id, createdAt, updatedAt) {
901
967
  return proto_Entity.create({ id: id, createdAt: createdAt, updatedAt: updatedAt });
902
968
  }
903
- // Whether shoppingList differs from what stored holds, so updateCart can skip a push. Compares raw
904
- // lists, since a message's updatedAt differs on every call. That works because the server echoes a
905
- // pushed list back with the items it was sent, so stored is what was last sent.
906
- // The server echoes the items in package id order, and the list's own order is kept in each item's
907
- // shoppingListOrder, which is compared with the item. So both lists are put in package id order
908
- // first. Otherwise a cart not picked in that order would differ from its echo and be pushed again,
909
- // which re-stamps updatedAt for every other device.
910
- // Also true when stored was never pushed (updateNumber 0, left by updateCartLocalOnly), so an
911
- // unsent local edit is still pushed.
969
+ // Whether shoppingList differs from what stored contains, so updateCart can skip a request. Compares
970
+ // raw lists, since a message's updatedAt differs on every call. That works because the server answers
971
+ // with the list it was sent, with its items, so stored is what was last sent.
972
+ // The server answers with the items in package id order, and the list's own order is kept in each
973
+ // item's shoppingListOrder, which is compared with the item. So both lists are put in package id
974
+ // order first. Otherwise a cart not picked in that order would differ from the answer and be sent
975
+ // again, which re-stamps updatedAt for every other device.
976
+ // Also true when stored was never sent (updateNumber 0, left by updateCartLocalOnly), so an
977
+ // unsent local edit is still sent.
912
978
  export function hasShoppingListChanged(stored, shoppingList) {
913
979
  if (stored === null)
914
- return pushedShoppingList(shoppingList, false) !== undefined;
980
+ return shoppingListToSend(shoppingList, false) !== undefined;
915
981
  if (getEntity(stored.entity).updateNumber === 0n)
916
982
  return true;
917
- return !proto_ShoppingList.equals(inPackageIdOrder(getCartShoppingList(stored)), inPackageIdOrder(pushedShoppingList(shoppingList, false)));
983
+ return !proto_ShoppingList.equals(inPackageIdOrder(getCartShoppingList(stored)), inPackageIdOrder(shoppingListToSend(shoppingList, false)));
918
984
  }
919
985
  // The same list with its items sorted by package id, the order the server sends them in
920
986
  function inPackageIdOrder(list) {
@@ -936,13 +1002,13 @@ function getCartShoppingList(message) {
936
1002
  return shoppingList === undefined || holdsNothing(shoppingList) ? undefined : shoppingList;
937
1003
  }
938
1004
  // Whether a list on the wire has no items. The items are what is judged, not the counts, as in
939
- // pushedShoppingList
1005
+ // shoppingListToSend
940
1006
  function holdsNothing(shoppingList) {
941
1007
  return shoppingList.existingItems.length === 0 && shoppingList.deletedItems.length === 0;
942
1008
  }
943
- // A shopping list to push, or absent when it holds nothing - see buildPartyPreferencesMessage.
1009
+ // A shopping list to send, or absent when it holds nothing - see buildPartyPreferencesMessage.
944
1010
  // Emptiness is judged by the items sent, not the counts, for the reason given at shoppingListToProto.
945
- function pushedShoppingList(shoppingList, isFinal) {
1011
+ function shoppingListToSend(shoppingList, isFinal) {
946
1012
  if (shoppingList === null)
947
1013
  return undefined;
948
1014
  if (shoppingList.existingItems.length === 0 && shoppingList.deletedItems.length === 0)
@@ -950,8 +1016,8 @@ function pushedShoppingList(shoppingList, isFinal) {
950
1016
  return shoppingListToProto(shoppingList, isFinal);
951
1017
  }
952
1018
  // The counts are the lengths of the item arrays, not the list's own counts. A read takes them from
953
- // the wire because the protocol sends a count even where it omits the items, but a push always sends
954
- // the items. Counting the arrays means a pushed list cannot disagree with its contents.
1019
+ // the wire because the protocol sends a count even where it omits the items, but a list that is sent
1020
+ // always has its items. Counting the arrays means a list cannot disagree with its contents.
955
1021
  function shoppingListToProto(shoppingList, isFinal) {
956
1022
  return proto_ShoppingList.create({
957
1023
  isFinal: isFinal,
@@ -1019,18 +1085,18 @@ export function entityETag(updateNumber, updatedAt) {
1019
1085
  return (updateNumber << UPDATED_AT_BITS) | updatedAt;
1020
1086
  }
1021
1087
  // A message this client wrote has no update number until a server assigns one, and has none in the
1022
- // store while unpushed (see pushedEntity). It is versioned at the update number of the copy it
1023
- // amends, which heldETag carries, so its updatedAt makes it the later copy of that version. If the
1088
+ // store while unsent (see entityToSend). It is versioned at the update number of the copy it
1089
+ // amends, which storedETag carries, so its updatedAt makes it the later copy of that version. If the
1024
1090
  // server sends the amended copy again, that copy does not replace it, but a later server version
1025
1091
  // with a greater update number does. A message that amends nothing is at zero. A message that has an
1026
1092
  // update number is versioned by it, as any entity is.
1027
- function messageToBlob(message, heldETag) {
1093
+ function messageToBlob(message, storedETag) {
1028
1094
  const entity = getEntity(message.entity);
1029
1095
  const updateNumber = entity.updateNumber !== 0n
1030
1096
  ? entity.updateNumber
1031
- : heldETag === null
1097
+ : storedETag === null
1032
1098
  ? 0n
1033
- : heldETag >> UPDATED_AT_BITS;
1099
+ : storedETag >> UPDATED_AT_BITS;
1034
1100
  return {
1035
1101
  id: entity.id,
1036
1102
  eTag: entityETag(updateNumber, entity.updatedAt),
@@ -1119,8 +1185,8 @@ function addressFromProto(address) {
1119
1185
  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);
1120
1186
  }
1121
1187
  // The reverse of addressFromProto, for a delivery address sent directly instead of by id - see
1122
- // buildOrderMessage. id is copied from Address, though it means nothing outside the list the address
1123
- // was read from, and this address is in none.
1188
+ // buildOrderMessage - and for the address of a party - see buildParty. id is copied from Address, as
1189
+ // it is the id of the address in the customer's list.
1124
1190
  function addressToProto(address) {
1125
1191
  return proto_Address.create({
1126
1192
  id: address.id,
@@ -1252,7 +1318,7 @@ function productFromProto(productProto, photos, countryCode) {
1252
1318
  }
1253
1319
  function orderFromProto(message, salesOrder, countryCode) {
1254
1320
  const entity = getEntity(message.entity);
1255
- 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);
1321
+ return Order.fromScaledValues(toEntityHeader(entity), message.senderId, message.status, 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);
1256
1322
  }
1257
1323
  // The one part of a cart message a customer is shown. Null when the cart holds nothing, so a cart
1258
1324
  // emptied by placing an order, one never created, and a list with no items (the merchant app stores
@@ -1281,4 +1347,6 @@ function takePhotos(attachmentsByOwner, owningEntityId, attachmentBaseUrl) {
1281
1347
  const UPDATED_AT_BITS = 64n;
1282
1348
  // The bytes of a blob that exists only for its eTag
1283
1349
  const NO_BYTES = new Uint8Array(0);
1350
+ // The width of a user id in its blob: an id is 64 bits
1351
+ const USER_ID_BYTE_COUNT = 8;
1284
1352
  const NO_PHOTOS = Object.freeze([]);
package/dist/Service.d.ts CHANGED
@@ -3,14 +3,13 @@ import { BusinessSyncInRequest as proto_BusinessSyncInRequest } from "./generate
3
3
  import { BusinessSyncInResponse as proto_BusinessSyncInResponse } from "./generated/BusinessSyncInResponse";
4
4
  import { BusinessSyncOutRequest as proto_BusinessSyncOutRequest } from "./generated/BusinessSyncOutRequest";
5
5
  import { BusinessSyncOutResponse as proto_BusinessSyncOutResponse } from "./generated/BusinessSyncOutResponse";
6
- import { CreateUserRequest as proto_CreateUserRequest } from "./generated/CreateUserRequest";
7
6
  import { ProfileSyncOutRequest as proto_ProfileSyncOutRequest } from "./generated/ProfileSyncOutRequest";
8
7
  import { ProfileSyncOutResponse as proto_ProfileSyncOutResponse } from "./generated/ProfileSyncOutResponse";
9
- import { User as proto_User } from "./generated/User";
8
+ import { UserRequest as proto_UserRequest } from "./generated/UserRequest";
10
9
  export default interface Service {
11
10
  businessSyncOut(request: proto_BusinessSyncOutRequest, authTokens: proto_AuthTokens | null): Promise<proto_BusinessSyncOutResponse>;
12
11
  businessSyncIn(request: proto_BusinessSyncInRequest, authTokens: proto_AuthTokens): Promise<proto_BusinessSyncInResponse>;
13
12
  profileSyncOut(request: proto_ProfileSyncOutRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
14
- createUser(request: proto_CreateUserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
15
- updateUser(user: proto_User, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
13
+ createUser(request: proto_UserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
14
+ updateUser(request: proto_UserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
16
15
  }
@@ -3,10 +3,9 @@ import { BusinessSyncInRequest as proto_BusinessSyncInRequest } from "./generate
3
3
  import { BusinessSyncInResponse as proto_BusinessSyncInResponse } from "./generated/BusinessSyncInResponse";
4
4
  import { BusinessSyncOutRequest as proto_BusinessSyncOutRequest } from "./generated/BusinessSyncOutRequest";
5
5
  import { BusinessSyncOutResponse as proto_BusinessSyncOutResponse } from "./generated/BusinessSyncOutResponse";
6
- import { CreateUserRequest as proto_CreateUserRequest } from "./generated/CreateUserRequest";
7
6
  import { ProfileSyncOutRequest as proto_ProfileSyncOutRequest } from "./generated/ProfileSyncOutRequest";
8
7
  import { ProfileSyncOutResponse as proto_ProfileSyncOutResponse } from "./generated/ProfileSyncOutResponse";
9
- import { User as proto_User } from "./generated/User";
8
+ import { UserRequest as proto_UserRequest } from "./generated/UserRequest";
10
9
  import Service from "./Service";
11
10
  export type FetchFunction = (url: string, init: RequestInit) => Promise<Response>;
12
11
  export default class ServiceProxy implements Service {
@@ -14,8 +13,8 @@ export default class ServiceProxy implements Service {
14
13
  businessSyncOut(request: proto_BusinessSyncOutRequest, authTokens: proto_AuthTokens | null): Promise<proto_BusinessSyncOutResponse>;
15
14
  businessSyncIn(request: proto_BusinessSyncInRequest, authTokens: proto_AuthTokens): Promise<proto_BusinessSyncInResponse>;
16
15
  profileSyncOut(request: proto_ProfileSyncOutRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
17
- createUser(request: proto_CreateUserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
18
- updateUser(user: proto_User, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
16
+ createUser(request: proto_UserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
17
+ updateUser(request: proto_UserRequest, authTokens: proto_AuthTokens): Promise<proto_ProfileSyncOutResponse>;
19
18
  private post;
20
19
  private send;
21
20
  private getFetchFunction;
@@ -2,10 +2,9 @@ import { BusinessSyncInRequest as proto_BusinessSyncInRequest } from "./generate
2
2
  import { BusinessSyncInResponse as proto_BusinessSyncInResponse } from "./generated/BusinessSyncInResponse";
3
3
  import { BusinessSyncOutRequest as proto_BusinessSyncOutRequest } from "./generated/BusinessSyncOutRequest";
4
4
  import { BusinessSyncOutResponse as proto_BusinessSyncOutResponse } from "./generated/BusinessSyncOutResponse";
5
- import { CreateUserRequest as proto_CreateUserRequest } from "./generated/CreateUserRequest";
6
5
  import { ProfileSyncOutRequest as proto_ProfileSyncOutRequest } from "./generated/ProfileSyncOutRequest";
7
6
  import { ProfileSyncOutResponse as proto_ProfileSyncOutResponse } from "./generated/ProfileSyncOutResponse";
8
- import { User as proto_User } from "./generated/User";
7
+ import { UserRequest as proto_UserRequest } from "./generated/UserRequest";
9
8
  import { IllegalOperationException } from "./Exceptions";
10
9
  import { isError, throwOnError } from "./HttpStatusCodes";
11
10
  import { buildAuthTokensBytes } from "./ProtoMap";
@@ -33,11 +32,10 @@ export default class ServiceProxy {
33
32
  return this.post(Routes.PROFILE_SYNC_OUT, proto_ProfileSyncOutRequest, request, proto_ProfileSyncOutResponse, authTokens);
34
33
  }
35
34
  async createUser(request, authTokens) {
36
- return this.post(Routes.CREATE_USER, proto_CreateUserRequest, request, proto_ProfileSyncOutResponse, authTokens);
35
+ return this.post(Routes.CREATE_USER, proto_UserRequest, request, proto_ProfileSyncOutResponse, authTokens);
37
36
  }
38
- // The User is the whole request, since there is no wrapper message for it
39
- async updateUser(user, authTokens) {
40
- return this.post(Routes.UPDATE_USER, proto_User, user, proto_ProfileSyncOutResponse, authTokens);
37
+ async updateUser(request, authTokens) {
38
+ return this.post(Routes.UPDATE_USER, proto_UserRequest, request, proto_ProfileSyncOutResponse, authTokens);
41
39
  }
42
40
  // One call: encode, send, throw the matching exception on an error status, decode. The message
43
41
  // types are passed in so every route is sent and read the same way.
@@ -16,9 +16,11 @@ export default class SyncCache {
16
16
  readDeletedProfile(containerName: string): Promise<proto_ProfileSyncOutResponse | null>;
17
17
  readProfileCursors(containerName: string): Promise<SyncState>;
18
18
  writeProfile(containerName: string, response: proto_ProfileSyncOutResponse): Promise<number>;
19
- storeProfile(containerName: string, response: proto_ProfileSyncOutResponse): Promise<number>;
20
19
  readAuthTokens(containerName: string): Promise<AuthTokens | null>;
21
20
  writeAuthTokens(containerName: string, authTokens: AuthTokens): Promise<void>;
21
+ readUserId(containerName: string): Promise<bigint | null>;
22
+ writeUserId(containerName: string, userId: bigint): Promise<void>;
23
+ deleteUserId(containerName: string): Promise<void>;
22
24
  readOrders(containerName: string): Promise<ReadonlyArray<proto_Message>>;
23
25
  readDeletedOrders(containerName: string): Promise<ReadonlyArray<proto_Message>>;
24
26
  readMessageUpdateNumber(containerName: string): Promise<bigint>;