@peabody-soft/mbs-model-ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/LICENCE +661 -0
  2. package/dist/Address.d.ts +33 -0
  3. package/dist/Address.js +97 -0
  4. package/dist/Attachment.d.ts +16 -0
  5. package/dist/Attachment.js +49 -0
  6. package/dist/AttachmentBaseUrl.d.ts +11 -0
  7. package/dist/AttachmentBaseUrl.js +41 -0
  8. package/dist/AuthTokens.d.ts +13 -0
  9. package/dist/AuthTokens.js +30 -0
  10. package/dist/Business.d.ts +44 -0
  11. package/dist/Business.js +87 -0
  12. package/dist/BusinessIdentifier.d.ts +8 -0
  13. package/dist/BusinessIdentifier.js +14 -0
  14. package/dist/BusinessLink.d.ts +21 -0
  15. package/dist/BusinessLink.js +61 -0
  16. package/dist/BusinessProfile.d.ts +11 -0
  17. package/dist/BusinessProfile.js +27 -0
  18. package/dist/CartEditor.d.ts +59 -0
  19. package/dist/CartEditor.js +324 -0
  20. package/dist/Catalog.d.ts +28 -0
  21. package/dist/Catalog.js +115 -0
  22. package/dist/Constants.d.ts +10 -0
  23. package/dist/Constants.js +43 -0
  24. package/dist/ContainerNames.d.ts +2 -0
  25. package/dist/ContainerNames.js +21 -0
  26. package/dist/Database.d.ts +15 -0
  27. package/dist/Database.js +1 -0
  28. package/dist/Entity.d.ts +27 -0
  29. package/dist/Entity.js +69 -0
  30. package/dist/Enums.d.ts +16 -0
  31. package/dist/Enums.js +18 -0
  32. package/dist/Exceptions.d.ts +4 -0
  33. package/dist/Exceptions.js +8 -0
  34. package/dist/GeneralSettings.d.ts +46 -0
  35. package/dist/GeneralSettings.js +99 -0
  36. package/dist/HttpStatusCodes.d.ts +2 -0
  37. package/dist/HttpStatusCodes.js +34 -0
  38. package/dist/Ids.d.ts +17 -0
  39. package/dist/Ids.js +78 -0
  40. package/dist/Item.d.ts +12 -0
  41. package/dist/Item.js +30 -0
  42. package/dist/LatLong.d.ts +7 -0
  43. package/dist/LatLong.js +17 -0
  44. package/dist/LiveData.d.ts +30 -0
  45. package/dist/LiveData.js +76 -0
  46. package/dist/Measures.d.ts +20 -0
  47. package/dist/Measures.js +34 -0
  48. package/dist/Message.d.ts +16 -0
  49. package/dist/Message.js +32 -0
  50. package/dist/MockDatabase.d.ts +19 -0
  51. package/dist/MockDatabase.js +73 -0
  52. package/dist/Model.d.ts +63 -0
  53. package/dist/Model.js +629 -0
  54. package/dist/Order.d.ts +31 -0
  55. package/dist/Order.js +70 -0
  56. package/dist/Package.d.ts +25 -0
  57. package/dist/Package.js +52 -0
  58. package/dist/PackageIdentifier.d.ts +8 -0
  59. package/dist/PackageIdentifier.js +14 -0
  60. package/dist/PaymentMethodDetail.d.ts +8 -0
  61. package/dist/PaymentMethodDetail.js +17 -0
  62. package/dist/Photo.d.ts +11 -0
  63. package/dist/Photo.js +29 -0
  64. package/dist/Price.d.ts +17 -0
  65. package/dist/Price.js +52 -0
  66. package/dist/PriceList.d.ts +3 -0
  67. package/dist/PriceList.js +5 -0
  68. package/dist/Product.d.ts +33 -0
  69. package/dist/Product.js +84 -0
  70. package/dist/ProductCategory.d.ts +10 -0
  71. package/dist/ProductCategory.js +18 -0
  72. package/dist/Profile.d.ts +20 -0
  73. package/dist/Profile.js +55 -0
  74. package/dist/ProtoMap.d.ts +71 -0
  75. package/dist/ProtoMap.js +1126 -0
  76. package/dist/Routes.d.ts +7 -0
  77. package/dist/Routes.js +12 -0
  78. package/dist/Service.d.ts +16 -0
  79. package/dist/Service.js +1 -0
  80. package/dist/ServiceExceptions.d.ts +40 -0
  81. package/dist/ServiceExceptions.js +84 -0
  82. package/dist/ServiceProxy.d.ts +25 -0
  83. package/dist/ServiceProxy.js +117 -0
  84. package/dist/ShoppingList.d.ts +64 -0
  85. package/dist/ShoppingList.js +188 -0
  86. package/dist/SyncCache.d.ts +28 -0
  87. package/dist/SyncCache.js +137 -0
  88. package/dist/SyncKeys.d.ts +5 -0
  89. package/dist/SyncKeys.js +41 -0
  90. package/dist/SyncState.d.ts +8 -0
  91. package/dist/SyncState.js +30 -0
  92. package/dist/Tax.d.ts +10 -0
  93. package/dist/Tax.js +30 -0
  94. package/dist/UserProfile.d.ts +16 -0
  95. package/dist/UserProfile.js +40 -0
  96. package/dist/WorkingDays.d.ts +33 -0
  97. package/dist/WorkingDays.js +121 -0
  98. package/dist/generated/Address.d.ts +77 -0
  99. package/dist/generated/Address.js +110 -0
  100. package/dist/generated/AddressList.d.ts +39 -0
  101. package/dist/generated/AddressList.js +60 -0
  102. package/dist/generated/AddressType.d.ts +15 -0
  103. package/dist/generated/AddressType.js +20 -0
  104. package/dist/generated/Attachment.d.ts +66 -0
  105. package/dist/generated/Attachment.js +89 -0
  106. package/dist/generated/AttachmentType.d.ts +154 -0
  107. package/dist/generated/AttachmentType.js +159 -0
  108. package/dist/generated/AuthTokens.d.ts +56 -0
  109. package/dist/generated/AuthTokens.js +74 -0
  110. package/dist/generated/AuthenticationMethod.d.ts +24 -0
  111. package/dist/generated/AuthenticationMethod.js +29 -0
  112. package/dist/generated/Business.d.ts +54 -0
  113. package/dist/generated/Business.js +75 -0
  114. package/dist/generated/BusinessIdentifier.d.ts +36 -0
  115. package/dist/generated/BusinessIdentifier.js +60 -0
  116. package/dist/generated/BusinessIdentifierType.d.ts +49 -0
  117. package/dist/generated/BusinessIdentifierType.js +54 -0
  118. package/dist/generated/BusinessLink.d.ts +76 -0
  119. package/dist/generated/BusinessLink.js +99 -0
  120. package/dist/generated/BusinessSyncInRequest.d.ts +42 -0
  121. package/dist/generated/BusinessSyncInRequest.js +60 -0
  122. package/dist/generated/BusinessSyncInResponse.d.ts +42 -0
  123. package/dist/generated/BusinessSyncInResponse.js +60 -0
  124. package/dist/generated/BusinessSyncOutRequest.d.ts +82 -0
  125. package/dist/generated/BusinessSyncOutRequest.js +107 -0
  126. package/dist/generated/BusinessSyncOutResponse.d.ts +106 -0
  127. package/dist/generated/BusinessSyncOutResponse.js +139 -0
  128. package/dist/generated/Configuration.d.ts +48 -0
  129. package/dist/generated/Configuration.js +63 -0
  130. package/dist/generated/Constants.d.ts +474 -0
  131. package/dist/generated/Constants.js +479 -0
  132. package/dist/generated/CreateUserRequest.d.ts +37 -0
  133. package/dist/generated/CreateUserRequest.js +59 -0
  134. package/dist/generated/DimensionUnit.d.ts +28 -0
  135. package/dist/generated/DimensionUnit.js +33 -0
  136. package/dist/generated/Dimensions.d.ts +41 -0
  137. package/dist/generated/Dimensions.js +76 -0
  138. package/dist/generated/Entity.d.ts +62 -0
  139. package/dist/generated/Entity.js +83 -0
  140. package/dist/generated/FulfillmentMode.d.ts +32 -0
  141. package/dist/generated/FulfillmentMode.js +37 -0
  142. package/dist/generated/GeneralSettings.d.ts +141 -0
  143. package/dist/generated/GeneralSettings.js +186 -0
  144. package/dist/generated/Item.d.ts +54 -0
  145. package/dist/generated/Item.js +82 -0
  146. package/dist/generated/LatLong.d.ts +36 -0
  147. package/dist/generated/LatLong.js +59 -0
  148. package/dist/generated/Message.d.ts +80 -0
  149. package/dist/generated/Message.js +114 -0
  150. package/dist/generated/MessageType.d.ts +34 -0
  151. package/dist/generated/MessageType.js +39 -0
  152. package/dist/generated/OrderStatus.d.ts +50 -0
  153. package/dist/generated/OrderStatus.js +55 -0
  154. package/dist/generated/Package.d.ts +63 -0
  155. package/dist/generated/Package.js +92 -0
  156. package/dist/generated/PackageIdentifier.d.ts +36 -0
  157. package/dist/generated/PackageIdentifier.js +60 -0
  158. package/dist/generated/PackageIdentifierType.d.ts +49 -0
  159. package/dist/generated/PackageIdentifierType.js +54 -0
  160. package/dist/generated/PartyPreferencesPayload.d.ts +41 -0
  161. package/dist/generated/PartyPreferencesPayload.js +60 -0
  162. package/dist/generated/PaymentMethod.d.ts +20 -0
  163. package/dist/generated/PaymentMethod.js +25 -0
  164. package/dist/generated/PaymentMethodDetail.d.ts +37 -0
  165. package/dist/generated/PaymentMethodDetail.js +59 -0
  166. package/dist/generated/Price.d.ts +52 -0
  167. package/dist/generated/Price.js +75 -0
  168. package/dist/generated/PriceList.d.ts +34 -0
  169. package/dist/generated/PriceList.js +51 -0
  170. package/dist/generated/Product.d.ts +77 -0
  171. package/dist/generated/Product.js +106 -0
  172. package/dist/generated/ProductCategory.d.ts +32 -0
  173. package/dist/generated/ProductCategory.js +51 -0
  174. package/dist/generated/ProductType.d.ts +42 -0
  175. package/dist/generated/ProductType.js +47 -0
  176. package/dist/generated/ProfileSyncOutRequest.d.ts +51 -0
  177. package/dist/generated/ProfileSyncOutRequest.js +75 -0
  178. package/dist/generated/ProfileSyncOutResponse.d.ts +56 -0
  179. package/dist/generated/ProfileSyncOutResponse.js +77 -0
  180. package/dist/generated/ReservedPackage.d.ts +34 -0
  181. package/dist/generated/ReservedPackage.js +39 -0
  182. package/dist/generated/Role.d.ts +71 -0
  183. package/dist/generated/Role.js +76 -0
  184. package/dist/generated/SalesOrderPayload.d.ts +85 -0
  185. package/dist/generated/SalesOrderPayload.js +123 -0
  186. package/dist/generated/ShoppingList.d.ts +99 -0
  187. package/dist/generated/ShoppingList.js +132 -0
  188. package/dist/generated/ShoppingListItem.d.ts +102 -0
  189. package/dist/generated/ShoppingListItem.js +139 -0
  190. package/dist/generated/TaskStatus.d.ts +20 -0
  191. package/dist/generated/TaskStatus.js +25 -0
  192. package/dist/generated/Tax.d.ts +37 -0
  193. package/dist/generated/Tax.js +60 -0
  194. package/dist/generated/TaxType.d.ts +25 -0
  195. package/dist/generated/TaxType.js +30 -0
  196. package/dist/generated/UrlAndToken.d.ts +54 -0
  197. package/dist/generated/UrlAndToken.js +67 -0
  198. package/dist/generated/User.d.ts +63 -0
  199. package/dist/generated/User.js +89 -0
  200. package/dist/generated/Weight.d.ts +33 -0
  201. package/dist/generated/Weight.js +60 -0
  202. package/dist/generated/WeightUnit.d.ts +28 -0
  203. package/dist/generated/WeightUnit.js +33 -0
  204. package/dist/generated/WorkingDays.d.ts +42 -0
  205. package/dist/generated/WorkingDays.js +68 -0
  206. package/dist/generated/WorkingHours.d.ts +33 -0
  207. package/dist/generated/WorkingHours.js +52 -0
  208. package/dist/generated/WorkingHoursRange.d.ts +42 -0
  209. package/dist/generated/WorkingHoursRange.js +59 -0
  210. package/dist/index.d.ts +48 -0
  211. package/dist/index.js +60 -0
  212. package/package.json +55 -0
