@labdigital/commercetools-mock 2.66.0 → 3.0.0-beta.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/README.md +31 -8
- package/dist/abstract-BKFcva6S.mjs +1044 -0
- package/dist/abstract-BKFcva6S.mjs.map +1 -0
- package/dist/config-BcNSzPZz.d.mts +1718 -0
- package/dist/index.d.mts +50 -1633
- package/dist/index.mjs +3769 -2653
- package/dist/index.mjs.map +1 -1
- package/dist/storage/sqlite.d.mts +59 -0
- package/dist/storage/sqlite.mjs +234 -0
- package/dist/storage/sqlite.mjs.map +1 -0
- package/package.json +26 -29
- package/src/ctMock.ts +125 -136
- package/src/helpers.ts +14 -6
- package/src/index.ts +5 -0
- package/src/lib/masking.ts +4 -5
- package/src/lib/product-review-statistics.test.ts +257 -294
- package/src/lib/review-statistics.ts +17 -4
- package/src/oauth/helpers.ts +7 -4
- package/src/oauth/server.test.ts +102 -62
- package/src/oauth/server.ts +215 -213
- package/src/oauth/store.ts +20 -6
- package/src/orderSearch.ts +3 -3
- package/src/product-projection-search.ts +38 -20
- package/src/product-search-availability.test.ts +31 -52
- package/src/product-search.ts +19 -10
- package/src/projectAPI.ts +6 -22
- package/src/repositories/abstract.ts +182 -48
- package/src/repositories/as-associate.test.ts +19 -19
- package/src/repositories/associate-role.ts +12 -23
- package/src/repositories/attribute-group.test.ts +23 -23
- package/src/repositories/attribute-group.ts +6 -4
- package/src/repositories/business-unit.test.ts +63 -57
- package/src/repositories/business-unit.ts +107 -55
- package/src/repositories/cart/actions.ts +96 -65
- package/src/repositories/cart/helpers.ts +15 -11
- package/src/repositories/cart/index.test.ts +136 -30
- package/src/repositories/cart/index.ts +76 -59
- package/src/repositories/cart-discount/actions.ts +12 -44
- package/src/repositories/cart-discount/index.ts +20 -8
- package/src/repositories/category/actions.ts +27 -27
- package/src/repositories/category/index.test.ts +13 -9
- package/src/repositories/category/index.ts +40 -23
- package/src/repositories/channel.test.ts +53 -51
- package/src/repositories/channel.ts +12 -22
- package/src/repositories/custom-object.ts +34 -25
- package/src/repositories/customer/actions.ts +47 -25
- package/src/repositories/customer/index.test.ts +11 -11
- package/src/repositories/customer/index.ts +65 -35
- package/src/repositories/customer-group.test.ts +44 -42
- package/src/repositories/customer-group.ts +12 -22
- package/src/repositories/discount-code/actions.ts +3 -19
- package/src/repositories/discount-code/index.ts +9 -4
- package/src/repositories/discount-group/index.ts +8 -3
- package/src/repositories/extension.test.ts +27 -27
- package/src/repositories/extension.ts +10 -5
- package/src/repositories/helpers.ts +126 -47
- package/src/repositories/inventory-entry/actions.ts +3 -24
- package/src/repositories/inventory-entry/index.ts +19 -11
- package/src/repositories/my-customer.ts +13 -12
- package/src/repositories/my-order.ts +5 -2
- package/src/repositories/order/actions.ts +84 -56
- package/src/repositories/order/index.test.ts +36 -31
- package/src/repositories/order/index.ts +83 -49
- package/src/repositories/order-edit.ts +8 -3
- package/src/repositories/payment/actions.ts +64 -44
- package/src/repositories/payment/helpers.ts +3 -3
- package/src/repositories/payment/index.ts +28 -12
- package/src/repositories/product/actions.ts +133 -98
- package/src/repositories/product/helpers.ts +29 -16
- package/src/repositories/product/index.ts +42 -25
- package/src/repositories/product-discount.ts +6 -4
- package/src/repositories/product-projection.ts +41 -21
- package/src/repositories/product-selection.ts +8 -15
- package/src/repositories/product-tailoring.ts +22 -3
- package/src/repositories/product-type.ts +45 -4
- package/src/repositories/project.ts +57 -13
- package/src/repositories/quote/actions.ts +5 -28
- package/src/repositories/quote/index.ts +29 -6
- package/src/repositories/quote-request/actions.ts +5 -28
- package/src/repositories/quote-request/index.test.ts +3 -3
- package/src/repositories/quote-request/index.ts +31 -11
- package/src/repositories/quote-staged/actions.ts +5 -28
- package/src/repositories/quote-staged/index.ts +22 -8
- package/src/repositories/recurrence-policy/index.ts +6 -4
- package/src/repositories/recurring-order/actions.ts +7 -32
- package/src/repositories/recurring-order/index.ts +8 -6
- package/src/repositories/review.test.ts +147 -142
- package/src/repositories/review.ts +31 -37
- package/src/repositories/shipping-method/actions.ts +11 -28
- package/src/repositories/shipping-method/index.ts +26 -15
- package/src/repositories/shopping-list/actions.ts +21 -31
- package/src/repositories/shopping-list/index.ts +44 -22
- package/src/repositories/standalone-price.ts +6 -4
- package/src/repositories/state.ts +15 -9
- package/src/repositories/store.ts +21 -32
- package/src/repositories/subscription.test.ts +22 -22
- package/src/repositories/subscription.ts +8 -3
- package/src/repositories/tax-category/index.ts +8 -3
- package/src/repositories/type/actions.ts +21 -3
- package/src/repositories/type/index.ts +5 -3
- package/src/repositories/zone.test.ts +112 -77
- package/src/repositories/zone.ts +5 -3
- package/src/schemas/generated/associate-role.ts +13 -0
- package/src/schemas/generated/attribute-group.ts +12 -0
- package/src/schemas/generated/business-unit.ts +38 -0
- package/src/schemas/generated/cart-discount.ts +33 -0
- package/src/schemas/generated/cart.ts +61 -0
- package/src/schemas/generated/category.ts +25 -0
- package/src/schemas/generated/channel.ts +21 -0
- package/src/schemas/generated/common.ts +1372 -0
- package/src/schemas/generated/custom-object.ts +11 -0
- package/src/schemas/generated/customer-group.ts +11 -0
- package/src/schemas/generated/customer.ts +47 -0
- package/src/schemas/generated/discount-code.ts +25 -0
- package/src/schemas/generated/discount-group.ts +13 -0
- package/src/schemas/generated/extension.ts +15 -0
- package/src/schemas/generated/index.ts +42 -0
- package/src/schemas/generated/inventory-entry.ts +20 -0
- package/src/schemas/generated/my-quote-request.ts +10 -0
- package/src/schemas/generated/order-edit.ts +18 -0
- package/src/schemas/generated/order-from-cart.ts +25 -0
- package/src/schemas/generated/payment.ts +30 -0
- package/src/schemas/generated/product-discount.ts +20 -0
- package/src/schemas/generated/product-selection.ts +18 -0
- package/src/schemas/generated/product-tailoring.ts +26 -0
- package/src/schemas/generated/product-type.ts +12 -0
- package/src/schemas/generated/product.ts +37 -0
- package/src/schemas/generated/quote-request.ts +19 -0
- package/src/schemas/generated/quote.ts +18 -0
- package/src/schemas/generated/recurrence-policy.ts +15 -0
- package/src/schemas/generated/recurring-order.ts +19 -0
- package/src/schemas/generated/review.ts +24 -0
- package/src/schemas/generated/shipping-method.ts +24 -0
- package/src/schemas/generated/shopping-list.ts +28 -0
- package/src/schemas/generated/staged-quote.ts +18 -0
- package/src/schemas/generated/standalone-price.ts +32 -0
- package/src/schemas/generated/state.ts +20 -0
- package/src/schemas/generated/store.ts +23 -0
- package/src/schemas/generated/subscription.ts +20 -0
- package/src/schemas/generated/tax-category.ts +12 -0
- package/src/schemas/generated/type.ts +17 -0
- package/src/schemas/generated/zone.ts +12 -0
- package/src/schemas/update-request.ts +3 -5
- package/src/server.ts +32 -4
- package/src/services/abstract.ts +207 -101
- package/src/services/as-associate-cart.test.ts +28 -36
- package/src/services/as-associate-cart.ts +15 -12
- package/src/services/as-associate-order.test.ts +33 -40
- package/src/services/as-associate-order.ts +15 -12
- package/src/services/as-associate-quote-request.ts +15 -12
- package/src/services/as-associate-shopping-list.test.ts +25 -35
- package/src/services/as-associate-shopping-list.ts +15 -12
- package/src/services/as-associate.test.ts +21 -15
- package/src/services/as-associate.ts +23 -22
- package/src/services/associate-roles.test.ts +16 -22
- package/src/services/associate-roles.ts +2 -2
- package/src/services/attribute-group.test.ts +40 -44
- package/src/services/attribute-group.ts +2 -2
- package/src/services/business-units.test.ts +227 -163
- package/src/services/business-units.ts +2 -2
- package/src/services/cart-discount.test.ts +253 -187
- package/src/services/cart-discount.ts +2 -2
- package/src/services/cart.test.ts +833 -832
- package/src/services/cart.ts +31 -12
- package/src/services/category.test.ts +208 -130
- package/src/services/category.ts +2 -2
- package/src/services/channel.test.ts +39 -44
- package/src/services/channel.ts +2 -2
- package/src/services/custom-object.test.ts +103 -79
- package/src/services/custom-object.ts +106 -38
- package/src/services/customer-group.test.ts +39 -44
- package/src/services/customer-group.ts +2 -2
- package/src/services/customer.test.ts +357 -292
- package/src/services/customer.ts +70 -23
- package/src/services/discount-code.test.ts +57 -68
- package/src/services/discount-code.ts +2 -2
- package/src/services/discount-group.test.ts +111 -134
- package/src/services/discount-group.ts +2 -2
- package/src/services/draft-validation.test.ts +255 -0
- package/src/services/extension.test.ts +39 -44
- package/src/services/extension.ts +2 -2
- package/src/services/inventory-entry.test.ts +106 -87
- package/src/services/inventory-entry.ts +2 -2
- package/src/services/my-business-unit.test.ts +82 -112
- package/src/services/my-business-unit.ts +25 -19
- package/src/services/my-cart.test.ts +46 -41
- package/src/services/my-cart.ts +32 -28
- package/src/services/my-customer.test.ts +153 -88
- package/src/services/my-customer.ts +130 -61
- package/src/services/my-order.ts +15 -12
- package/src/services/my-payment.test.ts +30 -24
- package/src/services/my-payment.ts +2 -2
- package/src/services/my-shopping-list.ts +2 -2
- package/src/services/order.test.ts +332 -276
- package/src/services/order.ts +45 -27
- package/src/services/payment.test.ts +31 -29
- package/src/services/payment.ts +2 -2
- package/src/services/product-discount.test.ts +39 -46
- package/src/services/product-discount.ts +2 -2
- package/src/services/product-projection.test.ts +176 -166
- package/src/services/product-projection.ts +31 -15
- package/src/services/product-selection.test.ts +17 -9
- package/src/services/product-selection.ts +2 -2
- package/src/services/product-type.test.ts +80 -21
- package/src/services/product-type.ts +2 -2
- package/src/services/product.test.ts +569 -534
- package/src/services/product.ts +14 -7
- package/src/services/project.test.ts +22 -12
- package/src/services/project.ts +28 -13
- package/src/services/quote-request.test.ts +36 -39
- package/src/services/quote-request.ts +2 -2
- package/src/services/quote-staged.ts +2 -2
- package/src/services/quote.ts +2 -2
- package/src/services/recurrence-policy.test.ts +114 -139
- package/src/services/recurrence-policy.ts +2 -2
- package/src/services/recurring-order.test.ts +149 -194
- package/src/services/recurring-order.ts +2 -2
- package/src/services/reviews.test.ts +127 -106
- package/src/services/reviews.ts +2 -2
- package/src/services/shipping-method.test.ts +96 -125
- package/src/services/shipping-method.ts +24 -12
- package/src/services/shopping-list.test.ts +183 -141
- package/src/services/shopping-list.ts +2 -2
- package/src/services/standalone-price.test.ts +60 -46
- package/src/services/standalone-price.ts +2 -2
- package/src/services/state.test.ts +20 -25
- package/src/services/state.ts +2 -2
- package/src/services/store.test.ts +26 -45
- package/src/services/store.ts +2 -2
- package/src/services/subscription.test.ts +39 -44
- package/src/services/subscription.ts +2 -2
- package/src/services/tax-category.test.ts +33 -36
- package/src/services/tax-category.ts +2 -2
- package/src/services/type.test.ts +45 -44
- package/src/services/type.ts +2 -2
- package/src/services/zone.test.ts +40 -44
- package/src/services/zone.ts +2 -2
- package/src/shipping.ts +41 -11
- package/src/storage/abstract.ts +248 -17
- package/src/storage/in-memory.ts +147 -290
- package/src/storage/sqlite.ts +429 -0
- package/src/storage/storage-map.ts +75 -0
- package/src/storage/storage.test-helpers.ts +97 -0
- package/src/storage/storage.test.ts +802 -0
- package/src/testing/associate-role.ts +28 -0
- package/src/testing/attribute-group.ts +27 -0
- package/src/testing/business-unit.ts +9 -8
- package/src/testing/cart-discount.ts +34 -0
- package/src/testing/cart.ts +20 -0
- package/src/testing/category.ts +25 -0
- package/src/testing/channel.ts +23 -0
- package/src/testing/custom-object.ts +27 -0
- package/src/testing/customer-group.ts +26 -0
- package/src/testing/customer.ts +36 -33
- package/src/testing/discount-code.ts +29 -0
- package/src/testing/discount-group.ts +27 -0
- package/src/testing/extension.ts +32 -0
- package/src/testing/index.ts +33 -0
- package/src/testing/inventory-entry.ts +26 -0
- package/src/testing/order.ts +27 -0
- package/src/testing/payment.ts +23 -0
- package/src/testing/product-discount.ts +33 -0
- package/src/testing/product-selection.ts +28 -0
- package/src/testing/product-type.ts +27 -0
- package/src/testing/product.ts +38 -0
- package/src/testing/quote-request.ts +29 -0
- package/src/testing/recurrence-policy.ts +33 -0
- package/src/testing/recurring-order.ts +32 -0
- package/src/testing/review.ts +24 -0
- package/src/testing/shipping-method.ts +31 -0
- package/src/testing/shopping-list.ts +25 -0
- package/src/testing/standalone-price.ts +31 -0
- package/src/testing/state.ts +21 -0
- package/src/testing/store.ts +26 -0
- package/src/testing/subscription.ts +38 -0
- package/src/testing/tax-category.ts +27 -0
- package/src/testing/type.ts +9 -6
- package/src/testing/zone.ts +22 -0
- package/src/validate.test.ts +122 -0
- package/src/validate.ts +78 -7
- package/src/.env +0 -0
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
import { v4 } from "uuid";
|
|
2
|
+
|
|
3
|
+
//#region src/exceptions.ts
|
|
4
|
+
var CommercetoolsError = class extends Error {
|
|
5
|
+
info;
|
|
6
|
+
statusCode;
|
|
7
|
+
errors;
|
|
8
|
+
constructor(info, statusCode = 400) {
|
|
9
|
+
super(info.message);
|
|
10
|
+
this.info = info;
|
|
11
|
+
this.statusCode = statusCode || 500;
|
|
12
|
+
this.errors = info.errors ?? [];
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/helpers.ts
|
|
18
|
+
const getBaseResourceProperties = (clientId) => {
|
|
19
|
+
const clientInfo = {
|
|
20
|
+
clientId: clientId ?? "",
|
|
21
|
+
isPlatformClient: false
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
id: v4(),
|
|
25
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26
|
+
lastModifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
+
version: 0,
|
|
28
|
+
createdBy: clientInfo,
|
|
29
|
+
lastModifiedBy: clientInfo
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Do a nested lookup by using a path. For example `foo.bar.value` will
|
|
34
|
+
* return obj['foo']['bar']['value']
|
|
35
|
+
*/
|
|
36
|
+
const nestedLookup = (obj, path) => {
|
|
37
|
+
if (!path || path === "") return obj;
|
|
38
|
+
const parts = path.split(".");
|
|
39
|
+
let val = obj;
|
|
40
|
+
for (let i = 0; i < parts.length; i++) {
|
|
41
|
+
const part = parts[i];
|
|
42
|
+
if (val === void 0) return;
|
|
43
|
+
val = val[part];
|
|
44
|
+
}
|
|
45
|
+
return val;
|
|
46
|
+
};
|
|
47
|
+
const queryParamsArray = (input) => {
|
|
48
|
+
if (input === void 0) return;
|
|
49
|
+
const values = Array.isArray(input) ? input : [input];
|
|
50
|
+
if (values.length < 1) return;
|
|
51
|
+
return values;
|
|
52
|
+
};
|
|
53
|
+
const queryParamsValue = (value) => {
|
|
54
|
+
const values = queryParamsArray(value);
|
|
55
|
+
if (values && values.length > 0) return values[0];
|
|
56
|
+
};
|
|
57
|
+
const cloneObject = (o) => JSON.parse(JSON.stringify(o));
|
|
58
|
+
const mapHeaderType = (outgoingHttpHeaders) => {
|
|
59
|
+
const headersInit = {};
|
|
60
|
+
for (const key in outgoingHttpHeaders) {
|
|
61
|
+
const value = outgoingHttpHeaders[key];
|
|
62
|
+
if (Array.isArray(value)) headersInit[key] = value.join(", ");
|
|
63
|
+
else if (value !== void 0) headersInit[key] = value.toString();
|
|
64
|
+
}
|
|
65
|
+
return headersInit;
|
|
66
|
+
};
|
|
67
|
+
const generateRandomString = (length) => {
|
|
68
|
+
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
69
|
+
let result = "";
|
|
70
|
+
for (let i = 0; i < length; i++) {
|
|
71
|
+
const randomIndex = Math.floor(Math.random() * 62);
|
|
72
|
+
result += characters[randomIndex];
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
//#region src/lib/expandParser.ts
|
|
79
|
+
const parseExpandClause = (clause) => {
|
|
80
|
+
const result = {
|
|
81
|
+
element: clause,
|
|
82
|
+
index: void 0,
|
|
83
|
+
rest: void 0
|
|
84
|
+
};
|
|
85
|
+
const pos = clause.indexOf(".");
|
|
86
|
+
if (pos > 0) {
|
|
87
|
+
result.element = clause.substring(0, pos);
|
|
88
|
+
result.rest = clause.substring(pos + 1);
|
|
89
|
+
}
|
|
90
|
+
const match = result.element.match(/\[([^\]+])]/);
|
|
91
|
+
if (match) {
|
|
92
|
+
result.index = match[1] === "*" ? "*" : Number.parseInt(match[1], 10);
|
|
93
|
+
result.element = result.element.substring(0, match.index);
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
//#endregion
|
|
99
|
+
//#region vendor/perplex/lexer-state.ts
|
|
100
|
+
/**
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
var LexerState = class LexerState {
|
|
104
|
+
source;
|
|
105
|
+
position;
|
|
106
|
+
tokenTypes;
|
|
107
|
+
constructor(source, position = 0) {
|
|
108
|
+
this.source = source;
|
|
109
|
+
this.position = position;
|
|
110
|
+
}
|
|
111
|
+
copy() {
|
|
112
|
+
return new LexerState(this.source, this.position);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region vendor/perplex/token.ts
|
|
118
|
+
/**
|
|
119
|
+
* @typedef {{
|
|
120
|
+
* start: Position,
|
|
121
|
+
* end: Position,
|
|
122
|
+
* }} TokenPosition
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* Represents a token instance
|
|
126
|
+
*/
|
|
127
|
+
var Token = class {
|
|
128
|
+
type;
|
|
129
|
+
match;
|
|
130
|
+
groups;
|
|
131
|
+
start;
|
|
132
|
+
end;
|
|
133
|
+
lexer;
|
|
134
|
+
/**
|
|
135
|
+
* Constructs a token
|
|
136
|
+
* @param {T} type The token type
|
|
137
|
+
* @param {string} match The string that the lexer consumed to create this token
|
|
138
|
+
* @param {string[]} groups Any RegExp groups that accrued during the match
|
|
139
|
+
* @param {number} start The string position where this match started
|
|
140
|
+
* @param {number} end The string position where this match ends
|
|
141
|
+
* @param {Lexer<T>} lexer The parent {@link Lexer}
|
|
142
|
+
*/
|
|
143
|
+
constructor(type, match, groups, start, end, lexer) {
|
|
144
|
+
/**
|
|
145
|
+
* The token type
|
|
146
|
+
* @type {T}
|
|
147
|
+
*/
|
|
148
|
+
this.type = type;
|
|
149
|
+
/**
|
|
150
|
+
* The string that the lexer consumed to create this token
|
|
151
|
+
* @type {string}
|
|
152
|
+
*/
|
|
153
|
+
this.match = match;
|
|
154
|
+
/**
|
|
155
|
+
* Any RegExp groups that accrued during the match
|
|
156
|
+
* @type {string[]}
|
|
157
|
+
*/
|
|
158
|
+
this.groups = groups;
|
|
159
|
+
/**
|
|
160
|
+
* The string position where this match started
|
|
161
|
+
* @type {number}
|
|
162
|
+
*/
|
|
163
|
+
this.start = start;
|
|
164
|
+
/**
|
|
165
|
+
* The string position where this match ends
|
|
166
|
+
* @type {number}
|
|
167
|
+
*/
|
|
168
|
+
this.end = end;
|
|
169
|
+
/**
|
|
170
|
+
* The parent {@link Lexer}
|
|
171
|
+
* @type {Lexer<T>}
|
|
172
|
+
*/
|
|
173
|
+
this.lexer = lexer;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Returns the bounds of this token, each in `{line, column}` format
|
|
177
|
+
* @return {TokenPosition}
|
|
178
|
+
*/
|
|
179
|
+
strpos() {
|
|
180
|
+
return {
|
|
181
|
+
start: this.lexer.strpos(this.start),
|
|
182
|
+
end: this.lexer.strpos(this.end)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
isEof() {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
var EOFToken = class extends Token {
|
|
190
|
+
constructor(lexer) {
|
|
191
|
+
const end = lexer.source.length;
|
|
192
|
+
super(null, "(eof)", [], end, end, lexer);
|
|
193
|
+
}
|
|
194
|
+
isEof() {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* @private
|
|
200
|
+
*/
|
|
201
|
+
const EOF = (lexer) => new EOFToken(lexer);
|
|
202
|
+
|
|
203
|
+
//#endregion
|
|
204
|
+
//#region vendor/perplex/token-types.ts
|
|
205
|
+
function toRegExp(str) {
|
|
206
|
+
return new RegExp(str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"));
|
|
207
|
+
}
|
|
208
|
+
function normalize(regex) {
|
|
209
|
+
if (typeof regex === "string") regex = toRegExp(regex);
|
|
210
|
+
if (!regex.source.startsWith("^")) return new RegExp(`^${regex.source}`, regex.flags);
|
|
211
|
+
else return regex;
|
|
212
|
+
}
|
|
213
|
+
function first(arr, predicate) {
|
|
214
|
+
let i = 0;
|
|
215
|
+
for (const item of arr) {
|
|
216
|
+
const result = predicate(item, i++);
|
|
217
|
+
if (result) return {
|
|
218
|
+
item,
|
|
219
|
+
result
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
var TokenTypes = class {
|
|
227
|
+
tokenTypes;
|
|
228
|
+
constructor() {
|
|
229
|
+
this.tokenTypes = [];
|
|
230
|
+
}
|
|
231
|
+
disable(type) {
|
|
232
|
+
return this.enable(type, false);
|
|
233
|
+
}
|
|
234
|
+
enable(type, enabled = true) {
|
|
235
|
+
this.tokenTypes.filter((t) => t.type == type).forEach((t) => t.enabled = enabled);
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
isEnabled(type) {
|
|
239
|
+
const ttypes = this.tokenTypes.filter((tt) => tt.type == type);
|
|
240
|
+
if (ttypes.length == 0) throw new Error(`Token of type ${type} does not exists`);
|
|
241
|
+
return ttypes[0].enabled;
|
|
242
|
+
}
|
|
243
|
+
peek(source, position) {
|
|
244
|
+
const s = source.substr(position);
|
|
245
|
+
return first(this.tokenTypes.filter((tt) => tt.enabled), (tt) => {
|
|
246
|
+
tt.regex.lastIndex = 0;
|
|
247
|
+
return tt.regex.exec(s);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
token(type, pattern, skip = false) {
|
|
251
|
+
this.tokenTypes.push({
|
|
252
|
+
type,
|
|
253
|
+
regex: normalize(pattern),
|
|
254
|
+
enabled: true,
|
|
255
|
+
skip
|
|
256
|
+
});
|
|
257
|
+
return this;
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region vendor/perplex/lexer.ts
|
|
263
|
+
/**
|
|
264
|
+
* @typedef {{
|
|
265
|
+
* line: number,
|
|
266
|
+
* column: number,
|
|
267
|
+
* }} Position
|
|
268
|
+
*/
|
|
269
|
+
/**
|
|
270
|
+
* Lexes a source-string into tokens.
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* const lex = perplex('...')
|
|
274
|
+
* .token('ID', /my-id-regex/)
|
|
275
|
+
* .token('(', /\(/)
|
|
276
|
+
* .token(')', /\)/)
|
|
277
|
+
* .token('WS', /\s+/, true) // true means 'skip'
|
|
278
|
+
*
|
|
279
|
+
* while ((let t = lex.next()).type != 'EOF') {
|
|
280
|
+
* console.log(t)
|
|
281
|
+
* }
|
|
282
|
+
* // alternatively:
|
|
283
|
+
* console.log(lex.toArray())
|
|
284
|
+
* // or:
|
|
285
|
+
* console.log(...lex)
|
|
286
|
+
*/
|
|
287
|
+
var Lexer = class {
|
|
288
|
+
_state;
|
|
289
|
+
_tokenTypes;
|
|
290
|
+
/**
|
|
291
|
+
* Creates a new Lexer instance
|
|
292
|
+
* @param {string} [source = ''] The source string to operate on.
|
|
293
|
+
*/
|
|
294
|
+
constructor(source = "") {
|
|
295
|
+
this._state = new LexerState(source);
|
|
296
|
+
this._tokenTypes = new TokenTypes();
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Gets the current lexer position
|
|
300
|
+
* @return {number} Returns the position
|
|
301
|
+
*/
|
|
302
|
+
get position() {
|
|
303
|
+
return this._state.position;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Sets the current lexer position
|
|
307
|
+
* @param {number} i The position to move to
|
|
308
|
+
*/
|
|
309
|
+
set position(i) {
|
|
310
|
+
this._state.position = i;
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Gets the source the lexer is operating on
|
|
314
|
+
* @return {string} Returns the source
|
|
315
|
+
*/
|
|
316
|
+
get source() {
|
|
317
|
+
return this._state.source;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Sets the source the lexer is operating on
|
|
321
|
+
* @param {string} s The source to set
|
|
322
|
+
*/
|
|
323
|
+
set source(s) {
|
|
324
|
+
this._state = new LexerState(s);
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Attaches this lexer to another lexer's state
|
|
328
|
+
* @param {Lexer<T>} other The other lexer to attach to
|
|
329
|
+
*/
|
|
330
|
+
attachTo(other) {
|
|
331
|
+
this._state = other._state;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Disables a token type
|
|
335
|
+
* @param {T} type The token type to disable
|
|
336
|
+
* @return {Lexer<T>}
|
|
337
|
+
*/
|
|
338
|
+
disable(type) {
|
|
339
|
+
this._tokenTypes.disable(type);
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Enables a token type
|
|
344
|
+
* @param {T} type The token type to enalbe
|
|
345
|
+
* @param {?boolean} [enabled=true] Whether to enable/disable the specified token type
|
|
346
|
+
* @return {Lexer<T>}
|
|
347
|
+
*/
|
|
348
|
+
enable(type, enabled) {
|
|
349
|
+
this._tokenTypes.enable(type, enabled);
|
|
350
|
+
return this;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Like {@link next}, but throws an exception if the next token is
|
|
354
|
+
* not of the required type.
|
|
355
|
+
* @param {T} type The token type expected from {@link next}
|
|
356
|
+
* @return {Token<T>} Returns the {@link Token} on success
|
|
357
|
+
*/
|
|
358
|
+
expect(type) {
|
|
359
|
+
const t = this.next();
|
|
360
|
+
if (t.type != type) {
|
|
361
|
+
const pos = t.strpos();
|
|
362
|
+
throw new Error("Expected " + type + (t ? ", got " + t.type : "") + " at " + pos.start.line + ":" + pos.start.column);
|
|
363
|
+
}
|
|
364
|
+
return t;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Looks up whether a token is enabled.
|
|
368
|
+
* @param tokenType The token type to look up
|
|
369
|
+
* @return {boolean} Returns whether the token is enabled
|
|
370
|
+
*/
|
|
371
|
+
isEnabled(tokenType) {
|
|
372
|
+
return this._tokenTypes.isEnabled(tokenType);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Consumes and returns the next {@link Token} in the source string.
|
|
376
|
+
* If there are no more tokens, it returns a {@link Token} of type `$EOF`
|
|
377
|
+
* @return {Token<T>}
|
|
378
|
+
*/
|
|
379
|
+
next() {
|
|
380
|
+
try {
|
|
381
|
+
const t = this.peek();
|
|
382
|
+
this._state.position = t.end;
|
|
383
|
+
return t;
|
|
384
|
+
} catch (e) {
|
|
385
|
+
this._state.position = e.end;
|
|
386
|
+
throw e;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Returns the next {@link Token} in the source string, but does
|
|
391
|
+
* not consume it.
|
|
392
|
+
* If there are no more tokens, it returns a {@link Token} of type `$EOF`
|
|
393
|
+
* @param {number} [position=`this.position`] The position at which to start reading
|
|
394
|
+
* @return {Token<T>}
|
|
395
|
+
*/
|
|
396
|
+
peek(position = this._state.position) {
|
|
397
|
+
const read = (i = position) => {
|
|
398
|
+
if (i >= this._state.source.length) return EOF(this);
|
|
399
|
+
const n = this._tokenTypes.peek(this._state.source, i);
|
|
400
|
+
if (!n || !n.result) throw new Error(`Unexpected input: ${this._state.source.substring(i, i + 1)} at (${this.strpos(i).line}:${this.strpos(i).column})`);
|
|
401
|
+
return n ? n.item.skip ? read(i + n.result[0].length) : new Token(n.item.type, n.result[0], n.result.map((x) => x), i, i + n.result[0].length, this) : null;
|
|
402
|
+
};
|
|
403
|
+
const t = read();
|
|
404
|
+
if (t) return t;
|
|
405
|
+
let unexpected = this._state.source.substring(position, position + 1);
|
|
406
|
+
try {
|
|
407
|
+
this.peek(position + 1);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
unexpected += e.unexpected;
|
|
410
|
+
}
|
|
411
|
+
const { line, column } = this.strpos(position);
|
|
412
|
+
const e = /* @__PURE__ */ new Error(`Unexpected input: ${unexpected} at (${line}:${column})`);
|
|
413
|
+
e.unexpected = unexpected;
|
|
414
|
+
e.end = position + unexpected.length;
|
|
415
|
+
throw e;
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Converts a string-index (relative to the source string) to a line and a column.
|
|
419
|
+
* @param {number} i The index to compute
|
|
420
|
+
* @return {Position}
|
|
421
|
+
*/
|
|
422
|
+
strpos(i) {
|
|
423
|
+
let lines = this._state.source.substring(0, i).split(/\r?\n/);
|
|
424
|
+
if (!Array.isArray(lines)) lines = [lines];
|
|
425
|
+
return {
|
|
426
|
+
line: lines.length,
|
|
427
|
+
column: lines[lines.length - 1].length + 1
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Converts the token stream to an array of Tokens
|
|
432
|
+
* @return {Token<T>[]} The array of tokens (not including (EOF))
|
|
433
|
+
*/
|
|
434
|
+
toArray() {
|
|
435
|
+
return [...this];
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Implements the Iterable protocol
|
|
439
|
+
* Iterates lazily over the entire token stream (not including (EOF))
|
|
440
|
+
* @return {Iterator<Token<T>>} Returns an iterator over all remaining tokens
|
|
441
|
+
*/
|
|
442
|
+
*[Symbol.iterator]() {
|
|
443
|
+
const oldState = this._state.copy();
|
|
444
|
+
this._state.position = 0;
|
|
445
|
+
let t;
|
|
446
|
+
while (!(t = this.next()).isEof()) yield t;
|
|
447
|
+
this._state = oldState;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Creates a new token type
|
|
451
|
+
* @param {T} type The token type
|
|
452
|
+
* @param {string|RegExp} pattern The pattern to match
|
|
453
|
+
* @param {?boolean} skip Whether this type of token should be skipped
|
|
454
|
+
* @return {Lexer<T>}
|
|
455
|
+
*/
|
|
456
|
+
token(type, pattern, skip) {
|
|
457
|
+
this._tokenTypes.token(type, pattern, skip);
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Creates a keyword
|
|
462
|
+
* @param kwd The keyword to add as a token
|
|
463
|
+
*/
|
|
464
|
+
keyword(kwd) {
|
|
465
|
+
return this.token(kwd, new RegExp(`${kwd}(?=\\W|$)`));
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Creates an operator
|
|
469
|
+
* @param op The operator to add as a token
|
|
470
|
+
*/
|
|
471
|
+
operator(op) {
|
|
472
|
+
const sOp = new String(op).valueOf();
|
|
473
|
+
return this.token(op, sOp);
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region vendor/pratt/index.ts
|
|
479
|
+
const createStop = () => {
|
|
480
|
+
let stopCalled = false;
|
|
481
|
+
return Object.assign((x) => {
|
|
482
|
+
stopCalled = true;
|
|
483
|
+
return x;
|
|
484
|
+
}, { isStopped() {
|
|
485
|
+
return stopCalled;
|
|
486
|
+
} });
|
|
487
|
+
};
|
|
488
|
+
/**
|
|
489
|
+
* A Pratt parser.
|
|
490
|
+
* @example
|
|
491
|
+
* const lex = new perplex.Lexer('1 + -2 * 3^4')
|
|
492
|
+
* .token('NUM', /\d+/)
|
|
493
|
+
* .token('+', /\+/)
|
|
494
|
+
* .token('-', /-/)
|
|
495
|
+
* .token('*', new RegExp('*'))
|
|
496
|
+
* .token('/', /\//)
|
|
497
|
+
* .token('^', /\^/)
|
|
498
|
+
* .token('(', /\(/)
|
|
499
|
+
* .token(')', /\)/)
|
|
500
|
+
* .token('$SKIP_WS', /\s+/)
|
|
501
|
+
*
|
|
502
|
+
* const parser = new Parser(lex)
|
|
503
|
+
* .builder()
|
|
504
|
+
* .nud('NUM', 100, t => parseInt(t.match))
|
|
505
|
+
* .nud('-', 10, (t, bp) => -parser.parse(bp))
|
|
506
|
+
* .nud('(', 10, (t, bp) => {
|
|
507
|
+
* const expr = parser.parse(bp)
|
|
508
|
+
* lex.expect(')')
|
|
509
|
+
* return expr
|
|
510
|
+
* })
|
|
511
|
+
* .bp(')', 0)
|
|
512
|
+
*
|
|
513
|
+
* .led('^', 20, (left, t, bp) => Math.pow(left, parser.parse(20 - 1)))
|
|
514
|
+
* .led('+', 30, (left, t, bp) => left + parser.parse(bp))
|
|
515
|
+
* .led('-', 30, (left, t, bp) => left - parser.parse(bp))
|
|
516
|
+
* .led('*', 40, (left, t, bp) => left * parser.parse(bp))
|
|
517
|
+
* .led('/', 40, (left, t, bp) => left / parser.parse(bp))
|
|
518
|
+
* .build()
|
|
519
|
+
* parser.parse()
|
|
520
|
+
* // => 161
|
|
521
|
+
*/
|
|
522
|
+
var Parser = class {
|
|
523
|
+
lexer;
|
|
524
|
+
_nuds;
|
|
525
|
+
_leds;
|
|
526
|
+
_bps;
|
|
527
|
+
/**
|
|
528
|
+
* Constructs a Parser instance
|
|
529
|
+
* @param {ILexer<T>} lexer The lexer to obtain tokens from
|
|
530
|
+
*/
|
|
531
|
+
constructor(lexer) {
|
|
532
|
+
/**
|
|
533
|
+
* The lexer that this parser is operating on.
|
|
534
|
+
* @type {ILexer<T>}
|
|
535
|
+
*/
|
|
536
|
+
this.lexer = lexer;
|
|
537
|
+
this._nuds = /* @__PURE__ */ new Map();
|
|
538
|
+
this._leds = /* @__PURE__ */ new Map();
|
|
539
|
+
this._bps = /* @__PURE__ */ new Map();
|
|
540
|
+
}
|
|
541
|
+
_type(tokenOrType) {
|
|
542
|
+
return tokenOrType && typeof tokenOrType.isEof == "function" ? tokenOrType.type : tokenOrType;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Create a {@link ParserBuilder}
|
|
546
|
+
* @return {ParserBuilder<T>} Returns the ParserBuilder
|
|
547
|
+
*/
|
|
548
|
+
builder() {
|
|
549
|
+
return new ParserBuilder(this);
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Define binding power for a token-type
|
|
553
|
+
* @param {IToken<T>|T} tokenOrType The token type to define the binding power for
|
|
554
|
+
* @returns {number} The binding power of the specified token type
|
|
555
|
+
*/
|
|
556
|
+
bp(tokenOrType) {
|
|
557
|
+
if (tokenOrType == null) return Number.NEGATIVE_INFINITY;
|
|
558
|
+
if (tokenOrType && typeof tokenOrType.isEof == "function" && tokenOrType.isEof()) return Number.NEGATIVE_INFINITY;
|
|
559
|
+
const type = this._type(tokenOrType);
|
|
560
|
+
const bp = this._bps.has(type) ? this._bps.get(type) : Number.POSITIVE_INFINITY;
|
|
561
|
+
return typeof bp == "function" ? bp() : bp;
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Computes the token's `nud` value and returns it
|
|
565
|
+
* @param {NudInfo<T>} info The info to compute the `nud` from
|
|
566
|
+
* @returns {any} The result of invoking the pertinent `nud` operator
|
|
567
|
+
*/
|
|
568
|
+
nud(info) {
|
|
569
|
+
const fn = this._nuds.get(info.token.type);
|
|
570
|
+
if (!fn) {
|
|
571
|
+
const { start } = info.token.strpos();
|
|
572
|
+
throw new Error(`Unexpected token: ${info.token.match} (at ${start.line}:${start.column})`);
|
|
573
|
+
}
|
|
574
|
+
return fn(info);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Computes a token's `led` value and returns it
|
|
578
|
+
* @param {LedInfo<T>} info The info to compute the `led` value for
|
|
579
|
+
* @returns {any} The result of invoking the pertinent `led` operator
|
|
580
|
+
*/
|
|
581
|
+
led(info) {
|
|
582
|
+
let fn = this._leds.get(info.token.type);
|
|
583
|
+
if (!fn) {
|
|
584
|
+
const { start } = info.token.strpos();
|
|
585
|
+
throw new Error(`Unexpected token: ${info.token.match} (at ${start.line}:${start.column})`);
|
|
586
|
+
}
|
|
587
|
+
return fn(info);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Kicks off the Pratt parser, and returns the result
|
|
591
|
+
* @param {ParseOpts<T>} opts The parse options
|
|
592
|
+
* @returns {any}
|
|
593
|
+
*/
|
|
594
|
+
parse(opts = { terminals: [0] }) {
|
|
595
|
+
const stop = opts.stop = opts.stop || createStop();
|
|
596
|
+
const check = () => {
|
|
597
|
+
if (stop.isStopped()) return false;
|
|
598
|
+
const t = this.lexer.peek();
|
|
599
|
+
const bp = this.bp(t);
|
|
600
|
+
return opts.terminals.reduce((canContinue, rbpOrType) => {
|
|
601
|
+
if (!canContinue) return false;
|
|
602
|
+
if (typeof rbpOrType == "number") return rbpOrType < bp;
|
|
603
|
+
if (typeof rbpOrType == "string") return t.type != rbpOrType;
|
|
604
|
+
}, true);
|
|
605
|
+
};
|
|
606
|
+
const mkinfo = (token) => {
|
|
607
|
+
return {
|
|
608
|
+
token,
|
|
609
|
+
bp: this.bp(token),
|
|
610
|
+
stop,
|
|
611
|
+
ctx: opts.ctx,
|
|
612
|
+
options: opts
|
|
613
|
+
};
|
|
614
|
+
};
|
|
615
|
+
if (!opts.terminals) opts.terminals = [0];
|
|
616
|
+
if (opts.terminals.length == 0) opts.terminals.push(0);
|
|
617
|
+
let left = this.nud(mkinfo(this.lexer.next()));
|
|
618
|
+
while (check()) {
|
|
619
|
+
const operator = this.lexer.next();
|
|
620
|
+
left = this.led(Object.assign(mkinfo(operator), { left }));
|
|
621
|
+
}
|
|
622
|
+
return left;
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
/**
|
|
626
|
+
* Builds `led`/`nud` rules for a {@link Parser}
|
|
627
|
+
*/
|
|
628
|
+
var ParserBuilder = class {
|
|
629
|
+
_parser;
|
|
630
|
+
/**
|
|
631
|
+
* Constructs a ParserBuilder
|
|
632
|
+
* See also: {@link Parser.builder}
|
|
633
|
+
* @param {Parser<T>} parser The parser
|
|
634
|
+
*/
|
|
635
|
+
constructor(parser) {
|
|
636
|
+
this._parser = parser;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Define `nud` for a token type
|
|
640
|
+
* @param {T} tokenType The token type
|
|
641
|
+
* @param {number} bp The binding power
|
|
642
|
+
* @param {NudFunction<T>} fn The function that will parse the token
|
|
643
|
+
* @return {ParserBuilder<T>} Returns this ParserBuilder
|
|
644
|
+
*/
|
|
645
|
+
nud(tokenType, bp, fn) {
|
|
646
|
+
this._parser._nuds.set(tokenType, fn);
|
|
647
|
+
this.bp(tokenType, bp);
|
|
648
|
+
return this;
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Define `led` for a token type
|
|
652
|
+
* @param {T} tokenType The token type
|
|
653
|
+
* @param {number} bp The binding power
|
|
654
|
+
* @param {LedFunction<T>} fn The function that will parse the token
|
|
655
|
+
* @return {ParserBuilder<T>} Returns this ParserBuilder
|
|
656
|
+
*/
|
|
657
|
+
led(tokenType, bp, fn) {
|
|
658
|
+
this._parser._leds.set(tokenType, fn);
|
|
659
|
+
this.bp(tokenType, bp);
|
|
660
|
+
return this;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Define both `led` and `nud` for a token type at once.
|
|
664
|
+
* The supplied `LedFunction` may be called with a null `left`
|
|
665
|
+
* parameter when invoked from a `nud` context.
|
|
666
|
+
* @param {strTng} tokenType The token type
|
|
667
|
+
* @param {number} bp The binding power
|
|
668
|
+
* @param {LedFunction<T>} fn The function that will parse the token
|
|
669
|
+
* @return {ParserBuilder<T>} Returns this ParserBuilder
|
|
670
|
+
*/
|
|
671
|
+
either(tokenType, bp, fn) {
|
|
672
|
+
return this.nud(tokenType, bp, (inf) => fn(Object.assign(inf, { left: null }))).led(tokenType, bp, fn);
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Define the binding power for a token type
|
|
676
|
+
* @param {T} tokenType The token type
|
|
677
|
+
* @param {BP} bp The binding power
|
|
678
|
+
* @return {ParserBuilder<T>} Returns this ParserBuilder
|
|
679
|
+
*/
|
|
680
|
+
bp(tokenType, bp) {
|
|
681
|
+
this._parser._bps.set(tokenType, bp);
|
|
682
|
+
return this;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Returns the parent {@link Parser} instance
|
|
686
|
+
* @returns {Parser<T>}
|
|
687
|
+
*/
|
|
688
|
+
build() {
|
|
689
|
+
return this._parser;
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region src/lib/haversine.ts
|
|
695
|
+
/**
|
|
696
|
+
* Returns the distance between src and dst as meters
|
|
697
|
+
*/
|
|
698
|
+
const haversineDistance = (src, dst) => {
|
|
699
|
+
const RADIUS_OF_EARTH_IN_KM = 6371;
|
|
700
|
+
const toRadian = (deg) => deg * (Math.PI / 180);
|
|
701
|
+
const dLat = toRadian(dst.latitude - src.latitude);
|
|
702
|
+
const dLon = toRadian(dst.longitude - src.longitude);
|
|
703
|
+
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRadian(src.latitude)) * Math.cos(toRadian(dst.latitude)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
704
|
+
return RADIUS_OF_EARTH_IN_KM * (2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))) * 1e3;
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
//#endregion
|
|
708
|
+
//#region src/lib/predicateParser.ts
|
|
709
|
+
/**
|
|
710
|
+
* This module implements the commercetools query predicate filter expression.
|
|
711
|
+
* Support should be 100% complete.
|
|
712
|
+
*
|
|
713
|
+
* See https://docs.commercetools.com/api/predicates/query
|
|
714
|
+
*/
|
|
715
|
+
var PredicateError = class {
|
|
716
|
+
message;
|
|
717
|
+
constructor(message) {
|
|
718
|
+
this.message = message;
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
const parseQueryExpression = (predicate) => {
|
|
722
|
+
if (Array.isArray(predicate)) {
|
|
723
|
+
const callbacks = predicate.map((item) => generateMatchFunc(item));
|
|
724
|
+
return (target, variables) => callbacks.every((callback) => callback(target, variables));
|
|
725
|
+
}
|
|
726
|
+
return generateMatchFunc(predicate);
|
|
727
|
+
};
|
|
728
|
+
const validateSymbol = (val) => {
|
|
729
|
+
if (!val.type) throw new PredicateError("Internal error");
|
|
730
|
+
if (val.type === "identifier") {
|
|
731
|
+
const char = val.value.charAt(0);
|
|
732
|
+
const line = val.pos?.start.line;
|
|
733
|
+
const column = val.pos?.start.column;
|
|
734
|
+
throw new PredicateError(`Invalid input '${char}', expected input parameter or primitive value (line ${line}, column ${column})`);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
const resolveSymbol = (val, vars) => {
|
|
738
|
+
if (val.type === "var") {
|
|
739
|
+
if (!(val.value in (vars ?? {}))) throw new PredicateError(`Missing parameter value for ${val.value}`);
|
|
740
|
+
return vars[val.value];
|
|
741
|
+
}
|
|
742
|
+
return val.value;
|
|
743
|
+
};
|
|
744
|
+
const resolveValue = (obj, val) => {
|
|
745
|
+
if (val.type !== "identifier") throw new PredicateError("Internal error");
|
|
746
|
+
if (val.value === "variants" && obj.masterVariant && obj.variants !== void 0) return [obj.masterVariant, ...obj.variants ?? []];
|
|
747
|
+
if (!(val.value in obj)) {
|
|
748
|
+
if (Array.isArray(obj)) return Object.values(obj).filter((v) => val.value in v).map((v) => v[val.value]);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
return obj[val.value];
|
|
752
|
+
};
|
|
753
|
+
const getLexer = (value) => new Lexer(value).token("AND", /and(?![-_a-z0-9]+)/i).token("OR", /or(?![-_a-z0-9]+)/i).token("NOT", /not(?![-_a-z0-9]+)/i).token("WITHIN", /within(?![-_a-z0-9]+)/i).token("IN", /in(?![-_a-z0-9]+)/i).token("MATCHES_IGNORE_CASE", /matches\s+ignore\s+case(?![-_a-z0-9]+)/i).token("CONTAINS", /contains(?![-_a-z0-9]+)/i).token("ALL", /all(?![-_a-z0-9]+)/i).token("ANY", /any(?![-_a-z0-9]+)/i).token("EMPTY", /empty(?![-_a-z0-9]+)/i).token("IS", /is(?![-_a-z0-9]+)/i).token("DEFINED", /defined(?![-_a-z0-9]+)/i).token("IDENTIFIER", /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/).token("FLOAT", /\d+\.\d+/).token("INT", /\d+/).token("VARIABLE", /:([-_A-Za-z0-9]+)/).token("BOOLEAN", /(true|false)/).token("IDENTIFIER", /[-_A-Za-z0-9]+/).token("STRING", /"((?:\\.|[^"\\])*)"/).token("STRING", /'((?:\\.|[^'\\])*)'/).token("COMMA", ",").token("(", "(").token(")", ")").token(">=", ">=").token("<=", "<=").token(">", ">").token("<", "<").token("!=", "!=").token("=", "=").token("\"", "\"").token("WS", /\s+/, true);
|
|
754
|
+
/**
|
|
755
|
+
* This function converts a query expression in to a callable which returns a
|
|
756
|
+
* boolean to indicate if the given object matches or not.
|
|
757
|
+
*
|
|
758
|
+
* This currently parses the predicate each time it is called, but it should be
|
|
759
|
+
* straight-forward to add a query cache (lru-cache)
|
|
760
|
+
*/
|
|
761
|
+
const generateMatchFunc = (predicate) => {
|
|
762
|
+
const lexer = getLexer(predicate);
|
|
763
|
+
const parser = new Parser(lexer).builder().nud("IDENTIFIER", 100, (t) => ({
|
|
764
|
+
type: "identifier",
|
|
765
|
+
value: t.token.match,
|
|
766
|
+
pos: t.token.strpos()
|
|
767
|
+
})).nud("BOOLEAN", 1, (t) => ({
|
|
768
|
+
type: "boolean",
|
|
769
|
+
value: t.token.match === "true",
|
|
770
|
+
pos: t.token.strpos()
|
|
771
|
+
})).nud("VARIABLE", 100, (t) => ({
|
|
772
|
+
type: "var",
|
|
773
|
+
value: t.token.groups[1],
|
|
774
|
+
pos: t.token.strpos()
|
|
775
|
+
})).nud("STRING", 100, (t) => ({
|
|
776
|
+
type: "string",
|
|
777
|
+
value: t.token.groups[1],
|
|
778
|
+
pos: t.token.strpos()
|
|
779
|
+
})).nud("INT", 1, (t) => ({
|
|
780
|
+
type: "int",
|
|
781
|
+
value: Number.parseInt(t.token.match, 10),
|
|
782
|
+
pos: t.token.strpos()
|
|
783
|
+
})).nud("FLOAT", 1, (t) => ({
|
|
784
|
+
type: "float",
|
|
785
|
+
value: Number.parseFloat(t.token.match),
|
|
786
|
+
pos: t.token.strpos()
|
|
787
|
+
})).nud("NOT", 100, ({ bp }) => {
|
|
788
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
789
|
+
return (obj) => !expr(obj);
|
|
790
|
+
}).nud("EMPTY", 10, ({ bp }) => "empty").nud("DEFINED", 10, ({ bp }) => "defined").led("AND", 5, ({ left, bp }) => {
|
|
791
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
792
|
+
return (obj, vars) => left(obj, vars) && expr(obj, vars);
|
|
793
|
+
}).led("OR", 5, ({ left, token, bp }) => {
|
|
794
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
795
|
+
return (obj, vars) => left(obj, vars) || expr(obj, vars);
|
|
796
|
+
}).led("COMMA", 1, ({ left, token, bp }) => {
|
|
797
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
798
|
+
if (Array.isArray(expr)) return [left, ...expr];
|
|
799
|
+
return [left, expr];
|
|
800
|
+
}).nud("(", 100, (t) => {
|
|
801
|
+
return parser.parse({ terminals: [")"] });
|
|
802
|
+
}).led("(", 100, ({ left, bp }) => {
|
|
803
|
+
const expr = parser.parse();
|
|
804
|
+
lexer.expect(")");
|
|
805
|
+
return (obj, vars) => {
|
|
806
|
+
if (Array.isArray(obj)) return obj.some((item) => {
|
|
807
|
+
const value = resolveValue(item, left);
|
|
808
|
+
if (value) return expr(value, vars);
|
|
809
|
+
return false;
|
|
810
|
+
});
|
|
811
|
+
const value = resolveValue(obj, left);
|
|
812
|
+
if (value) {
|
|
813
|
+
if (Array.isArray(value)) return value.some((item) => expr(item, vars));
|
|
814
|
+
return expr(value, vars);
|
|
815
|
+
}
|
|
816
|
+
return false;
|
|
817
|
+
};
|
|
818
|
+
}).bp(")", 0).led("=", 20, ({ left, bp }) => {
|
|
819
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
820
|
+
validateSymbol(expr);
|
|
821
|
+
return (obj, vars) => {
|
|
822
|
+
if (Array.isArray(obj)) return obj.some((item) => {
|
|
823
|
+
const value = resolveValue(item, left);
|
|
824
|
+
const other = resolveSymbol(expr, vars);
|
|
825
|
+
if (Array.isArray(value)) return !!value.some((elem) => elem === other);
|
|
826
|
+
return value === other;
|
|
827
|
+
});
|
|
828
|
+
const resolvedValue = resolveValue(obj, left);
|
|
829
|
+
const resolvedSymbol = resolveSymbol(expr, vars);
|
|
830
|
+
if (Array.isArray(resolvedValue)) return !!resolvedValue.some((elem) => elem === resolvedSymbol);
|
|
831
|
+
return resolvedValue === resolvedSymbol;
|
|
832
|
+
};
|
|
833
|
+
}).led("!=", 20, ({ left, bp }) => {
|
|
834
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
835
|
+
validateSymbol(expr);
|
|
836
|
+
return (obj, vars) => resolveValue(obj, left) !== resolveSymbol(expr, vars);
|
|
837
|
+
}).led(">", 20, ({ left, bp }) => {
|
|
838
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
839
|
+
validateSymbol(expr);
|
|
840
|
+
return (obj, vars) => resolveValue(obj, left) > resolveSymbol(expr, vars);
|
|
841
|
+
}).led(">=", 20, ({ left, bp }) => {
|
|
842
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
843
|
+
validateSymbol(expr);
|
|
844
|
+
return (obj, vars) => resolveValue(obj, left) >= resolveSymbol(expr, vars);
|
|
845
|
+
}).led("<", 20, ({ left, bp }) => {
|
|
846
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
847
|
+
validateSymbol(expr);
|
|
848
|
+
return (obj, vars) => resolveValue(obj, left) < resolveSymbol(expr, vars);
|
|
849
|
+
}).led("<=", 20, ({ left, bp }) => {
|
|
850
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
851
|
+
validateSymbol(expr);
|
|
852
|
+
return (obj, vars) => resolveValue(obj, left) <= resolveSymbol(expr, vars);
|
|
853
|
+
}).led("IS", 20, ({ left, bp }) => {
|
|
854
|
+
let invert = false;
|
|
855
|
+
if (lexer.peek().type === "NOT") {
|
|
856
|
+
invert = true;
|
|
857
|
+
lexer.next();
|
|
858
|
+
}
|
|
859
|
+
switch (parser.parse({ terminals: [bp - 1] })) {
|
|
860
|
+
case "empty":
|
|
861
|
+
if (!invert) return (obj, vars) => {
|
|
862
|
+
return resolveValue(obj, left).length === 0;
|
|
863
|
+
};
|
|
864
|
+
return (obj, vars) => {
|
|
865
|
+
return resolveValue(obj, left).length !== 0;
|
|
866
|
+
};
|
|
867
|
+
case "defined":
|
|
868
|
+
if (!invert) return (obj, vars) => {
|
|
869
|
+
return resolveValue(obj, left) !== void 0;
|
|
870
|
+
};
|
|
871
|
+
return (obj, vars) => {
|
|
872
|
+
return resolveValue(obj, left) === void 0;
|
|
873
|
+
};
|
|
874
|
+
default: throw new Error("Unexpected");
|
|
875
|
+
}
|
|
876
|
+
}).led("IN", 20, ({ left, bp }) => {
|
|
877
|
+
const firstToken = lexer.peek();
|
|
878
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
879
|
+
if (firstToken.match === "(") lexer.expect(")");
|
|
880
|
+
return (obj, vars) => {
|
|
881
|
+
let symbols = expr;
|
|
882
|
+
if (!Array.isArray(symbols)) symbols = [expr];
|
|
883
|
+
const inValues = symbols.flatMap((item) => resolveSymbol(item, vars));
|
|
884
|
+
const value = resolveValue(obj, left);
|
|
885
|
+
if (Array.isArray(value)) return inValues.some((inValue) => value.includes(inValue));
|
|
886
|
+
return inValues.includes(value);
|
|
887
|
+
};
|
|
888
|
+
}).led("MATCHES_IGNORE_CASE", 20, ({ left, bp }) => {
|
|
889
|
+
const expr = parser.parse({ terminals: [bp - 1] });
|
|
890
|
+
validateSymbol(expr);
|
|
891
|
+
return (obj, vars) => {
|
|
892
|
+
const value = resolveValue(obj, left);
|
|
893
|
+
const other = resolveSymbol(expr, vars);
|
|
894
|
+
if (typeof value !== "string") throw new PredicateError(`The field '${left.value}' does not support this expression.`);
|
|
895
|
+
return value.toLowerCase() === other.toLowerCase();
|
|
896
|
+
};
|
|
897
|
+
}).led("WITHIN", 20, ({ left, bp }) => {
|
|
898
|
+
const type = lexer.next();
|
|
899
|
+
if (type.match !== "circle") throw new PredicateError(`Invalid input '${type.match}', expected circle`);
|
|
900
|
+
lexer.expect("(");
|
|
901
|
+
const expr = parser.parse({ terminals: [")"] });
|
|
902
|
+
lexer.expect(")");
|
|
903
|
+
return (obj, vars) => {
|
|
904
|
+
const value = resolveValue(obj, left);
|
|
905
|
+
if (!value) return false;
|
|
906
|
+
const maxDistance = resolveSymbol(expr[2], vars);
|
|
907
|
+
return haversineDistance({
|
|
908
|
+
longitude: value[0],
|
|
909
|
+
latitude: value[1]
|
|
910
|
+
}, {
|
|
911
|
+
longitude: resolveSymbol(expr[0], vars),
|
|
912
|
+
latitude: resolveSymbol(expr[1], vars)
|
|
913
|
+
}) <= maxDistance;
|
|
914
|
+
};
|
|
915
|
+
}).led("CONTAINS", 20, ({ left, bp }) => {
|
|
916
|
+
const keyword = lexer.next();
|
|
917
|
+
let expr = parser.parse();
|
|
918
|
+
if (!Array.isArray(expr)) expr = [expr];
|
|
919
|
+
return (obj, vars) => {
|
|
920
|
+
const value = resolveValue(obj, left);
|
|
921
|
+
if (!Array.isArray(value)) throw new PredicateError(`The field '${left.value}' does not support this expression.`);
|
|
922
|
+
const array = expr.map((item) => resolveSymbol(item, vars));
|
|
923
|
+
if (keyword.type === "ALL") return array.every((item) => value.includes(item));
|
|
924
|
+
return array.some((item) => value.includes(item));
|
|
925
|
+
};
|
|
926
|
+
}).build();
|
|
927
|
+
const result = parser.parse();
|
|
928
|
+
if (typeof result !== "function") {
|
|
929
|
+
const lines = predicate.split("\n");
|
|
930
|
+
const column = lines[lines.length - 1].length;
|
|
931
|
+
throw new PredicateError(`Unexpected end of input, expected SphereIdentifierChar, comparison operator, not, in, contains, is, within or matches (line ${lines.length}, column ${column})`);
|
|
932
|
+
}
|
|
933
|
+
return result;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
//#endregion
|
|
937
|
+
//#region src/storage/abstract.ts
|
|
938
|
+
var AbstractStorage = class {
|
|
939
|
+
/**
|
|
940
|
+
* Close the storage backend and release any resources.
|
|
941
|
+
* Override this in subclasses that hold external resources (e.g. database connections).
|
|
942
|
+
*/
|
|
943
|
+
close() {}
|
|
944
|
+
async expand(projectKey, obj, clause) {
|
|
945
|
+
if (!clause) return obj;
|
|
946
|
+
if (Array.isArray(clause)) for (const c of clause) await this._resolveResource(projectKey, obj, c);
|
|
947
|
+
else await this._resolveResource(projectKey, obj, clause);
|
|
948
|
+
return obj;
|
|
949
|
+
}
|
|
950
|
+
async getByResourceIdentifier(projectKey, identifier) {
|
|
951
|
+
if (identifier.id) {
|
|
952
|
+
const resource = await this.get(projectKey, identifier.typeId, identifier.id);
|
|
953
|
+
if (resource) return resource;
|
|
954
|
+
throw new CommercetoolsError({
|
|
955
|
+
code: "ReferencedResourceNotFound",
|
|
956
|
+
message: `The referenced object of type '${identifier.typeId}' with id '${identifier.id}' was not found. It either doesn't exist, or it can't be accessed from this endpoint (e.g., if the endpoint filters by store or customer account).`,
|
|
957
|
+
typeId: identifier.typeId,
|
|
958
|
+
id: identifier.id
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
if (identifier.key) {
|
|
962
|
+
const resource = await this.getByKey(projectKey, identifier.typeId, identifier.key);
|
|
963
|
+
if (resource) return resource;
|
|
964
|
+
throw new CommercetoolsError({
|
|
965
|
+
code: "ReferencedResourceNotFound",
|
|
966
|
+
message: `The referenced object of type '${identifier.typeId}' with key '${identifier.key}' was not found. It either doesn't exist, or it can't be accessed from this endpoint (e.g., if the endpoint filters by store or customer account).`,
|
|
967
|
+
typeId: identifier.typeId,
|
|
968
|
+
key: identifier.key
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
throw new CommercetoolsError({
|
|
972
|
+
code: "InvalidJsonInput",
|
|
973
|
+
message: "Request body does not contain valid JSON.",
|
|
974
|
+
detailedErrorMessage: "ResourceIdentifier requires an 'id' xor a 'key'"
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
async search(projectKey, typeId, params) {
|
|
978
|
+
let resources = await this.all(projectKey, typeId);
|
|
979
|
+
if (params.where) try {
|
|
980
|
+
const filterFunc = parseQueryExpression(params.where);
|
|
981
|
+
resources = resources.filter((resource) => filterFunc(resource, {}));
|
|
982
|
+
} catch (err) {
|
|
983
|
+
throw new CommercetoolsError({
|
|
984
|
+
code: "InvalidInput",
|
|
985
|
+
message: err.message
|
|
986
|
+
}, 400);
|
|
987
|
+
}
|
|
988
|
+
const totalResources = resources.length;
|
|
989
|
+
const offset = params.offset || 0;
|
|
990
|
+
const limit = params.limit || 20;
|
|
991
|
+
resources = resources.slice(offset, offset + limit);
|
|
992
|
+
if (params.expand !== void 0) resources = await Promise.all(resources.map((resource) => this.expand(projectKey, resource, params.expand)));
|
|
993
|
+
return {
|
|
994
|
+
count: resources.length,
|
|
995
|
+
total: totalResources,
|
|
996
|
+
offset,
|
|
997
|
+
limit,
|
|
998
|
+
results: resources
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
async _resolveResource(projectKey, obj, expand) {
|
|
1002
|
+
const params = parseExpandClause(expand);
|
|
1003
|
+
if (params.index === "*") {
|
|
1004
|
+
const reference = obj[params.element];
|
|
1005
|
+
if (params.element === "lineItems" && params.rest?.startsWith("variant") && reference.every((item) => item.variant === void 0 && item.variantId !== void 0)) for (const item of reference) await this._resolveShoppingListLineItemVariant(projectKey, item);
|
|
1006
|
+
}
|
|
1007
|
+
if (!params.index) {
|
|
1008
|
+
const reference = obj[params.element];
|
|
1009
|
+
if (reference === void 0) return;
|
|
1010
|
+
await this._resolveReference(projectKey, reference, params.rest);
|
|
1011
|
+
} else if (params.index === "*") {
|
|
1012
|
+
const reference = obj[params.element];
|
|
1013
|
+
if (reference === void 0 || !Array.isArray(reference)) return;
|
|
1014
|
+
for (const itemRef of reference) await this._resolveReference(projectKey, itemRef, params.rest);
|
|
1015
|
+
} else {
|
|
1016
|
+
const reference = obj[params.element][params.index];
|
|
1017
|
+
if (reference === void 0) return;
|
|
1018
|
+
await this._resolveReference(projectKey, reference, params.rest);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
async _resolveReference(projectKey, reference, expand) {
|
|
1022
|
+
if (reference === void 0) return;
|
|
1023
|
+
if (reference.typeId !== void 0 && (reference.id !== void 0 || reference.key !== void 0)) {
|
|
1024
|
+
if (!reference.obj) reference.obj = await this.getByResourceIdentifier(projectKey, {
|
|
1025
|
+
typeId: reference.typeId,
|
|
1026
|
+
id: reference.id,
|
|
1027
|
+
key: reference.key
|
|
1028
|
+
});
|
|
1029
|
+
if (expand) await this._resolveResource(projectKey, reference.obj, expand);
|
|
1030
|
+
} else if (expand) await this._resolveResource(projectKey, reference, expand);
|
|
1031
|
+
}
|
|
1032
|
+
async _resolveShoppingListLineItemVariant(projectKey, lineItem) {
|
|
1033
|
+
const product = await this.getByResourceIdentifier(projectKey, {
|
|
1034
|
+
typeId: "product",
|
|
1035
|
+
id: lineItem.productId
|
|
1036
|
+
});
|
|
1037
|
+
if (!product) return;
|
|
1038
|
+
lineItem.variant = [product.masterData.current.masterVariant, ...product.masterData.current.variants].find((e) => e.id === lineItem.variantId);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
//#endregion
|
|
1043
|
+
export { parseExpandClause as a, getBaseResourceProperties as c, queryParamsArray as d, queryParamsValue as f, Lexer as i, mapHeaderType as l, parseQueryExpression as n, cloneObject as o, CommercetoolsError as p, Parser as r, generateRandomString as s, AbstractStorage as t, nestedLookup as u };
|
|
1044
|
+
//# sourceMappingURL=abstract-BKFcva6S.mjs.map
|