@labdigital/commercetools-mock 1.5.0 → 1.6.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 (129) hide show
  1. package/README.md +5 -4
  2. package/dist/index.cjs +105 -17
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +29 -7
  5. package/dist/index.d.ts +29 -7
  6. package/dist/index.js +105 -17
  7. package/dist/index.js.map +1 -1
  8. package/package.json +4 -3
  9. package/src/constants.ts +2 -2
  10. package/src/ctMock.ts +176 -176
  11. package/src/exceptions.ts +10 -10
  12. package/src/helpers.ts +26 -26
  13. package/src/index.test.ts +173 -173
  14. package/src/index.ts +3 -3
  15. package/src/lib/expandParser.ts +19 -19
  16. package/src/lib/haversine.test.ts +13 -13
  17. package/src/lib/haversine.ts +14 -14
  18. package/src/lib/masking.ts +15 -15
  19. package/src/lib/parser.ts +2 -2
  20. package/src/lib/predicateParser.test.ts +204 -204
  21. package/src/lib/predicateParser.ts +398 -398
  22. package/src/lib/projectionSearchFilter.test.ts +168 -168
  23. package/src/lib/projectionSearchFilter.ts +272 -269
  24. package/src/lib/proxy.ts +8 -8
  25. package/src/oauth/errors.ts +4 -4
  26. package/src/oauth/helpers.ts +6 -6
  27. package/src/oauth/server.ts +103 -101
  28. package/src/oauth/store.ts +27 -27
  29. package/src/priceSelector.test.ts +68 -68
  30. package/src/priceSelector.ts +70 -70
  31. package/src/product-projection-search.ts +296 -296
  32. package/src/projectAPI.test.ts +3 -3
  33. package/src/projectAPI.ts +46 -46
  34. package/src/repositories/abstract.ts +190 -190
  35. package/src/repositories/associate-role.ts +10 -7
  36. package/src/repositories/attribute-group.ts +63 -8
  37. package/src/repositories/business-unit.ts +10 -7
  38. package/src/repositories/cart-discount.ts +134 -134
  39. package/src/repositories/cart.ts +517 -514
  40. package/src/repositories/category.ts +170 -167
  41. package/src/repositories/channel.ts +114 -111
  42. package/src/repositories/custom-object.ts +66 -63
  43. package/src/repositories/customer-group.ts +72 -69
  44. package/src/repositories/customer.ts +90 -90
  45. package/src/repositories/discount-code.ts +171 -168
  46. package/src/repositories/errors.ts +15 -15
  47. package/src/repositories/extension.ts +79 -76
  48. package/src/repositories/helpers.ts +180 -180
  49. package/src/repositories/index.ts +39 -39
  50. package/src/repositories/inventory-entry.ts +98 -95
  51. package/src/repositories/my-order.ts +11 -11
  52. package/src/repositories/order-edit.ts +29 -29
  53. package/src/repositories/order.test.ts +191 -191
  54. package/src/repositories/order.ts +393 -393
  55. package/src/repositories/payment.ts +155 -155
  56. package/src/repositories/product-discount.ts +149 -149
  57. package/src/repositories/product-projection.ts +116 -52
  58. package/src/repositories/product-selection.ts +31 -31
  59. package/src/repositories/product-type.ts +156 -156
  60. package/src/repositories/product.ts +600 -597
  61. package/src/repositories/project.ts +136 -135
  62. package/src/repositories/quote-request.ts +19 -19
  63. package/src/repositories/quote.ts +19 -19
  64. package/src/repositories/review.ts +24 -24
  65. package/src/repositories/shipping-method.ts +217 -217
  66. package/src/repositories/shopping-list.ts +49 -49
  67. package/src/repositories/staged-quote.ts +20 -20
  68. package/src/repositories/standalone-price.ts +72 -61
  69. package/src/repositories/state.ts +84 -84
  70. package/src/repositories/store.ts +114 -114
  71. package/src/repositories/subscription.ts +40 -40
  72. package/src/repositories/tax-category.ts +98 -98
  73. package/src/repositories/type.ts +157 -157
  74. package/src/repositories/zone.ts +71 -71
  75. package/src/server.ts +2 -2
  76. package/src/services/abstract.ts +173 -173
  77. package/src/services/attribute-group.ts +16 -0
  78. package/src/services/cart-discount.ts +8 -8
  79. package/src/services/cart.test.ts +409 -409
  80. package/src/services/cart.ts +50 -50
  81. package/src/services/category.test.ts +25 -25
  82. package/src/services/category.ts +8 -8
  83. package/src/services/channel.ts +8 -8
  84. package/src/services/custom-object.test.ts +184 -184
  85. package/src/services/custom-object.ts +48 -48
  86. package/src/services/customer-group.ts +8 -8
  87. package/src/services/customer.test.ts +151 -151
  88. package/src/services/customer.ts +27 -27
  89. package/src/services/discount-code.ts +8 -8
  90. package/src/services/extension.ts +8 -8
  91. package/src/services/index.ts +52 -44
  92. package/src/services/inventory-entry.test.ts +162 -162
  93. package/src/services/inventory-entry.ts +8 -8
  94. package/src/services/my-cart.test.ts +78 -78
  95. package/src/services/my-cart.ts +28 -28
  96. package/src/services/my-customer.test.ts +44 -44
  97. package/src/services/my-customer.ts +53 -53
  98. package/src/services/my-order.ts +20 -20
  99. package/src/services/my-payment.test.ts +65 -65
  100. package/src/services/my-payment.ts +8 -8
  101. package/src/services/order.test.ts +527 -527
  102. package/src/services/order.ts +31 -31
  103. package/src/services/payment.test.ts +65 -65
  104. package/src/services/payment.ts +8 -8
  105. package/src/services/product-discount.ts +8 -8
  106. package/src/services/product-projection.test.ts +492 -428
  107. package/src/services/product-projection.ts +32 -18
  108. package/src/services/product-type.test.ts +56 -56
  109. package/src/services/product-type.ts +8 -8
  110. package/src/services/product.test.ts +510 -510
  111. package/src/services/product.ts +8 -8
  112. package/src/services/project.ts +34 -34
  113. package/src/services/shipping-method.test.ts +81 -81
  114. package/src/services/shipping-method.ts +12 -12
  115. package/src/services/shopping-list.ts +8 -8
  116. package/src/services/standalone-price.test.ts +256 -256
  117. package/src/services/standalone-price.ts +8 -8
  118. package/src/services/state.test.ts +42 -42
  119. package/src/services/state.ts +8 -8
  120. package/src/services/store.test.ts +57 -57
  121. package/src/services/store.ts +8 -8
  122. package/src/services/subscription.ts +8 -8
  123. package/src/services/tax-category.test.ts +61 -61
  124. package/src/services/tax-category.ts +8 -8
  125. package/src/services/type.ts +8 -8
  126. package/src/services/zone.ts +8 -8
  127. package/src/storage/abstract.ts +58 -58
  128. package/src/storage/in-memory.ts +419 -419
  129. package/src/types.ts +82 -82
