@onchaindb/sdk 0.4.5 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.claude/settings.local.json +10 -2
  2. package/README.md +422 -355
  3. package/dist/batch.d.ts +1 -10
  4. package/dist/batch.d.ts.map +1 -1
  5. package/dist/batch.js +4 -26
  6. package/dist/batch.js.map +1 -1
  7. package/dist/client.d.ts +29 -43
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +198 -323
  10. package/dist/client.js.map +1 -1
  11. package/dist/database.d.ts +14 -131
  12. package/dist/database.d.ts.map +1 -1
  13. package/dist/database.js +35 -131
  14. package/dist/database.js.map +1 -1
  15. package/dist/index.d.ts +6 -9
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1 -15
  18. package/dist/index.js.map +1 -1
  19. package/dist/query-sdk/ConditionBuilder.d.ts +3 -11
  20. package/dist/query-sdk/ConditionBuilder.d.ts.map +1 -1
  21. package/dist/query-sdk/ConditionBuilder.js +10 -48
  22. package/dist/query-sdk/ConditionBuilder.js.map +1 -1
  23. package/dist/query-sdk/NestedBuilders.d.ts +33 -30
  24. package/dist/query-sdk/NestedBuilders.d.ts.map +1 -1
  25. package/dist/query-sdk/NestedBuilders.js +46 -43
  26. package/dist/query-sdk/NestedBuilders.js.map +1 -1
  27. package/dist/query-sdk/QueryBuilder.d.ts +4 -2
  28. package/dist/query-sdk/QueryBuilder.d.ts.map +1 -1
  29. package/dist/query-sdk/QueryBuilder.js +47 -169
  30. package/dist/query-sdk/QueryBuilder.js.map +1 -1
  31. package/dist/query-sdk/QueryResult.d.ts +0 -38
  32. package/dist/query-sdk/QueryResult.d.ts.map +1 -1
  33. package/dist/query-sdk/QueryResult.js +1 -227
  34. package/dist/query-sdk/QueryResult.js.map +1 -1
  35. package/dist/query-sdk/index.d.ts +1 -1
  36. package/dist/query-sdk/index.d.ts.map +1 -1
  37. package/dist/query-sdk/index.js.map +1 -1
  38. package/dist/query-sdk/operators.d.ts +32 -28
  39. package/dist/query-sdk/operators.d.ts.map +1 -1
  40. package/dist/query-sdk/operators.js +45 -155
  41. package/dist/query-sdk/operators.js.map +1 -1
  42. package/dist/types.d.ts +153 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js.map +1 -1
  45. package/jest.config.js +4 -0
  46. package/package.json +1 -1
  47. package/skills.md +0 -1
  48. package/src/client.ts +242 -745
  49. package/src/database.ts +70 -493
  50. package/src/index.ts +40 -193
  51. package/src/query-sdk/ConditionBuilder.ts +37 -89
  52. package/src/query-sdk/NestedBuilders.ts +90 -92
  53. package/src/query-sdk/QueryBuilder.ts +59 -218
  54. package/src/query-sdk/QueryResult.ts +4 -330
  55. package/src/query-sdk/README.md +214 -583
  56. package/src/query-sdk/index.ts +1 -1
  57. package/src/query-sdk/operators.ts +91 -200
  58. package/src/query-sdk/tests/FieldConditionBuilder.test.ts +70 -71
  59. package/src/query-sdk/tests/LogicalOperator.test.ts +43 -82
  60. package/src/query-sdk/tests/NestedBuilders.test.ts +229 -309
  61. package/src/query-sdk/tests/QueryBuilder.test.ts +5 -5
  62. package/src/query-sdk/tests/QueryResult.test.ts +41 -435
  63. package/src/query-sdk/tests/comprehensive.test.ts +4 -185
  64. package/src/tests/client-requests.test.ts +280 -0
  65. package/src/tests/client-validation.test.ts +80 -0
  66. package/src/types.ts +229 -8
  67. package/src/batch.ts +0 -257
  68. package/src/query-sdk/dist/ConditionBuilder.d.ts +0 -22
  69. package/src/query-sdk/dist/ConditionBuilder.js +0 -90
  70. package/src/query-sdk/dist/FieldConditionBuilder.d.ts +0 -1
  71. package/src/query-sdk/dist/FieldConditionBuilder.js +0 -6
  72. package/src/query-sdk/dist/NestedBuilders.d.ts +0 -43
  73. package/src/query-sdk/dist/NestedBuilders.js +0 -144
  74. package/src/query-sdk/dist/OnChainDB.d.ts +0 -19
  75. package/src/query-sdk/dist/OnChainDB.js +0 -123
  76. package/src/query-sdk/dist/QueryBuilder.d.ts +0 -70
  77. package/src/query-sdk/dist/QueryBuilder.js +0 -295
  78. package/src/query-sdk/dist/QueryResult.d.ts +0 -52
  79. package/src/query-sdk/dist/QueryResult.js +0 -293
  80. package/src/query-sdk/dist/SelectionBuilder.d.ts +0 -20
  81. package/src/query-sdk/dist/SelectionBuilder.js +0 -80
  82. package/src/query-sdk/dist/adapters/HttpClientAdapter.d.ts +0 -27
  83. package/src/query-sdk/dist/adapters/HttpClientAdapter.js +0 -170
  84. package/src/query-sdk/dist/index.d.ts +0 -36
  85. package/src/query-sdk/dist/index.js +0 -27
  86. package/src/query-sdk/dist/operators.d.ts +0 -56
  87. package/src/query-sdk/dist/operators.js +0 -289
  88. package/src/query-sdk/dist/tests/setup.d.ts +0 -15
  89. package/src/query-sdk/dist/tests/setup.js +0 -46
  90. package/src/query-sdk/jest.config.js +0 -25
  91. package/src/query-sdk/package.json +0 -46
  92. package/src/query-sdk/tests/aggregations.test.ts +0 -653
  93. package/src/query-sdk/tests/integration.test.ts +0 -608
  94. package/src/query-sdk/tests/operators.test.ts +0 -327
  95. package/src/query-sdk/tests/unit.test.ts +0 -794
  96. package/src/query-sdk/tsconfig.json +0 -26
  97. package/src/query-sdk/yarn.lock +0 -3092
