@atomic-ehr/fhirpath 0.0.1-canary.35b105d.20250724165800

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 (57) hide show
  1. package/README.md +307 -0
  2. package/dist/index.d.ts +225 -0
  3. package/dist/index.js +8185 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +51 -0
  6. package/src/analyzer/analyzer.ts +486 -0
  7. package/src/analyzer/model-provider.ts +244 -0
  8. package/src/analyzer/schemas/index.ts +2 -0
  9. package/src/analyzer/schemas/types.ts +40 -0
  10. package/src/analyzer/types.ts +142 -0
  11. package/src/api/builder.ts +148 -0
  12. package/src/api/errors.ts +134 -0
  13. package/src/api/expression.ts +152 -0
  14. package/src/api/index.ts +57 -0
  15. package/src/api/registry.ts +128 -0
  16. package/src/api/types.ts +154 -0
  17. package/src/compiler/compiler.ts +579 -0
  18. package/src/compiler/index.ts +2 -0
  19. package/src/compiler/prototype-context-adapter.ts +99 -0
  20. package/src/compiler/types.ts +23 -0
  21. package/src/index.ts +52 -0
  22. package/src/interpreter/README.md +78 -0
  23. package/src/interpreter/interpreter.ts +485 -0
  24. package/src/interpreter/types.ts +110 -0
  25. package/src/lexer/char-tables.ts +37 -0
  26. package/src/lexer/errors.ts +31 -0
  27. package/src/lexer/index.ts +5 -0
  28. package/src/lexer/lexer.ts +745 -0
  29. package/src/lexer/token.ts +104 -0
  30. package/src/parser/ast.ts +123 -0
  31. package/src/parser/index.ts +3 -0
  32. package/src/parser/parser.ts +701 -0
  33. package/src/parser/pprint.ts +169 -0
  34. package/src/registry/default-analyzers.ts +257 -0
  35. package/src/registry/default-compilers.ts +31 -0
  36. package/src/registry/index.ts +93 -0
  37. package/src/registry/operations/arithmetic.ts +506 -0
  38. package/src/registry/operations/collection.ts +425 -0
  39. package/src/registry/operations/comparison.ts +432 -0
  40. package/src/registry/operations/existence.ts +703 -0
  41. package/src/registry/operations/filtering.ts +358 -0
  42. package/src/registry/operations/literals.ts +341 -0
  43. package/src/registry/operations/logical.ts +402 -0
  44. package/src/registry/operations/math.ts +128 -0
  45. package/src/registry/operations/membership.ts +132 -0
  46. package/src/registry/operations/string.ts +507 -0
  47. package/src/registry/operations/subsetting.ts +174 -0
  48. package/src/registry/operations/type-checking.ts +162 -0
  49. package/src/registry/operations/type-conversion.ts +404 -0
  50. package/src/registry/operations/type-operators.ts +307 -0
  51. package/src/registry/operations/utility.ts +542 -0
  52. package/src/registry/registry.ts +146 -0
  53. package/src/registry/types.ts +161 -0
  54. package/src/registry/utils/evaluation-helpers.ts +93 -0
  55. package/src/registry/utils/index.ts +3 -0
  56. package/src/registry/utils/type-system.ts +173 -0
  57. package/src/runtime/context.ts +179 -0