@@ -8,136 +8,136 @@ import { haversineDistance } from './haversine.js'
8
8
  import { Lexer, Parser, type ITokenPosition } from './parser.js'
9
9
 
10
10
  export class PredicateError {
11
- message: string
11
+ message: string
12
12
 
13
- constructor(message: string) {
14
- this.message = message
15
- }
13
+ constructor(message: string) {
14
+ this.message = message
15
+ }
16
16
  }
17
17
 
18
18
  type MatchFunc = (target: any, variables: VariableMap) => boolean
19
19
  type VariableMap = {
20
- [key: string]: any
20
+ [key: string]: any
21
21
  }
22
22
 
23
23
  export const matchesPredicate = (
24
- predicate: string | string[] | undefined,
25
- target: any,
26
- variables?: VariableMap
24
+ predicate: string | string[] | undefined,
25
+ target: any,
26
+ variables?: VariableMap
27
27
  ): boolean => {
28
- if (!predicate) {
29
- return true
30
- }
31
-
32
- if (Array.isArray(predicate)) {
33
- return predicate.every((item) => {
34
- const func = generateMatchFunc(item)
35
- return func(target, variables || {})
36
- })
37
- } else {
38
- const func = generateMatchFunc(predicate)
39
- return func(target, variables || {})
40
- }
28
+ if (!predicate) {
29
+ return true
30
+ }
31
+
32
+ if (Array.isArray(predicate)) {
33
+ return predicate.every((item) => {
34
+ const func = generateMatchFunc(item)
35
+ return func(target, variables ?? {})
36
+ })
37
+ } else {
38
+ const func = generateMatchFunc(predicate)
39
+ return func(target, variables ?? {})
40
+ }
41
41
  }
42
42
 
43
43
  export const parseQueryExpression = (
44
- predicate: string | string[]
44
+ predicate: string | string[]
45
45
  ): MatchFunc => {
46
- if (Array.isArray(predicate)) {
47
- const callbacks = predicate.map((item) => generateMatchFunc(item))
48
- return (target: any, variables: VariableMap) =>
49
- callbacks.every((callback) => callback(target, variables))
50
- } else {
51
- return generateMatchFunc(predicate)
52
- }
46
+ if (Array.isArray(predicate)) {
47
+ const callbacks = predicate.map((item) => generateMatchFunc(item))
48
+ return (target: any, variables: VariableMap) =>
49
+ callbacks.every((callback) => callback(target, variables))
50
+ } else {
51
+ return generateMatchFunc(predicate)
52
+ }
53
53
  }