@@ -0,0 +1,137 @@
1
+ import Ids from "./Ids";
2
+ import * as ProtoMap from "./ProtoMap";
3
+ // What the server has already said, so that the next call need only ask for what has changed since. A read cache and nothing else: everything in it came out of a response, so giving a container up costs one refetch.
4
+ // Not write through, not an identity (it stamps nothing) and not a sync - a caller calls the Service and hands the response in, which is why the cursors are the caller's. It holds the wire and only the wire, and ProtoMap reads every field. See SYNC LAYER in ../Prompt.txt.
5
+ // One response is one write: the entities it brought are stored and the entities its tombstones
6
+ // named are deleted, in a single batch. There is nothing to work out beyond that, because the
7
+ // server sends a tombstone for every entity it deleted, children included - so this cache never
8
+ // infers a deletion from another one, and a write costs one transaction whether or not the page
9
+ // happened to carry a tombstone.
10
+ export default class SyncCache {
11
+ constructor(_database) {
12
+ this._database = _database;
13
+ this._containers = new Map();
14
+ }
15
+ // Everything cached for one business, as the response that carried it, or null before anything has been written for it.
16
+ async readBusiness(containerName) {
17
+ return ProtoMap.businessResponseFromBlobs(await this.readAll(containerName));
18
+ }
19
+ // Takes in what one business sync out returned, one blob per entity versioned by Entity.updatedAt, so the later of two copies wins and a page that arrives twice changes nothing the second time.
20
+ // What its tombstones named goes in the same batch, deleted rather than stored: a page brings what
21
+ // has changed and says what has gone, and applying half of that would leave the cache holding an
22
+ // entity the server has already reported dead.
23
+ // Answers how much of the page it kept - blobs stored plus ids deleted, which is what the container
24
+ // now differs by. That is how a caller tells a response that changed something from one that
25
+ // changed nothing, and what it publishes on: the store keeps a blob only when its eTag is later, so
26
+ // a response can be rejected in full, and one that was cannot change what a later read of this
27
+ // container answers - see MODEL.
28
+ async writeBusiness(containerName, response) {
29
+ return this.write(containerName, ProtoMap.businessResponseToCacheWrite(response));
30
+ }
31
+ // The same for one profile: the customer, the businesses they may reach, and the link to each.
32
+ async readProfile(containerName) {
33
+ return ProtoMap.profileResponseFromBlobs(await this.readAll(containerName));
34
+ }
35
+ async writeProfile(containerName, response) {
36
+ return this.write(containerName, ProtoMap.profileResponseToCacheWrite(response));
37
+ }
38
+ // The credentials that sign calls about this container - the user token in a profile container, the link token in a business one - and null before the server has issued either.
39
+ async readAuthTokens(containerName) {
40
+ const blob = await (await this.getContainer(containerName)).read(Ids.AUTH_TOKENS_BLOB_ID);
41
+ if (blob === null)
42
+ return null;
43
+ return ProtoMap.authTokensFromBytes(blob.bytes);
44
+ }
45
+ // Unconditional, unlike everything else here: a reissued token is not a later version of an earlier one, so there is nothing to compare and the latest wins.
46
+ // Keeps only what this server issued - see ProtoMap.authTokensToBlob for why the authentication token is dropped whatever is passed in.
47
+ async writeAuthTokens(containerName, authTokens) {
48
+ await this.writeBlobs(containerName, [ProtoMap.authTokensToBlob(authTokens)]);
49
+ }
50
+ // The orders of whoever the responses written here answered, since a server sends a caller only their own messages.
51
+ // Nothing here records which caller that was, or notices when it changes - see THE MESSAGES OF A BUSINESS ARE NOT PUBLIC.
52
+ async readOrders(containerName) {
53
+ return ProtoMap.orderMessagesFromBlobs(await this.readAll(containerName));
54
+ }
55
+ // The cursor a push sends: the highest update number among this customer's messages already
56
+ // cached here, cart and orders alike. Not persisted, like every cursor in this library - read off
57
+ // the store fresh each time rather than kept anywhere.
58
+ async readMessageUpdateNumber(containerName) {
59
+ return ProtoMap.highestCachedMessageUpdateNumber(await this.readAll(containerName));
60
+ }
61
+ // Refuses a message that is not an order, since only the payload says which it is: one stored as the other would never be read back.
62
+ async writeOrder(containerName, order) {
63
+ return this.writeBlobs(containerName, [ProtoMap.orderToBlob(order)]);
64
+ }
65
+ // The cart in this container, or null when there is none. Two carts mean two callers, which is the same open question as the orders above.
66
+ async readCart(containerName) {
67
+ return ProtoMap.cartMessageFromBlobs(await this.readAll(containerName));
68
+ }
69
+ async writeCart(containerName, cart) {
70
+ return this.writeBlobs(containerName, [ProtoMap.cartToBlob(cart)]);
71
+ }
72
+ // Drops the cart this container holds, and answers zero when it holds none. The one delete here
73
+ // that no tombstone asked for, and the only reason it exists: a cart written before anybody
74
+ // signed in is handed over to the container named for the membership, and the copy left behind
75
+ // would otherwise be read by the next anonymous visitor and by the next customer to sign in - see
76
+ // Model.businessSyncIn. Nothing else about a container is a caller's to remove: everything in one
77
+ // came off the wire, and what the server has not said is gone stays.
78
+ async deleteCart(containerName) {
79
+ const cart = await this.readCart(containerName);
80
+ if (cart === null)
81
+ return 0;
82
+ return this.write(containerName, {
83
+ blobs: NO_BLOBS,
84
+ deletedIds: [ProtoMap.cartToBlob(cart).id],
85
+ });
86
+ }
87
+ // The messages a sync in response carried, in a single batch - a pushed order and the cart it
88
+ // emptied belong in one write, so that this cache cannot come to hold the order without the
89
+ // emptying. Tombstones included, the same as any other entity array read off the wire: this is
90
+ // what a push's own messages are stored as once the server has echoed them back with real update
91
+ // numbers, and it is also how anything else of this customer's that the response carried lands
92
+ // here. Neither is checked against a payload type, unlike the two writers above - those are for a
93
+ // caller handing in a message it was given, where storing an order as a cart would be a mistake
94
+ // worth catching, and this is for messages the response itself is the authority on.
95
+ async writeMessages(containerName, messages) {
96
+ return this.write(containerName, ProtoMap.messagesToCacheWrite(messages));
97
+ }
98
+ async readAll(containerName) {
99
+ return (await this.getContainer(containerName)).readAll();
100
+ }
101
+ // For the writers that store one thing and delete nothing - a credential or a single message,
102
+ // none of which a tombstone ever arrives for on its own
103
+ async writeBlobs(containerName, blobs) {
104
+ return this.write(containerName, { blobs: blobs, deletedIds: NO_DELETES });
105
+ }
106
+ // One transaction per call, which is what makes an interrupted write repeat itself rather than leave the cache part written.
107
+ // The count the store answers with covers only what it stored: a delete is unconditional, so
108
+ // there is nothing for it to compare and nothing for it to report. Counting the ids instead can
109
+ // therefore call a page that deleted nothing a page that changed something, when a tombstone
110
+ // names an entity this container never held - which is the harmless direction, since a caller
111
+ // republishes once for nothing rather than going on showing an entity the server has said is gone.
112
+ async write(containerName, cacheWrite) {
113
+ const blobs = cacheWrite.blobs;
114
+ const deletedIds = cacheWrite.deletedIds;
115
+ if (blobs.length === 0 && deletedIds.length === 0)
116
+ return 0;
117
+ const storedCount = await (await this.getContainer(containerName)).write(blobs, deletedIds);
118
+ return storedCount + deletedIds.length;
119
+ }
120
+ // Held against their names, so each container is fetched once however often it is used. The promise is held rather than the container, so two calls arriving together share one fetch.
121
+ // A caller that deletes a container from the store gives up this cache with it: nothing here can tell that what it holds is no longer what the store would hand out.
122
+ getContainer(containerName) {
123
+ const held = this._containers.get(containerName);
124
+ if (held !== undefined)
125
+ return held;
126
+ const pending = this._database.getContainer(containerName);
127
+ this._containers.set(containerName, pending);
128
+ // A failure is not remembered, or one unlucky read would poison this name for as long as this cache lives
129
+ void pending.catch(() => {
130
+ if (this._containers.get(containerName) === pending)
131
+ this._containers.delete(containerName);
132
+ });
133
+ return pending;
134
+ }
135
+ }
136
+ export const NO_DELETES = Object.freeze([]);
137
+ const NO_BLOBS = Object.freeze([]);
@@ -0,0 +1,5 @@
1
+ export declare const BUSINESS_SYNC_IN: ReadonlyArray<number>;
2
+ export declare const PROFILE_SYNC_IN: ReadonlyArray<number>;
3
+ export declare const BUSINESS_SYNC_OUT: ReadonlyArray<number>;
4
+ export declare const ITEM_ENTITY_TYPE_IDS: ReadonlyArray<number>;
5
+ export declare function keyOf(entityTypeId: number): number;
@@ -0,0 +1,41 @@
1
+ import { Constants as proto_Constants } from "./generated/Constants";
2
+ // Which update numbers a SyncState keeps, and what entity type counts under which key. Kept here
3
+ // rather than in SyncState since these are facts about the protocol, not about counting.
4
+ // A state refuses keys outside its set on purpose: Business arrives from both sync outs with
5
+ // unrelated update numbers, and these must never mix.
6
+ // The cursors a business sync in sends: the business itself, its attachments, its configurations,
7
+ // its messages and its items. These are the five cursor fields of a BusinessSyncOutRequest.
8
+ export const BUSINESS_SYNC_IN = Object.freeze([
9
+ proto_Constants.BUSINESS_ENTITY_TYPE_ID,
10
+ proto_Constants.ATTACHMENT_ENTITY_TYPE_ID,
11
+ proto_Constants.CONFIGURATION_ENTITY_TYPE_ID,
12
+ proto_Constants.MESSAGE_ENTITY_TYPE_ID,
13
+ proto_Constants.ITEM_ENTITY_TYPE_ID,
14
+ ]);
15
+ // The cursors a profile sync in sends - the three entity types Java keeps in a profile database.
16
+ // BUSINESS also appears above, with an unrelated number in each; never mix the two.
17
+ export const PROFILE_SYNC_IN = Object.freeze([
18
+ proto_Constants.USER_ENTITY_TYPE_ID,
19
+ proto_Constants.BUSINESS_ENTITY_TYPE_ID,
20
+ proto_Constants.BUSINESS_LINK_ENTITY_TYPE_ID,
21
+ ]);
22
+ // Update numbers a business sync out records (as a BusinessSyncInResponse returns them). Message
23
+ // is the only entity type a storefront may push, so this is a set of one.
24
+ export const BUSINESS_SYNC_OUT = Object.freeze([
25
+ proto_Constants.MESSAGE_ENTITY_TYPE_ID,
26
+ ]);
27
+ // The five entity types that share the item table, and therefore share one update number
28
+ export const ITEM_ENTITY_TYPE_IDS = Object.freeze([
29
+ proto_Constants.PACKAGE_ENTITY_TYPE_ID,
30
+ proto_Constants.PRODUCT_ENTITY_TYPE_ID,
31
+ proto_Constants.PRODUCT_CATEGORY_ENTITY_TYPE_ID,
32
+ proto_Constants.PRICE_ENTITY_TYPE_ID,
33
+ proto_Constants.PRICE_LIST_ENTITY_TYPE_ID,
34
+ ]);
35
+ // The key an entity counts under: one update number per table, not per class - a product's number
36
+ // is the shared item number.
37
+ export function keyOf(entityTypeId) {
38
+ return ITEM_ENTITY_TYPE_IDS.includes(entityTypeId)
39
+ ? proto_Constants.ITEM_ENTITY_TYPE_ID
40
+ : entityTypeId;
41
+ }
@@ -0,0 +1,8 @@
1
+ export default class SyncState {
2
+ private readonly _keys;
3
+ constructor(_keys: ReadonlyArray<number>);
4
+ getUpdateNumber(entityTypeId: number): bigint;
5
+ raiseUpdateNumber(entityTypeId: number, updateNumber: bigint): boolean;
6
+ private checkKey;
7
+ private readonly _updateNumbers;
8
+ }
@@ -0,0 +1,30 @@
1
+ import { InvalidArgumentException } from "./Exceptions";
2
+ import * as SyncKeys from "./SyncKeys";
3
+ // How far one read has got, as an update number per entity type computed from what arrives and sent back on the next call, which is what makes a read incremental. One class for both directions - only the keys differ.
4
+ // Held in memory by whoever is reading and stored nowhere, so a new session asks from zero and is sent what it already holds: that costs bandwidth alone, since the store keeps a blob only when its eTag is the greater. "In" and "out" name the direction relative to this store, the opposite way round to the calls of the wire.
5
+ export default class SyncState {
6
+ // The keys this state accepts, which is the whole difference between a business state and a profile one. Anything else is refused, because a Business arrives from both calls with an unrelated update number in each.
7
+ constructor(_keys) {
8
+ this._keys = _keys;
9
+ this._updateNumbers = new Map();
10
+ }
11
+ // Zero (an unset key) asks the server for everything of that type - what a first read sends for every key.
12
+ getUpdateNumber(entityTypeId) {
13
+ return this._updateNumbers.get(this.checkKey(entityTypeId)) ?? 0n;
14
+ }
15
+ // Raises the cursor for this entity type (tombstones included - skipping one would refetch it for ever) and reports whether it moved.
16
+ // Numbers only ever rise; a lower one is ignored as stale or redelivered.
17
+ raiseUpdateNumber(entityTypeId, updateNumber) {
18
+ const key = this.checkKey(entityTypeId);
19
+ if (updateNumber <= (this._updateNumbers.get(key) ?? 0n))
20
+ return false;
21
+ this._updateNumbers.set(key, updateNumber);
22
+ return true;
23
+ }
24
+ checkKey(entityTypeId) {
25
+ const key = SyncKeys.keyOf(entityTypeId);
26
+ if (!this._keys.includes(key))
27
+ throw new InvalidArgumentException(`Entity type ${entityTypeId} has no update number in this sync state`);
28
+ return key;
29
+ }
30
+ }
package/dist/Tax.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { TaxType as proto_TaxType } from "./generated/TaxType";
2
+ export default class Tax {
3
+ private readonly _type;
4
+ private readonly _rate;
5
+ static fromScaledValues(type: proto_TaxType, scaledRate: number): Tax;
6
+ constructor(_type: proto_TaxType, _rate: number);
7
+ get type(): proto_TaxType;
8
+ get countryCode(): number;
9
+ get rate(): number;
10
+ }
package/dist/Tax.js ADDED
@@ -0,0 +1,30 @@
1
+ import { taxRateFromScaledValue } from "./Constants";
2
+ // A tax and its rate, applying to a product or a shopping-list line. Rate is exposed as a
3
+ // percentage (18 for 18%), though the wire carries it scaled by Constants.TAX_RATE_FACTOR.
4
+ // Deliberately doesn't compute tax on an amount - only a line item knows the amount, and does
5
+ // that itself.
6
+ export default class Tax {
7
+ // Only ProtoMap calls this: it is the one place that sees a rate still multiplied by
8
+ // Constants.TAX_RATE_FACTOR.
9
+ static fromScaledValues(type, scaledRate) {
10
+ return new Tax(type, taxRateFromScaledValue(scaledRate));
11
+ }
12
+ constructor(_type, _rate) {
13
+ this._type = _type;
14
+ this._rate = _rate;
15
+ }
16
+ get type() {
17
+ return this._type;
18
+ }
19
+ // The country this tax belongs to, ISO 3166-1 numeric. A TaxType value is not a raw Java tax
20
+ // code - it is Java TaxType.createId(country, taxCode), the country in its high bits - so the
21
+ // country comes back out of the value itself rather than having to be carried alongside it.
22
+ // Zero for TaxType.UNSPECIFIED_TAX_TYPE, no country's code being zero.
23
+ get countryCode() {
24
+ return this._type >> 8;
25
+ }
26
+ // A percentage: 18 means 18%
27
+ get rate() {
28
+ return this._rate;
29
+ }
30
+ }
@@ -0,0 +1,16 @@
1
+ import { AddressList } from "./Address";
2
+ import Entity, { EntityHeader } from "./Entity";
3
+ export default class UserProfile extends Entity {
4
+ private readonly _firstName;
5
+ private readonly _lastName;
6
+ private readonly _mobilePhoneNumber;
7
+ private readonly _addresses;
8
+ private readonly _preferredLanguageCode;
9
+ constructor(header: EntityHeader, _firstName: string, _lastName: string | null, _mobilePhoneNumber: string, _addresses: AddressList, _preferredLanguageCode: string | null);
10
+ get firstName(): string;
11
+ get lastName(): string | null;
12
+ get mobilePhoneNumber(): string;
13
+ get addresses(): AddressList;
14
+ get preferredLanguageCode(): string | null;
15
+ get formattedName(): string;
16
+ }
@@ -0,0 +1,40 @@
1
+ import Entity from "./Entity";
2
+ // The customer: whose orders and cart the business sees. Named for the database it lives in
3
+ // (profile, not business) - a signed-out storefront has no user but still shows its catalog.
4
+ // Exactly the User message, with no derived state added.
5
+ export default class UserProfile extends Entity {
6
+ constructor(header, _firstName, _lastName, _mobilePhoneNumber, _addresses, _preferredLanguageCode) {
7
+ super(header);
8
+ this._firstName = _firstName;
9
+ this._lastName = _lastName;
10
+ this._mobilePhoneNumber = _mobilePhoneNumber;
11
+ this._addresses = _addresses;
12
+ this._preferredLanguageCode = _preferredLanguageCode;
13
+ }
14
+ get firstName() {
15
+ return this._firstName;
16
+ }
17
+ get lastName() {
18
+ return this._lastName;
19
+ }
20
+ // E.164 formatted
21
+ get mobilePhoneNumber() {
22
+ return this._mobilePhoneNumber;
23
+ }
24
+ // The delivery addresses of this customer, and which of them is the default. This is also what
25
+ // resolves Order.deliveryAddressId, which is an id into this list.
26
+ get addresses() {
27
+ return this._addresses;
28
+ }
29
+ // BCP 47 / ISO 639
30
+ get preferredLanguageCode() {
31
+ return this._preferredLanguageCode;
32
+ }
33
+ // The two names with a space between them, and just the first name when there is no last name.
34
+ // Computed here rather than sent.
35
+ get formattedName() {
36
+ if (this._lastName === null || this._lastName.length === 0)
37
+ return this._firstName;
38
+ return `${this._firstName} ${this._lastName}`;
39
+ }
40
+ }
@@ -0,0 +1,33 @@
1
+ export default class WorkingDays {
2
+ private readonly _days;
3
+ private readonly _workingHours;
4
+ constructor(_days: ReadonlyArray<boolean>, _workingHours: ReadonlyArray<WorkingHours>);
5
+ get days(): ReadonlyArray<boolean>;
6
+ get workingHours(): ReadonlyArray<WorkingHours>;
7
+ get includesAllDaysOfWeek(): boolean;
8
+ isWorkingDay(dayOfWeek: number): boolean;
9
+ getWorkingHours(dayOfWeek: number): WorkingHours | null;
10
+ isOpenAllDay(dayOfWeek: number): boolean;
11
+ isOpenAt(dayOfWeek: number, minuteOfDay: number): boolean;
12
+ private static checkDayOfWeek;
13
+ static readonly ALL_DAYS: WorkingDays;
14
+ }
15
+ export declare class WorkingHours {
16
+ private readonly _ranges;
17
+ constructor(_ranges: ReadonlyArray<WorkingHoursRange>);
18
+ get ranges(): ReadonlyArray<WorkingHoursRange>;
19
+ get rangeCount(): number;
20
+ get isAllDay(): boolean;
21
+ contains(minuteOfDay: number): boolean;
22
+ static readonly ALL_DAY: WorkingHours;
23
+ }
24
+ export declare class WorkingHoursRange {
25
+ private readonly _openMinute;
26
+ private readonly _closeMinute;
27
+ constructor(_openMinute: number, _closeMinute: number);
28
+ get openMinute(): number;
29
+ get closeMinute(): number;
30
+ get isAllDay(): boolean;
31
+ contains(minuteOfDay: number): boolean;
32
+ overlaps(other: WorkingHoursRange): boolean;
33
+ }
@@ -0,0 +1,121 @@
1
+ import { Constants as proto_Constants } from "./generated/Constants";
2
+ import { InvalidArgumentException } from "./Exceptions";
3
+ // Days and hours a business works. Absence is the permissive value throughout: no WorkingDays
4
+ // means every day (ALL_DAYS); a day with no hours/ranges means worked all day.
5
+ // No time zone here (the protocol carries none) - only a caller that knows the business's zone
6
+ // can turn a moment into these day/minute values.
7
+ class WorkingDays {
8
+ constructor(_days, _workingHours) {
9
+ this._days = _days;
10
+ this._workingHours = _workingHours;
11
+ }
12
+ // Index 0 is Sunday and index 6 is Saturday, the same numbering as Date.getDay. Shorter than
13
+ // seven entries is normal, and a missing entry is false.
14
+ get days() {
15
+ return this._days;
16
+ }
17
+ // Indexed exactly as days above. Shorter than days, or empty, is normal: see the comment on
18
+ // this class for what a missing entry means.
19
+ get workingHours() {
20
+ return this._workingHours;
21
+ }
22
+ get includesAllDaysOfWeek() {
23
+ for (let dayOfWeek = 0; dayOfWeek < proto_Constants.DAYS_IN_WEEK; ++dayOfWeek)
24
+ if (!this.isWorkingDay(dayOfWeek))
25
+ return false;
26
+ return true;
27
+ }
28
+ isWorkingDay(dayOfWeek) {
29
+ WorkingDays.checkDayOfWeek(dayOfWeek);
30
+ return this._days[dayOfWeek] === true;
31
+ }
32
+ // The hours of a day that is worked, or null when that day is worked all day long or is not
33
+ // worked at all. A UI showing opening times treats null as all day, having tested the day first.
34
+ getWorkingHours(dayOfWeek) {
35
+ WorkingDays.checkDayOfWeek(dayOfWeek);
36
+ if (dayOfWeek >= this._workingHours.length)
37
+ return null;
38
+ const workingHours = this._workingHours[dayOfWeek];
39
+ return workingHours.isAllDay ? null : workingHours;
40
+ }
41
+ isOpenAllDay(dayOfWeek) {
42
+ return this.isWorkingDay(dayOfWeek) && this.getWorkingHours(dayOfWeek) === null;
43
+ }
44
+ // minuteOfDay is minutes after local midnight, from 0 to Constants.MINUTES_PER_DAY - 1
45
+ isOpenAt(dayOfWeek, minuteOfDay) {
46
+ if (!this.isWorkingDay(dayOfWeek))
47
+ return false;
48
+ const workingHours = this.getWorkingHours(dayOfWeek);
49
+ return workingHours === null || workingHours.contains(minuteOfDay);
50
+ }
51
+ static checkDayOfWeek(dayOfWeek) {
52
+ if (!Number.isInteger(dayOfWeek) || dayOfWeek < 0 || dayOfWeek >= proto_Constants.DAYS_IN_WEEK)
53
+ throw new InvalidArgumentException(`Not a day of the week: ${dayOfWeek}`);
54
+ }
55
+ }
56
+ // What an absent WorkingDays means: the business works every day, all day long
57
+ WorkingDays.ALL_DAYS = new WorkingDays(Object.freeze([true, true, true, true, true, true, true]), Object.freeze([]));
58
+ export default WorkingDays;
59
+ // The hours of a single day that are worked, as the stretches of it that are.
60
+ export class WorkingHours {
61
+ constructor(_ranges) {
62
+ this._ranges = _ranges;
63
+ }
64
+ // Ordered by openMinute, never overlapping, and never touching: a range that closes exactly when
65
+ // the next opens arrives as one unbroken range.
66
+ get ranges() {
67
+ return this._ranges;
68
+ }
69
+ get rangeCount() {
70
+ return this._ranges.length;
71
+ }
72
+ // No ranges at all means all day long, which is also what one range covering the whole day means
73
+ get isAllDay() {
74
+ return (this._ranges.length === 0 || (this._ranges.length === 1 && this._ranges[0].isAllDay));
75
+ }
76
+ contains(minuteOfDay) {
77
+ if (this.isAllDay)
78
+ return true;
79
+ for (const range of this._ranges)
80
+ if (range.contains(minuteOfDay))
81
+ return true;
82
+ return false;
83
+ }
84
+ }
85
+ // What a missing or empty entry of WorkingDays.workingHours means
86
+ WorkingHours.ALL_DAY = new WorkingHours(Object.freeze([]));
87
+ // One stretch of a day that is worked, in minutes after local midnight.
88
+ export class WorkingHoursRange {
89
+ constructor(_openMinute, _closeMinute) {
90
+ this._openMinute = _openMinute;
91
+ this._closeMinute = _closeMinute;
92
+ // Validated on the way in: a payload that breaks protocol rules fails the sync rather than
93
+ // producing opening times a customer would trust as fact.
94
+ if (!Number.isInteger(_openMinute) ||
95
+ _openMinute < 0 ||
96
+ _openMinute >= proto_Constants.MINUTES_PER_DAY)
97
+ throw new InvalidArgumentException(`Not a minute of the day: ${_openMinute}`);
98
+ if (!Number.isInteger(_closeMinute) ||
99
+ _closeMinute <= _openMinute ||
100
+ _closeMinute > proto_Constants.MINUTES_PER_DAY)
101
+ throw new InvalidArgumentException(`Work cannot stop at minute ${_closeMinute} having started at ${_openMinute}`);
102
+ }
103
+ // Worked. From 0 to Constants.MINUTES_PER_DAY - 1.
104
+ get openMinute() {
105
+ return this._openMinute;
106
+ }
107
+ // Not worked; MINUTES_PER_DAY means work stops at midnight. Always > openMinute - a range never
108
+ // spans midnight, so overnight work is two ranges on two days.
109
+ get closeMinute() {
110
+ return this._closeMinute;
111
+ }
112
+ get isAllDay() {
113
+ return this._openMinute === 0 && this._closeMinute === proto_Constants.MINUTES_PER_DAY;
114
+ }
115
+ contains(minuteOfDay) {
116
+ return minuteOfDay >= this._openMinute && minuteOfDay < this._closeMinute;
117
+ }
118
+ overlaps(other) {
119
+ return this._openMinute < other._closeMinute && other._openMinute < this._closeMinute;
120
+ }
121
+ }
@@ -0,0 +1,77 @@
1
+ import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
2
+ import type { IBinaryWriter } from "@protobuf-ts/runtime";
3
+ import type { BinaryReadOptions } from "@protobuf-ts/runtime";
4
+ import type { IBinaryReader } from "@protobuf-ts/runtime";
5
+ import type { PartialMessage } from "@protobuf-ts/runtime";
6
+ import { MessageType } from "@protobuf-ts/runtime";
7
+ import { AddressType } from "./AddressType";
8
+ /**
9
+ * A postal address - a delivery address of a customer, or an address of a business
10
+ *
11
+ * @generated from protobuf message com.merabills.storefront.Address
12
+ */
13
+ export interface Address {
14
+ /**
15
+ * Unique id of this address within the list that holds it
16
+ *
17
+ * @generated from protobuf field: int64 id = 1
18
+ */
19
+ id: bigint;
20
+ /**
21
+ * @generated from protobuf field: com.merabills.storefront.AddressType addressType = 2
22
+ */
23
+ addressType: AddressType;
24
+ /**
25
+ * Flat / house / building number (at most Constants.ADDRESS_UNIT_NUMBER_MAX_LENGTH)
26
+ *
27
+ * @generated from protobuf field: optional string unitNumber = 3
28
+ */
29
+ unitNumber?: string;
30
+ /**
31
+ * Street, locality or neighbourhood (at most Constants.ADDRESS_COMMUNITY_MAX_LENGTH)
32
+ *
33
+ * @generated from protobuf field: optional string community = 4
34
+ */
35
+ community?: string;
36
+ /**
37
+ * At most Constants.ADDRESS_LINE2_MAX_LENGTH
38
+ *
39
+ * @generated from protobuf field: optional string line2 = 5
40
+ */
41
+ line2?: string;
42
+ /**
43
+ * At most Constants.ADDRESS_CITY_MAX_LENGTH
44
+ *
45
+ * @generated from protobuf field: optional string city = 6
46
+ */
47
+ city?: string;
48
+ /**
49
+ * At most Constants.ADDRESS_STATE_MAX_LENGTH
50
+ *
51
+ * @generated from protobuf field: optional string state = 7
52
+ */
53
+ state?: string;
54
+ /**
55
+ * Postal code / PIN code (at most Constants.ADDRESS_PIN_CODE_MAX_LENGTH)
56
+ *
57
+ * @generated from protobuf field: optional string pinCode = 8
58
+ */
59
+ pinCode?: string;
60
+ /**
61
+ * ISO 3166-1 numeric country code
62
+ *
63
+ * @generated from protobuf field: int32 countryCode = 9
64
+ */
65
+ countryCode: number;
66
+ }
67
+ declare class Address$Type extends MessageType<Address> {
68
+ constructor();
69
+ create(value?: PartialMessage<Address>): Address;
70
+ internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Address): Address;
71
+ internalBinaryWrite(message: Address, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
72
+ }
73
+ /**
74
+ * @generated MessageType for protobuf message com.merabills.storefront.Address
75
+ */
76
+ export declare const Address: Address$Type;
77
+ export {};