@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.
- package/dist/superleap-flow.js +2666 -39
- package/dist/superleap-flow.js.map +1 -1
- package/dist/superleap-flow.min.js +2 -2
- package/index.js +1 -1
- package/package.json +1 -1
package/dist/superleap-flow.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @superleapai/flow-ui v2.
|
|
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:
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
16807
|
+
console.log("[Superleap-Flow] Library ready - v2.2.2");
|
|
14181
16808
|
}, 0);
|
|
14182
16809
|
}
|
|
14183
16810
|
}
|