54
54
 
55
55
  type TypeSymbol = {
56
- type: 'var' | 'boolean' | 'string' | 'float' | 'int' | 'identifier'
57
- value: any
58
- pos?: ITokenPosition
56
+ type: 'var' | 'boolean' | 'string' | 'float' | 'int' | 'identifier'
57
+ value: any
58
+ pos?: ITokenPosition
59
59
  }
60
60
 
61
61
  const validateSymbol = (val: TypeSymbol) => {
62
- if (!val.type) {
63
- throw new PredicateError('Internal error')
64
- }
65
-
66
- if (val.type === 'identifier') {
67
- const char = val.value.charAt(0)
68
- const line = val.pos?.start.line
69
- const column = val.pos?.start.column
70
-
71
- throw new PredicateError(
72
- `Invalid input '${char}', expected input parameter or primitive value (line ${line}, column ${column})`
73
- )
74
- }
62
+ if (!val.type) {
63
+ throw new PredicateError('Internal error')
64
+ }
65
+
66
+ if (val.type === 'identifier') {
67
+ const char = val.value.charAt(0)
68
+ const line = val.pos?.start.line
69
+ const column = val.pos?.start.column
70
+
71
+ throw new PredicateError(
72
+ `Invalid input '${char}', expected input parameter or primitive value (line ${line}, column ${column})`
73
+ )
74
+ }
75
75
  }
76
76
 
77
77
  const resolveSymbol = (val: TypeSymbol, vars: VariableMap): any => {
78
- if (val.type === 'var') {
79
- if (!(val.value in vars)) {
80
- throw new PredicateError(`Missing parameter value for ${val.value}`)
81
- }
82
- return vars[val.value]
83
- }
84
-
85
- return val.value
78
+ if (val.type === 'var') {
79
+ if (!(val.value in (vars ?? {}))) {
80
+ throw new PredicateError(`Missing parameter value for ${val.value}`)
81
+ }
82
+ return vars[val.value]
83
+ }
84
+
85
+ return val.value
86
86
  }
87
87
 
88
88
  const resolveValue = (obj: any, val: TypeSymbol): any => {
89
- if (val.type !== 'identifier') {
90
- throw new PredicateError('Internal error')
91
- }
92
-
93
- if (!(val.value in obj)) {
94
- if (Array.isArray(obj)) {
95
- return Object.values(obj)
96
- .filter((v) => val.value in v)
97
- .map((v) => v[val.value])
98
- }
99
- throw new PredicateError(`The field '${val.value}' does not exist.`)
100
- }
101
-
102
- return obj[val.value]
89
+ if (val.type !== 'identifier') {
90
+ throw new PredicateError('Internal error')
91
+ }
92
+
93
+ if (!(val.value in obj)) {
94
+ if (Array.isArray(obj)) {
95
+ return Object.values(obj)
96
+ .filter((v) => val.value in v)
97
+ .map((v) => v[val.value])
98
+ }
99
+ throw new PredicateError(`The field '${val.value}' does not exist.`)
100
+ }
101
+
102
+ return obj[val.value]
103
103
  }
104
104
 
105
105
  const getLexer = (value: string) =>