@@ -47,7 +47,7 @@ export {SelectionBuilder} from './SelectionBuilder';
47
47
  export {ConditionBuilder} from './ConditionBuilder';
48
48
  export {FieldConditionBuilder, LogicalOperator, Condition} from './operators';
49
49
  export {NestedConditionBuilder, NestedFieldConditionBuilder} from './NestedBuilders';
50
- export {QueryResult, NumericSummary, createQueryResult} from './QueryResult';
50
+ export {QueryResult, createQueryResult} from './QueryResult';
51
51
 
52
52
  // HTTP Client adapters
53
53
  export {
@@ -1,6 +1,6 @@
1
1
  import { BetweenValue, DateRange, Val } from './index';
2
2
 
3
- // Condition represents a single field condition
3
+ // Condition represents a single field condition (internal)
4
4
  export interface Condition {
5
5
  field: string;
6
6
  operator: string;
@@ -18,22 +18,10 @@ export function createNestedQuery(fieldPath: string, operator: string, value: Va
18
18
  }
19
19
 
20
20
  const pathParts = fieldPath.split('.');
21
- let result: any = {
22
- [operator]: value
23
- };
21
+ let result: any = { [operator]: value };
24
22
 
25
- // Build nested structure from inside out
26
23
  for (let i = pathParts.length - 1; i >= 0; i--) {
27
- const part = pathParts[i];
28
- if (i === pathParts.length - 1) {
29
- result = {
30
- [part]: result
31
- };
32
- } else {
33
- result = {
34
- [part]: result
35
- };
36
- }
24
+ result = { [pathParts[i]]: result };
37
25
  }
38
26
 
39
27
  return result;
@@ -52,8 +40,7 @@ export class LogicalOperator {
52
40
  public type: LogicalOperatorType,
53
41
  public conditions: LogicalOperator[] = [],
54
42
  public condition?: Condition
55
- ) {
56
- }
43
+ ) {}
57
44
 
58
45
  static And(conditions: LogicalOperator[]): LogicalOperator {
59
46
  return new LogicalOperator(LogicalOperatorType.AND, conditions);
@@ -71,24 +58,32 @@ export class LogicalOperator {
71
58
  return new LogicalOperator(LogicalOperatorType.CONDITION, [], condition);
72
59
  }
73
60
 
61
+ // Chain: wrap this operator inside a new AND group with additional conditions
62
+ and(...conditions: LogicalOperator[]): LogicalOperator {
63
+ return LogicalOperator.And([this, ...conditions]);
64
+ }
65
+
66
+ // Chain: wrap this operator inside a new OR group with additional conditions
67
+ or(...conditions: LogicalOperator[]): LogicalOperator {
68
+ return LogicalOperator.Or([this, ...conditions]);
69
+ }
70
+
71
+ // Chain: wrap this operator inside a new NOT group with additional conditions
72
+ not(...conditions: LogicalOperator[]): LogicalOperator {
73
+ return LogicalOperator.Not([this, ...conditions]);
74
+ }
75
+
74
76
  // Convert to composable query structure for HTTP requests
75
77
  toComposable(): any {
76
78
  switch (this.type) {
77
79
  case LogicalOperatorType.AND:
78
- return {
79
- [LogicalOperatorType.AND]: this.conditions.map(c => c.toComposable())
80
- };
80
+ return { [LogicalOperatorType.AND]: this.conditions.map(c => c.toComposable()) };
81
81
  case LogicalOperatorType.OR:
82
- return {
83
- [LogicalOperatorType.OR]: this.conditions.map(c => c.toComposable())
84
- };
82
+ return { [LogicalOperatorType.OR]: this.conditions.map(c => c.toComposable()) };
85
83
  case LogicalOperatorType.NOT:
86
- return {
87
- [LogicalOperatorType.NOT]: this.conditions.map(c => c.toComposable())
88
- };
84
+ return { [LogicalOperatorType.NOT]: this.conditions.map(c => c.toComposable()) };
89
85
  case LogicalOperatorType.CONDITION:
90
86
  if (!this.condition) throw new Error('Condition is required for CONDITION type');
91
- // Use dot notation helper to create nested structures
92
87
  return createNestedQuery(this.condition.field, this.condition.operator, this.condition.value);
93
88
  default:
94
89
  throw new Error(`Unknown logical operator type: ${this.type}`);
@@ -96,240 +91,136 @@ export class LogicalOperator {
96
91
  }
97
92
  }
98
93
 
99
- // Field condition builder for creating individual field conditions
100
- // Only includes operators that actually exist in the Rust implementation
94
+ // Field condition builder all methods return LogicalOperator directly.
95
+ // No need to wrap with LogicalOperator.Condition() at call sites.
101
96
  export class FieldConditionBuilder {
102
- constructor(private fieldName: string) {
97
+ constructor(private fieldName: string) {}
98
+
99
+ private cond(operator: string, value: Val | BetweenValue | DateRange): LogicalOperator {
100
+ return LogicalOperator.Condition({ field: this.fieldName, operator, value });
103
101
  }
104
102
 
105
- // ===== BASE OPERATORS (BaseOperator) =====
103
+ // ===== BASE OPERATORS =====
106
104
 
107
- equals(value: Val): Condition {
108
- return {
109
- field: this.fieldName,
110
- operator: 'is',
111
- value
112
- };
105
+ equals(value: Val): LogicalOperator {
106
+ return this.cond('is', value);
113
107
  }
114
108
 
115
- notEquals(value: Val): Condition {
116
- return {
117
- field: this.fieldName,
118
- operator: 'isNot',
119
- value
120
- };
109
+ notEquals(value: Val): LogicalOperator {
110
+ return this.cond('isNot', value);
121
111
  }
122
112
 
123
- in(values: Val[]): Condition {
124
- return {
125
- field: this.fieldName,
126
- operator: 'in',
127
- value: values as any
128
- };
113
+ in(values: Val[]): LogicalOperator {
114
+ return this.cond('in', values as any);
129
115
  }
130
116
 
131
- notIn(values: Val[]): Condition {
132
- return {
133
- field: this.fieldName,
134
- operator: 'notIn',
135
- value: values as any
136
- };
117
+ notIn(values: Val[]): LogicalOperator {
118
+ return this.cond('notIn', values as any);
137
119
  }
138
120
 
139
- isNull(): Condition {
140
- return {
141
- field: this.fieldName,
142
- operator: 'isNull',
143
- value: true
144
- };
121
+ isNull(): LogicalOperator {
122
+ return this.cond('isNull', true);
145
123
  }
146
124
 
147
- isNotNull(): Condition {
148
- return {
149
- field: this.fieldName,
150
- operator: 'isNull',
151
- value: false
152
- };
125
+ isNotNull(): LogicalOperator {
126
+ return this.cond('isNull', false);
153
127
  }
154
128
 
155
- exists(): Condition {
156
- return {
157
- field: this.fieldName,
158
- operator: 'exists',
159
- value: true
160
- };
129
+ exists(): LogicalOperator {
130
+ return this.cond('exists', true);
161
131
  }
162
132
 
163
- notExists(): Condition {
164
- return {
165
- field: this.fieldName,
166
- operator: 'exists',
167
- value: false
168
- };
133
+ notExists(): LogicalOperator {
134
+ return this.cond('exists', false);
169
135
  }
170
136
 
171
- // ===== STRING OPERATORS (StringOperator) =====
137
+ // ===== STRING OPERATORS =====
172
138
 
173
- startsWith(value: string): Condition {
174
- return {
175
- field: this.fieldName,
176
- operator: 'startsWith',
177
- value
178
- };
139
+ startsWith(value: string): LogicalOperator {
140
+ return this.cond('startsWith', value);
179
141
  }
180
142
 
181
- endsWith(value: string): Condition {
182
- return {
183
- field: this.fieldName,
184
- operator: 'endsWith',
185
- value
186
- };
143
+ endsWith(value: string): LogicalOperator {
144
+ return this.cond('endsWith', value);
187
145
  }
188
146
 
189
- contains(value: string): Condition {
190
- return {
191
- field: this.fieldName,
192
- operator: 'includes',
193
- value
194
- };
147
+ contains(value: string): LogicalOperator {
148
+ return this.cond('includes', value);
195
149
  }
196
150
 
197
- regExpMatches(pattern: string): Condition {
198
- return {
199
- field: this.fieldName,
200
- operator: 'regExpMatches',
201
- value: pattern
202
- };
151
+ regExpMatches(pattern: string): LogicalOperator {
152
+ return this.cond('regExpMatches', pattern);
203
153
  }
204
154
 
205
- includesCaseInsensitive(value: string): Condition {
206
- return {
207
- field: this.fieldName,
208
- operator: 'includesCaseInsensitive',
209
- value
210
- };
155
+ includesCaseInsensitive(value: string): LogicalOperator {
156
+ return this.cond('includesCaseInsensitive', value);
211
157
  }
212
158
 
213
- startsWithCaseInsensitive(value: string): Condition {
214
- return {
215
- field: this.fieldName,
216
- operator: 'startsWithCaseInsensitive',
217
- value
218
- };
159
+ startsWithCaseInsensitive(value: string): LogicalOperator {
160
+ return this.cond('startsWithCaseInsensitive', value);
219
161
  }
220
162
 
221
- endsWithCaseInsensitive(value: string): Condition {
222
- return {
223
- field: this.fieldName,
224
- operator: 'endsWithCaseInsensitive',
225
- value
226
- };
163
+ endsWithCaseInsensitive(value: string): LogicalOperator {
164
+ return this.cond('endsWithCaseInsensitive', value);
227
165
  }
228
166
 
229
- // ===== NUMBER OPERATORS (NumberOperator) =====
167
+ // ===== NUMBER OPERATORS =====
230
168
 
231
- greaterThan(value: Val): Condition {
232
- return {
233
- field: this.fieldName,
234
- operator: 'greaterThan',
235
- value
236
- };
169
+ greaterThan(value: Val): LogicalOperator {
170
+ return this.cond('greaterThan', value);
237
171
  }
238
172
 
239
- lessThan(value: Val): Condition {
240
- return {
241
- field: this.fieldName,
242
- operator: 'lessThan',
243
- value
244
- };
173
+ lessThan(value: Val): LogicalOperator {
174
+ return this.cond('lessThan', value);
245
175
  }
246
176
 
247
- greaterThanOrEqual(value: Val): Condition {
248
- return {
249
- field: this.fieldName,
250
- operator: 'greaterThanOrEqual',
251
- value
252
- };
177
+ greaterThanOrEqual(value: Val): LogicalOperator {
178
+ return this.cond('greaterThanOrEqual', value);
253
179
  }
254
180
 
255
- lessThanOrEqual(value: Val): Condition {
256
- return {
257
- field: this.fieldName,
258
- operator: 'lessThanOrEqual',
259
- value
260
- };
181
+ lessThanOrEqual(value: Val): LogicalOperator {
182
+ return this.cond('lessThanOrEqual', value);
261
183
  }
262
184
 
263
- // ===== IP OPERATORS (IpOperator) =====
264
-
265
- isLocalIp(): Condition {
266
- return {
267
- field: this.fieldName,
268
- operator: 'isLocalIp',
269
- value: true
270
- };
185
+ between(min: Val, max: Val): LogicalOperator {
186
+ return this.cond('betweenOp', { from: min, to: max } as any);
271
187
  }
272
188
 
273
- isExternalIp(): Condition {
274
- return {
275
- field: this.fieldName,
276
- operator: 'isExternalIp',
277
- value: true
278
- };
279
- }
189
+ // ===== IP OPERATORS =====
280
190
 
281
- // ===== MISC OPERATORS (MiscOperator) =====
282
-
283
- b64(value: string): Condition {
284
- return {
285
- field: this.fieldName,
286
- operator: 'b64',
287
- value
288
- };
191
+ isLocalIp(): LogicalOperator {
192
+ return this.cond('isLocalIp', true);
289
193
  }
290
194
 
291
- inDataset(dataset: string): Condition {
292
- return {
293
- field: this.fieldName,
294
- operator: 'inDataset',
295
- value: dataset
296
- };
195
+ isExternalIp(): LogicalOperator {
196
+ return this.cond('isExternalIp', true);
297
197
  }
298
198
 
299
- inCountry(countryCode: string): Condition {
300
- return {
301
- field: this.fieldName,
302
- operator: 'inCountry',
303
- value: countryCode
304
- };
199
+ // ===== MISC OPERATORS =====
200
+
201
+ b64(value: string): LogicalOperator {
202
+ return this.cond('b64', value);
305
203
  }
306
204
 
307
- cidr(cidr: string): Condition {
308
- return {
309
- field: this.fieldName,
310
- operator: 'CIDR',
311
- value: cidr
312
- };
205
+ inDataset(dataset: string): LogicalOperator {
206
+ return this.cond('inDataset', dataset);
313
207
  }
314
208
 
315
- // ===== BETWEEN OPERATOR =====
209
+ inCountry(countryCode: string): LogicalOperator {
210
+ return this.cond('inCountry', countryCode);
211
+ }
316
212
 
317
- between(min: Val, max: Val): Condition {
318
- return {
319
- field: this.fieldName,
320
- operator: 'betweenOp',
321
- value: { from: min, to: max } as any
322
- };
213
+ cidr(cidr: string): LogicalOperator {
214
+ return this.cond('CIDR', cidr);
323
215
  }
324
216
 
325
- // ===== CONVENIENCE METHODS =====
217
+ // ===== CONVENIENCE =====
326
218
 
327
- // Boolean checks (using base operators)
328
- isTrue(): Condition {
219
+ isTrue(): LogicalOperator {
329
220
  return this.equals(true);
330
221
  }
331
222
 
332
- isFalse(): Condition {
223
+ isFalse(): LogicalOperator {
333
224
  return this.equals(false);
334
225
  }
335
- }
226
+ }
@@ -1,84 +1,83 @@
1
1
  import { FieldConditionBuilder, LogicalOperator } from '../index';
2
2
 
3
3
  describe('FieldConditionBuilder', () => {
4
- test('should use correct operators for basic methods', () => {
5
- const builder = new FieldConditionBuilder('name');
4
+ let builder: FieldConditionBuilder;
6
5
 
7
- expect(builder.equals('John').operator).toBe('is');
8
- expect(builder.contains('test').operator).toBe('includes');
9
- });
10
-
11
- test('should support dot notation in field paths', () => {
12
- const condition = new FieldConditionBuilder('user.profile.bio').equals('Developer');
13
- const logicalOp = LogicalOperator.Condition(condition);
14
- const composable = logicalOp.toComposable();
15
-
16
- const expected = {
17
- user: {
18
- profile: {
19
- bio: {
20
- is: 'Developer'
21
- }
22
- }
23
- }
24
- };
25
-
26
- expect(composable).toEqual(expected);
27
- });
28
-
29
- test('should have all required operators available', () => {
30
- const builder = new FieldConditionBuilder('test_field');
31
-
32
- // Only test operators that actually exist in the Rust implementation
33
- const operators = [
34
- 'equals', 'notEquals', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual',
35
- 'contains', 'startsWith', 'endsWith', 'in', 'notIn', 'exists', 'notExists',
36
- 'isNull', 'isNotNull', 'regExpMatches', 'includesCaseInsensitive',
37
- 'startsWithCaseInsensitive', 'endsWithCaseInsensitive', 'between',
38
- 'isLocalIp', 'isExternalIp', 'b64', 'inDataset', 'inCountry', 'cidr',
39
- 'isTrue', 'isFalse'
40
- ];
41
-
42
- operators.forEach(op => {
43
- expect(typeof (builder as any)[op]).toBe('function');
6
+ beforeEach(() => {
7
+ builder = new FieldConditionBuilder('field');
44
8
  });
45
- });
46
9
 
47
- test('should support advanced operators (string, IP, misc)', () => {
48
- const stringBuilder = new FieldConditionBuilder('text');
49
- const regexCondition = stringBuilder.regExpMatches('^test.*');
50
- expect(regexCondition.operator).toBe('regExpMatches');
10
+ describe('Comparison operators', () => {
11
+ test('equals', () => expect(builder.equals('v').toComposable()).toEqual({ field: { is: 'v' } }));
12
+ test('notEquals', () => expect(builder.notEquals('v').toComposable()).toEqual({ field: { isNot: 'v' } }));
13
+ test('greaterThan', () => expect(builder.greaterThan(10).toComposable()).toEqual({ field: { greaterThan: 10 } }));
14
+ test('lessThan', () => expect(builder.lessThan(10).toComposable()).toEqual({ field: { lessThan: 10 } }));
15
+ test('greaterThanOrEqual', () => expect(builder.greaterThanOrEqual(10).toComposable()).toEqual({ field: { greaterThanOrEqual: 10 } }));
16
+ test('lessThanOrEqual', () => expect(builder.lessThanOrEqual(10).toComposable()).toEqual({ field: { lessThanOrEqual: 10 } }));
17
+ test('between', () => expect(builder.between(10, 20).toComposable()).toEqual({ field: { betweenOp: { from: 10, to: 20 } } }));
18
+ });
51
19
 
52
- const caseInsensitiveCondition = stringBuilder.includesCaseInsensitive('Test');
53
- expect(caseInsensitiveCondition.operator).toBe('includesCaseInsensitive');
20
+ describe('String operators', () => {
21
+ test('contains', () => expect(builder.contains('sub').toComposable()).toEqual({ field: { includes: 'sub' } }));
22
+ test('startsWith', () => expect(builder.startsWith('pre').toComposable()).toEqual({ field: { startsWith: 'pre' } }));
23
+ test('endsWith', () => expect(builder.endsWith('suf').toComposable()).toEqual({ field: { endsWith: 'suf' } }));
24
+ test('regExpMatches', () => expect(builder.regExpMatches('.*').toComposable()).toEqual({ field: { regExpMatches: '.*' } }));
25
+ test('includesCaseInsensitive', () => expect(builder.includesCaseInsensitive('T').toComposable()).toEqual({ field: { includesCaseInsensitive: 'T' } }));
26
+ test('startsWithCaseInsensitive', () => expect(builder.startsWithCaseInsensitive('P').toComposable()).toEqual({ field: { startsWithCaseInsensitive: 'P' } }));
27
+ test('endsWithCaseInsensitive', () => expect(builder.endsWithCaseInsensitive('S').toComposable()).toEqual({ field: { endsWithCaseInsensitive: 'S' } }));
28
+ });
54
29
 
55
- const ipBuilder = new FieldConditionBuilder('ip_address');
56
- const ipLocalCondition = ipBuilder.isLocalIp();
57
- expect(ipLocalCondition.operator).toBe('isLocalIp');
58
-
59
- const ipExternalCondition = ipBuilder.isExternalIp();
60
- expect(ipExternalCondition.operator).toBe('isExternalIp');
30
+ describe('Array operators', () => {
31
+ test('in', () => expect(builder.in([1, 2, 3]).toComposable()).toEqual({ field: { in: [1, 2, 3] } }));
32
+ test('notIn', () => expect(builder.notIn(['a', 'b']).toComposable()).toEqual({ field: { notIn: ['a', 'b'] } }));
33
+ });
61
34
 
62
- const b64Condition = ipBuilder.b64('dGVzdA==');
63
- expect(b64Condition.operator).toBe('b64');
64
-
65
- const countryCondition = ipBuilder.inCountry('US');
66
- expect(countryCondition.operator).toBe('inCountry');
35
+ describe('Existence operators', () => {
36
+ test('exists', () => expect(builder.exists().toComposable()).toEqual({ field: { exists: true } }));
37
+ test('notExists', () => expect(builder.notExists().toComposable()).toEqual({ field: { exists: false } }));
38
+ test('isNull', () => expect(builder.isNull().toComposable()).toEqual({ field: { isNull: true } }));
39
+ test('isNotNull', () => expect(builder.isNotNull().toComposable()).toEqual({ field: { isNull: false } }));
40
+ });
67
41
 
68
- const cidrCondition = ipBuilder.cidr('192.168.1.0/24');
69
- expect(cidrCondition.operator).toBe('CIDR');
42
+ describe('Boolean convenience', () => {
43
+ test('isTrue', () => expect(builder.isTrue().toComposable()).toEqual({ field: { is: true } }));
44
+ test('isFalse', () => expect(builder.isFalse().toComposable()).toEqual({ field: { is: false } }));
45
+ });
70
46
 
71
- const datasetCondition = ipBuilder.inDataset('malware_ips');
72
- expect(datasetCondition.operator).toBe('inDataset');
47
+ describe('Network / security operators', () => {
48
+ test('isLocalIp', () => expect(builder.isLocalIp().toComposable()).toEqual({ field: { isLocalIp: true } }));
49
+ test('isExternalIp', () => expect(builder.isExternalIp().toComposable()).toEqual({ field: { isExternalIp: true } }));
50
+ test('b64', () => expect(builder.b64('dGVzdA==').toComposable()).toEqual({ field: { b64: 'dGVzdA==' } }));
51
+ test('inDataset', () => expect(builder.inDataset('malware_ips').toComposable()).toEqual({ field: { inDataset: 'malware_ips' } }));
52
+ test('inCountry', () => expect(builder.inCountry('US').toComposable()).toEqual({ field: { inCountry: 'US' } }));
53
+ test('cidr', () => expect(builder.cidr('192.168.1.0/24').toComposable()).toEqual({ field: { CIDR: '192.168.1.0/24' } }));
54
+ });
73
55
 
74
- const betweenCondition = stringBuilder.between(5, 15);
75
- expect(betweenCondition.operator).toBe('betweenOp');
76
- });
56
+ describe('Dot-notation fields', () => {
57
+ test('handles nested path', () => {
58
+ expect(new FieldConditionBuilder('user.profile.bio').equals('Dev').toComposable()).toEqual({
59
+ user: { profile: { bio: { is: 'Dev' } } }
60
+ });
61
+ });
62
+
63
+ test('handles deeply nested path', () => {
64
+ expect(new FieldConditionBuilder('a.b.c.d').greaterThan(10).toComposable()).toEqual({
65
+ a: { b: { c: { d: { greaterThan: 10 } } } }
66
+ });
67
+ });
68
+ });
77
69
 
78
- test('should handle empty field names gracefully', () => {
79
- expect(() => {
80
- const condition = new FieldConditionBuilder('').equals('test');
81
- LogicalOperator.Condition(condition).toComposable();
82
- }).not.toThrow();
83
- });
84
- });
70
+ describe('Returns LogicalOperator', () => {
71
+ test('each method returns a LogicalOperator with correct condition', () => {
72
+ const lo = new FieldConditionBuilder('name').equals('John');
73
+ expect(lo.type).toBe('condition');
74
+ expect(lo.condition?.field).toBe('name');
75
+ expect(lo.condition?.operator).toBe('is');
76
+ expect(lo.condition?.value).toBe('John');
77
+ });
78
+
79
+ test('empty field name does not throw', () => {
80
+ expect(() => new FieldConditionBuilder('').equals('test').toComposable()).not.toThrow();
81
+ });
82
+ });
83
+ });