@peabody-soft/mbs-model-ts 1.3.0 → 1.5.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.
- package/dist/CartEditor.d.ts +10 -5
- package/dist/CartEditor.js +232 -125
- package/dist/Catalog.js +26 -18
- package/dist/Enums.d.ts +1 -1
- package/dist/Enums.js +2 -1
- package/dist/GeneralSettings.d.ts +4 -2
- package/dist/GeneralSettings.js +18 -7
- package/dist/Ids.d.ts +2 -0
- package/dist/Ids.js +10 -0
- package/dist/Message.js +3 -2
- package/dist/Model.d.ts +5 -0
- package/dist/Model.js +263 -50
- package/dist/Package.js +2 -2
- package/dist/Price.js +6 -2
- package/dist/Product.d.ts +5 -3
- package/dist/Product.js +30 -18
- package/dist/Profile.d.ts +1 -0
- package/dist/Profile.js +91 -4
- package/dist/ProtoMap.d.ts +15 -8
- package/dist/ProtoMap.js +267 -140
- package/dist/ShoppingList.d.ts +30 -1
- package/dist/ShoppingList.js +345 -35
- package/dist/SyncCache.d.ts +9 -0
- package/dist/SyncCache.js +93 -30
- package/dist/SyncState.js +1 -1
- package/dist/Tax.d.ts +12 -4
- package/dist/Tax.js +33 -4
- package/dist/TaxHelper.d.ts +12 -0
- package/dist/TaxHelper.js +93 -0
- package/dist/TaxType.d.ts +7 -0
- package/dist/TaxType.js +32 -0
- package/dist/WorkingDays.d.ts +3 -2
- package/dist/WorkingDays.js +16 -12
- package/dist/generated/AttachmentType.d.ts +19 -1
- package/dist/generated/AttachmentType.js +18 -0
- package/dist/generated/AuthTokens.d.ts +3 -1
- package/dist/generated/BusinessLink.d.ts +4 -1
- package/dist/generated/BusinessSyncInRequest.d.ts +11 -3
- package/dist/generated/BusinessSyncInResponse.d.ts +5 -4
- package/dist/generated/BusinessSyncOutRequest.d.ts +37 -10
- package/dist/generated/BusinessSyncOutRequest.js +42 -29
- package/dist/generated/BusinessSyncOutResponse.d.ts +14 -1
- package/dist/generated/Configuration.d.ts +2 -1
- package/dist/generated/CreateUserRequest.d.ts +8 -2
- package/dist/generated/Entity.d.ts +7 -3
- package/dist/generated/GeneralSettings.d.ts +14 -4
- package/dist/generated/GeneralSettings.js +9 -1
- package/dist/generated/Message.d.ts +4 -2
- package/dist/generated/Product.d.ts +10 -1
- package/dist/generated/Product.js +9 -1
- package/dist/generated/ProfileSyncOutRequest.d.ts +17 -1
- package/dist/generated/ProfileSyncOutResponse.d.ts +8 -3
- package/dist/generated/SalesOrderPayload.d.ts +7 -2
- package/dist/generated/ShoppingList.d.ts +6 -3
- package/dist/generated/ShoppingListItem.d.ts +21 -6
- package/dist/generated/Tax.d.ts +4 -4
- package/dist/generated/Tax.js +7 -8
- package/dist/generated/WorkingDays.d.ts +9 -1
- package/dist/generated/WorkingDays.js +12 -4
- package/dist/generated/WorkingHoursRange.d.ts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -3
- package/package.json +55 -55
- package/dist/generated/TaxType.d.ts +0 -25
- package/dist/generated/TaxType.js +0 -30
package/dist/Product.js
CHANGED
|
@@ -2,7 +2,7 @@ import Item from "./Item";
|
|
|
2
2
|
// A product in the catalog: goods, a service, a raw material or a fixed asset. Built complete
|
|
3
3
|
// (photos/packages resolved first) so everything here is readonly except category (via parentItemId).
|
|
4
4
|
export default class Product extends Item {
|
|
5
|
-
constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _photos,
|
|
5
|
+
constructor(header, name, description, productCategoryId, isDisabled, _productType, _brand, _isManufacturedByBusiness, _manufacturer, _manufacturerAddress, _taxes, _features, _isForOfflineSaleOnly, _photos, packages) {
|
|
6
6
|
super(header, name, description, productCategoryId, isDisabled);
|
|
7
7
|
this._productType = _productType;
|
|
8
8
|
this._brand = _brand;
|
|
@@ -11,8 +11,11 @@ export default class Product extends Item {
|
|
|
11
11
|
this._manufacturerAddress = _manufacturerAddress;
|
|
12
12
|
this._taxes = _taxes;
|
|
13
13
|
this._features = _features;
|
|
14
|
+
this._isForOfflineSaleOnly = _isForOfflineSaleOnly;
|
|
14
15
|
this._photos = _photos;
|
|
15
|
-
this
|
|
16
|
+
// A package that cannot be bought is of no use to anyone who has this product, so it is not
|
|
17
|
+
// kept - whoever builds a product, and whatever they hand it
|
|
18
|
+
this._packages = Object.freeze(packages.filter((pkg) => pkg.isBuyable));
|
|
16
19
|
}
|
|
17
20
|
// The category this product is in, or Constants.ENTITY_ID_NULL when it is in none
|
|
18
21
|
get productCategoryId() {
|
|
@@ -24,8 +27,8 @@ export default class Product extends Item {
|
|
|
24
27
|
get brand() {
|
|
25
28
|
return this._brand;
|
|
26
29
|
}
|
|
27
|
-
//
|
|
28
|
-
//
|
|
30
|
+
// False only when the business says it does not make this product itself: what is sent is false,
|
|
31
|
+
// or nothing, which Product.proto says is true.
|
|
29
32
|
get isManufacturedByBusiness() {
|
|
30
33
|
return this._isManufacturedByBusiness;
|
|
31
34
|
}
|
|
@@ -35,6 +38,8 @@ export default class Product extends Item {
|
|
|
35
38
|
get manufacturerAddress() {
|
|
36
39
|
return this._manufacturerAddress;
|
|
37
40
|
}
|
|
41
|
+
// The taxes INCLUDED in the price of every package of this product (Price) - not added to it.
|
|
42
|
+
// A UI shows a price as it is, and shows these only as what a bill's total includes.
|
|
38
43
|
get taxes() {
|
|
39
44
|
return this._taxes;
|
|
40
45
|
}
|
|
@@ -42,22 +47,32 @@ export default class Product extends Item {
|
|
|
42
47
|
get features() {
|
|
43
48
|
return this._features;
|
|
44
49
|
}
|
|
50
|
+
// True when the business sells this product only offline: the server sends such a product like any
|
|
51
|
+
// other, flagged, and it is for this model to not offer it to a customer - see isBuyable. A catalog
|
|
52
|
+
// never holds one, so only a product that is built by hand, not one that is read from a catalog,
|
|
53
|
+
// can be seen to say so.
|
|
54
|
+
get isForOfflineSaleOnly() {
|
|
55
|
+
return this._isForOfflineSaleOnly;
|
|
56
|
+
}
|
|
45
57
|
get photos() {
|
|
46
58
|
return this._photos;
|
|
47
59
|
}
|
|
48
|
-
// Every variant of this product, in the order they arrived - ordering them for display
|
|
49
|
-
// decision, see Catalog.comparePackages. A
|
|
60
|
+
// Every buyable variant of this product, in the order they arrived - ordering them for display
|
|
61
|
+
// is a UI decision, see Catalog.comparePackages. A variant that cannot be bought is not here, and
|
|
62
|
+
// a product with none cannot be bought.
|
|
50
63
|
get packages() {
|
|
51
64
|
return this._packages;
|
|
52
65
|
}
|
|
53
|
-
// The cheapest final price across variants (what a listing shows as "from"), or null if
|
|
54
|
-
//
|
|
66
|
+
// The cheapest final price across variants (what a listing shows as "from"), or null if there
|
|
67
|
+
// are no variants. Compares final price, not regular, since that's what a customer would
|
|
68
|
+
// actually pay. Every variant is taxed at this product's rates, so prices with taxes in them
|
|
69
|
+
// order the variants as prices without would.
|
|
55
70
|
get lowestPrice() {
|
|
56
71
|
let result = null;
|
|
57
72
|
for (const pkg of this._packages) {
|
|
58
73
|
const price = pkg.price;
|
|
59
74
|
if (price === null)
|
|
60
|
-
continue; //
|
|
75
|
+
continue; // Not so of a buyable variant, though the type does not say
|
|
61
76
|
if (result === null || price.finalPrice < result.finalPrice)
|
|
62
77
|
result = price;
|
|
63
78
|
}
|
|
@@ -70,15 +85,12 @@ export default class Product extends Item {
|
|
|
70
85
|
return true;
|
|
71
86
|
return false;
|
|
72
87
|
}
|
|
73
|
-
// True when this product itself is enabled
|
|
74
|
-
//
|
|
75
|
-
//
|
|
88
|
+
// True when this product itself is enabled, is not one that the business sells only offline, and
|
|
89
|
+
// it has at least one buyable variant, which is to say any variant, since it keeps no other. The
|
|
90
|
+
// server still filters out draft and non-public products before they ever arrive, but isDisabled
|
|
91
|
+
// can change after an item exists, and the server does not filter out what is sold only offline
|
|
92
|
+
// (it sends the flag), so both are checked here rather than trusted to have been filtered.
|
|
76
93
|
get isBuyable() {
|
|
77
|
-
|
|
78
|
-
return false;
|
|
79
|
-
for (const pkg of this._packages)
|
|
80
|
-
if (pkg.isBuyable)
|
|
81
|
-
return true;
|
|
82
|
-
return false;
|
|
94
|
+
return !this.isDisabled && !this._isForOfflineSaleOnly && this._packages.length > 0;
|
|
83
95
|
}
|
|
84
96
|
}
|
package/dist/Profile.d.ts
CHANGED
package/dist/Profile.js
CHANGED
|
@@ -15,8 +15,16 @@ export default class Profile {
|
|
|
15
15
|
this._businesses = Object.freeze(businesses.filter((business) => linkedBusinessIds.has(business.id)));
|
|
16
16
|
for (const business of this._businesses)
|
|
17
17
|
this._businessMap.set(business.id, business);
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// One link per business is reached through, and it is the one that names this customer. A profile can hold more
|
|
19
|
+
// than one link to a business - one that is not this customer's is not what a customer writes to a business with, nor
|
|
20
|
+
// what a link token of theirs signs - so a link that does not name this customer does not take the place of one that
|
|
21
|
+
// does, whichever comes last. Where none names them (no user is held yet), the last is kept.
|
|
22
|
+
const isOwn = (link) => this._user !== null && link.linkedEntityId === this._user.id;
|
|
23
|
+
for (const businessLink of this._businessLinks) {
|
|
24
|
+
const held = this._businessLinkMap.get(businessLink.businessId);
|
|
25
|
+
if (held === undefined || isOwn(businessLink) || !isOwn(held))
|
|
26
|
+
this._businessLinkMap.set(businessLink.businessId, businessLink);
|
|
27
|
+
}
|
|
20
28
|
}
|
|
21
29
|
// The id of the profile container this was built from, and what Model.signIn was given
|
|
22
30
|
get profileId() {
|
|
@@ -42,10 +50,13 @@ export default class Profile {
|
|
|
42
50
|
getBusinessLink(businessEntityId) {
|
|
43
51
|
return this._businessLinkMap.get(businessEntityId) ?? null;
|
|
44
52
|
}
|
|
45
|
-
// The same, by the url friendly business id a storefront is reached by
|
|
53
|
+
// The same, by the url friendly business id a storefront is reached by - however it is written in the url, which
|
|
54
|
+
// the server finds a business by whatever its case, and which the one here is not to fail: a customer who has joined
|
|
55
|
+
// it is not to be taken for one who has not, and asked to join it again
|
|
46
56
|
getBusinessLinkFor(businessId) {
|
|
57
|
+
const wanted = canonicalizeBusinessId(businessId);
|
|
47
58
|
for (const business of this._businesses)
|
|
48
|
-
if (business.businessId ===
|
|
59
|
+
if (canonicalizeBusinessId(business.businessId) === wanted)
|
|
49
60
|
return this.getBusinessLink(business.id);
|
|
50
61
|
return null;
|
|
51
62
|
}
|
|
@@ -53,3 +64,79 @@ export default class Profile {
|
|
|
53
64
|
return `Profile(profileId=${this._profileId}, user=${this._user?.formattedName ?? "none"}, businesses=${this._businesses.length})`;
|
|
54
65
|
}
|
|
55
66
|
}
|
|
67
|
+
// A business id as the server makes it canonical (Java Business.canonicalizeBusinessId) to find one by it: with its spaces
|
|
68
|
+
// normalized the way Java StringHelper.normalizeSpace does, and in lower case. A url is typed, and linked to, in any case, and a
|
|
69
|
+
// business is the same business in all of them.
|
|
70
|
+
// The spaces are not only those at either end, because a business id can hold some: Java makes one out of the display name when
|
|
71
|
+
// none is given ("Warm Loaf_1a2b3c4d"), and takes whatever is typed, so a url that has two spaces where the id has one is still
|
|
72
|
+
// that business. normalizeSpace is followed step for step, so as to give what Java gives for every string, quirks included: it
|
|
73
|
+
// leaves a string alone that has no whitespace at either end and none twice in a row, and a non-breaking space is not whitespace
|
|
74
|
+
// to it, so one between two words stays as it was, and is made a plain space only when something else of the string is normalized.
|
|
75
|
+
// What Java then refuses, an id that is not between four and sixty-four characters long, is not refused here, since this is only
|
|
76
|
+
// the comparison: the server answers a read of one with a NotFoundException, as it does for a business that does not exist.
|
|
77
|
+
export function canonicalizeBusinessId(businessId) {
|
|
78
|
+
return normalizeSpace(businessId).toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
// Java StringHelper.normalizeSpace, which answers null where this answers an empty string: a string of nothing but whitespace
|
|
81
|
+
function normalizeSpace(input) {
|
|
82
|
+
if (!needsNormalization(input))
|
|
83
|
+
return input;
|
|
84
|
+
let result = "";
|
|
85
|
+
let whitespaceCount = 0;
|
|
86
|
+
let isAtStart = true;
|
|
87
|
+
for (let index = 0; index < input.length; ++index) {
|
|
88
|
+
const unit = input.charCodeAt(index);
|
|
89
|
+
if (isWhitespace(unit)) {
|
|
90
|
+
if (whitespaceCount === 0 && !isAtStart)
|
|
91
|
+
result += " ";
|
|
92
|
+
++whitespaceCount;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
isAtStart = false;
|
|
96
|
+
result += unit === NO_BREAK_SPACE ? " " : input.charAt(index);
|
|
97
|
+
whitespaceCount = 0;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (isAtStart)
|
|
101
|
+
return "";
|
|
102
|
+
// Java String.trim, which takes every character up to a space off either end, and not the trim of a script, which takes the
|
|
103
|
+
// spaces of Unicode. The space that stands for whitespace at the end goes with it: Java drops that one first, to the same end
|
|
104
|
+
let first = 0;
|
|
105
|
+
let last = result.length;
|
|
106
|
+
while (first < last && result.charCodeAt(first) <= SPACE)
|
|
107
|
+
++first;
|
|
108
|
+
while (last > first && result.charCodeAt(last - 1) <= SPACE)
|
|
109
|
+
--last;
|
|
110
|
+
return result.slice(first, last);
|
|
111
|
+
}
|
|
112
|
+
// Java StringHelper.needsNormalization: whitespace at either end, or two together between
|
|
113
|
+
function needsNormalization(input) {
|
|
114
|
+
if (input.length === 0)
|
|
115
|
+
return false;
|
|
116
|
+
if (isWhitespace(input.charCodeAt(0)) || isWhitespace(input.charCodeAt(input.length - 1)))
|
|
117
|
+
return true;
|
|
118
|
+
let wasWhitespace = false;
|
|
119
|
+
for (let index = 1; index < input.length - 1; ++index) {
|
|
120
|
+
const isNowWhitespace = isWhitespace(input.charCodeAt(index));
|
|
121
|
+
if (isNowWhitespace && wasWhitespace)
|
|
122
|
+
return true;
|
|
123
|
+
wasWhitespace = isNowWhitespace;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
// Java Character.isWhitespace: the spaces of Unicode that do not hold a place (so not the no-break ones, U+00A0, U+2007 and
|
|
128
|
+
// U+202F) and the controls that are spaces. Not what a regular expression's \s is, which has the no-break spaces and the
|
|
129
|
+
// byte order mark, and has not the four separators U+001C to U+001F
|
|
130
|
+
function isWhitespace(unit) {
|
|
131
|
+
if (unit === SPACE)
|
|
132
|
+
return true;
|
|
133
|
+
if (unit >= 0x09 && unit <= 0x0d)
|
|
134
|
+
return true; // Tab, line feed, vertical tab, form feed and carriage return
|
|
135
|
+
if (unit >= 0x1c && unit <= 0x1f)
|
|
136
|
+
return true; // The four separators
|
|
137
|
+
if (unit >= 0x2000 && unit <= 0x200a)
|
|
138
|
+
return unit !== 0x2007;
|
|
139
|
+
return unit === 0x1680 || unit === 0x2028 || unit === 0x2029 || unit === 0x205f || unit === 0x3000;
|
|
140
|
+
}
|
|
141
|
+
const SPACE = 0x20;
|
|
142
|
+
const NO_BREAK_SPACE = 0xa0;
|
package/dist/ProtoMap.d.ts
CHANGED
|
@@ -34,25 +34,32 @@ export declare function getProfileResponseEntities(response: proto_ProfileSyncOu
|
|
|
34
34
|
export declare function businessResponseToCacheWrite(response: proto_BusinessSyncOutResponse): CacheWrite;
|
|
35
35
|
export declare function profileResponseToCacheWrite(response: proto_ProfileSyncOutResponse): CacheWrite;
|
|
36
36
|
export declare function getBusinessResponseAttachmentBaseUrlExpiry(response: proto_BusinessSyncOutResponse | null): bigint;
|
|
37
|
-
export declare function orderToBlob(order: proto_Message): StoredBlob;
|
|
37
|
+
export declare function orderToBlob(order: proto_Message, heldETag: bigint | null): StoredBlob;
|
|
38
38
|
export declare function isPartyPreferencesMessage(message: proto_Message): boolean;
|
|
39
39
|
export declare function getMessageUpdatedAt(message: proto_Message): bigint;
|
|
40
|
-
export declare function
|
|
40
|
+
export declare function getMessageId(message: proto_Message): bigint;
|
|
41
|
+
export declare function partyPreferencesToBlob(partyPreferences: proto_Message, heldETag: bigint | null): StoredBlob;
|
|
41
42
|
export declare function businessResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_BusinessSyncOutResponse | null;
|
|
42
43
|
export declare function profileResponseFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_ProfileSyncOutResponse | null;
|
|
43
44
|
export declare function orderMessagesFromBlobs(blobs: ReadonlyArray<StoredBlob>): ReadonlyArray<proto_Message>;
|
|
44
45
|
export declare function partyPreferencesMessageFromMessages(messages: ReadonlyArray<proto_Message>): proto_Message | null;
|
|
45
46
|
export declare function partyPreferencesMessageFromBlobs(blobs: ReadonlyArray<StoredBlob>): proto_Message | null;
|
|
46
|
-
export declare function buildBusinessSyncOutRequest(
|
|
47
|
-
export declare function
|
|
47
|
+
export declare function buildBusinessSyncOutRequest(business: string | bigint, syncInState: SyncState, attachmentBaseUrlExpiry: bigint): proto_BusinessSyncOutRequest;
|
|
48
|
+
export declare function cursorsToBlobs(cursors: SyncState, movedKeys: ReadonlySet<number>): Array<StoredBlob>;
|
|
49
|
+
export declare function cursorsFromBlobs(keys: ReadonlyArray<number>, blobs: ReadonlyArray<StoredBlob | null>): SyncState;
|
|
50
|
+
export declare function raiseBusinessCursors(cursors: SyncState, response: proto_BusinessSyncOutResponse): ReadonlySet<number>;
|
|
48
51
|
export declare function buildBusinessSyncInRequest(messageUpdateNumber: bigint, messages: ReadonlyArray<proto_Message>): proto_BusinessSyncInRequest;
|
|
49
52
|
export declare function getSyncInResponseMessages(response: proto_BusinessSyncInResponse): ReadonlyArray<proto_Message>;
|
|
50
53
|
export declare function buildProfileSyncOutRequest(syncInState: SyncState, joinBusinessId?: bigint): proto_ProfileSyncOutRequest;
|
|
51
|
-
export declare function
|
|
54
|
+
export declare function raiseProfileCursors(cursors: SyncState, response: proto_ProfileSyncOutResponse): ReadonlySet<number>;
|
|
52
55
|
export declare function buildUser(userProfile: UserProfile): proto_User;
|
|
53
56
|
export declare function buildUpdateUser(userProfile: UserProfile): proto_User;
|
|
54
57
|
export declare function hasJoinedBusinessLink(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
|
|
55
58
|
export declare function hasJoinedBusiness(joinBusinessId: bigint, response: proto_ProfileSyncOutResponse): boolean;
|
|
59
|
+
export declare function getBusinessResponseCountryCode(response: proto_BusinessSyncOutResponse | null): number | undefined;
|
|
60
|
+
export declare function getBusinessResponseBusinessEntityId(response: proto_BusinessSyncOutResponse | null): bigint | null;
|
|
61
|
+
export declare function getBusinessResponseBusinessId(response: proto_BusinessSyncOutResponse | null): string | null;
|
|
62
|
+
export declare function isBusinessResponseOnlineShoppingOn(response: proto_BusinessSyncOutResponse | null): boolean;
|
|
56
63
|
export declare function buildAuthTokens(authTokens: AuthTokens): proto_AuthTokens;
|
|
57
64
|
export declare function authTokensFromProto(authTokens: proto_AuthTokens): AuthTokens;
|
|
58
65
|
export declare function buildAuthTokensBytes(authTokens: proto_AuthTokens): Uint8Array;
|
|
@@ -65,10 +72,10 @@ export declare function nextBusinessSyncOutRequest(request: proto_BusinessSyncOu
|
|
|
65
72
|
export declare function nextProfileSyncOutRequest(request: proto_ProfileSyncOutRequest, response: proto_ProfileSyncOutResponse): proto_ProfileSyncOutRequest | null;
|
|
66
73
|
export declare function buildBusiness(response: proto_BusinessSyncOutResponse): Business | null;
|
|
67
74
|
export declare function buildProfile(profileId: string, response: proto_ProfileSyncOutResponse): Profile | null;
|
|
68
|
-
export declare function buildOrder(message: proto_Message): Order | null;
|
|
69
|
-
export declare function buildShoppingList(message: proto_Message): ShoppingList;
|
|
75
|
+
export declare function buildOrder(message: proto_Message, countryCode: number): Order | null;
|
|
76
|
+
export declare function buildShoppingList(message: proto_Message, countryCode: number): ShoppingList;
|
|
70
77
|
export declare function buildPartyPreferencesMessage(messageId: bigint, existing: proto_Message | null, senderId: bigint, shoppingList: ShoppingList | null, fulfillmentMode: proto_FulfillmentMode | null, now: bigint): proto_Message;
|
|
71
78
|
export declare function buildOrderMessage(orderId: bigint, senderId: bigint, shoppingList: ShoppingList, fulfillmentMode: proto_FulfillmentMode, deliveryAddressId: bigint, deliveryAddress: Address | null, note: string | null, creationDate: bigint): proto_Message;
|
|
72
79
|
export declare function messagesToCacheWrite(messages: ReadonlyArray<proto_Message>): CacheWrite;
|
|
73
|
-
export declare function highestCachedMessageUpdateNumber(blobs: ReadonlyArray<StoredBlob>): bigint;
|
|
74
80
|
export declare function hasShoppingListChanged(stored: proto_Message | null, shoppingList: ShoppingList): boolean;
|
|
81
|
+
export declare function entityETag(updateNumber: bigint, updatedAt: bigint): bigint;
|