106
- new Lexer(value)
107
-
108
- .token('AND', /and(?![-_a-z0-9]+)/i)
109
- .token('OR', /or(?![-_a-z0-9]+)/i)
110
- .token('NOT', /not(?![-_a-z0-9]+)/i)
111
-
112
- .token('WITHIN', /within(?![-_a-z0-9]+)/i)
113
- .token('IN', /in(?![-_a-z0-9]+)/i)
114
- .token('MATCHES_IGNORE_CASE', /matches\s+ignore\s+case(?![-_a-z0-9]+)/i)
115
- .token('CONTAINS', /contains(?![-_a-z0-9]+)/i)
116
- .token('ALL', /all(?![-_a-z0-9]+)/i)
117
- .token('ANY', /any(?![-_a-z0-9]+)/i)
118
- .token('EMPTY', /empty(?![-_a-z0-9]+)/i)
119
- .token('IS', /is(?![-_a-z0-9]+)/i)
120
- .token('DEFINED', /defined(?![-_a-z0-9]+)/i)
121
-
122
- .token('FLOAT', /\d+\.\d+/)
123
- .token('INT', /\d+/)
124
- .token('VARIABLE', /:([-_A-Za-z0-9]+)/)
125
- .token('BOOLEAN', /(true|false)/)
126
- .token('IDENTIFIER', /[-_A-Za-z0-9]+/)
127
- .token('STRING', /"((?:\\.|[^"\\])*)"/)
128
- .token('STRING', /'((?:\\.|[^'\\])*)'/)
129
-
130
- .token('COMMA', ',')
131
- .token('(', '(')
132
- .token(')', ')')
133
- .token('>=', '>=')
134
- .token('<=', '<=')
135
- .token('>', '>')
136
- .token('<', '<')
137
- .token('!=', '!=')
138
- .token('=', '=')
139
- .token('"', '"')
140
- .token('WS', /\s+/, true) // skip
106
+ new Lexer(value)
107
+
108
+ .token('AND', /and(?![-_a-z0-9]+)/i)
109
+ .token('OR', /or(?![-_a-z0-9]+)/i)
110
+ .token('NOT', /not(?![-_a-z0-9]+)/i)
111
+
112
+ .token('WITHIN', /within(?![-_a-z0-9]+)/i)
113
+ .token('IN', /in(?![-_a-z0-9]+)/i)
114
+ .token('MATCHES_IGNORE_CASE', /matches\s+ignore\s+case(?![-_a-z0-9]+)/i)
115
+ .token('CONTAINS', /contains(?![-_a-z0-9]+)/i)
116
+ .token('ALL', /all(?![-_a-z0-9]+)/i)
117
+ .token('ANY', /any(?![-_a-z0-9]+)/i)
118
+ .token('EMPTY', /empty(?![-_a-z0-9]+)/i)
119
+ .token('IS', /is(?![-_a-z0-9]+)/i)
120
+ .token('DEFINED', /defined(?![-_a-z0-9]+)/i)
121
+
122
+ .token('FLOAT', /\d+\.\d+/)
123
+ .token('INT', /\d+/)
124
+ .token('VARIABLE', /:([-_A-Za-z0-9]+)/)
125
+ .token('BOOLEAN', /(true|false)/)
126
+ .token('IDENTIFIER', /[-_A-Za-z0-9]+/)
127
+ .token('STRING', /"((?:\\.|[^"\\])*)"/)
128
+ .token('STRING', /'((?:\\.|[^'\\])*)'/)
129
+
130
+ .token('COMMA', ',')
131
+ .token('(', '(')
132
+ .token(')', ')')
133
+ .token('>=', '>=')
134
+ .token('<=', '<=')
135
+ .token('>', '>')
136
+ .token('<', '<')
137
+ .token('!=', '!=')
138
+ .token('=', '=')
139
+ .token('"', '"')
140
+ .token('WS', /\s+/, true) // skip
141
141
 
142
142
  /**
143
143
  * This function converts a query expression in to a callable which returns a
@@ -147,300 +147,300 @@ const getLexer = (value: string) =>
147
147
  * straight-forward to add a query cache (lru-cache)
148
148
  */
