quickbase-js 2.3.0 → 2.4.1
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/README.md +145 -1107
- package/dist/auth/ticket.d.ts.map +1 -1
- package/dist/auth/ticket.js +4 -3
- package/dist/auth/ticket.js.map +1 -1
- package/dist/client/request.d.ts +4 -0
- package/dist/client/request.d.ts.map +1 -1
- package/dist/client/request.js +15 -3
- package/dist/client/request.js.map +1 -1
- package/dist/generated/client.d.ts +2 -0
- package/dist/generated/client.d.ts.map +1 -1
- package/dist/generated/client.js +134 -113
- package/dist/generated/client.js.map +1 -1
- package/dist/generated/types.d.ts +2700 -2256
- package/dist/generated/types.d.ts.map +1 -1
- package/dist/generated/types.js +0 -2
- package/dist/generated/types.js.map +1 -1
- package/dist/quickbase.esm.js +3 -3
- package/dist/quickbase.esm.js.map +3 -3
- package/dist/quickbase.min.js +3 -3
- package/dist/quickbase.min.js.map +3 -3
- package/package.json +16 -13
- package/src/auth/index.ts +55 -0
- package/src/auth/sso-token.ts +143 -0
- package/src/auth/temp-token.ts +160 -0
- package/src/auth/ticket.ts +329 -0
- package/src/auth/token-cache.ts +97 -0
- package/src/auth/types.ts +50 -0
- package/src/auth/user-token.ts +38 -0
- package/src/cli/schema.ts +512 -0
- package/src/client/index.ts +36 -0
- package/src/client/pagination.ts +337 -0
- package/src/client/request.ts +544 -0
- package/src/client/retry.ts +169 -0
- package/src/client/throttle.ts +105 -0
- package/src/core/config.ts +160 -0
- package/src/core/dates.ts +63 -0
- package/src/core/errors.ts +349 -0
- package/src/core/index.ts +50 -0
- package/src/core/logger.ts +113 -0
- package/src/core/schema.ts +213 -0
- package/src/core/transform.ts +295 -0
- package/src/core/types.ts +337 -0
- package/src/generated/client.ts +1785 -0
- package/src/generated/operations.json +672 -0
- package/src/generated/types.ts +4884 -0
- package/src/index.ts +259 -0
- package/src/xml/codepages.ts +119 -0
- package/src/xml/dbvars.ts +75 -0
- package/src/xml/errors.ts +133 -0
- package/src/xml/fields.ts +121 -0
- package/src/xml/groups.ts +560 -0
- package/src/xml/html.ts +152 -0
- package/src/xml/index.ts +1196 -0
- package/src/xml/metadata.ts +111 -0
- package/src/xml/records.ts +301 -0
- package/src/xml/request.ts +238 -0
- package/src/xml/roles.ts +321 -0
- package/src/xml/schema.ts +436 -0
- package/src/xml/transform.ts +381 -0
- package/src/xml/types.ts +602 -0
- package/src/xml/users.ts +221 -0
- package/src/xml/webhooks.ts +260 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination support with fluent API
|
|
3
|
+
*
|
|
4
|
+
* Provides automatic pagination for QuickBase API endpoints that return
|
|
5
|
+
* paginated results. Supports both skip-based and token-based pagination.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Fetch single page (default)
|
|
10
|
+
* const page = await client.runQuery({ ... });
|
|
11
|
+
*
|
|
12
|
+
* // Fetch all pages automatically
|
|
13
|
+
* const all = await client.runQuery({ ... }).all();
|
|
14
|
+
*
|
|
15
|
+
* // Fetch with record limit
|
|
16
|
+
* const limited = await client.runQuery({ ... }).paginate({ limit: 500 });
|
|
17
|
+
*
|
|
18
|
+
* // Explicitly single page (if autoPaginate is enabled globally)
|
|
19
|
+
* const single = await client.runQuery({ ... }).noPaginate();
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pagination options for controlling automatic pagination behavior
|
|
25
|
+
*/
|
|
26
|
+
export interface PaginationOptions {
|
|
27
|
+
/** Maximum number of records to fetch across all pages */
|
|
28
|
+
limit?: number;
|
|
29
|
+
/** Starting offset for skip-based pagination */
|
|
30
|
+
skip?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Metadata returned with paginated responses
|
|
35
|
+
*/
|
|
36
|
+
export interface PaginationMetadata {
|
|
37
|
+
totalRecords?: number;
|
|
38
|
+
numRecords?: number;
|
|
39
|
+
numFields?: number;
|
|
40
|
+
skip?: number;
|
|
41
|
+
top?: number;
|
|
42
|
+
nextPageToken?: string;
|
|
43
|
+
nextToken?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A paginated response from the QuickBase API
|
|
48
|
+
*/
|
|
49
|
+
export interface PaginatedResponse<T = unknown> {
|
|
50
|
+
data?: T[];
|
|
51
|
+
fields?: Array<{ id: number; label: string; type: string }>;
|
|
52
|
+
metadata: PaginationMetadata;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Determines the type of pagination from a response
|
|
58
|
+
*/
|
|
59
|
+
export type PaginationType = 'skip' | 'token' | 'none';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect pagination type from response metadata
|
|
63
|
+
*/
|
|
64
|
+
export function detectPaginationType(metadata: PaginationMetadata): PaginationType {
|
|
65
|
+
if (metadata.totalRecords !== undefined && metadata.skip !== undefined) {
|
|
66
|
+
return 'skip';
|
|
67
|
+
}
|
|
68
|
+
if (metadata.nextPageToken !== undefined || metadata.nextToken !== undefined) {
|
|
69
|
+
return 'token';
|
|
70
|
+
}
|
|
71
|
+
return 'none';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if a response has more pages available
|
|
76
|
+
*/
|
|
77
|
+
export function hasMorePages(
|
|
78
|
+
response: PaginatedResponse,
|
|
79
|
+
paginationType: PaginationType,
|
|
80
|
+
currentCount: number
|
|
81
|
+
): boolean {
|
|
82
|
+
const { metadata } = response;
|
|
83
|
+
|
|
84
|
+
if (paginationType === 'skip') {
|
|
85
|
+
return (
|
|
86
|
+
metadata.totalRecords !== undefined &&
|
|
87
|
+
currentCount < metadata.totalRecords
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (paginationType === 'token') {
|
|
92
|
+
const nextToken = metadata.nextPageToken ?? metadata.nextToken;
|
|
93
|
+
return nextToken !== undefined && nextToken !== '';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find the data array key in a response object
|
|
101
|
+
*/
|
|
102
|
+
export function findDataKey(response: PaginatedResponse): string {
|
|
103
|
+
for (const key of Object.keys(response)) {
|
|
104
|
+
if (Array.isArray(response[key])) {
|
|
105
|
+
return key;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return 'data';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a response looks like a paginated response
|
|
113
|
+
*/
|
|
114
|
+
export function isPaginatedResponse(response: unknown): response is PaginatedResponse {
|
|
115
|
+
if (!response || typeof response !== 'object') {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const obj = response as Record<string, unknown>;
|
|
120
|
+
|
|
121
|
+
// Must have metadata object
|
|
122
|
+
if (!obj.metadata || typeof obj.metadata !== 'object') {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Must have an array field
|
|
127
|
+
const hasArrayField = Object.values(obj).some(Array.isArray);
|
|
128
|
+
if (!hasArrayField) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const metadata = obj.metadata as PaginationMetadata;
|
|
133
|
+
|
|
134
|
+
// Check for pagination indicators
|
|
135
|
+
return (
|
|
136
|
+
metadata.totalRecords !== undefined ||
|
|
137
|
+
metadata.numRecords !== undefined ||
|
|
138
|
+
metadata.nextPageToken !== undefined ||
|
|
139
|
+
metadata.nextToken !== undefined
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Request executor function type
|
|
145
|
+
*/
|
|
146
|
+
export type RequestExecutor<T> = () => Promise<T>;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Request executor with pagination parameters
|
|
150
|
+
*/
|
|
151
|
+
export type PaginatedRequestExecutor<T> = (params: {
|
|
152
|
+
skip?: number;
|
|
153
|
+
nextPageToken?: string;
|
|
154
|
+
nextToken?: string;
|
|
155
|
+
}) => Promise<T>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A thenable request that supports fluent pagination methods
|
|
159
|
+
*
|
|
160
|
+
* This class wraps an API request and allows chaining pagination methods
|
|
161
|
+
* while still being directly awaitable.
|
|
162
|
+
*/
|
|
163
|
+
export class PaginatedRequest<T> implements PromiseLike<T> {
|
|
164
|
+
private readonly executor: RequestExecutor<T>;
|
|
165
|
+
private readonly paginatedExecutor?: PaginatedRequestExecutor<T>;
|
|
166
|
+
private readonly defaultAutoPaginate: boolean;
|
|
167
|
+
|
|
168
|
+
constructor(
|
|
169
|
+
executor: RequestExecutor<T>,
|
|
170
|
+
options?: {
|
|
171
|
+
paginatedExecutor?: PaginatedRequestExecutor<T>;
|
|
172
|
+
autoPaginate?: boolean;
|
|
173
|
+
}
|
|
174
|
+
) {
|
|
175
|
+
this.executor = executor;
|
|
176
|
+
this.paginatedExecutor = options?.paginatedExecutor;
|
|
177
|
+
this.defaultAutoPaginate = options?.autoPaginate ?? false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Implement PromiseLike to allow direct awaiting
|
|
182
|
+
* Default behavior respects the global autoPaginate setting
|
|
183
|
+
*/
|
|
184
|
+
then<TResult1 = T, TResult2 = never>(
|
|
185
|
+
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null,
|
|
186
|
+
onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null
|
|
187
|
+
): Promise<TResult1 | TResult2> {
|
|
188
|
+
const promise = this.defaultAutoPaginate ? this.all() : this.executor();
|
|
189
|
+
return promise.then(onfulfilled, onrejected);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Fetch all pages and combine results
|
|
194
|
+
*/
|
|
195
|
+
async all(): Promise<T> {
|
|
196
|
+
return this.paginate({});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Fetch pages with optional limit
|
|
201
|
+
*/
|
|
202
|
+
async paginate(options: PaginationOptions = {}): Promise<T> {
|
|
203
|
+
if (!this.paginatedExecutor) {
|
|
204
|
+
// Not a paginated endpoint, just execute normally
|
|
205
|
+
return this.executor();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const { limit, skip: initialSkip = 0 } = options;
|
|
209
|
+
let allRecords: unknown[] = [];
|
|
210
|
+
let lastResponse: PaginatedResponse | null = null;
|
|
211
|
+
let paginationType: PaginationType = 'none';
|
|
212
|
+
let currentSkip = initialSkip;
|
|
213
|
+
let nextToken: string | undefined;
|
|
214
|
+
|
|
215
|
+
// Fetch first page
|
|
216
|
+
const firstResponse = await this.paginatedExecutor({ skip: initialSkip }) as PaginatedResponse;
|
|
217
|
+
|
|
218
|
+
if (!isPaginatedResponse(firstResponse)) {
|
|
219
|
+
// Not a paginated response, return as-is
|
|
220
|
+
return firstResponse as T;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
lastResponse = firstResponse;
|
|
224
|
+
paginationType = detectPaginationType(firstResponse.metadata);
|
|
225
|
+
|
|
226
|
+
const dataKey = findDataKey(firstResponse);
|
|
227
|
+
const firstPageData = (firstResponse[dataKey] as unknown[]) || [];
|
|
228
|
+
|
|
229
|
+
// Apply limit to first page if needed
|
|
230
|
+
if (limit !== undefined && firstPageData.length > limit) {
|
|
231
|
+
allRecords = firstPageData.slice(0, limit);
|
|
232
|
+
} else {
|
|
233
|
+
allRecords = [...firstPageData];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Initialize continuation state
|
|
237
|
+
if (paginationType === 'skip') {
|
|
238
|
+
currentSkip = initialSkip + firstPageData.length;
|
|
239
|
+
} else if (paginationType === 'token') {
|
|
240
|
+
nextToken = firstResponse.metadata.nextPageToken ?? firstResponse.metadata.nextToken;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Fetch remaining pages
|
|
244
|
+
while (hasMorePages(lastResponse, paginationType, allRecords.length)) {
|
|
245
|
+
// Check limit
|
|
246
|
+
if (limit !== undefined && allRecords.length >= limit) {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Fetch next page
|
|
251
|
+
let nextResponse: PaginatedResponse;
|
|
252
|
+
|
|
253
|
+
if (paginationType === 'skip') {
|
|
254
|
+
nextResponse = await this.paginatedExecutor({ skip: currentSkip }) as PaginatedResponse;
|
|
255
|
+
} else if (paginationType === 'token' && nextToken) {
|
|
256
|
+
const tokenKey = lastResponse.metadata.nextPageToken !== undefined
|
|
257
|
+
? 'nextPageToken'
|
|
258
|
+
: 'nextToken';
|
|
259
|
+
nextResponse = await this.paginatedExecutor({ [tokenKey]: nextToken }) as PaginatedResponse;
|
|
260
|
+
} else {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lastResponse = nextResponse;
|
|
265
|
+
const pageData = (nextResponse[dataKey] as unknown[]) || [];
|
|
266
|
+
|
|
267
|
+
// Check for empty response
|
|
268
|
+
if (pageData.length === 0) {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Apply limit if needed
|
|
273
|
+
if (limit !== undefined) {
|
|
274
|
+
const remainingCapacity = limit - allRecords.length;
|
|
275
|
+
allRecords = allRecords.concat(pageData.slice(0, remainingCapacity));
|
|
276
|
+
} else {
|
|
277
|
+
allRecords = allRecords.concat(pageData);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Update continuation state
|
|
281
|
+
if (paginationType === 'skip') {
|
|
282
|
+
currentSkip += pageData.length;
|
|
283
|
+
} else if (paginationType === 'token') {
|
|
284
|
+
nextToken = nextResponse.metadata.nextPageToken ?? nextResponse.metadata.nextToken;
|
|
285
|
+
if (!nextToken) {
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Build final response
|
|
292
|
+
const finalResponse: PaginatedResponse = {
|
|
293
|
+
...lastResponse,
|
|
294
|
+
[dataKey]: allRecords,
|
|
295
|
+
metadata: {
|
|
296
|
+
...lastResponse.metadata,
|
|
297
|
+
numRecords: allRecords.length,
|
|
298
|
+
skip: initialSkip,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
// Clear pagination tokens in final response
|
|
303
|
+
if (paginationType === 'token') {
|
|
304
|
+
delete finalResponse.metadata.nextPageToken;
|
|
305
|
+
delete finalResponse.metadata.nextToken;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return finalResponse as T;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Explicitly fetch only a single page (no pagination)
|
|
313
|
+
*/
|
|
314
|
+
async noPaginate(): Promise<T> {
|
|
315
|
+
return this.executor();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get the underlying promise (for compatibility)
|
|
320
|
+
*/
|
|
321
|
+
toPromise(): Promise<T> {
|
|
322
|
+
return this.then();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Create a paginated request wrapper
|
|
328
|
+
*/
|
|
329
|
+
export function createPaginatedRequest<T>(
|
|
330
|
+
executor: RequestExecutor<T>,
|
|
331
|
+
options?: {
|
|
332
|
+
paginatedExecutor?: PaginatedRequestExecutor<T>;
|
|
333
|
+
autoPaginate?: boolean;
|
|
334
|
+
}
|
|
335
|
+
): PaginatedRequest<T> {
|
|
336
|
+
return new PaginatedRequest(executor, options);
|
|
337
|
+
}
|