@superleapai/flow-ui 2.2.2 → 2.2.3

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.
@@ -1,12 +1,12 @@
1
1
  /**
2
- * @superleapai/flow-ui v2.1.1
2
+ * @superleapai/flow-ui v2.2.2
3
3
  * A reusable design system for building multi-step forms
4
4
  *
5
5
  * Copyright (c) 2024-present SuperLeap
6
6
  * Licensed under MIT
7
7
  *
8
8
  * Build: development
9
- * Date: 2026-02-12T07:18:55.044Z
9
+ * Date: 2026-02-12T07:58:50.601Z
10
10
  *
11
11
  * For documentation and examples, visit:
12
12
  * https://github.com/superleap/superleap-flow
@@ -15,7 +15,2634 @@
15
15
  'use strict';
16
16
 
17
17
  // ============================================
18
- // File 1/36: core/superleapClient.js
18
+ // File 1/37: node_modules/superleap-sdk/superleap.js
19
+ // ============================================
20
+
21
+ /**
22
+ * SuperLeap CRM SDK
23
+ * A Django-like SDK for interacting with SuperLeap API
24
+ * Version: 2.0.0 - Optimized for minimal API calls with opt-in caching
25
+ */
26
+
27
+ (function (global) {
28
+
29
+ // Data type mappings
30
+ const DataType = {
31
+ Int: 1,
32
+ Varchar: 5,
33
+ Checkbox: 6,
34
+ DateTime: 7,
35
+ Date: 8,
36
+ SecondsSinceMidnight: 9,
37
+ Email: 11,
38
+ Text: 14,
39
+ SingleSelect: 15,
40
+ MultiSelect: 16,
41
+ Url: 17,
42
+ FileUpload: 20,
43
+ PhoneNumber: 21,
44
+ Rating: 22,
45
+ Decimal: 4,
46
+ Currency: 23,
47
+ Stage: 24,
48
+ Location: 18,
49
+ Region: 27,
50
+ MultiFileUpload: 29,
51
+ };
52
+
53
+ const RelationshipType = {
54
+ OneToOne: 1,
55
+ OneToMany: 2,
56
+ ManyToOne: 3,
57
+ Restricted: 6,
58
+ };
59
+
60
+ /**
61
+ * Cache entry class with TTL support
62
+ * @class CacheEntry
63
+ */
64
+ class CacheEntry {
65
+ constructor(value, ttl = null) {
66
+ this.value = value;
67
+ this.createdAt = Date.now();
68
+ // Validate TTL - ensure it's a positive number or null
69
+ this.ttl = ttl && typeof ttl === "number" && ttl > 0 ? ttl : null;
70
+ }
71
+
72
+ isExpired() {
73
+ if (!this.ttl) return false;
74
+ return Date.now() - this.createdAt > this.ttl;
75
+ }
76
+
77
+ getValue() {
78
+ return this.isExpired() ? null : this.value;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Cache manager class
84
+ * @class CacheManager
85
+ */
86
+ class CacheManager {
87
+ constructor(options = {}) {
88
+ this.enabled = options.enabled !== false; // Default to enabled
89
+ this.maxSize = options.maxSize || 1000; // Maximum number of cache entries
90
+ this.defaultTTL = options.defaultTTL || 5 * 60 * 1000; // 5 minutes default
91
+ this.storage = new Map();
92
+ this.stats = {
93
+ hits: 0,
94
+ misses: 0,
95
+ sets: 0,
96
+ deletes: 0,
97
+ evictions: 0,
98
+ };
99
+ }
100
+
101
+ /**
102
+ * Get value from cache
103
+ * @param {string} key - Cache key
104
+ * @returns {any} Cached value or null if not found/expired
105
+ */
106
+ get(key) {
107
+ if (!this.enabled) return null;
108
+
109
+ const entry = this.storage.get(key);
110
+ if (!entry) {
111
+ this.stats.misses++;
112
+ return null;
113
+ }
114
+
115
+ if (entry.isExpired()) {
116
+ this.storage.delete(key);
117
+ this.stats.misses++;
118
+ return null;
119
+ }
120
+
121
+ this.stats.hits++;
122
+ return entry.getValue();
123
+ }
124
+
125
+ /**
126
+ * Set value in cache
127
+ * @param {string} key - Cache key
128
+ * @param {any} value - Value to cache
129
+ * @param {number} ttl - Time to live in milliseconds (optional)
130
+ */
131
+ set(key, value, ttl = null) {
132
+ if (!this.enabled) return;
133
+
134
+ // Validate TTL
135
+ const validTTL =
136
+ ttl && typeof ttl === "number" && ttl > 0 ? ttl : this.defaultTTL;
137
+
138
+ // Evict if cache is full
139
+ if (this.storage.size >= this.maxSize) {
140
+ this._evictOldest();
141
+ }
142
+
143
+ const entry = new CacheEntry(value, validTTL);
144
+ this.storage.set(key, entry);
145
+ this.stats.sets++;
146
+ }
147
+
148
+ /**
149
+ * Delete value from cache
150
+ * @param {string} key - Cache key
151
+ * @returns {boolean} True if key was found and deleted
152
+ */
153
+ delete(key) {
154
+ if (!this.enabled) return false;
155
+
156
+ const deleted = this.storage.delete(key);
157
+ if (deleted) {
158
+ this.stats.deletes++;
159
+ }
160
+ return deleted;
161
+ }
162
+
163
+ /**
164
+ * Clear all cache entries
165
+ */
166
+ clear() {
167
+ this.storage.clear();
168
+ this.stats = {
169
+ hits: 0,
170
+ misses: 0,
171
+ sets: 0,
172
+ deletes: 0,
173
+ evictions: 0,
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Get cache statistics
179
+ * @returns {Object} Cache statistics
180
+ */
181
+ getStats() {
182
+ return {
183
+ ...this.stats,
184
+ size: this.storage.size,
185
+ maxSize: this.maxSize,
186
+ hitRate:
187
+ this.stats.hits + this.stats.misses > 0
188
+ ? (
189
+ (this.stats.hits / (this.stats.hits + this.stats.misses)) *
190
+ 100
191
+ ).toFixed(2) + "%"
192
+ : "0%",
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Evict oldest cache entry (LRU-like behavior)
198
+ * @private
199
+ */
200
+ _evictOldest() {
201
+ let oldestKey = null;
202
+ let oldestTime = Date.now();
203
+
204
+ for (const [key, entry] of this.storage.entries()) {
205
+ if (entry.createdAt < oldestTime) {
206
+ oldestTime = entry.createdAt;
207
+ oldestKey = key;
208
+ }
209
+ }
210
+
211
+ if (oldestKey) {
212
+ this.storage.delete(oldestKey);
213
+ this.stats.evictions++;
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Clean expired entries
219
+ * @returns {number} Number of entries cleaned
220
+ */
221
+ cleanExpired() {
222
+ let cleaned = 0;
223
+ for (const [key, entry] of this.storage.entries()) {
224
+ if (entry.isExpired()) {
225
+ this.storage.delete(key);
226
+ cleaned++;
227
+ }
228
+ }
229
+ return cleaned;
230
+ }
231
+ }
232
+
233
+ // Operators constants
234
+ const Operators = {
235
+ EQ: "eq",
236
+ NEQ: "neq",
237
+ GT: "gt",
238
+ GTE: "gte",
239
+ LT: "lt",
240
+ LTE: "lte",
241
+ IN: "in",
242
+ NOT_IN: "nin",
243
+ LIKE: "ilike",
244
+ NOT_LIKE: "not_ilike",
245
+ SW: "starts_with",
246
+ EW: "ends_with",
247
+ IS_EMPTY: "is_empty",
248
+ NOT_EMPTY: "not_empty",
249
+ CONTAINS: "contains",
250
+ NOT_CONTAINS: "not_contains",
251
+ BETWEEN: "between",
252
+ LOWER_EQ: "leq",
253
+ LOWER_NOT_EQ: "nleq",
254
+ IN_ACCESSIBLE_OWNERS: "in_accessible_owners",
255
+ };
256
+
257
+ // Advanced Date Operators constants
258
+ const AdvancedDateOperators = {
259
+ TODAY: "is_today",
260
+ ON: "on",
261
+ IS_YESTERDAY: "is_yesterday",
262
+ BEFORE_TODAY: "before_today",
263
+ AFTER_TODAY: "after_today",
264
+ TOMORROW: "tomorrow",
265
+ THIS_WEEK: "this_week",
266
+ NEXT_WEEK: "next_week",
267
+ LAST_WEEK: "last_week",
268
+ LAST_SEVEN_DAYS: "last_seven_days",
269
+ LAST_TEN_DAYS: "last_ten_days",
270
+ LAST_FIFTEEN_DAYS: "last_fifteen_days",
271
+ LAST_THIRTY_DAYS: "last_thirty_days",
272
+ THIS_MONTH: "this_month",
273
+ NEXT_MONTH: "next_month",
274
+ LAST_MONTH: "last_month",
275
+ LAST_MONTH_TILL_DATE: "last_month_till_date",
276
+ LAST_NINETY_DAYS: "last_ninety_days",
277
+ THIS_QUARTER: "this_quarter",
278
+ NEXT_QUARTER: "next_quarter",
279
+ LAST_QUARTER: "last_quarter",
280
+ LAST_180_DAYS: "last_180_days",
281
+ LAST_365_DAYS: "last_365_days",
282
+ THIS_YEAR: "this_year",
283
+ NEXT_YEAR: "next_year",
284
+ LAST_YEAR: "last_year",
285
+ ALL_TIME: "all_time",
286
+ };
287
+
288
+ /**
289
+ * Helper function to check if a field value is empty
290
+ * @param {any} value - The value to check
291
+ * @returns {boolean} True if the value is considered empty
292
+ * @private
293
+ */
294
+ function isFieldValueEmpty(value) {
295
+ if (
296
+ (typeof value === "string" && value?.trim() === "") ||
297
+ value === null ||
298
+ value === undefined ||
299
+ (Array.isArray(value) && value.length === 0)
300
+ )
301
+ return true;
302
+ }
303
+
304
+ /**
305
+ * Helper function to check if a value can be empty for a given operator
306
+ * @param {string} operator - The operator to check
307
+ * @returns {boolean} True if the operator allows empty values
308
+ * @private
309
+ */
310
+ function isValueCanBeEmptyForOperator(operator) {
311
+ return (
312
+ Operators.IS_EMPTY === operator ||
313
+ Operators.NOT_EMPTY === operator ||
314
+ Operators.IN_ACCESSIBLE_OWNERS === operator ||
315
+ AdvancedDateOperators.TODAY === operator ||
316
+ AdvancedDateOperators.IS_YESTERDAY === operator ||
317
+ AdvancedDateOperators.BEFORE_TODAY === operator ||
318
+ AdvancedDateOperators.THIS_WEEK === operator ||
319
+ AdvancedDateOperators.LAST_WEEK === operator ||
320
+ AdvancedDateOperators.LAST_SEVEN_DAYS === operator ||
321
+ AdvancedDateOperators.LAST_FIFTEEN_DAYS === operator ||
322
+ AdvancedDateOperators.LAST_THIRTY_DAYS === operator ||
323
+ AdvancedDateOperators.THIS_MONTH === operator ||
324
+ AdvancedDateOperators.LAST_MONTH === operator ||
325
+ AdvancedDateOperators.LAST_MONTH_TILL_DATE === operator ||
326
+ AdvancedDateOperators.THIS_QUARTER === operator ||
327
+ AdvancedDateOperators.LAST_QUARTER === operator ||
328
+ AdvancedDateOperators.THIS_YEAR === operator ||
329
+ AdvancedDateOperators.LAST_YEAR === operator ||
330
+ AdvancedDateOperators.AFTER_TODAY === operator
331
+ );
332
+ }
333
+
334
+ /**
335
+ * Count valid conditions in a filter (recursive helper)
336
+ * @param {Object|Array} item - A filter item (condition, nested and/or, or array)
337
+ * @returns {number} Number of valid conditions
338
+ * @private
339
+ */
340
+ function countValidConditionsRecursive(item) {
341
+ if (!item || typeof item !== "object") {
342
+ return 0;
343
+ }
344
+
345
+ // If it's a direct condition (has field and operator)
346
+ if (item.field && item.operator) {
347
+ if (isValueCanBeEmptyForOperator(item.operator)) {
348
+ return 1;
349
+ }
350
+ return !isFieldValueEmpty(item.value) ? 1 : 0;
351
+ }
352
+
353
+ // If it's an array, count recursively for each item
354
+ if (Array.isArray(item)) {
355
+ return item.reduce((sum, subItem) => {
356
+ return sum + countValidConditionsRecursive(subItem);
357
+ }, 0);
358
+ }
359
+
360
+ // If it has nested 'and' or 'or', count recursively
361
+ let count = 0;
362
+ if (item.and && Array.isArray(item.and)) {
363
+ count += countValidConditionsRecursive(item.and);
364
+ }
365
+ if (item.or && Array.isArray(item.or)) {
366
+ count += countValidConditionsRecursive(item.or);
367
+ }
368
+
369
+ return count;
370
+ }
371
+
372
+ /**
373
+ * Count valid conditions in a filter
374
+ * @param {Object} filter - The filter object
375
+ * @param {string} mainCondition - The main condition type ('and' or 'or')
376
+ * @returns {number} Number of valid conditions
377
+ * @private
378
+ */
379
+ function countValidConditions(filter, mainCondition) {
380
+ const mainConditionGroups = filter[mainCondition];
381
+ if (!Array.isArray(mainConditionGroups)) {
382
+ return 0;
383
+ }
384
+ return countValidConditionsRecursive(mainConditionGroups);
385
+ }
386
+
387
+ /**
388
+ * Query builder class for constructing filters
389
+ * @class QueryBuilder
390
+ */
391
+ class QueryBuilder {
392
+ /**
393
+ * Create a new QueryBuilder instance
394
+ * @param {string} objectSlug - The object slug to query
395
+ * @param {SuperLeapSDK} sdk - The SDK instance
396
+ */
397
+ constructor(objectSlug, sdk) {
398
+ this.objectSlug = objectSlug;
399
+ this.sdk = sdk;
400
+ this.query = {
401
+ fields: [],
402
+ filters: {
403
+ and: [],
404
+ or: [],
405
+ },
406
+ sort: [],
407
+ };
408
+ this._executed = false;
409
+ }
410
+
411
+ /**
412
+ * Make QueryBuilder thenable (awaitable)
413
+ * @param {Function} onFulfilled - Success callback
414
+ * @param {Function} onRejected - Error callback
415
+ * @returns {Promise} Promise that resolves to array of RecordInstance objects
416
+ * @example
417
+ * // Await the query builder directly
418
+ * const records = await query;
419
+ */
420
+ then(onFulfilled, onRejected) {
421
+ return this._execute().then(onFulfilled, onRejected);
422
+ }
423
+
424
+ /**
425
+ * Make QueryBuilder catchable for error handling
426
+ * @param {Function} onRejected - Error callback
427
+ * @returns {Promise} Promise
428
+ */
429
+ catch(onRejected) {
430
+ return this._execute().catch(onRejected);
431
+ }
432
+
433
+ /**
434
+ * Make QueryBuilder finallyable for cleanup
435
+ * @param {Function} onFinally - Finally callback
436
+ * @returns {Promise} Promise
437
+ */
438
+ finally(onFinally) {
439
+ return this._execute().finally(onFinally);
440
+ }
441
+
442
+ /**
443
+ * Select specific fields
444
+ * @param {...string} fields - Fields to select (supports dot notation)
445
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
446
+ * @example
447
+ * // Select specific fields
448
+ * query.select('name', 'email', 'created_at')
449
+ *
450
+ * // Select related fields using dot notation
451
+ * query.select('user.name', 'user.email')
452
+ */
453
+ select(...fields) {
454
+ this.query.fields = [...new Set([...this.query.fields, ...fields])];
455
+ return this;
456
+ }
457
+
458
+ /**
459
+ * Select all fields for the query
460
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
461
+ * @example
462
+ * // Select all fields
463
+ * query.selectAll()
464
+ *
465
+ * // Use in a chain
466
+ * const records = await model.selectAll().limit(5);
467
+ */
468
+ selectAll() {
469
+ // Set flag to select all fields when executing the query
470
+ this._selectAll = true;
471
+
472
+ // For APIs that require empty fields array to return all fields
473
+ this.query.fields = [];
474
+
475
+ return this;
476
+ }
477
+
478
+ /**
479
+ * Add WHERE conditions using object notation
480
+ * @param {Object} conditions - Filter conditions as key-value pairs
481
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
482
+ * @example
483
+ * // Simple where conditions
484
+ * query.where({ name: 'John', status: 'active' })
485
+ */
486
+ where(conditions) {
487
+ Object.entries(conditions).forEach(([field, value]) => {
488
+ this.query.filters.and.push({
489
+ field,
490
+ operator: "eq",
491
+ value,
492
+ });
493
+ });
494
+ return this;
495
+ }
496
+
497
+ /**
498
+ * Add complex AND conditions
499
+ * @param {Function} callback - Callback that receives a QueryBuilder for AND conditions
500
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
501
+ * @example
502
+ * // Complex AND conditions
503
+ * query.whereAnd(q => {
504
+ * q.gte('age', 18);
505
+ * q.contains('name', 'John');
506
+ * })
507
+ */
508
+ whereAnd(callback) {
509
+ const subQuery = new QueryBuilder(this.objectSlug, this.sdk);
510
+ callback(subQuery);
511
+
512
+ const hasAnd = subQuery.query.filters.and.length > 0;
513
+ const hasOr = subQuery.query.filters.or.length > 0;
514
+
515
+ if (!hasAnd && !hasOr) {
516
+ return this; // Nothing to add
517
+ }
518
+
519
+ const conditions = [];
520
+
521
+ // Collect all AND conditions
522
+ if (hasAnd) {
523
+ conditions.push(...subQuery.query.filters.and);
524
+ }
525
+
526
+ // Collect OR conditions as a wrapped group
527
+ if (hasOr) {
528
+ conditions.push({ or: subQuery.query.filters.or });
529
+ }
530
+
531
+ // Optimization: if only one condition, add it directly
532
+ if (conditions.length === 1) {
533
+ this.query.filters.and.push(conditions[0]);
534
+ } else {
535
+ // Multiple conditions: wrap them in { and: [...] }
536
+ this.query.filters.and.push({ and: conditions });
537
+ }
538
+
539
+ return this;
540
+ }
541
+ /**
542
+ * Add complex OR conditions
543
+ * @param {Function} callback - Callback that receives a QueryBuilder for OR conditions
544
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
545
+ * @example
546
+ * // Complex OR conditions
547
+ * query.whereOr(q => {
548
+ * q.contains('name', 'a');
549
+ * q.contains('name', 'b').whereAnd(r=>{r.startswith("email",'a')});
550
+ * q.isnotempty('email');
551
+ * })
552
+ */
553
+ whereOr(callback) {
554
+ const subQuery = new QueryBuilder(this.objectSlug, this.sdk);
555
+ callback(subQuery);
556
+
557
+ const hasAnd = subQuery.query.filters.and.length > 0;
558
+ const hasOr = subQuery.query.filters.or.length > 0;
559
+
560
+ if (!hasAnd && !hasOr) {
561
+ return this; // Nothing to add
562
+ }
563
+
564
+ const conditions = [];
565
+
566
+ // Collect all AND conditions (they become OR siblings)
567
+ if (hasAnd) {
568
+ conditions.push(...subQuery.query.filters.and);
569
+ }
570
+
571
+ // Collect all OR conditions as direct siblings
572
+ if (hasOr) {
573
+ conditions.push(...subQuery.query.filters.or);
574
+ }
575
+
576
+ // Push all conditions to the OR array
577
+ conditions.forEach((condition) => {
578
+ this.query.filters.or.push(condition);
579
+ });
580
+
581
+ return this;
582
+ }
583
+
584
+ /**
585
+ * Add filter conditions with operators
586
+ * @param {string} field - Field name (supports dot notation)
587
+ * @param {string} operator - Filter operator (eq, contains, gt, lt, etc.)
588
+ * @param {any} value - Filter value
589
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
590
+ * @example
591
+ * // Basic filter
592
+ * query.filter('name', 'contains', 'John')
593
+ *
594
+ * // Numeric filter
595
+ * query.filter('age', 'gte', 18)
596
+ */
597
+ filter(field, operator, value) {
598
+ this.query.filters.and.push({
599
+ field,
600
+ operator,
601
+ value,
602
+ });
603
+ return this;
604
+ }
605
+
606
+ // Django-style filter methods with correct backend operators
607
+
608
+ /**
609
+ * Exact match filter
610
+ * @param {string} field - Field name
611
+ * @param {any} value - Value to match exactly
612
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
613
+ */
614
+ exact(field, value) {
615
+ return this.filter(field, "eq", value);
616
+ }
617
+
618
+ /**
619
+ * Case-insensitive exact match filter
620
+ * @param {string} field - Field name
621
+ * @param {string} value - Value to match exactly (case-insensitive)
622
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
623
+ */
624
+ iexact(field, value) {
625
+ return this.filter(field, "leq", value);
626
+ }
627
+
628
+ /**
629
+ * Not exact match filter
630
+ * @param {string} field - Field name
631
+ * @param {any} value - Value to not match exactly
632
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
633
+ */
634
+ notexact(field, value) {
635
+ return this.filter(field, "neq", value);
636
+ }
637
+
638
+ /**
639
+ * Case-insensitive not exact match filter
640
+ * @param {string} field - Field name
641
+ * @param {string} value - Value to not match exactly (case-insensitive)
642
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
643
+ */
644
+ notiexact(field, value) {
645
+ return this.filter(field, "nleq", value);
646
+ }
647
+
648
+ /**
649
+ * Contains text filter (case-sensitive)
650
+ * @param {string} field - Field name
651
+ * @param {string} value - Text to search for
652
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
653
+ */
654
+ contains(field, value) {
655
+ return this.filter(field, "contains", value);
656
+ }
657
+
658
+ /**
659
+ * Contains text filter (case-insensitive)
660
+ * @param {string} field - Field name
661
+ * @param {string} value - Text to search for
662
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
663
+ */
664
+ icontains(field, value) {
665
+ return this.filter(field, "ilike", value);
666
+ }
667
+
668
+ /**
669
+ * Starts with text filter
670
+ * @param {string} field - Field name
671
+ * @param {string} value - Text that field should start with
672
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
673
+ */
674
+ startswith(field, value) {
675
+ return this.filter(field, "starts_with", value);
676
+ }
677
+
678
+ /**
679
+ * Ends with text filter
680
+ * @param {string} field - Field name
681
+ * @param {string} value - Text that field should end with
682
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
683
+ */
684
+ endswith(field, value) {
685
+ return this.filter(field, "ends_with", value);
686
+ }
687
+
688
+ /**
689
+ * In array filter
690
+ * @param {string} field - Field name
691
+ * @param {Array} values - Array of values to match
692
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
693
+ */
694
+ in(field, values) {
695
+ return this.filter(field, "in", values);
696
+ }
697
+
698
+ /**
699
+ * Not in array filter
700
+ * @param {string} field - Field name
701
+ * @param {Array} values - Array of values to exclude
702
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
703
+ */
704
+ notin(field, values) {
705
+ return this.filter(field, "nin", values);
706
+ }
707
+
708
+ /**
709
+ * Greater than filter
710
+ * @param {string} field - Field name
711
+ * @param {number|string} value - Value to compare against
712
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
713
+ */
714
+ gt(field, value) {
715
+ return this.filter(field, "gt", value);
716
+ }
717
+
718
+ /**
719
+ * Greater than or equal filter
720
+ * @param {string} field - Field name
721
+ * @param {number|string} value - Value to compare against
722
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
723
+ */
724
+ gte(field, value) {
725
+ return this.filter(field, "gte", value);
726
+ }
727
+
728
+ /**
729
+ * Less than filter
730
+ * @param {string} field - Field name
731
+ * @param {number|string} value - Value to compare against
732
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
733
+ */
734
+ lt(field, value) {
735
+ return this.filter(field, "lt", value);
736
+ }
737
+
738
+ /**
739
+ * Less than or equal filter
740
+ * @param {string} field - Field name
741
+ * @param {number|string} value - Value to compare against
742
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
743
+ */
744
+ lte(field, value) {
745
+ return this.filter(field, "lte", value);
746
+ }
747
+
748
+ /**
749
+ * Between values filter
750
+ * @param {string} field - Field name
751
+ * @param {Array} value - Array with [min, max] values
752
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
753
+ */
754
+ between(field, value) {
755
+ return this.filter(field, "between", value);
756
+ }
757
+
758
+ /**
759
+ * Does not contain text filter
760
+ * @param {string} field - Field name
761
+ * @param {string} value - Text that should not be contained
762
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
763
+ */
764
+ notcontains(field, value) {
765
+ return this.filter(field, "not_contains", value);
766
+ }
767
+
768
+ /**
769
+ * Field is empty filter
770
+ * @param {string} field - Field name
771
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
772
+ */
773
+ isempty(field) {
774
+ return this.filter(field, "is_empty", null);
775
+ }
776
+
777
+ /**
778
+ * Field is not empty filter
779
+ * @param {string} field - Field name
780
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
781
+ */
782
+ isnotempty(field) {
783
+ return this.filter(field, "not_empty", null);
784
+ }
785
+
786
+ /**
787
+ * Field exists filter
788
+ * @param {string} field - Field name
789
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
790
+ */
791
+ exists(field) {
792
+ return this.filter(field, "exists", null);
793
+ }
794
+
795
+ /**
796
+ * Field does not exist filter
797
+ * @param {string} field - Field name
798
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
799
+ */
800
+ notexists(field) {
801
+ return this.filter(field, "not_exists", null);
802
+ }
803
+
804
+ /**
805
+ * Like pattern filter
806
+ * @param {string} field - Field name
807
+ * @param {string} value - Pattern to match
808
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
809
+ */
810
+ like(field, value) {
811
+ return this.filter(field, "like", value);
812
+ }
813
+
814
+ /**
815
+ * Case-insensitive like pattern filter
816
+ * @param {string} field - Field name
817
+ * @param {string} value - Pattern to match
818
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
819
+ */
820
+ ilike(field, value) {
821
+ return this.filter(field, "ilike", value);
822
+ }
823
+
824
+ // Legacy support
825
+ /**
826
+ * Field is null filter (legacy)
827
+ * @param {string} field - Field name
828
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
829
+ * @deprecated Use isempty() instead
830
+ */
831
+ isnull(field) {
832
+ console.warn("isnull is deprecated, use isempty() instead");
833
+ return this.filter(field, "is_empty", null);
834
+ }
835
+
836
+ /**
837
+ * Field is not null filter (legacy)
838
+ * @param {string} field - Field name
839
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
840
+ * @deprecated Use isnotempty() instead
841
+ */
842
+ isnotnull(field) {
843
+ console.warn("isnotnull is deprecated, use isnotempty() instead");
844
+ return this.filter(field, "not_empty", null);
845
+ }
846
+
847
+ /**
848
+ * Add filter using complete filter structure directly
849
+ * @param {Object} filter - Complete filter object structure
850
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
851
+ * @example
852
+ * // Direct filter with complete filter structure
853
+ * query.filterBy({ field: 'name', operator: 'contains', value: 'John' })
854
+ *
855
+ * // Multiple filters
856
+ * query.filterBy({ field: 'status', operator: 'eq', value: 'active' })
857
+ * .filterBy({ field: 'age', operator: 'gte', value: 18 })
858
+ *
859
+ * // Complex filter with array value
860
+ * query.filterBy({ field: 'category', operator: 'in', value: ['A', 'B', 'C'] })
861
+ *
862
+ * // Complex AND/OR filters
863
+ * query.filterBy({
864
+ * and: [
865
+ * { field: 'status', operator: 'eq', value: 'active' },
866
+ * { field: 'age', operator: 'gte', value: 18 }
867
+ * ]
868
+ * })
869
+ */
870
+ filterBy(filter) {
871
+ if (!filter || typeof filter !== "object") {
872
+ throw new Error("Filter must be an object");
873
+ }
874
+
875
+ // Validate filter before adding it
876
+ let hasValidConditions = false;
877
+
878
+ // Check if it's a simple condition (has field and operator)
879
+ if (filter.field && filter.operator) {
880
+ // Simple condition validation
881
+ if (
882
+ isValueCanBeEmptyForOperator(filter.operator) ||
883
+ !isFieldValueEmpty(filter.value)
884
+ ) {
885
+ hasValidConditions = true;
886
+ }
887
+ } else {
888
+ // Complex condition validation (and/or structure)
889
+ if (filter.and && countValidConditions(filter, "and") > 0) {
890
+ hasValidConditions = true;
891
+ }
892
+ if (filter.or && countValidConditions(filter, "or") > 0) {
893
+ hasValidConditions = true;
894
+ }
895
+ }
896
+
897
+ // Only add the filter if it has valid conditions
898
+ if (hasValidConditions) {
899
+ // If filter only has 'and' property (no 'or' and no direct field/operator),
900
+ // merge its conditions into this.query.filters.and to avoid extra wrapping
901
+ if (
902
+ filter.and &&
903
+ Array.isArray(filter.and) &&
904
+ !filter.or &&
905
+ !filter.field &&
906
+ !filter.operator
907
+ ) {
908
+ this.query.filters.and.push(...filter.and);
909
+ }
910
+ // If filter only has 'or' property, merge into this.query.filters.or
911
+ else if (
912
+ filter.or &&
913
+ Array.isArray(filter.or) &&
914
+ !filter.and &&
915
+ !filter.field &&
916
+ !filter.operator
917
+ ) {
918
+ this.query.filters.or.push(...filter.or);
919
+ }
920
+ // Otherwise, push the entire filter object (for mixed and/or or simple conditions)
921
+ else {
922
+ this.query.filters.and.push(filter);
923
+ }
924
+ } else {
925
+ console.warn(
926
+ "FilterBy: Skipping filter with no valid conditions",
927
+ filter
928
+ );
929
+ }
930
+
931
+ return this;
932
+ }
933
+
934
+ /**
935
+ * Order by fields
936
+ * @param {...string} fields - Fields to order by (prefix with '-' for descending)
937
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
938
+ * @example
939
+ * // Order by single field ascending
940
+ * query.orderBy('name')
941
+ *
942
+ * // Order by multiple fields
943
+ * query.orderBy('name', '-created_at')
944
+ */
945
+ orderBy(...fields) {
946
+ this.query.sort = fields.map((field) => {
947
+ if (field.startsWith("-")) {
948
+ return { field: field.slice(1), direction: "desc" };
949
+ }
950
+ return { field, direction: "asc" };
951
+ });
952
+ return this;
953
+ }
954
+
955
+ /**
956
+ * Limit results
957
+ * @param {number} limit - Maximum number of results
958
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
959
+ * @example
960
+ * // Limit to 10 results
961
+ * query.limit(10)
962
+ */
963
+ limit(limit) {
964
+ if (!this.query.pagination) {
965
+ this.query.pagination = { page: 1, page_size: limit };
966
+ } else {
967
+ this.query.pagination.page_size = limit;
968
+ }
969
+ return this;
970
+ }
971
+
972
+ /**
973
+ * Set page number
974
+ * @param {number} page - Page number (1-based)
975
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
976
+ * @example
977
+ * // Get page 2
978
+ * query.page(2)
979
+ */
980
+ page(page) {
981
+ if (!this.query.pagination) {
982
+ this.query.pagination = { page: page, page_size: 100 };
983
+ } else {
984
+ this.query.pagination.page = page;
985
+ }
986
+ return this;
987
+ }
988
+
989
+ /**
990
+ * Offset results (converted to page)
991
+ * @param {number} offset - Number of results to skip
992
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
993
+ * @example
994
+ * // Skip first 20 results
995
+ * query.offset(20)
996
+ */
997
+ offset(offset) {
998
+ if (!this.query.pagination) {
999
+ this.query.pagination = {
1000
+ page: Math.floor(offset / 100) + 1,
1001
+ page_size: 100,
1002
+ };
1003
+ } else {
1004
+ const pageSize = this.query.pagination.page_size;
1005
+ this.query.pagination.page = Math.floor(offset / pageSize) + 1;
1006
+ }
1007
+ return this;
1008
+ }
1009
+
1010
+ /**
1011
+ * Execute the query and return results (internal method)
1012
+ * @returns {Promise<RecordInstance[]>} Array of RecordInstance objects
1013
+ * @private
1014
+ */
1015
+ async _execute() {
1016
+ if (this._executed) {
1017
+ console.warn("Query already executed. Create a new query builder.");
1018
+ return [];
1019
+ }
1020
+
1021
+ try {
1022
+ // Handle selectAll in two ways:
1023
+ // 1. Try to get explicit field list from schema
1024
+ // 2. If that fails, use empty array approach which works for some APIs
1025
+ if (this._selectAll === true) {
1026
+ try {
1027
+ // First attempt: Get schema and extract field names
1028
+ const model = this.sdk.getModel(this.objectSlug);
1029
+ const schema = await model.getSchema();
1030
+
1031
+ if (
1032
+ schema &&
1033
+ schema.org_object_columns &&
1034
+ Array.isArray(schema.org_object_columns)
1035
+ ) {
1036
+ // Get all field slugs from the schema
1037
+ this.query.fields = schema.org_object_columns
1038
+ .filter((col) => col && col.slug)
1039
+ .filter(
1040
+ (col) =>
1041
+ !col.relation ||
1042
+ (col.relation &&
1043
+ col.relationship_type !== RelationshipType.OneToMany)
1044
+ )
1045
+ .map((col) => col.slug);
1046
+ if (this.query.fields.length > 0) {
1047
+ console.log(
1048
+ `Using ${this.query.fields.length} explicit fields from schema`
1049
+ );
1050
+ } else {
1051
+ console.warn(
1052
+ "Schema returned no valid fields, using empty fields array"
1053
+ );
1054
+ }
1055
+ } else {
1056
+ console.warn(
1057
+ "No valid schema structure found, using empty fields array"
1058
+ );
1059
+ }
1060
+ } catch (error) {
1061
+ console.error("Error fetching schema fields:", error);
1062
+ // Second approach: Empty fields array often means "all fields" in many APIs
1063
+ console.log("Falling back to empty fields array");
1064
+ }
1065
+ }
1066
+
1067
+ // Prepare query
1068
+ const queryData = { ...this.query };
1069
+
1070
+ // Ensure id field is always included if we have other fields
1071
+ if (queryData.fields.length > 0 && !queryData.fields.includes("id")) {
1072
+ queryData.fields.push("id");
1073
+ }
1074
+
1075
+ // Add default sorting if none specified
1076
+ if (queryData.sort.length === 0) {
1077
+ queryData.sort = [{ field: "id", direction: "desc" }];
1078
+ }
1079
+
1080
+ // Build filter structure - API requires filters to be present
1081
+ queryData.filters = this._buildFilterStructure(queryData.filters);
1082
+
1083
+ // If no filters, provide empty AND structure
1084
+ if (!queryData.filters) {
1085
+ queryData.filters = { and: [] };
1086
+ }
1087
+
1088
+ console.log("Executing query with fields:", queryData.fields);
1089
+ // Use user-defined cache options or defaults
1090
+ const cacheOptions = this._cacheOptions || {};
1091
+ const result = await this.sdk.getRecords(
1092
+ this.objectSlug,
1093
+ queryData,
1094
+ cacheOptions
1095
+ );
1096
+ this._executed = true;
1097
+
1098
+ if (result.success) {
1099
+ // Handle different response structures
1100
+ let records = [];
1101
+ if (Array.isArray(result.data)) {
1102
+ records = result.data;
1103
+ } else if (result.data && Array.isArray(result.data.records)) {
1104
+ records = result.data.records;
1105
+ } else if (
1106
+ result.data &&
1107
+ result.data.data &&
1108
+ Array.isArray(result.data.data)
1109
+ ) {
1110
+ records = result.data.data;
1111
+ }
1112
+
1113
+ console.log(`Query returned ${records.length} records`);
1114
+
1115
+ // Convert to RecordInstance objects
1116
+ return records.map(
1117
+ (record) =>
1118
+ new RecordInstance(record, this.sdk.getModel(this.objectSlug))
1119
+ );
1120
+ } else {
1121
+ console.warn("Query failed:", result.message);
1122
+ return [];
1123
+ }
1124
+ } catch (error) {
1125
+ console.error("Error executing query:", error);
1126
+ return [];
1127
+ }
1128
+ }
1129
+
1130
+ /**
1131
+ * Build proper filter structure for complex queries
1132
+ * @param {Object} filters - Filter object
1133
+ * @returns {Object|null}
1134
+ * @private
1135
+ */
1136
+ _buildFilterStructure(filters) {
1137
+ if (!filters || (!filters.and.length && !filters.or.length)) {
1138
+ return null;
1139
+ }
1140
+
1141
+ const result = {};
1142
+
1143
+ if (filters.and.length > 0) {
1144
+ result.and = filters.and;
1145
+ }
1146
+
1147
+ if (filters.or.length > 0) {
1148
+ result.or = filters.or;
1149
+ }
1150
+
1151
+ return result;
1152
+ }
1153
+
1154
+ /**
1155
+ * Get first result
1156
+ * @returns {Promise<RecordInstance|null>} First RecordInstance or null if no results
1157
+ * @example
1158
+ * // Get first matching record
1159
+ * const record = await query.first();
1160
+ */
1161
+ async first() {
1162
+ this.limit(1);
1163
+ const results = await this._execute();
1164
+ return results.length > 0 ? results[0] : null;
1165
+ }
1166
+
1167
+ /**
1168
+ * Get count of results
1169
+ * @returns {Promise<number>} Number of matching records
1170
+ * @example
1171
+ * // Get count of matching records
1172
+ * const count = await query.count();
1173
+ */
1174
+ async count() {
1175
+ const filterData = this._buildFilterStructure(this.query.filters);
1176
+
1177
+ // Use user-defined cache options or defaults
1178
+ const cacheOptions = this._cacheOptions || {};
1179
+
1180
+ const result = await this.sdk.getRecordsCount(
1181
+ this.objectSlug,
1182
+ filterData,
1183
+ cacheOptions
1184
+ );
1185
+ return result.success ? result.data.count : 0;
1186
+ }
1187
+
1188
+ /**
1189
+ * Check if any records exist
1190
+ * @returns {Promise<boolean>} True if records exist, false otherwise
1191
+ * @example
1192
+ * // Check if any records match the query
1193
+ * const exists = await query.exists();
1194
+ */
1195
+ async exists() {
1196
+ const count = await this.count();
1197
+ return count > 0;
1198
+ }
1199
+
1200
+ /**
1201
+ * Set cache options for this query
1202
+ * @param {Object} options - Cache options
1203
+ * @param {boolean} options.useCache - Whether to use cache (default: false)
1204
+ * @param {number} options.cacheTTL - Custom TTL for this query
1205
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
1206
+ * @example
1207
+ * // Enable cache for this query
1208
+ * const records = await query.cache().where({ status: 'active' });
1209
+ *
1210
+ * // Use custom TTL for this query
1211
+ * const records = await query.cacheTTL(60 * 1000).where({ status: 'active' });
1212
+ */
1213
+ cacheOptions(options = {}) {
1214
+ this._cacheOptions = options;
1215
+ return this;
1216
+ }
1217
+
1218
+ /**
1219
+ * Disable cache for this query
1220
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
1221
+ * @example
1222
+ * // Force fresh data
1223
+ * const records = await query.noCache().where({ status: 'active' });
1224
+ */
1225
+ noCache() {
1226
+ return this.cacheOptions({ useCache: false });
1227
+ }
1228
+
1229
+ /**
1230
+ * Set custom TTL for this query
1231
+ * @param {number} ttl - TTL in milliseconds
1232
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
1233
+ * @example
1234
+ * // Cache for 1 hour
1235
+ * const records = await query.cacheTTL(60 * 60 * 1000).where({ status: 'active' });
1236
+ */
1237
+ cacheTTL(ttl) {
1238
+ return this.cacheOptions({ useCache: true, cacheTTL: ttl });
1239
+ }
1240
+
1241
+ /**
1242
+ * Enable caching for this query
1243
+ * @param {Object} options - Cache options
1244
+ * @param {number} options.ttl - Custom TTL for this query (optional)
1245
+ * @returns {QueryBuilder} Returns this QueryBuilder instance for method chaining
1246
+ * @example
1247
+ * // Enable caching with default TTL (can be at the end of chain)
1248
+ * const records = await query.where({ status: 'active' }).cache();
1249
+ *
1250
+ * // Enable caching with custom TTL (can be at the end of chain)
1251
+ * const records = await query.where({ status: 'active' }).cache({ ttl: 60 * 60 * 1000 });
1252
+ *
1253
+ * // Enable caching in the middle of chain
1254
+ * const records = await query.select('name', 'email').cache().where({ status: 'active' }).limit(10);
1255
+ */
1256
+ cache(options = {}) {
1257
+ return this.cacheOptions({ useCache: true, cacheTTL: options.ttl });
1258
+ }
1259
+ }
1260
+
1261
+ /**
1262
+ * Record instance class
1263
+ * @class RecordInstance
1264
+ */
1265
+ class RecordInstance {
1266
+ /**
1267
+ * Create a new RecordInstance
1268
+ * @param {Object} data - Record data
1269
+ * @param {Model} model - Model instance
1270
+ */
1271
+ constructor(data, model) {
1272
+ if (!data || typeof data !== "object") {
1273
+ throw new Error("Record data must be a valid object");
1274
+ }
1275
+ if (!model) {
1276
+ throw new Error("Model instance is required");
1277
+ }
1278
+ this.data = data;
1279
+ this.model = model;
1280
+ this.id = data.id;
1281
+ }
1282
+
1283
+ /**
1284
+ * Get field value
1285
+ * @param {string} field - Field name
1286
+ * @returns {any} Field value
1287
+ * @example
1288
+ * // Get a field value
1289
+ * const name = record.get('name');
1290
+ */
1291
+ get(field) {
1292
+ if (!this.data || typeof this.data !== "object") {
1293
+ throw new Error("Record data is not valid");
1294
+ }
1295
+ const availableFields = Object.keys(this.data);
1296
+ if (!(field in this.data)) {
1297
+ throw new Error(
1298
+ `Field '${field}' not found. Available fields: ${availableFields.join(
1299
+ ", "
1300
+ )}`
1301
+ );
1302
+ }
1303
+ return this.data[field];
1304
+ }
1305
+
1306
+ /**
1307
+ * Set field value
1308
+ * @param {string} field - Field name
1309
+ * @param {any} value - Field value
1310
+ * @example
1311
+ * // Set a field value
1312
+ * record.set('name', 'New Name');
1313
+ */
1314
+ set(field, value) {
1315
+ this.data[field] = value;
1316
+ }
1317
+
1318
+ /**
1319
+ * Update this record
1320
+ * @param {Object} updateData - Data to update
1321
+ * @returns {Promise<RecordInstance>} Updated RecordInstance
1322
+ * @example
1323
+ * @deprecated Use mode('object').update(id,updatedData) instead
1324
+ * // Update record fields
1325
+ * const updated = await record.update({ name: 'New Name', status: 'active' });
1326
+ */
1327
+ async update(updateData) {
1328
+ console.warn(
1329
+ "update is deprecated, use model('object').update(id,updatedData) instead"
1330
+ );
1331
+ try {
1332
+ const updatedRecord = await this.model.update(this.id, updateData);
1333
+ // Update our local data with the response from the server
1334
+ this.data = updatedRecord.data;
1335
+ return this;
1336
+ } catch (error) {
1337
+ throw new Error(error.message || "Update failed");
1338
+ }
1339
+ }
1340
+
1341
+ /**
1342
+ * Delete this record
1343
+ * @returns {Promise<boolean>} True if deletion was successful
1344
+ * @example
1345
+ * // Delete the record
1346
+ * const deleted = await record.delete();
1347
+ */
1348
+ async delete() {
1349
+ const result = await this.model.delete(this.id);
1350
+ return result.success;
1351
+ }
1352
+
1353
+ /**
1354
+ * Refresh this record from the server
1355
+ * @returns {Promise<RecordInstance>} Refreshed RecordInstance
1356
+ * @example
1357
+ * // Refresh record data from server
1358
+ * const refreshed = await record.refresh();
1359
+ */
1360
+ async refresh() {
1361
+ const fresh = await this.model.get(this.id);
1362
+ this.data = fresh.data;
1363
+ return this;
1364
+ }
1365
+
1366
+ /**
1367
+ * Convert to plain object
1368
+ * @returns {Object} Plain object representation of the record
1369
+ * @example
1370
+ * // Convert to plain object
1371
+ * const plainObject = record.toJSON();
1372
+ */
1373
+ toJSON() {
1374
+ return this.data;
1375
+ }
1376
+ }
1377
+
1378
+ /**
1379
+ * Model class representing a SuperLeap object
1380
+ * @class Model
1381
+ */
1382
+ class Model {
1383
+ /**
1384
+ * Create a new Model instance
1385
+ * @param {string} objectSlug - Object slug
1386
+ * @param {SuperLeapSDK} sdk - SDK instance
1387
+ */
1388
+ constructor(objectSlug, sdk) {
1389
+ this.objectSlug = objectSlug;
1390
+ this.sdk = sdk;
1391
+ this._schema = null;
1392
+ this._schemaLoaded = false;
1393
+ }
1394
+
1395
+ /**
1396
+ * Get schema for this object (lazy loaded)
1397
+ * @returns {Promise<Object>} Object schema
1398
+ * @example
1399
+ * // Get object schema
1400
+ * const schema = await model.getSchema();
1401
+ */
1402
+ async getSchema() {
1403
+ try {
1404
+ if (!this._schemaLoaded) {
1405
+ console.log(`Fetching schema for ${this.objectSlug}...`);
1406
+ const result = await this.sdk.getObjectSchema(this.objectSlug);
1407
+ if (result.success && result.data) {
1408
+ this._schema = result.data;
1409
+ this._schemaLoaded = true;
1410
+ console.log(`Schema loaded for ${this.objectSlug}`);
1411
+ } else {
1412
+ console.warn(
1413
+ `Failed to load schema for ${this.objectSlug}:`,
1414
+ result.message
1415
+ );
1416
+ }
1417
+ }
1418
+ return this._schema;
1419
+ } catch (error) {
1420
+ console.error(`Error loading schema for ${this.objectSlug}:`, error);
1421
+ return null;
1422
+ }
1423
+ }
1424
+
1425
+ /**
1426
+ * Get field information
1427
+ * @param {string} fieldSlug - Field slug
1428
+ * @returns {Promise<Object|null>} Field information or null if not found
1429
+ * @example
1430
+ * // Get field information
1431
+ * const field = await model.getField('name');
1432
+ */
1433
+ async getField(fieldSlug) {
1434
+ const schema = await this.getSchema();
1435
+ if (!schema || !schema.org_object_columns) return null;
1436
+
1437
+ const column = schema.org_object_columns.find(
1438
+ (col) => col.slug === fieldSlug
1439
+ );
1440
+ if (!column) return null;
1441
+
1442
+ return {
1443
+ slug: column.slug,
1444
+ displayName: column.display_name,
1445
+ dataType: column.column_data_type,
1446
+ isRequired: column.is_required === 1,
1447
+ isUnique: column.is_unique === 1,
1448
+ enumGroup: column.enum_group,
1449
+ constraints: column.constraints,
1450
+ properties: column.properties,
1451
+ };
1452
+ }
1453
+
1454
+ /**
1455
+ * Get all fields
1456
+ * @returns {Promise<Array>} Array of field objects
1457
+ * @example
1458
+ * // Get all fields
1459
+ * const fields = await model.getFields();
1460
+ */
1461
+ async getFields() {
1462
+ try {
1463
+ const schema = await this.getSchema();
1464
+ if (!schema) {
1465
+ console.warn(`No schema available for ${this.objectSlug}`);
1466
+ return [];
1467
+ }
1468
+
1469
+ if (
1470
+ !schema.org_object_columns ||
1471
+ !Array.isArray(schema.org_object_columns)
1472
+ ) {
1473
+ console.warn(
1474
+ `Schema for ${this.objectSlug} doesn't contain org_object_columns array`
1475
+ );
1476
+ return [];
1477
+ }
1478
+
1479
+ console.log(
1480
+ `Found ${schema.org_object_columns.length} fields for ${this.objectSlug}`
1481
+ );
1482
+
1483
+ return schema.org_object_columns.map((column) => ({
1484
+ slug: column.slug,
1485
+ displayName: column.display_name,
1486
+ dataType: column.column_data_type,
1487
+ isRequired: column.is_required === 1,
1488
+ isUnique: column.is_unique === 1,
1489
+ enumGroup: column.enum_group,
1490
+ constraints: column.constraints,
1491
+ properties: column.properties,
1492
+ }));
1493
+ } catch (error) {
1494
+ console.error(`Error getting fields for ${this.objectSlug}:`, error);
1495
+ return [];
1496
+ }
1497
+ }
1498
+
1499
+ /**
1500
+ * Create a new query builder with field selection
1501
+ * @param {...string} fields - Fields to select (supports dot notation)
1502
+ * @returns {QueryBuilder} New QueryBuilder instance
1503
+ * @example
1504
+ * // Select specific fields
1505
+ * const query = model.select('name', 'email', 'created_at');
1506
+ */
1507
+ select(...fields) {
1508
+ const queryBuilder = new QueryBuilder(this.objectSlug, this.sdk);
1509
+ return queryBuilder.select(...fields);
1510
+ }
1511
+
1512
+ /**
1513
+ * Create a new query builder with all fields selected
1514
+ * @returns {QueryBuilder} New QueryBuilder instance
1515
+ * @example
1516
+ * // Select all fields
1517
+ * const query = model.selectAll();
1518
+ * @deprecated Use select() with fields
1519
+ */
1520
+ selectAll() {
1521
+ console.warn("selectAll is deprecated, use select() with fields instead");
1522
+ const queryBuilder = new QueryBuilder(this.objectSlug, this.sdk);
1523
+ return queryBuilder.selectAll();
1524
+ }
1525
+
1526
+ /**
1527
+ * Create a new record
1528
+ * @param {Object} data - Record data
1529
+ * @returns {Promise<RecordInstance>} Created RecordInstance
1530
+ * @example
1531
+ * // Create a new record
1532
+ * const record = await model.create({ name: 'John Doe', email: 'john@example.com' });
1533
+ */
1534
+ async create(data) {
1535
+ const result = await this.sdk.createRecord(this.objectSlug, data);
1536
+ if (result.success) {
1537
+ // Invalidate cache for this object since we added a new record
1538
+ this.sdk.invalidateCache(this.objectSlug);
1539
+ return new RecordInstance(result.data, this);
1540
+ }
1541
+ throw new Error(JSON.stringify(result.data));
1542
+ }
1543
+
1544
+ /**
1545
+ * Get record by ID with all fields
1546
+ * @param {string} id - Record ID
1547
+ * @returns {Promise<RecordInstance>} RecordInstance
1548
+ * @example
1549
+ * // Get record by ID
1550
+ * const record = await model.get('record-id');
1551
+ * @deprecated Use select()
1552
+ */
1553
+ async get(id) {
1554
+ console.warn("get is deprecated, use select() instead");
1555
+ const result = await this.sdk.getRecordById(this.objectSlug, id);
1556
+ if (result.success) {
1557
+ return new RecordInstance(result.data, this);
1558
+ }
1559
+ throw new Error(result.message || "Record not found");
1560
+ }
1561
+
1562
+ /**
1563
+ * Update record
1564
+ * @param {string} id - Record ID
1565
+ * @param {Object} data - Update data
1566
+ * @returns {Promise<RecordInstance>} Updated RecordInstance
1567
+ * @example
1568
+ * // Update record
1569
+ * const updated = await model.update('record-id', { name: 'Updated Name' });
1570
+ */
1571
+ async update(id, data) {
1572
+ const result = await this.sdk.updateRecord(this.objectSlug, id, data);
1573
+
1574
+ if (result.success) {
1575
+ // Invalidate cache for this object since we updated a record
1576
+ this.sdk.invalidateCache(this.objectSlug);
1577
+ return new RecordInstance(result.data || {}, this);
1578
+ }
1579
+ }
1580
+
1581
+ /**
1582
+ * Delete record
1583
+ * @param {string} id - Record ID
1584
+ * @returns {Promise<boolean>} True if deletion was successful
1585
+ * @example
1586
+ * // Delete record
1587
+ * const deleted = await model.delete('record-id');
1588
+ */
1589
+ async delete(id) {
1590
+ const result = await this.sdk.deleteRecord(this.objectSlug, id);
1591
+ if (result.success) {
1592
+ // Invalidate cache for this object since we deleted a record
1593
+ this.sdk.invalidateCache(this.objectSlug);
1594
+ }
1595
+ return result.success;
1596
+ }
1597
+
1598
+ /**
1599
+ * Delete multiple records
1600
+ * @param {string[]} ids - Array of record IDs
1601
+ * @returns {Promise<boolean>} True if deletion was successful
1602
+ * @example
1603
+ * // Delete multiple records
1604
+ * const deleted = await model.deleteMany(['id1', 'id2', 'id3']);
1605
+ */
1606
+ async deleteMany(ids) {
1607
+ const result = await this.sdk.deleteRecords(this.objectSlug, ids);
1608
+ if (result.success) {
1609
+ // Invalidate cache for this object since we deleted records
1610
+ this.sdk.invalidateCache(this.objectSlug);
1611
+ }
1612
+ return result.success;
1613
+ }
1614
+
1615
+ /**
1616
+ * Get or create a record
1617
+ * @param {Object} filters - Filter conditions to find existing record
1618
+ * @param {Object} defaults - Default values for creation
1619
+ * @returns {Promise<{record: RecordInstance, created: boolean}>} Result object with record and created flag
1620
+ * @deprecated dont use at all
1621
+ * @example
1622
+ * // Get or create record
1623
+ * const result = await model.getOrCreate(
1624
+ * { email: 'john@example.com' },
1625
+ * { name: 'John Doe', status: 'active' }
1626
+ * );
1627
+ */
1628
+ async getOrCreate(filters, defaults = {}) {
1629
+ console.warn("getOrCreate is deprecated, use select() instead");
1630
+ const existing = await this.selectAll().where(filters).first();
1631
+ if (existing) {
1632
+ // existing is already a RecordInstance from first(), don't wrap it again
1633
+ return { record: existing, created: false };
1634
+ }
1635
+
1636
+ const createData = { ...defaults, ...filters };
1637
+ const newRecord = await this.create(createData);
1638
+ console.warn("getOrCreate is deprecated, use select() instead");
1639
+ return { record: newRecord, created: true };
1640
+ }
1641
+
1642
+ /**
1643
+ * Update or create a record
1644
+ * @param {Object} filters - Filter conditions to find existing record
1645
+ * @param {Object} defaults - Default values for creation/update
1646
+ * @returns {Promise<{record: RecordInstance, created: boolean}>} Result object with record and created flag
1647
+ * @deprecated Use model('object').update(id,updatedData) instead
1648
+ * @example
1649
+ * // Update or create record
1650
+ * const result = await model.updateOrCreate(
1651
+ * { email: 'john@example.com' },
1652
+ * { name: 'John Doe', status: 'active' }
1653
+ * );
1654
+ */
1655
+ async updateOrCreate(filters, defaults = {}) {
1656
+ console.warn(
1657
+ "updateOrCreate is deprecated, use model('object').update(id,updatedData) instead"
1658
+ );
1659
+ const existing = await this.selectAll().where(filters).first();
1660
+ if (existing) {
1661
+ const updated = await this.update(existing.id, defaults);
1662
+ return { record: updated, created: false };
1663
+ }
1664
+
1665
+ const createData = { ...defaults, ...filters };
1666
+ const newRecord = await this.create(createData);
1667
+ return { record: newRecord, created: true };
1668
+ }
1669
+
1670
+ /**
1671
+ * Bulk create records using the bulk_upsert API
1672
+ * @param {Array} records - Array of record data
1673
+ * @param {boolean} ignoreDuplicates - Whether to ignore duplicates (default: true for create)
1674
+ * @returns {Promise<Array<RecordInstance>>} Array of created RecordInstance objects
1675
+ * @example
1676
+ * // Bulk create records
1677
+ * const records = await model.bulkCreate([
1678
+ * { name: 'John', email: 'john@example.com' },
1679
+ * { name: 'Jane', email: 'jane@example.com' }
1680
+ * ]);
1681
+ */
1682
+ async bulkCreate(
1683
+ records,
1684
+ ignoreDuplicates = true,
1685
+ enablePartialUpsert = false
1686
+ ) {
1687
+ const result = await this.sdk.bulkUpsert(
1688
+ this.objectSlug,
1689
+ records,
1690
+ true,
1691
+ enablePartialUpsert
1692
+ );
1693
+ if (result.success) {
1694
+ // Invalidate cache for this object since we created records
1695
+ this.sdk.invalidateCache(this.objectSlug);
1696
+
1697
+ // Handle the response structure - look for records property first, then fall back to data
1698
+ const responseRecords = result.data?.records || [];
1699
+ return responseRecords.map(
1700
+ (recordData) => new RecordInstance(recordData, this)
1701
+ );
1702
+ }
1703
+ throw new Error(result.message || "Bulk create failed");
1704
+ }
1705
+
1706
+ /**
1707
+ * Bulk update records using the bulk_upsert API
1708
+ * @param {Array} records - Array of record data with IDs
1709
+ * @param {boolean} ignoreDuplicates - Whether to ignore duplicates (default: false for update)
1710
+ * @returns {Promise<Array<RecordInstance>>} Array of updated RecordInstance objects
1711
+ * @example
1712
+ * // Bulk update records
1713
+ * const records = await model.bulkUpdate([
1714
+ * { id: 'record-id-1', name: 'Updated John' },
1715
+ * { id: 'record-id-2', email: 'updated@example.com' }
1716
+ * ]);
1717
+ */
1718
+ async bulkUpdate(
1719
+ records,
1720
+ ignoreDuplicates = false,
1721
+ enablePartialUpsert = false
1722
+ ) {
1723
+ const result = await this.sdk.bulkUpsert(
1724
+ this.objectSlug,
1725
+ records,
1726
+ ignoreDuplicates,
1727
+ enablePartialUpsert,
1728
+ true
1729
+ );
1730
+ if (result.success) {
1731
+ // Invalidate cache for this object since we updated records
1732
+ this.sdk.invalidateCache(this.objectSlug);
1733
+
1734
+ // Handle the response structure - look for records property first, then fall back to data
1735
+ const responseRecords = result.data?.records || [];
1736
+ return responseRecords.map(
1737
+ (recordData) => new RecordInstance(recordData, this)
1738
+ );
1739
+ }
1740
+ throw new Error(result.message || "Bulk update failed");
1741
+ }
1742
+ }
1743
+
1744
+ /**
1745
+ * Main SDK class
1746
+ * @class SuperLeapSDK
1747
+ */
1748
+ class SuperLeapSDK {
1749
+ /**
1750
+ * Create a new SuperLeapSDK instance
1751
+ * @param {Object} options - Configuration options
1752
+ * @param {string} options.apiKey - API key for authentication
1753
+ * @param {string} options.baseUrl - Base URL for API requests
1754
+ * @param {string} options.clientId - Client ID for authentication
1755
+ * @param {string} options.clientSecret - Client secret for authentication
1756
+ * @param {Object} options.cache - Cache configuration options
1757
+ * @param {boolean} options.cache.enabled - Enable/disable caching (default: false)
1758
+ * @param {number} options.cache.maxSize - Maximum cache entries (default: 1000)
1759
+ * @param {number} options.cache.defaultTTL - Default TTL in milliseconds (default: 5 minutes)
1760
+ * @param {Object} options.cache.ttl - Specific TTL settings for different operations
1761
+ * @param {number} options.cache.ttl.schema - TTL for schema cache (default: 30 minutes)
1762
+ * @param {number} options.cache.ttl.records - TTL for record queries (default: 2 minutes)
1763
+ * @param {number} options.cache.ttl.count - TTL for count queries (default: 1 minute)
1764
+ */
1765
+ constructor(options = {}) {
1766
+ this.apiKey = options.apiKey || null;
1767
+ this.baseUrl = options.baseUrl || "https://app.superleap.dev/api/v1";
1768
+ this.clientId = options.clientId || "";
1769
+ this.clientSecret = options.clientSecret || "";
1770
+
1771
+ // Validate cache options
1772
+ const cacheOptions = options.cache || {};
1773
+ if (
1774
+ cacheOptions.maxSize &&
1775
+ (typeof cacheOptions.maxSize !== "number" || cacheOptions.maxSize <= 0)
1776
+ ) {
1777
+ console.warn("Invalid cache maxSize, using default: 1000");
1778
+ cacheOptions.maxSize = 1000;
1779
+ }
1780
+ if (
1781
+ cacheOptions.defaultTTL &&
1782
+ (typeof cacheOptions.defaultTTL !== "number" ||
1783
+ cacheOptions.defaultTTL <= 0)
1784
+ ) {
1785
+ console.warn("Invalid cache defaultTTL, using default: 5 minutes");
1786
+ cacheOptions.defaultTTL = 5 * 60 * 1000;
1787
+ }
1788
+
1789
+ // Initialize cache manager
1790
+ this.cache = new CacheManager(cacheOptions);
1791
+
1792
+ // Cache TTL settings with validation
1793
+ this.cacheTTL = {
1794
+ schema:
1795
+ cacheOptions.ttl?.schema &&
1796
+ typeof cacheOptions.ttl.schema === "number" &&
1797
+ cacheOptions.ttl.schema > 0
1798
+ ? cacheOptions.ttl.schema
1799
+ : 30 * 60 * 1000, // 30 minutes
1800
+ records:
1801
+ cacheOptions.ttl?.records &&
1802
+ typeof cacheOptions.ttl.records === "number" &&
1803
+ cacheOptions.ttl.records > 0
1804
+ ? cacheOptions.ttl.records
1805
+ : 2 * 60 * 1000, // 2 minutes
1806
+ count:
1807
+ cacheOptions.ttl?.count &&
1808
+ typeof cacheOptions.ttl.count === "number" &&
1809
+ cacheOptions.ttl.count > 0
1810
+ ? cacheOptions.ttl.count
1811
+ : 60 * 1000, // 1 minute
1812
+ user:
1813
+ cacheOptions.ttl?.user &&
1814
+ typeof cacheOptions.ttl.user === "number" &&
1815
+ cacheOptions.ttl.user > 0
1816
+ ? cacheOptions.ttl.user
1817
+ : 10 * 60 * 1000, // 10 minutes
1818
+ };
1819
+
1820
+ this._models = new Map();
1821
+ this._schemas = new Map();
1822
+ }
1823
+
1824
+ /**
1825
+ * Get model by object slug (creates if not exists)
1826
+ * @param {string} objectSlug - Object slug
1827
+ * @returns {Model} Model instance
1828
+ * @example
1829
+ * // Get model for 'user' object
1830
+ * const userModel = sdk.getModel('user');
1831
+ */
1832
+ getModel(objectSlug) {
1833
+ if (!this._models.has(objectSlug)) {
1834
+ this._models.set(objectSlug, new Model(objectSlug, this));
1835
+ }
1836
+ return this._models.get(objectSlug);
1837
+ }
1838
+
1839
+ async getCurrentUserData() {
1840
+ const result = await this.request("/org/user/me", {
1841
+ cache: {
1842
+ useCache: false,
1843
+ ttl: this.cacheTTL.user,
1844
+ },
1845
+ });
1846
+ return result?.data;
1847
+ }
1848
+
1849
+ async getCurrentUser() {
1850
+ const result = await this.request("/org/user/me", {
1851
+ cache: {
1852
+ useCache: false,
1853
+ ttl: this.cacheTTL.user,
1854
+ },
1855
+ });
1856
+
1857
+ const userData = {
1858
+ user: {
1859
+ id: result.data.user_id,
1860
+ name: result.data.user_name,
1861
+ email: result.data.user_email,
1862
+ },
1863
+ orgId: result.data.org_id,
1864
+ timezone: result.data.timezone,
1865
+ };
1866
+
1867
+ return userData;
1868
+ }
1869
+
1870
+ /**
1871
+ * Get model using property access
1872
+ * @param {string} objectSlug - Object slug
1873
+ * @returns {Model} Model instance
1874
+ * @example
1875
+ * // Get model using property access
1876
+ * const userModel = sdk.model('user');
1877
+ */
1878
+ model(objectSlug) {
1879
+ return this.getModel(objectSlug);
1880
+ }
1881
+
1882
+ /**
1883
+ * Get schema by object slug (cached)
1884
+ * @param {string} objectSlug - Object slug
1885
+ * @returns {Promise<Object>} Schema object
1886
+ * @example
1887
+ * // Get object schema
1888
+ * const schema = await sdk.getObjectSchema('user');
1889
+ */
1890
+ async getObjectSchema(objectSlug) {
1891
+ try {
1892
+ console.log(`Fetching schema from API for ${objectSlug}`);
1893
+ const result = await this.request(`/org/objects/${objectSlug}`, {
1894
+ cache: {
1895
+ useCache: false,
1896
+ ttl: this.cacheTTL.schema,
1897
+ },
1898
+ });
1899
+
1900
+ if (result.success && result.data) {
1901
+ console.log(`Successfully fetched schema for ${objectSlug}`);
1902
+ // Also update legacy cache for backward compatibility
1903
+ this._schemas.set(objectSlug, result.data);
1904
+ } else {
1905
+ console.warn(
1906
+ `Failed to fetch schema for ${objectSlug}:`,
1907
+ result.message
1908
+ );
1909
+ }
1910
+
1911
+ return result;
1912
+ } catch (error) {
1913
+ console.error(`Error fetching schema for ${objectSlug}:`, error);
1914
+ return {
1915
+ success: false,
1916
+ data: null,
1917
+ message: error instanceof Error ? error.message : "Unknown error",
1918
+ };
1919
+ }
1920
+ }
1921
+
1922
+ /**
1923
+ * Check if a request is cacheable
1924
+ * @param {string} endpoint - API endpoint
1925
+ * @param {string} method - HTTP method
1926
+ * @param {Object} body - Request body
1927
+ * @returns {boolean} True if request is cacheable
1928
+ * @private
1929
+ */
1930
+ _isCacheableRequest(endpoint, method, body) {
1931
+ // Cache GET requests
1932
+ if (method === "GET") return true;
1933
+
1934
+ // Cache certain POST requests that are read-only
1935
+ if (method === "POST") {
1936
+ // Cache filter requests
1937
+ if (endpoint.includes("/filter") && !endpoint.includes("/delete")) {
1938
+ return true;
1939
+ }
1940
+ // Cache count requests
1941
+ if (endpoint.includes("/filter/count")) {
1942
+ return true;
1943
+ }
1944
+ // Cache schema requests
1945
+ if (
1946
+ endpoint.includes("/org/objects/") &&
1947
+ !endpoint.includes("/record")
1948
+ ) {
1949
+ return true;
1950
+ }
1951
+ }
1952
+
1953
+ return false;
1954
+ }
1955
+
1956
+ /**
1957
+ * Generate cache key for a request
1958
+ * @param {string} endpoint - API endpoint
1959
+ * @param {string} method - HTTP method
1960
+ * @param {Object} body - Request body
1961
+ * @returns {string} Cache key
1962
+ * @private
1963
+ */
1964
+ _generateCacheKey(endpoint, method, body) {
1965
+ const keyParts = [method, endpoint];
1966
+
1967
+ if (body && Object.keys(body).length > 0) {
1968
+ try {
1969
+ // Sort body keys for consistent cache keys
1970
+ const sortedBody = Object.keys(body)
1971
+ .sort()
1972
+ .reduce((result, key) => {
1973
+ result[key] = body[key];
1974
+ return result;
1975
+ }, {});
1976
+ keyParts.push(JSON.stringify(sortedBody));
1977
+ } catch (error) {
1978
+ // Handle circular references or other JSON.stringify errors
1979
+ console.warn(
1980
+ "Failed to generate cache key, using fallback:",
1981
+ error.message
1982
+ );
1983
+ keyParts.push("fallback:" + Date.now());
1984
+ }
1985
+ }
1986
+
1987
+ return keyParts.join(":");
1988
+ }
1989
+
1990
+ /**
1991
+ * Get default TTL for an endpoint
1992
+ * @param {string} endpoint - API endpoint
1993
+ * @returns {number} TTL in milliseconds
1994
+ * @private
1995
+ */
1996
+ _getDefaultTTL(endpoint) {
1997
+ // Schema requests - long TTL
1998
+ if (endpoint.includes("/org/objects/") && !endpoint.includes("/record")) {
1999
+ return this.cacheTTL.schema;
2000
+ }
2001
+
2002
+ // User data requests
2003
+ if (endpoint.includes("/org/user/me")) {
2004
+ return this.cacheTTL.user;
2005
+ }
2006
+
2007
+ // Count requests - short TTL
2008
+ if (endpoint.includes("/filter/count")) {
2009
+ return this.cacheTTL.count;
2010
+ }
2011
+
2012
+ // Record filter requests - medium TTL
2013
+ if (endpoint.includes("/filter")) {
2014
+ return this.cacheTTL.records;
2015
+ }
2016
+
2017
+ // Default TTL
2018
+ return this.cache.defaultTTL;
2019
+ }
2020
+
2021
+ /**
2022
+ * Set the API key for authentication
2023
+ * @param {string} apiKey - The Bearer token
2024
+ * @example
2025
+ * // Set API key
2026
+ * sdk.setApiKey('your-api-key');
2027
+ */
2028
+ setApiKey(apiKey) {
2029
+ this.apiKey = apiKey;
2030
+ }
2031
+
2032
+ /**
2033
+ * Set client credentials
2034
+ * @param {string} clientId - Client ID
2035
+ * @param {string} clientSecret - Client Secret
2036
+ * @example
2037
+ * // Set client credentials
2038
+ * sdk.setClientCredentials('client-id', 'client-secret');
2039
+ */
2040
+ setClientCredentials(clientId, clientSecret) {
2041
+ this.clientId = clientId;
2042
+ this.clientSecret = clientSecret;
2043
+ }
2044
+
2045
+ /**
2046
+ * Enable or disable caching
2047
+ * @param {boolean} enabled - Whether to enable caching
2048
+ * @example
2049
+ * // Disable caching
2050
+ * sdk.setCacheEnabled(false);
2051
+ */
2052
+ setCacheEnabled(enabled) {
2053
+ this.cache.enabled = enabled;
2054
+ }
2055
+
2056
+ /**
2057
+ * Clear all cache entries
2058
+ * @example
2059
+ * // Clear all cache
2060
+ * sdk.clearCache();
2061
+ */
2062
+ clearCache() {
2063
+ this.cache.clear();
2064
+ this._schemas.clear();
2065
+ this._models.clear();
2066
+ }
2067
+
2068
+ /**
2069
+ * Get cache statistics
2070
+ * @returns {Object} Cache statistics
2071
+ * @example
2072
+ * // Get cache stats
2073
+ * const stats = sdk.getCacheStats();
2074
+ * console.log('Hit rate:', stats.hitRate);
2075
+ */
2076
+ getCacheStats() {
2077
+ return this.cache.getStats();
2078
+ }
2079
+
2080
+ /**
2081
+ * Invalidate cache for specific object
2082
+ * @param {string} objectSlug - Object slug to invalidate
2083
+ * @example
2084
+ * // Invalidate cache for user object
2085
+ * sdk.invalidateCache('user');
2086
+ */
2087
+ invalidateCache(objectSlug) {
2088
+ if (objectSlug) {
2089
+ try {
2090
+ // Clear cache entries that match the object slug
2091
+ const keysToDelete = [];
2092
+ for (const [key] of this.cache.storage.entries()) {
2093
+ if (
2094
+ key.includes(`/org/objects/${objectSlug}`) ||
2095
+ key.includes(`schema:${objectSlug}`) ||
2096
+ key.includes(`fields:${objectSlug}`)
2097
+ ) {
2098
+ keysToDelete.push(key);
2099
+ }
2100
+ }
2101
+
2102
+ keysToDelete.forEach((key) => this.cache.delete(key));
2103
+ this._schemas.delete(objectSlug);
2104
+ this._models.delete(objectSlug);
2105
+ } catch (error) {
2106
+ console.warn("Error during cache invalidation:", error.message);
2107
+ // Fallback: clear all cache
2108
+ this.clearCache();
2109
+ }
2110
+ } else {
2111
+ // Clear all cache
2112
+ this.clearCache();
2113
+ }
2114
+ }
2115
+
2116
+ /**
2117
+ * Clean expired cache entries
2118
+ * @returns {number} Number of entries cleaned
2119
+ * @example
2120
+ * // Clean expired entries
2121
+ * const cleaned = sdk.cleanExpiredCache();
2122
+ */
2123
+ cleanExpiredCache() {
2124
+ return this.cache.cleanExpired();
2125
+ }
2126
+
2127
+ /**
2128
+ * Make a request to the API
2129
+ * @param {string} endpoint - API endpoint
2130
+ * @param {Object} options - Request options
2131
+ * @param {string} options.method - HTTP method
2132
+ * @param {Object} options.headers - Request headers
2133
+ * @param {Object|FormData} options.body - Request body
2134
+ * @param {Object} options.cache - Cache options
2135
+ * @param {boolean} options.cache.useCache - Whether to use cache (default: false)
2136
+ * @param {number} options.cache.ttl - Custom TTL for this request
2137
+ * @param {string} options.cache.key - Custom cache key (auto-generated if not provided)
2138
+ * @returns {Promise<Object>} Response object
2139
+ * @example
2140
+ * // Make a GET request
2141
+ * const response = await sdk.request('/org/objects');
2142
+ *
2143
+ * // Make a POST request
2144
+ * const response = await sdk.request('/org/objects/user/record', {
2145
+ * method: 'POST',
2146
+ * body: { name: 'John Doe' }
2147
+ * });
2148
+ *
2149
+ * // Make a request with custom cache options
2150
+ * const response = await sdk.request('/org/objects/user/filter', {
2151
+ * method: 'POST',
2152
+ * body: { filters: { and: [{ field: 'status', operator: 'eq', value: 'active' }] } },
2153
+ * cache: { useCache: true, ttl: 60 * 1000 }
2154
+ * });
2155
+ */
2156
+ async request(endpoint, options = {}) {
2157
+ const { method = "GET", headers = {}, body, cache = {} } = options;
2158
+ const { useCache = false, ttl = null, key = null } = cache;
2159
+
2160
+ const requestHeaders = {
2161
+ "Content-Type": "application/json",
2162
+ "Client-ID": this.clientId,
2163
+ "Client-Secret": this.clientSecret,
2164
+ ...headers,
2165
+ };
2166
+
2167
+ if (this.apiKey) {
2168
+ requestHeaders.Authorization = `Bearer ${this.apiKey}`;
2169
+ }
2170
+
2171
+ const isFormData = body instanceof FormData;
2172
+ if (isFormData) {
2173
+ delete requestHeaders["Content-Type"];
2174
+ }
2175
+
2176
+ const url = `${this.baseUrl}${
2177
+ endpoint.startsWith("/") ? endpoint : `/${endpoint}`
2178
+ }`;
2179
+
2180
+ // Handle caching for GET requests and certain POST requests
2181
+ if (
2182
+ useCache &&
2183
+ (method === "GET" || this._isCacheableRequest(endpoint, method, body))
2184
+ ) {
2185
+ // Generate cache key if not provided
2186
+ const cacheKey = key || this._generateCacheKey(endpoint, method, body);
2187
+
2188
+ // Check cache first
2189
+ const cached = this.cache.get(cacheKey);
2190
+ if (cached) {
2191
+ console.log(`Using cached response for ${endpoint}`);
2192
+ async function nextTick(value) {
2193
+ return new Promise((resolve) => {
2194
+ setTimeout(() => resolve(value), 0);
2195
+ });
2196
+ }
2197
+
2198
+ return nextTick(cached);
2199
+ }
2200
+
2201
+ // Make the actual request
2202
+ const result = await this._makeRequest(
2203
+ url,
2204
+ method,
2205
+ requestHeaders,
2206
+ body,
2207
+ isFormData
2208
+ );
2209
+
2210
+ // Cache successful responses
2211
+ if (result.success) {
2212
+ const cacheTTL = ttl || this._getDefaultTTL(endpoint);
2213
+ this.cache.set(cacheKey, result, cacheTTL);
2214
+ }
2215
+
2216
+ return result;
2217
+ }
2218
+
2219
+ // For non-cacheable requests, make the request directly
2220
+ return await this._makeRequest(
2221
+ url,
2222
+ method,
2223
+ requestHeaders,
2224
+ body,
2225
+ isFormData
2226
+ );
2227
+ }
2228
+
2229
+ /**
2230
+ * Make the actual HTTP request (internal method)
2231
+ * @param {string} url - Full URL
2232
+ * @param {string} method - HTTP method
2233
+ * @param {Object} headers - Request headers
2234
+ * @param {Object|FormData} body - Request body
2235
+ * @param {boolean} isFormData - Whether body is FormData
2236
+ * @returns {Promise<Object>} Response object
2237
+ * @private
2238
+ */
2239
+ async _makeRequest(url, method, headers, body, isFormData) {
2240
+ try {
2241
+ const response = await fetch(url, {
2242
+ method,
2243
+ headers: headers,
2244
+ body: isFormData ? body : body ? JSON.stringify(body) : undefined,
2245
+ });
2246
+
2247
+ // Check if response is JSON before parsing
2248
+ const contentType = response.headers.get("content-type");
2249
+ let responseData = {};
2250
+
2251
+ if (contentType && contentType.includes("application/json")) {
2252
+ try {
2253
+ responseData = await response.json();
2254
+ } catch (jsonError) {
2255
+ console.warn("Failed to parse JSON response:", jsonError);
2256
+ responseData = { message: await response.text() };
2257
+ }
2258
+ } else {
2259
+ // Handle non-JSON responses (like HTML error pages)
2260
+ const textResponse = await response.text();
2261
+ responseData = { message: textResponse };
2262
+ }
2263
+
2264
+ return {
2265
+ data: responseData.data || null,
2266
+ status: response.status,
2267
+ success: response.ok,
2268
+ message: responseData.message || response.statusText,
2269
+ };
2270
+ } catch (error) {
2271
+ console.error("API request failed:", error);
2272
+ return {
2273
+ data: error,
2274
+ status: 0,
2275
+ success: false,
2276
+ message:
2277
+ error instanceof Error ? error.message : "Unknown error occurred",
2278
+ };
2279
+ }
2280
+ }
2281
+
2282
+ /**
2283
+ * Get records with filtering
2284
+ * @param {string} objectSlug - Object slug
2285
+ * @param {Object} filtersData - Filter data
2286
+ * @param {Object} options - Additional options
2287
+ * @param {boolean} options.useCache - Whether to use cache (default: false)
2288
+ * @param {number} options.cacheTTL - Custom TTL for this request
2289
+ * @returns {Promise<Object>} Response object
2290
+ * @example
2291
+ * // Get records with filters
2292
+ * const response = await sdk.getRecords('user', {
2293
+ * filters: { and: [{ field: 'status', operator: 'eq', value: 'active' }] }
2294
+ * });
2295
+ *
2296
+ * // Get records with cache
2297
+ * const response = await sdk.getRecords('user', filtersData, { useCache: true });
2298
+ */
2299
+ async getRecords(objectSlug, filtersData = null, options = {}) {
2300
+ const { useCache = false, cacheTTL = null } = options;
2301
+
2302
+ return this.request(`/org/objects/${objectSlug}/filter`, {
2303
+ method: "POST",
2304
+ body: { query: filtersData },
2305
+ cache: {
2306
+ useCache,
2307
+ ttl: cacheTTL || this.cacheTTL.records,
2308
+ },
2309
+ });
2310
+ }
2311
+
2312
+ /**
2313
+ * Get record by ID
2314
+ * @param {string} objectSlug - Object slug
2315
+ * @param {string} id - Record ID
2316
+ * @returns {Promise<Object>} Response object
2317
+ * @example
2318
+ * // Get record by ID
2319
+ * const response = await sdk.getRecordById('user', 'user-id');
2320
+ */
2321
+ async getRecordById(objectSlug, id) {
2322
+ return this.request(`/org/objects/${objectSlug}/record/${id}`, {
2323
+ cache: {
2324
+ useCache: false,
2325
+ ttl: this.cacheTTL.records,
2326
+ },
2327
+ });
2328
+ }
2329
+
2330
+ /**
2331
+ * Create a new record
2332
+ * @param {string} objectSlug - Object slug
2333
+ * @param {Object} createData - Record data
2334
+ * @returns {Promise<Object>} Response object
2335
+ * @example
2336
+ * // Create a new record
2337
+ * const response = await sdk.createRecord('user', {
2338
+ * name: 'John Doe',
2339
+ * email: 'john@example.com'
2340
+ * });
2341
+ */
2342
+ async createRecord(objectSlug, createData) {
2343
+ return this.request(`/org/objects/${objectSlug}/record`, {
2344
+ method: "POST",
2345
+ body: createData,
2346
+ cache: {
2347
+ useCache: false, // Don't cache create operations
2348
+ },
2349
+ });
2350
+ }
2351
+
2352
+ /**
2353
+ * Update a record
2354
+ * @param {string} objectSlug - Object slug
2355
+ * @param {string} id - Record ID
2356
+ * @param {Object} updateData - Update data
2357
+ * @returns {Promise<Object>} Response object
2358
+ * @example
2359
+ * // Update a record
2360
+ * const response = await sdk.updateRecord('user', 'user-id', {
2361
+ * name: 'Updated Name'
2362
+ * });
2363
+ */
2364
+ async updateRecord(objectSlug, id, updateData) {
2365
+ return this.request(`/org/objects/${objectSlug}/record/${id}`, {
2366
+ method: "PUT",
2367
+ body: updateData,
2368
+ cache: {
2369
+ useCache: false, // Don't cache update operations
2370
+ },
2371
+ });
2372
+ }
2373
+
2374
+ /**
2375
+ * Delete a record
2376
+ * @param {string} objectSlug - Object slug
2377
+ * @param {string} id - Record ID
2378
+ * @returns {Promise<Object>} Response object
2379
+ * @example
2380
+ * // Delete a record
2381
+ * const response = await sdk.deleteRecord('user', 'user-id');
2382
+ */
2383
+ async deleteRecord(objectSlug, recordId) {
2384
+ return this.request(`/org/objects/${objectSlug}/record/delete`, {
2385
+ method: "POST",
2386
+ body: { record_ids: [recordId] },
2387
+ cache: {
2388
+ useCache: false, // Don't cache delete operations
2389
+ },
2390
+ });
2391
+ }
2392
+
2393
+ /**
2394
+ * Delete multiple records
2395
+ * @param {string} objectSlug - Object slug
2396
+ * @param {string[]} recordIds - Record IDs
2397
+ * @returns {Promise<Object>} Response object
2398
+ * @example
2399
+ * // Delete multiple records
2400
+ * const response = await sdk.deleteRecords('user', ['id1', 'id2', 'id3']);
2401
+ */
2402
+ async deleteRecords(objectSlug, recordIds) {
2403
+ return this.request(`/org/objects/${objectSlug}/record/delete`, {
2404
+ method: "POST",
2405
+ body: { record_ids: recordIds },
2406
+ cache: {
2407
+ useCache: false, // Don't cache delete operations
2408
+ },
2409
+ });
2410
+ }
2411
+
2412
+ /**
2413
+ * Get record count
2414
+ * @param {string} objectSlug - Object slug
2415
+ * @param {Object} filtersData - Filter data
2416
+ * @param {Object} options - Additional options
2417
+ * @param {boolean} options.useCache - Whether to use cache (default: false)
2418
+ * @param {number} options.cacheTTL - Custom TTL for this request
2419
+ * @returns {Promise<Object>} Response object
2420
+ * @example
2421
+ * // Get record count
2422
+ * const response = await sdk.getRecordsCount('user', {
2423
+ * and: [{ field: 'status', operator: 'eq', value: 'active' }]
2424
+ * });
2425
+ *
2426
+ * // Get count with cache
2427
+ * const response = await sdk.getRecordsCount('user', filtersData, { useCache: true });
2428
+ */
2429
+ async getRecordsCount(objectSlug, filtersData = null, options = {}) {
2430
+ const { useCache = false, cacheTTL = null } = options;
2431
+
2432
+ const query = {};
2433
+ if (filtersData) {
2434
+ query.filters = filtersData;
2435
+ }
2436
+
2437
+ return this.request(`/org/objects/${objectSlug}/filter/count`, {
2438
+ method: "POST",
2439
+ body: { query },
2440
+ cache: {
2441
+ useCache,
2442
+ ttl: cacheTTL || this.cacheTTL.count,
2443
+ },
2444
+ });
2445
+ }
2446
+
2447
+ /**
2448
+ * Bulk upsert records (create or update)
2449
+ * @param {string} objectSlug - Object slug
2450
+ * @param {Array} records - Array of record data
2451
+ * @param {boolean} ignoreDuplicates - Whether to ignore duplicates
2452
+ * @returns {Promise<Object>} Response object
2453
+ * @example
2454
+ * // Bulk upsert records
2455
+ * const response = await sdk.bulkUpsert('lead', [
2456
+ * { name: 'John', email: 'john@example.com' },
2457
+ * { id: 'record-id', name: 'Updated John' }
2458
+ * ], false);
2459
+ */
2460
+ async bulkUpsert(
2461
+ objectSlug,
2462
+ records,
2463
+ ignoreDuplicates = false,
2464
+ enablePartialUpsert = false,
2465
+ disableCreate = false
2466
+ ) {
2467
+ return this.request(`/org/objects/${objectSlug}/record/bulk_upsert`, {
2468
+ method: "POST",
2469
+ body: {
2470
+ values: records,
2471
+ ignore_duplicates: ignoreDuplicates,
2472
+ enable_partial_upsert: enablePartialUpsert,
2473
+ disable_create: disableCreate,
2474
+ },
2475
+ cache: {
2476
+ useCache: false, // Don't cache bulk operations
2477
+ },
2478
+ });
2479
+ }
2480
+
2481
+ /**
2482
+ * Transactional bulk create records across multiple objects
2483
+ * @param {Array} records - Array of objects with object_slug and record properties
2484
+ * @param {boolean} partialUpsert - Whether to enable partial upsert (default: false)
2485
+ * @returns {Promise<Object>} Response object
2486
+ * @example
2487
+ * // Transactional bulk create records across multiple objects
2488
+ * const response = await sdk.transactionalBulkCreate([
2489
+ * { object_slug: 'user', record: { name: 'John', email: 'john@example.com' } },
2490
+ * { object_slug: 'lead', record: { name: 'Jane', email: 'jane@example.com' } }
2491
+ * ]);
2492
+ */
2493
+ async transactionalBulkCreate(records, partialUpsert = false) {
2494
+ // Transform records from [{object_slug:'',record:recordData}] to {object_slug: records[]}
2495
+ const transformedValues = {};
2496
+
2497
+ records.forEach((record) => {
2498
+ const { object_slug, record: recordData } = record;
2499
+ if (!transformedValues[object_slug]) {
2500
+ transformedValues[object_slug] = [];
2501
+ }
2502
+ transformedValues[object_slug].push(recordData);
2503
+ });
2504
+
2505
+ return this.request(`/org/objects/record/bulk_upsert`, {
2506
+ method: "POST",
2507
+ body: {
2508
+ values: transformedValues,
2509
+ ignore_duplicates: true,
2510
+ partial_upsert: partialUpsert,
2511
+ },
2512
+ cache: {
2513
+ useCache: false, // Don't cache bulk operations
2514
+ },
2515
+ });
2516
+ }
2517
+
2518
+ /**
2519
+ * Transactional bulk update records across multiple objects
2520
+ * @param {Array} records - Array of objects with object_slug and record properties (record must contain id)
2521
+ * @param {boolean} partialUpsert - Whether to enable partial upsert (default: false)
2522
+ * @returns {Promise<Object>} Response object
2523
+ * @example
2524
+ * // Transactional bulk update records across multiple objects
2525
+ * const response = await sdk.transactionalBulkUpdate([
2526
+ * { object_slug: 'user', record: { id: 'user-id', name: 'Updated John' } },
2527
+ * { object_slug: 'lead', record: { id: 'lead-id', status: 'qualified' } }
2528
+ * ]);
2529
+ */
2530
+ async transactionalBulkUpdate(records, partialUpsert = false) {
2531
+ // Transform records from [{object_slug:'',record:recordData}] to {object_slug: records[]}
2532
+ const transformedValues = {};
2533
+
2534
+ records.forEach((record) => {
2535
+ const { object_slug, record: recordData } = record;
2536
+ if (!transformedValues[object_slug]) {
2537
+ transformedValues[object_slug] = [];
2538
+ }
2539
+ transformedValues[object_slug].push(recordData);
2540
+ });
2541
+
2542
+ return this.request(`/org/objects/record/bulk_upsert`, {
2543
+ method: "POST",
2544
+ body: {
2545
+ values: transformedValues,
2546
+ ignore_duplicates: false,
2547
+ partial_upsert: partialUpsert,
2548
+ disable_create: true,
2549
+ },
2550
+ cache: {
2551
+ useCache: false, // Don't cache bulk operations
2552
+ },
2553
+ });
2554
+ }
2555
+ /**
2556
+ * Transactional bulk upsert records across multiple objects (create or update)
2557
+ * @param {Array} records - Array of objects with object_slug and record properties (id in record required for updates)
2558
+ * @param {boolean} partialUpsert - Whether to enable partial upsert (default: false)
2559
+ * @returns {Promise<Object>} Response object
2560
+ * @example
2561
+ * // Transactional bulk upsert records across multiple objects
2562
+ * const response = await sdk.transactionalBulkUpsert([
2563
+ * { object_slug: 'user', record: { name: 'John', email: 'john@example.com' } }, // Create
2564
+ * { object_slug: 'lead', record: { id: 'lead-id', status: 'qualified' } } // Update
2565
+ * ]);
2566
+ */
2567
+ async transactionalBulkUpsert(records, partialUpsert = false) {
2568
+ // Transform records from [{object_slug:'',record:recordData}] to {object_slug: records[]}
2569
+ const transformedValues = {};
2570
+
2571
+ records.forEach((record) => {
2572
+ const { object_slug, record: recordData } = record;
2573
+ if (!transformedValues[object_slug]) {
2574
+ transformedValues[object_slug] = [];
2575
+ }
2576
+ transformedValues[object_slug].push(recordData);
2577
+ });
2578
+
2579
+ return this.request(`/org/objects/record/bulk_upsert`, {
2580
+ method: "POST",
2581
+ body: {
2582
+ values: transformedValues,
2583
+ ignore_duplicates: false,
2584
+ partial_upsert: partialUpsert,
2585
+ },
2586
+ cache: {
2587
+ useCache: false, // Don't cache bulk operations
2588
+ },
2589
+ });
2590
+ }
2591
+ }
2592
+
2593
+ /**
2594
+ * Create a new SDK instance
2595
+ * @param {Object} options - Configuration options
2596
+ * @returns {SuperLeapSDK} SuperLeapSDK instance
2597
+ * @example
2598
+ * // Create SDK instance
2599
+ * const sdk = createSuperLeapSDK({
2600
+ * apiKey: 'your-api-key',
2601
+ * baseUrl: 'https://app.superleap.dev/api/v1'
2602
+ * });
2603
+ */
2604
+ function createSuperLeapSDK(options = {}) {
2605
+ return new SuperLeapSDK(options);
2606
+ }
2607
+
2608
+ // Expose to global scope
2609
+ global.SuperLeapSDK = SuperLeapSDK;
2610
+ global.createSuperLeapSDK = createSuperLeapSDK;
2611
+ global.CacheManager = CacheManager;
2612
+ global.CacheEntry = CacheEntry;
2613
+
2614
+ // Support CommonJS and AMD
2615
+ if (typeof module !== "undefined" && module.exports) {
2616
+ module.exports = {
2617
+ SuperLeapSDK,
2618
+ createSuperLeapSDK,
2619
+ DataType,
2620
+ RelationshipType,
2621
+ CacheManager,
2622
+ CacheEntry,
2623
+ Operators,
2624
+ AdvancedDateOperators,
2625
+ };
2626
+ } else if (typeof define === "function" && define.amd) {
2627
+ define([], function () {
2628
+ return {
2629
+ SuperLeapSDK,
2630
+ createSuperLeapSDK,
2631
+ DataType,
2632
+ RelationshipType,
2633
+ CacheManager,
2634
+ CacheEntry,
2635
+ Operators,
2636
+ AdvancedDateOperators,
2637
+ };
2638
+ });
2639
+ }
2640
+ })(typeof window !== "undefined" ? window : undefined);
2641
+
2642
+
2643
+
2644
+ // ============================================
2645
+ // File 2/37: core/superleapClient.js
19
2646
  // ============================================
20
2647
 
21
2648
  /**
@@ -167,7 +2794,7 @@
167
2794
 
168
2795
 
169
2796
  // ============================================
170
- // File 2/36: core/bridge.js
2797
+ // File 3/37: core/bridge.js
171
2798
  // ============================================
172
2799
 
173
2800
  /**
@@ -695,7 +3322,7 @@
695
3322
 
696
3323
 
697
3324
  // ============================================
698
- // File 3/36: core/crm.js
3325
+ // File 4/37: core/crm.js
699
3326
  // ============================================
700
3327
 
701
3328
  /**
@@ -1037,7 +3664,7 @@
1037
3664
 
1038
3665
 
1039
3666
  // ============================================
1040
- // File 4/36: components/label.js
3667
+ // File 5/37: components/label.js
1041
3668
  // ============================================
1042
3669
 
1043
3670
  /**
@@ -1154,7 +3781,7 @@
1154
3781
 
1155
3782
 
1156
3783
  // ============================================
1157
- // File 5/36: core/flow.js
3784
+ // File 6/37: core/flow.js
1158
3785
  // ============================================
1159
3786
 
1160
3787
  /**
@@ -2889,7 +5516,7 @@
2889
5516
 
2890
5517
 
2891
5518
  // ============================================
2892
- // File 6/36: components/toast.js
5519
+ // File 7/37: components/toast.js
2893
5520
  // ============================================
2894
5521
 
2895
5522
  /**
@@ -3238,7 +5865,7 @@
3238
5865
 
3239
5866
 
3240
5867
  // ============================================
3241
- // File 7/36: components/alert.js
5868
+ // File 8/37: components/alert.js
3242
5869
  // ============================================
3243
5870
 
3244
5871
  /**
@@ -3526,7 +6153,7 @@
3526
6153
 
3527
6154
 
3528
6155
  // ============================================
3529
- // File 8/36: components/button.js
6156
+ // File 9/37: components/button.js
3530
6157
  // ============================================
3531
6158
 
3532
6159
  /**
@@ -3733,7 +6360,7 @@
3733
6360
 
3734
6361
 
3735
6362
  // ============================================
3736
- // File 9/36: components/spinner.js
6363
+ // File 10/37: components/spinner.js
3737
6364
  // ============================================
3738
6365
 
3739
6366
  /**
@@ -3875,7 +6502,7 @@
3875
6502
 
3876
6503
 
3877
6504
  // ============================================
3878
- // File 10/36: components/badge.js
6505
+ // File 11/37: components/badge.js
3879
6506
  // ============================================
3880
6507
 
3881
6508
  /**
@@ -4016,7 +6643,7 @@
4016
6643
 
4017
6644
 
4018
6645
  // ============================================
4019
- // File 11/36: components/avatar.js
6646
+ // File 12/37: components/avatar.js
4020
6647
  // ============================================
4021
6648
 
4022
6649
  /**
@@ -4217,7 +6844,7 @@
4217
6844
 
4218
6845
 
4219
6846
  // ============================================
4220
- // File 12/36: components/icon.js
6847
+ // File 13/37: components/icon.js
4221
6848
  // ============================================
4222
6849
 
4223
6850
  /**
@@ -4423,7 +7050,7 @@
4423
7050
 
4424
7051
 
4425
7052
  // ============================================
4426
- // File 13/36: components/popover.js
7053
+ // File 14/37: components/popover.js
4427
7054
  // ============================================
4428
7055
 
4429
7056
  /**
@@ -4669,7 +7296,7 @@
4669
7296
 
4670
7297
 
4671
7298
  // ============================================
4672
- // File 14/36: components/select.js
7299
+ // File 15/37: components/select.js
4673
7300
  // ============================================
4674
7301
 
4675
7302
  /**
@@ -5219,7 +7846,7 @@
5219
7846
 
5220
7847
 
5221
7848
  // ============================================
5222
- // File 15/36: components/enum-select.js
7849
+ // File 16/37: components/enum-select.js
5223
7850
  // ============================================
5224
7851
 
5225
7852
  /**
@@ -6051,7 +8678,7 @@
6051
8678
 
6052
8679
 
6053
8680
  // ============================================
6054
- // File 16/36: components/record-select.js
8681
+ // File 17/37: components/record-select.js
6055
8682
  // ============================================
6056
8683
 
6057
8684
  /**
@@ -6968,7 +9595,7 @@
6968
9595
 
6969
9596
 
6970
9597
  // ============================================
6971
- // File 17/36: components/multiselect.js
9598
+ // File 18/37: components/multiselect.js
6972
9599
  // ============================================
6973
9600
 
6974
9601
  /**
@@ -7325,7 +9952,7 @@
7325
9952
 
7326
9953
 
7327
9954
  // ============================================
7328
- // File 18/36: components/enum-multiselect.js
9955
+ // File 19/37: components/enum-multiselect.js
7329
9956
  // ============================================
7330
9957
 
7331
9958
  /**
@@ -8195,7 +10822,7 @@
8195
10822
 
8196
10823
 
8197
10824
  // ============================================
8198
- // File 19/36: components/record-multiselect.js
10825
+ // File 20/37: components/record-multiselect.js
8199
10826
  // ============================================
8200
10827
 
8201
10828
  /**
@@ -9157,7 +11784,7 @@
9157
11784
 
9158
11785
 
9159
11786
  // ============================================
9160
- // File 20/36: components/input.js
11787
+ // File 21/37: components/input.js
9161
11788
  // ============================================
9162
11789
 
9163
11790
  /**
@@ -9422,7 +12049,7 @@
9422
12049
 
9423
12050
 
9424
12051
  // ============================================
9425
- // File 21/36: components/currency.js
12052
+ // File 22/37: components/currency.js
9426
12053
  // ============================================
9427
12054
 
9428
12055
  /**
@@ -9655,7 +12282,7 @@
9655
12282
 
9656
12283
 
9657
12284
  // ============================================
9658
- // File 22/36: components/textarea.js
12285
+ // File 23/37: components/textarea.js
9659
12286
  // ============================================
9660
12287
 
9661
12288
  /**
@@ -9775,7 +12402,7 @@
9775
12402
 
9776
12403
 
9777
12404
  // ============================================
9778
- // File 23/36: components/checkbox.js
12405
+ // File 24/37: components/checkbox.js
9779
12406
  // ============================================
9780
12407
 
9781
12408
  /**
@@ -10035,7 +12662,7 @@
10035
12662
 
10036
12663
 
10037
12664
  // ============================================
10038
- // File 24/36: components/radio-group.js
12665
+ // File 25/37: components/radio-group.js
10039
12666
  // ============================================
10040
12667
 
10041
12668
  /**
@@ -10446,7 +13073,7 @@
10446
13073
 
10447
13074
 
10448
13075
  // ============================================
10449
- // File 25/36: components/enumeration.js
13076
+ // File 26/37: components/enumeration.js
10450
13077
  // ============================================
10451
13078
 
10452
13079
  /**
@@ -10665,7 +13292,7 @@
10665
13292
 
10666
13293
 
10667
13294
  // ============================================
10668
- // File 26/36: components/time-picker.js
13295
+ // File 27/37: components/time-picker.js
10669
13296
  // ============================================
10670
13297
 
10671
13298
  /**
@@ -11028,7 +13655,7 @@
11028
13655
 
11029
13656
 
11030
13657
  // ============================================
11031
- // File 27/36: components/duration/duration-utils.js
13658
+ // File 28/37: components/duration/duration-utils.js
11032
13659
  // ============================================
11033
13660
 
11034
13661
  /**
@@ -11198,7 +13825,7 @@
11198
13825
 
11199
13826
 
11200
13827
  // ============================================
11201
- // File 28/36: components/duration/duration-constants.js
13828
+ // File 29/37: components/duration/duration-constants.js
11202
13829
  // ============================================
11203
13830
 
11204
13831
  /**
@@ -11250,7 +13877,7 @@
11250
13877
 
11251
13878
 
11252
13879
  // ============================================
11253
- // File 29/36: components/duration/duration.js
13880
+ // File 30/37: components/duration/duration.js
11254
13881
  // ============================================
11255
13882
 
11256
13883
  /**
@@ -11704,7 +14331,7 @@
11704
14331
 
11705
14332
 
11706
14333
  // ============================================
11707
- // File 30/36: components/date-time-picker/date-time-picker-utils.js
14334
+ // File 31/37: components/date-time-picker/date-time-picker-utils.js
11708
14335
  // ============================================
11709
14336
 
11710
14337
  /**
@@ -11963,7 +14590,7 @@
11963
14590
 
11964
14591
 
11965
14592
  // ============================================
11966
- // File 31/36: components/date-time-picker/date-time-picker.js
14593
+ // File 32/37: components/date-time-picker/date-time-picker.js
11967
14594
  // ============================================
11968
14595
 
11969
14596
  /**
@@ -12499,7 +15126,7 @@
12499
15126
 
12500
15127
 
12501
15128
  // ============================================
12502
- // File 32/36: components/phone-input/phone-utils.js
15129
+ // File 33/37: components/phone-input/phone-utils.js
12503
15130
  // ============================================
12504
15131
 
12505
15132
  /**
@@ -12662,7 +15289,7 @@
12662
15289
 
12663
15290
 
12664
15291
  // ============================================
12665
- // File 33/36: components/phone-input/phone-input.js
15292
+ // File 34/37: components/phone-input/phone-input.js
12666
15293
  // ============================================
12667
15294
 
12668
15295
  /**
@@ -13060,7 +15687,7 @@
13060
15687
 
13061
15688
 
13062
15689
  // ============================================
13063
- // File 34/36: components/file-input.js
15690
+ // File 35/37: components/file-input.js
13064
15691
  // ============================================
13065
15692
 
13066
15693
  /**
@@ -13599,7 +16226,7 @@
13599
16226
 
13600
16227
 
13601
16228
  // ============================================
13602
- // File 35/36: components/table.js
16229
+ // File 36/37: components/table.js
13603
16230
  // ============================================
13604
16231
 
13605
16232
  /**
@@ -13940,7 +16567,7 @@
13940
16567
 
13941
16568
 
13942
16569
  // ============================================
13943
- // File 36/36: index.js
16570
+ // File 37/37: index.js
13944
16571
  // ============================================
13945
16572
 
13946
16573
  /**
@@ -14177,7 +16804,7 @@
14177
16804
  },
14178
16805
  });
14179
16806
  document.dispatchEvent(event);
14180
- console.log("[Superleap-Flow] Library ready - v2.1.1");
16807
+ console.log("[Superleap-Flow] Library ready - v2.2.2");
14181
16808
  }, 0);
14182
16809
  }
14183
16810
  }