149
149
  const generateMatchFunc = (predicate: string): MatchFunc => {
150
- const lexer = getLexer(predicate)
151
- const parser = new Parser(lexer)
152
- .builder()
153
- .nud(
154
- 'IDENTIFIER',
155
- 100,
156
- (t) =>
157
- ({
158
- type: 'identifier',
159
- value: t.token.match,
160
- pos: t.token.strpos(),
161
- } as TypeSymbol)
162
- )
163
- .nud(
164
- 'BOOLEAN',
165
- 1,
166
- (t) =>
167
- ({
168
- type: 'boolean',
169
- value: t.token.match === 'true' ? true : false,
170
- pos: t.token.strpos(),
171
- } as TypeSymbol)
172
- )
173
- .nud(
174
- 'VARIABLE',
175
- 100,
176
- (t) =>
177
- ({
178
- type: 'var',
179
- // @ts-ignore
180
- value: t.token.groups[1],
181
- pos: t.token.strpos(),
182
- } as TypeSymbol)
183
- )
184
- .nud(
185
- 'STRING',
186
- 100,
187
- (t) =>
188
- ({
189
- type: 'string',
190
- // @ts-ignore
191
- value: t.token.groups[1],
192
- pos: t.token.strpos(),
193
- } as TypeSymbol)
194
- )
195
- .nud(
196
- 'INT',
197
- 1,
198
- (t) =>
199
- ({
200
- type: 'int',
201
- value: parseInt(t.token.match, 10),
202
- pos: t.token.strpos(),
203
- } as TypeSymbol)
204
- )
205
- .nud(
206
- 'FLOAT',
207
- 1,
208
- (t) =>
209
- ({
210
- type: 'float',
211
- value: parseFloat(t.token.match),
212
- pos: t.token.strpos(),
213
- } as TypeSymbol)
214
- )
215
- .nud('NOT', 100, ({ bp }) => {
216
- const expr = parser.parse({ terminals: [bp - 1] })
217
- return (obj: any) => !expr(obj)
218
- })
219
- .nud('EMPTY', 10, ({ bp }) => 'empty')
220
- .nud('DEFINED', 10, ({ bp }) => 'defined')
221
-
222
- .led('AND', 5, ({ left, bp }) => {
223
- const expr = parser.parse({ terminals: [bp - 1] })
224
- return (obj: any) => left(obj) && expr(obj)
225
- })
226
- .led('OR', 5, ({ left, token, bp }) => {
227
- const expr = parser.parse({ terminals: [bp - 1] })
228
- return (obj: any, vars: object) => left(obj, vars) || expr(obj, vars)
229
- })
230
- .led('COMMA', 1, ({ left, token, bp }) => {
231
- const expr: any = parser.parse({ terminals: [bp - 1] })
232
- if (Array.isArray(expr)) {
233
- return [left, ...expr]
234
- } else {
235
- return [left, expr]
236
- }
237
- })
238
- .nud('(', 100, (t) => {
239
- const expr: any = parser.parse({ terminals: [')'] })
240
- return expr
241
- })
242
- .led('(', 100, ({ left, bp }) => {
243
- const expr = parser.parse()
244
- lexer.expect(')')
245
- return (obj: any, vars: object) => {
246
- const value = resolveValue(obj, left)
247
- if (value) {
248
- return expr(value)
249
- }
250
- return false
251
- }
252
- })
253
- .bp(')', 0)
254
- .led('=', 20, ({ left, bp }) => {
255
- const expr = parser.parse({ terminals: [bp - 1] })
256
- validateSymbol(expr)
257
-
258
- return (obj: any, vars: VariableMap) => {
259
- const resolvedValue = resolveValue(obj, left)
260
- const resolvedSymbol = resolveSymbol(expr, vars)
261
- if (Array.isArray(resolvedValue)) {
262
- return !!resolvedValue.some((elem) => elem === resolvedSymbol)
263
- }
264
- return resolvedValue === resolvedSymbol
265
- }
266
- })
267
- .led('!=', 20, ({ left, bp }) => {
268
- const expr = parser.parse({ terminals: [bp - 1] })
269
- validateSymbol(expr)
270
- return (obj: any, vars: VariableMap) =>
271
- resolveValue(obj, left) !== resolveSymbol(expr, vars)
272
- })
273
- .led('>', 20, ({ left, bp }) => {
274
- const expr = parser.parse({ terminals: [bp - 1] })
275
- validateSymbol(expr)
276
-
277
- return (obj: any, vars: object) =>
278
- resolveValue(obj, left) > resolveSymbol(expr, vars)
279
- })
280
- .led('>=', 20, ({ left, bp }) => {
281
- const expr = parser.parse({ terminals: [bp - 1] })
282
- validateSymbol(expr)
283
-
284
- return (obj: any, vars: object) =>
285
- resolveValue(obj, left) >= resolveSymbol(expr, vars)
286
- })
287
- .led('<', 20, ({ left, bp }) => {
288
- const expr = parser.parse({ terminals: [bp - 1] })
289
- validateSymbol(expr)
290
-
291
- return (obj: any, vars: object) =>
292
- resolveValue(obj, left) < resolveSymbol(expr, vars)
293
- })
294
- .led('<=', 20, ({ left, bp }) => {
295
- const expr = parser.parse({ terminals: [bp - 1] })
296
- validateSymbol(expr)
297
-
298
- return (obj: any, vars: object) =>
299
- resolveValue(obj, left) <= resolveSymbol(expr, vars)
300
- })
301
- .led('IS', 20, ({ left, bp }) => {
302
- let invert = false
303
-
304
- // Peek if this is a `is not` statement
305
- const next = lexer.peek()
306
- if (next.type === 'NOT') {
307
- invert = true
308
- lexer.next()
309
- }
310
-
311
- const expr: any = parser.parse({ terminals: [bp - 1] })
312
-
313
- switch (expr) {
314
- case 'empty': {
315
- if (!invert) {
316
- return (obj: any, vars: VariableMap) => {
317
- const val = resolveValue(obj, left)
318
- return val.length === 0
319
- }
320
- } else {
321
- return (obj: any, vars: VariableMap) => {
322
- const val = resolveValue(obj, left)
323
- return val.length !== 0
324
- }
325
- }
326
- }
327
- case 'defined': {
328
- if (!invert) {
329
- return (obj: any, vars: VariableMap) => {
330
- const val = resolveValue(obj, left)
331
- return val !== undefined
332
- }
333
- } else {
334
- return (obj: any, vars: VariableMap) => {
335
- const val = resolveValue(obj, left)
336
- return val === undefined
337
- }
338
- }
339
- }
340
- default: {
341
- throw new Error('Unexpected')
342
- }
343
- }
344
- })
345
- .led('IN', 20, ({ left, bp }) => {
346
- const expr = parser.parse({ terminals: [bp - 1] })
347
- return (obj: any, vars: object) => {
348
- let symbols = expr
349
- if (!Array.isArray(symbols)) {
350
- symbols = [expr]
351
- }
352
-
353
- const inValues = symbols.map((item: TypeSymbol) =>
354
- resolveSymbol(item, vars)
355
- )
356
- return inValues.includes(resolveValue(obj, left))
357
- }
358
- })
359
- .led('MATCHES_IGNORE_CASE', 20, ({ left, bp }) => {
360
- const expr = parser.parse({ terminals: [bp - 1] })
361
- validateSymbol(expr)
362
-
363
- return (obj: any, vars: VariableMap) => {
364
- const value = resolveValue(obj, left)
365
- const other = resolveSymbol(expr, vars)
366
-
367
- if (typeof value != 'string') {
368
- throw new PredicateError(
369
- `The field '${left.value}' does not support this expression.`
370
- )
371
- }
372
- return value.toLowerCase() === other.toLowerCase()
373
- }
374
- })
375
- .led('WITHIN', 20, ({ left, bp }) => {
376
- const type = lexer.next()
377
-
378
- if (type.match !== 'circle') {
379
- throw new PredicateError(
380
- `Invalid input '${type.match}', expected circle`
381
- )
382
- }
383
-
384
- lexer.expect('(')
385
- const expr = parser.parse({ terminals: [')'] })
386
-
387
- return (obj: any, vars: object) => {
388
- const value = resolveValue(obj, left)
389
- if (!value) return false
390
-
391
- const maxDistance = resolveSymbol(expr[2], vars)
392
- const distance = haversineDistance(
393
- {
394
- longitude: value[0],
395
- latitude: value[1],
396
- },
397
- {
398
- longitude: resolveSymbol(expr[0], vars),
399
- latitude: resolveSymbol(expr[1], vars),
400
- }
401
- )
402
- return distance <= maxDistance
403
- }
404
- })
405
- .led('CONTAINS', 20, ({ left, bp }) => {
406
- const keyword = lexer.next()
407
-
408
- let expr = parser.parse()
409
- if (!Array.isArray(expr)) {
410
- expr = [expr]
411
- }
412
-
413
- return (obj: any, vars: object) => {
414
- const value = resolveValue(obj, left)
415
-
416
- if (!Array.isArray(value)) {
417
- throw new PredicateError(
418
- `The field '${left.value}' does not support this expression.`
419
- )
420
- }
421
-
422
- const array = expr.map((item: TypeSymbol) => resolveSymbol(item, vars))
423
- if (keyword.type === 'ALL') {
424
- return array.every((item: any) => value.includes(item))
425
- } else {
426
- return array.some((item: any) => value.includes(item))
427
- }
428
- }
429
- })
430
-
431
- .build()
432
-
433
- const result = parser.parse()
434
-
435
- if (typeof result !== 'function') {
436
- const lines = predicate.split('\n')
437
- const column = lines[lines.length - 1].length
438
-
439
- throw new PredicateError(
440
- `Unexpected end of input, expected SphereIdentifierChar, comparison ` +
441
- `operator, not, in, contains, is, within or matches` +
442
- ` (line ${lines.length}, column ${column})`
443
- )
444
- }
445
- return result
150
+ const lexer = getLexer(predicate)
151
+ const parser = new Parser(lexer)
152
+ .builder()
153
+ .nud(
154
+ 'IDENTIFIER',
155
+ 100,
156
+ (t) =>
157
+ ({
158
+ type: 'identifier',
159
+ value: t.token.match,
160
+ pos: t.token.strpos(),
161
+ }) as TypeSymbol
162
+ )
163
+ .nud(
164
+ 'BOOLEAN',
165
+ 1,
166
+ (t) =>
167
+ ({
168
+ type: 'boolean',
169
+ value: t.token.match === 'true' ? true : false,
170
+ pos: t.token.strpos(),
171
+ }) as TypeSymbol
172
+ )
173
+ .nud(
174
+ 'VARIABLE',
175
+ 100,
176
+ (t) =>
177
+ ({
178
+ type: 'var',
179
+ // @ts-ignore
180
+ value: t.token.groups[1],
181
+ pos: t.token.strpos(),
182
+ }) as TypeSymbol
183
+ )
184
+ .nud(
185
+ 'STRING',
186
+ 100,
187
+ (t) =>
188
+ ({
189
+ type: 'string',
190
+ // @ts-ignore
191
+ value: t.token.groups[1],
192
+ pos: t.token.strpos(),
193
+ }) as TypeSymbol
194
+ )
195
+ .nud(
196
+ 'INT',
197
+ 1,
198
+ (t) =>
199
+ ({
200
+ type: 'int',
201
+ value: parseInt(t.token.match, 10),
202
+ pos: t.token.strpos(),
203
+ }) as TypeSymbol
204
+ )
205
+ .nud(
206
+ 'FLOAT',
207
+ 1,
208
+ (t) =>
209
+ ({
210
+ type: 'float',
211
+ value: parseFloat(t.token.match),
212
+ pos: t.token.strpos(),
213
+ }) as TypeSymbol
214
+ )
215
+ .nud('NOT', 100, ({ bp }) => {
216
+ const expr = parser.parse({ terminals: [bp - 1] })
217
+ return (obj: any) => !expr(obj)
218
+ })
219
+ .nud('EMPTY', 10, ({ bp }) => 'empty')
220
+ .nud('DEFINED', 10, ({ bp }) => 'defined')
221
+
222
+ .led('AND', 5, ({ left, bp }) => {
223
+ const expr = parser.parse({ terminals: [bp - 1] })
224
+ return (obj: any) => left(obj) && expr(obj)
225
+ })
226
+ .led('OR', 5, ({ left, token, bp }) => {
227
+ const expr = parser.parse({ terminals: [bp - 1] })
228
+ return (obj: any, vars: object) => left(obj, vars) || expr(obj, vars)
229
+ })
230
+ .led('COMMA', 1, ({ left, token, bp }) => {
231
+ const expr: any = parser.parse({ terminals: [bp - 1] })
232
+ if (Array.isArray(expr)) {
233
+ return [left, ...expr]
234
+ } else {
235
+ return [left, expr]
236
+ }
237
+ })
238
+ .nud('(', 100, (t) => {
239
+ const expr: any = parser.parse({ terminals: [')'] })
240
+ return expr
241
+ })
242
+ .led('(', 100, ({ left, bp }) => {
243
+ const expr = parser.parse()
244
+ lexer.expect(')')
245
+ return (obj: any, vars: object) => {
246
+ const value = resolveValue(obj, left)
247
+ if (value) {
248
+ return expr(value, vars)
249
+ }
250
+ return false
251
+ }
252
+ })
253
+ .bp(')', 0)
254
+ .led('=', 20, ({ left, bp }) => {
255
+ const expr = parser.parse({ terminals: [bp - 1] })
256
+ validateSymbol(expr)
257
+
258
+ return (obj: any, vars: VariableMap) => {
259
+ const resolvedValue = resolveValue(obj, left)
260
+ const resolvedSymbol = resolveSymbol(expr, vars)
261
+ if (Array.isArray(resolvedValue)) {
262
+ return !!resolvedValue.some((elem) => elem === resolvedSymbol)
263
+ }
264
+ return resolvedValue === resolvedSymbol
265
+ }
266
+ })
267
+ .led('!=', 20, ({ left, bp }) => {
268
+ const expr = parser.parse({ terminals: [bp - 1] })
269
+ validateSymbol(expr)
270
+ return (obj: any, vars: VariableMap) =>
271
+ resolveValue(obj, left) !== resolveSymbol(expr, vars)
272
+ })
273
+ .led('>', 20, ({ left, bp }) => {
274
+ const expr = parser.parse({ terminals: [bp - 1] })
275
+ validateSymbol(expr)
276
+
277
+ return (obj: any, vars: object) =>
278
+ resolveValue(obj, left) > resolveSymbol(expr, vars)
279
+ })
280
+ .led('>=', 20, ({ left, bp }) => {
281
+ const expr = parser.parse({ terminals: [bp - 1] })
282
+ validateSymbol(expr)
283
+
284
+ return (obj: any, vars: object) =>
285
+ resolveValue(obj, left) >= resolveSymbol(expr, vars)
286
+ })
287
+ .led('<', 20, ({ left, bp }) => {
288
+ const expr = parser.parse({ terminals: [bp - 1] })
289
+ validateSymbol(expr)
290
+
291
+ return (obj: any, vars: object) =>
292
+ resolveValue(obj, left) < resolveSymbol(expr, vars)
293
+ })
294
+ .led('<=', 20, ({ left, bp }) => {
295
+ const expr = parser.parse({ terminals: [bp - 1] })
296
+ validateSymbol(expr)
297
+
298
+ return (obj: any, vars: object) =>
299
+ resolveValue(obj, left) <= resolveSymbol(expr, vars)
300
+ })
301
+ .led('IS', 20, ({ left, bp }) => {
302
+ let invert = false
303
+
304
+ // Peek if this is a `is not` statement
305
+ const next = lexer.peek()
306
+ if (next.type === 'NOT') {
307
+ invert = true
308
+ lexer.next()
309
+ }
310
+
311
+ const expr: any = parser.parse({ terminals: [bp - 1] })
312
+
313
+ switch (expr) {
314
+ case 'empty': {
315
+ if (!invert) {
316
+ return (obj: any, vars: VariableMap) => {
317
+ const val = resolveValue(obj, left)
318
+ return val.length === 0
319
+ }
320
+ } else {
321
+ return (obj: any, vars: VariableMap) => {
322
+ const val = resolveValue(obj, left)
323
+ return val.length !== 0
324
+ }
325
+ }
326
+ }
327
+ case 'defined': {
328
+ if (!invert) {
329
+ return (obj: any, vars: VariableMap) => {
330
+ const val = resolveValue(obj, left)
331
+ return val !== undefined
332
+ }
333
+ } else {
334
+ return (obj: any, vars: VariableMap) => {
335
+ const val = resolveValue(obj, left)
336
+ return val === undefined
337
+ }
338
+ }
339
+ }
340
+ default: {
341
+ throw new Error('Unexpected')
342
+ }
343
+ }
344
+ })
345
+ .led('IN', 20, ({ left, bp }) => {
346
+ const expr = parser.parse({ terminals: [bp - 1] })
347
+ return (obj: any, vars: object) => {
348
+ let symbols = expr
349
+ if (!Array.isArray(symbols)) {
350
+ symbols = [expr]
351
+ }
352
+
353
+ const inValues = symbols.map((item: TypeSymbol) =>
354
+ resolveSymbol(item, vars)
355
+ )
356
+ return inValues.includes(resolveValue(obj, left))
357
+ }
358
+ })
359
+ .led('MATCHES_IGNORE_CASE', 20, ({ left, bp }) => {
360
+ const expr = parser.parse({ terminals: [bp - 1] })
361
+ validateSymbol(expr)
362
+
363
+ return (obj: any, vars: VariableMap) => {
364
+ const value = resolveValue(obj, left)
365
+ const other = resolveSymbol(expr, vars)
366
+
367
+ if (typeof value != 'string') {
368
+ throw new PredicateError(
369
+ `The field '${left.value}' does not support this expression.`
370
+ )
371
+ }
372
+ return value.toLowerCase() === other.toLowerCase()
373
+ }
374
+ })
375
+ .led('WITHIN', 20, ({ left, bp }) => {
376
+ const type = lexer.next()
377
+
378
+ if (type.match !== 'circle') {
379
+ throw new PredicateError(
380
+ `Invalid input '${type.match}', expected circle`
381
+ )
382
+ }
383
+
384
+ lexer.expect('(')
385
+ const expr = parser.parse({ terminals: [')'] })
386
+
387
+ return (obj: any, vars: object) => {
388
+ const value = resolveValue(obj, left)
389
+ if (!value) return false
390
+
391
+ const maxDistance = resolveSymbol(expr[2], vars)
392
+ const distance = haversineDistance(
393
+ {
394
+ longitude: value[0],
395
+ latitude: value[1],
396
+ },
397
+ {
398
+ longitude: resolveSymbol(expr[0], vars),
399
+ latitude: resolveSymbol(expr[1], vars),
400
+ }
401
+ )
402
+ return distance <= maxDistance
403
+ }
404
+ })
405
+ .led('CONTAINS', 20, ({ left, bp }) => {
406
+ const keyword = lexer.next()
407
+
408
+ let expr = parser.parse()
409
+ if (!Array.isArray(expr)) {
410
+ expr = [expr]
411
+ }
412
+
413
+ return (obj: any, vars: object) => {
414
+ const value = resolveValue(obj, left)
415
+
416
+ if (!Array.isArray(value)) {
417
+ throw new PredicateError(
418
+ `The field '${left.value}' does not support this expression.`
419
+ )
420
+ }
421
+
422
+ const array = expr.map((item: TypeSymbol) => resolveSymbol(item, vars))
423
+ if (keyword.type === 'ALL') {
424
+ return array.every((item: any) => value.includes(item))
425
+ } else {
426
+ return array.some((item: any) => value.includes(item))
427
+ }
428
+ }
429
+ })
430
+
431
+ .build()
432
+
433
+ const result = parser.parse()
434
+
435
+ if (typeof result !== 'function') {
436
+ const lines = predicate.split('\n')
437
+ const column = lines[lines.length - 1].length
438
+
439
+ throw new PredicateError(
440
+ `Unexpected end of input, expected SphereIdentifierChar, comparison ` +
441
+ `operator, not, in, contains, is, within or matches` +
442
+ ` (line ${lines.length}, column ${column})`
443
+ )
444
+ }
445
+ return result
446
446
  }