@@ -0,0 +1,402 @@
1
+ import { TokenType } from '../../lexer/token';
2
+ import type { Operator } from '../types';
3
+ import { defaultOperatorAnalyze } from '../default-analyzers';
4
+ import { toBoolean, toSingleton } from '../utils';
5
+
6
+ export const andOperator: Operator = {
7
+ name: 'and',
8
+ kind: 'operator',
9
+
10
+ syntax: {
11
+ form: 'infix',
12
+ token: TokenType.AND,
13
+ precedence: 3,
14
+ associativity: 'left',
15
+ notation: 'a and b'
16
+ },
17
+
18
+ signature: {
19
+ parameters: [
20
+ { name: 'left', types: { kind: 'any' }, cardinality: 'any' },
21
+ { name: 'right', types: { kind: 'any' }, cardinality: 'any' }
22
+ ],
23
+ output: {
24
+ type: 'Boolean',
25
+ cardinality: 'singleton'
26
+ },
27
+ propagatesEmpty: false // Special three-valued logic
28
+ },
29
+
30
+ analyze: defaultOperatorAnalyze,
31
+
32
+ evaluate: (interpreter, context, input, left, right) => {
33
+ // Three-valued logic for and:
34
+ // true and true = true
35
+ // true and false = false
36
+ // true and empty = empty
37
+ // false and anything = false
38
+ // empty and true = empty
39
+ // empty and false = false
40
+ // empty and empty = empty
41
+
42
+ const leftEmpty = left.length === 0;
43
+ const rightEmpty = right.length === 0;
44
+
45
+ if (!leftEmpty) {
46
+ const leftBool = toBoolean(toSingleton(left));
47
+ if (!leftBool) {
48
+ // false and anything = false
49
+ return { value: [false], context };
50
+ }
51
+ // left is true
52
+ if (rightEmpty) {
53
+ // true and empty = empty
54
+ return { value: [], context };
55
+ }
56
+ // true and right
57
+ const rightBool = toBoolean(toSingleton(right));
58
+ return { value: [rightBool], context };
59
+ }
60
+
61
+ // left is empty
62
+ if (rightEmpty) {
63
+ // empty and empty = empty
64
+ return { value: [], context };
65
+ }
66
+
67
+ const rightBool = toBoolean(toSingleton(right));
68
+ if (!rightBool) {
69
+ // empty and false = false
70
+ return { value: [false], context };
71
+ }
72
+ // empty and true = empty
73
+ return { value: [], context };
74
+ },
75
+
76
+ compile: (compiler, input, args) => ({
77
+ fn: (ctx) => {
78
+ const left = args[0]?.fn(ctx) || [];
79
+ const right = args[1]?.fn(ctx) || [];
80
+
81
+ const leftEmpty = left.length === 0;
82
+ const rightEmpty = right.length === 0;
83
+
84
+ if (!leftEmpty) {
85
+ const leftBool = toBoolean(toSingleton(left));
86
+ if (!leftBool) return [false];
87
+ if (rightEmpty) return [];
88
+ return [toBoolean(toSingleton(right))];
89
+ }
90
+
91
+ if (rightEmpty) return [];
92
+ const rightBool = toBoolean(toSingleton(right));
93
+ if (!rightBool) return [false];
94
+ return [];
95
+ },
96
+ type: compiler.resolveType('Boolean'),
97
+ isSingleton: true,
98
+ source: `${args[0]?.source || ''} and ${args[1]?.source || ''}`
99
+ })
100
+ };
101
+
102
+ export const orOperator: Operator = {
103
+ name: 'or',
104
+ kind: 'operator',
105
+
106
+ syntax: {
107
+ form: 'infix',
108
+ token: TokenType.OR,
109
+ precedence: 2,
110
+ associativity: 'left',
111
+ notation: 'a or b'
112
+ },
113
+
114
+ signature: {
115
+ parameters: [
116
+ { name: 'left', types: { kind: 'any' }, cardinality: 'any' },
117
+ { name: 'right', types: { kind: 'any' }, cardinality: 'any' }
118
+ ],
119
+ output: {
120
+ type: 'Boolean',
121
+ cardinality: 'singleton'
122
+ },
123
+ propagatesEmpty: false // Special three-valued logic
124
+ },
125
+
126
+ analyze: defaultOperatorAnalyze,
127
+
128
+ evaluate: (interpreter, context, input, left, right) => {
129
+ // Three-valued logic for or:
130
+ // true or anything = true
131
+ // false or true = true
132
+ // false or false = false
133
+ // false or empty = empty
134
+ // empty or true = true
135
+ // empty or false = empty
136
+ // empty or empty = empty
137
+
138
+ const leftEmpty = left.length === 0;
139
+ const rightEmpty = right.length === 0;
140
+
141
+ if (!leftEmpty) {
142
+ const leftBool = toBoolean(toSingleton(left));
143
+ if (leftBool) {
144
+ // true or anything = true
145
+ return { value: [true], context };
146
+ }
147
+ // left is false
148
+ if (rightEmpty) {
149
+ // false or empty = empty
150
+ return { value: [], context };
151
+ }
152
+ // false or right
153
+ const rightBool = toBoolean(toSingleton(right));
154
+ return { value: [rightBool], context };
155
+ }
156
+
157
+ // left is empty
158
+ if (rightEmpty) {
159
+ // empty or empty = empty
160
+ return { value: [], context };
161
+ }
162
+
163
+ const rightBool = toBoolean(toSingleton(right));
164
+ if (rightBool) {
165
+ // empty or true = true
166
+ return { value: [true], context };
167
+ }
168
+ // empty or false = empty
169
+ return { value: [], context };
170
+ },
171
+
172
+ compile: (compiler, input, args) => ({
173
+ fn: (ctx) => {
174
+ const left = args[0]?.fn(ctx) || [];
175
+ const right = args[1]?.fn(ctx) || [];
176
+
177
+ const leftEmpty = left.length === 0;
178
+ const rightEmpty = right.length === 0;
179
+
180
+ if (!leftEmpty) {
181
+ const leftBool = toBoolean(toSingleton(left));
182
+ if (leftBool) return [true];
183
+ if (rightEmpty) return [];
184
+ return [toBoolean(toSingleton(right))];
185
+ }
186
+
187
+ if (rightEmpty) return [];
188
+ const rightBool = toBoolean(toSingleton(right));
189
+ if (rightBool) return [true];
190
+ return [];
191
+ },
192
+ type: compiler.resolveType('Boolean'),
193
+ isSingleton: true,
194
+ source: `${args[0]?.source || ''} or ${args[1]?.source || ''}`
195
+ })
196
+ };
197
+
198
+ export const notOperator: Operator = {
199
+ name: 'not',
200
+ kind: 'operator',
201
+
202
+ syntax: {
203
+ form: 'prefix',
204
+ token: TokenType.NOT,
205
+ precedence: 10, // High precedence for unary operators
206
+ notation: 'not a'
207
+ },
208
+
209
+ signature: {
210
+ parameters: [
211
+ { name: 'operand', types: { kind: 'any' }, cardinality: 'any' }
212
+ ],
213
+ output: {
214
+ type: 'Boolean',
215
+ cardinality: 'preserve-input'
216
+ },
217
+ propagatesEmpty: false // not empty = true
218
+ },
219
+
220
+ analyze: defaultOperatorAnalyze,
221
+
222
+ evaluate: (interpreter, context, input, operand) => {
223
+ if (operand.length === 0) {
224
+ // not empty = true
225
+ return { value: [true], context };
226
+ }
227
+
228
+ // Apply not to each element
229
+ const result = operand.map((item: any) => !toBoolean(item));
230
+ return { value: result, context };
231
+ },
232
+
233
+ compile: (compiler, input, args) => ({
234
+ fn: (ctx) => {
235
+ const operand = args[0]?.fn(ctx) || [];
236
+ if (operand.length === 0) return [true];
237
+ return operand.map((item: any) => !toBoolean(item));
238
+ },
239
+ type: compiler.resolveType('Boolean'),
240
+ isSingleton: args[0]?.isSingleton ?? false,
241
+ source: `not ${args[0]?.source || ''}`
242
+ })
243
+ };
244
+
245
+ export const xorOperator: Operator = {
246
+ name: 'xor',
247
+ kind: 'operator',
248
+
249
+ syntax: {
250
+ form: 'infix',
251
+ token: TokenType.XOR,
252
+ precedence: 2,
253
+ associativity: 'left',
254
+ notation: 'a xor b'
255
+ },
256
+
257
+ signature: {
258
+ parameters: [
259
+ { name: 'left', types: { kind: 'any' }, cardinality: 'any' },
260
+ { name: 'right', types: { kind: 'any' }, cardinality: 'any' }
261
+ ],
262
+ output: {
263
+ type: 'Boolean',
264
+ cardinality: 'singleton'
265
+ },
266
+ propagatesEmpty: true
267
+ },
268
+
269
+ analyze: defaultOperatorAnalyze,
270
+
271
+ evaluate: (interpreter, context, input, left, right) => {
272
+ if (left.length === 0 || right.length === 0) return { value: [], context };
273
+
274
+ const leftBool = toBoolean(toSingleton(left));
275
+ const rightBool = toBoolean(toSingleton(right));
276
+
277
+ // XOR: true when exactly one is true
278
+ return { value: [leftBool !== rightBool], context };
279
+ },
280
+
281
+ compile: (compiler, input, args) => ({
282
+ fn: (ctx) => {
283
+ const left = args[0]?.fn(ctx) || [];
284
+ const right = args[1]?.fn(ctx) || [];
285
+
286
+ if (left.length === 0 || right.length === 0) return [];
287
+
288
+ const leftBool = toBoolean(toSingleton(left));
289
+ const rightBool = toBoolean(toSingleton(right));
290
+
291
+ return [leftBool !== rightBool];
292
+ },
293
+ type: compiler.resolveType('Boolean'),
294
+ isSingleton: true,
295
+ source: `${args[0]?.source || ''} xor ${args[1]?.source || ''}`
296
+ })
297
+ };
298
+
299
+ export const impliesOperator: Operator = {
300
+ name: 'implies',
301
+ kind: 'operator',
302
+
303
+ syntax: {
304
+ form: 'infix',
305
+ token: TokenType.IMPLIES,
306
+ precedence: 1, // Lowest precedence
307
+ associativity: 'left',
308
+ notation: 'a implies b'
309
+ },
310
+
311
+ signature: {
312
+ parameters: [
313
+ { name: 'left', types: { kind: 'any' }, cardinality: 'any' },
314
+ { name: 'right', types: { kind: 'any' }, cardinality: 'any' }
315
+ ],
316
+ output: {
317
+ type: 'Boolean',
318
+ cardinality: 'singleton'
319
+ },
320
+ propagatesEmpty: false // Special three-valued logic
321
+ },
322
+
323
+ analyze: defaultOperatorAnalyze,
324
+
325
+ evaluate: (interpreter, context, input, left, right) => {
326
+ // Implies truth table:
327
+ // true implies true = true
328
+ // true implies false = false
329
+ // true implies empty = empty
330
+ // false implies anything = true
331
+ // empty implies true = true
332
+ // empty implies false = empty
333
+ // empty implies empty = empty
334
+
335
+ const leftEmpty = left.length === 0;
336
+ const rightEmpty = right.length === 0;
337
+
338
+ if (!leftEmpty) {
339
+ const leftBool = toBoolean(toSingleton(left));
340
+ if (!leftBool) {
341
+ // false implies anything = true
342
+ return { value: [true], context };
343
+ }
344
+ // left is true
345
+ if (rightEmpty) {
346
+ // true implies empty = empty
347
+ return { value: [], context };
348
+ }
349
+ // true implies right
350
+ const rightBool = toBoolean(toSingleton(right));
351
+ return { value: [rightBool], context };
352
+ }
353
+
354
+ // left is empty
355
+ if (rightEmpty) {
356
+ // empty implies empty = empty
357
+ return { value: [], context };
358
+ }
359
+
360
+ const rightBool = toBoolean(toSingleton(right));
361
+ if (rightBool) {
362
+ // empty implies true = true
363
+ return { value: [true], context };
364
+ }
365
+ // empty implies false = empty
366
+ return { value: [], context };
367
+ },
368
+
369
+ compile: (compiler, input, args) => ({
370
+ fn: (ctx) => {
371
+ const left = args[0]?.fn(ctx) || [];
372
+ const right = args[1]?.fn(ctx) || [];
373
+
374
+ const leftEmpty = left.length === 0;
375
+ const rightEmpty = right.length === 0;
376
+
377
+ if (!leftEmpty) {
378
+ const leftBool = toBoolean(toSingleton(left));
379
+ if (!leftBool) return [true];
380
+ if (rightEmpty) return [];
381
+ return [toBoolean(toSingleton(right))];
382
+ }
383
+
384
+ if (rightEmpty) return [];
385
+ const rightBool = toBoolean(toSingleton(right));
386
+ if (rightBool) return [true];
387
+ return [];
388
+ },
389
+ type: compiler.resolveType('Boolean'),
390
+ isSingleton: true,
391
+ source: `${args[0]?.source || ''} implies ${args[1]?.source || ''}`
392
+ })
393
+ };
394
+
395
+ // Export all logical operators
396
+ export const logicalOperators = [
397
+ andOperator,
398
+ orOperator,
399
+ notOperator,
400
+ xorOperator,
401
+ impliesOperator
402
+ ];
@@ -0,0 +1,128 @@
1
+ import type { Function } from '../types';
2
+ import { defaultFunctionAnalyze } from '../default-analyzers';
3
+ import { defaultFunctionCompile } from '../default-compilers';
4
+ import { CollectionUtils } from '../../interpreter/types';
5
+
6
+ export const absFunction: Function = {
7
+ name: 'abs',
8
+ kind: 'function',
9
+
10
+ syntax: {
11
+ notation: 'abs()'
12
+ },
13
+
14
+ signature: {
15
+ input: {
16
+ types: { kind: 'union', types: ['Integer', 'Decimal', 'Quantity'] },
17
+ cardinality: 'singleton'
18
+ },
19
+ parameters: [],
20
+ output: {
21
+ type: 'preserve-input',
22
+ cardinality: 'singleton'
23
+ },
24
+ propagatesEmpty: true,
25
+ deterministic: true
26
+ },
27
+
28
+ analyze: defaultFunctionAnalyze,
29
+
30
+ evaluate: (interpreter, context, input) => {
31
+ if (input.length === 0) {
32
+ return { value: [], context };
33
+ }
34
+ const num = CollectionUtils.toSingleton(input);
35
+ return { value: [Math.abs(num)], context };
36
+ },
37
+
38
+ compile: defaultFunctionCompile
39
+ };
40
+
41
+ export const roundFunction: Function = {
42
+ name: 'round',
43
+ kind: 'function',
44
+
45
+ syntax: {
46
+ notation: 'round(precision)'
47
+ },
48
+
49
+ signature: {
50
+ input: {
51
+ types: { kind: 'union', types: ['Decimal'] },
52
+ cardinality: 'singleton'
53
+ },
54
+ parameters: [
55
+ {
56
+ name: 'precision',
57
+ kind: 'value',
58
+ types: { kind: 'primitive', types: ['Integer'] },
59
+ cardinality: 'singleton',
60
+ optional: true
61
+ }
62
+ ],
63
+ output: {
64
+ type: 'Decimal',
65
+ cardinality: 'singleton'
66
+ },
67
+ propagatesEmpty: true,
68
+ deterministic: true
69
+ },
70
+
71
+ analyze: defaultFunctionAnalyze,
72
+
73
+ evaluate: (interpreter, context, input, precision) => {
74
+ if (input.length === 0) {
75
+ return { value: [], context };
76
+ }
77
+ const num = CollectionUtils.toSingleton(input);
78
+
79
+ if (precision === undefined) {
80
+ return { value: [Math.round(num)], context };
81
+ }
82
+
83
+ const factor = Math.pow(10, precision);
84
+ return { value: [Math.round(num * factor) / factor], context };
85
+ },
86
+
87
+ compile: defaultFunctionCompile
88
+ };
89
+
90
+ export const sqrtFunction: Function = {
91
+ name: 'sqrt',
92
+ kind: 'function',
93
+
94
+ syntax: {
95
+ notation: 'sqrt()'
96
+ },
97
+
98
+ signature: {
99
+ input: {
100
+ types: { kind: 'union', types: ['Decimal'] },
101
+ cardinality: 'singleton'
102
+ },
103
+ parameters: [],
104
+ output: {
105
+ type: 'Decimal',
106
+ cardinality: 'singleton'
107
+ },
108
+ propagatesEmpty: true,
109
+ deterministic: true
110
+ },
111
+
112
+ analyze: defaultFunctionAnalyze,
113
+
114
+ evaluate: (interpreter, context, input) => {
115
+ if (input.length === 0) {
116
+ return { value: [], context };
117
+ }
118
+ const num = CollectionUtils.toSingleton(input);
119
+
120
+ if (num < 0) {
121
+ return { value: [], context };
122
+ }
123
+
124
+ return { value: [Math.sqrt(num)], context };
125
+ },
126
+
127
+ compile: defaultFunctionCompile
128
+ };
@@ -0,0 +1,132 @@
1
+ import { TokenType } from '../../lexer/token';
2
+ import type { Operator } from '../types';
3
+ import { defaultOperatorAnalyze } from '../default-analyzers';
4
+ import { defaultOperatorCompile } from '../default-compilers';
5
+
6
+ // Helper function to check if value is in collection
7
+ function isIn(value: any, collection: any[]): boolean {
8
+ return collection.some(item => {
9
+ if (value === item) return true;
10
+ if (value == null || item == null) return false;
11
+
12
+ // Handle complex equality
13
+ if (typeof value === 'object' && typeof item === 'object') {
14
+ return JSON.stringify(value) === JSON.stringify(item);
15
+ }
16
+
17
+ return false;
18
+ });
19
+ }
20
+
21
+
22
+ export const inOperator: Operator = {
23
+ name: 'in',
24
+ kind: 'operator',
25
+ syntax: {
26
+ form: 'infix',
27
+ token: TokenType.IN,
28
+ precedence: 10,
29
+ associativity: 'left',
30
+ notation: 'a in b'
31
+ },
32
+ signature: {
33
+ parameters: [{ name: 'left' }, { name: 'right' }],
34
+ propagatesEmpty: false,
35
+ output: {
36
+ type: 'Boolean',
37
+ cardinality: 'singleton'
38
+ }
39
+ },
40
+ analyze: defaultOperatorAnalyze,
41
+ evaluate: (_interpreter, context, _input, element, collection) => {
42
+ if (element.length === 0 || collection.length === 0) return { value: [], context };
43
+
44
+ // Handle string substring check
45
+ if (collection.length === 1 && typeof collection[0] === 'string' && typeof element[0] === 'string') {
46
+ return { value: [collection[0].includes(element[0])], context };
47
+ }
48
+
49
+ return { value: [isIn(element[0], collection)], context };
50
+ },
51
+ compile: (compiler, _input, args) => {
52
+ const [leftExpr, rightExpr] = args;
53
+ if (!leftExpr || !rightExpr) {
54
+ throw new Error('in operator requires two arguments');
55
+ }
56
+ return {
57
+ fn: (ctx) => {
58
+ const left = leftExpr.fn(ctx);
59
+ const right = rightExpr.fn(ctx);
60
+ if (left.length === 0 || right.length === 0) return [];
61
+
62
+ // Handle string substring check
63
+ if (right.length === 1 && typeof right[0] === 'string' && typeof left[0] === 'string') {
64
+ return [right[0].includes(left[0])];
65
+ }
66
+
67
+ return [isIn(left[0], right)];
68
+ },
69
+ type: compiler.resolveType('Boolean'),
70
+ isSingleton: true
71
+ };
72
+ }
73
+ };
74
+
75
+ export const containsOperator: Operator = {
76
+ name: 'contains',
77
+ kind: 'operator',
78
+ syntax: {
79
+ form: 'infix',
80
+ token: TokenType.CONTAINS,
81
+ precedence: 10,
82
+ associativity: 'left',
83
+ notation: 'a contains b'
84
+ },
85
+ signature: {
86
+ parameters: [{ name: 'left' }, { name: 'right' }],
87
+ propagatesEmpty: false,
88
+ output: {
89
+ type: 'Boolean',
90
+ cardinality: 'singleton'
91
+ }
92
+ },
93
+ analyze: defaultOperatorAnalyze,
94
+ evaluate: (_interpreter, context, _input, collection, element) => {
95
+ if (collection.length === 0 || element.length === 0) return { value: [], context };
96
+
97
+ // Handle string substring check
98
+ if (collection.length === 1 && typeof collection[0] === 'string' && typeof element[0] === 'string') {
99
+ return { value: [collection[0].includes(element[0])], context };
100
+ }
101
+
102
+ return { value: [isIn(element[0], collection)], context };
103
+ },
104
+ compile: (compiler, _input, args) => {
105
+ const [leftExpr, rightExpr] = args;
106
+ if (!leftExpr || !rightExpr) {
107
+ throw new Error('contains operator requires two arguments');
108
+ }
109
+ return {
110
+ fn: (ctx) => {
111
+ const left = leftExpr.fn(ctx);
112
+ const right = rightExpr.fn(ctx);
113
+ if (left.length === 0 || right.length === 0) return [];
114
+
115
+ // Handle string substring check
116
+ if (left.length === 1 && typeof left[0] === 'string' && typeof right[0] === 'string') {
117
+ return [left[0].includes(right[0])];
118
+ }
119
+
120
+ return [isIn(right[0], left)];
121
+ },
122
+ type: compiler.resolveType('Boolean'),
123
+ isSingleton: true
124
+ };
125
+ }
126
+ };
127
+
128
+ // Export membership operators
129
+ export const membershipOperators = [
130
+ inOperator,
131
+ containsOperator
132
+ ];