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.
Files changed (62) hide show
  1. package/README.md +145 -1107
  2. package/dist/auth/ticket.d.ts.map +1 -1
  3. package/dist/auth/ticket.js +4 -3
  4. package/dist/auth/ticket.js.map +1 -1
  5. package/dist/client/request.d.ts +4 -0
  6. package/dist/client/request.d.ts.map +1 -1
  7. package/dist/client/request.js +15 -3
  8. package/dist/client/request.js.map +1 -1
  9. package/dist/generated/client.d.ts +2 -0
  10. package/dist/generated/client.d.ts.map +1 -1
  11. package/dist/generated/client.js +134 -113
  12. package/dist/generated/client.js.map +1 -1
  13. package/dist/generated/types.d.ts +2700 -2256
  14. package/dist/generated/types.d.ts.map +1 -1
  15. package/dist/generated/types.js +0 -2
  16. package/dist/generated/types.js.map +1 -1
  17. package/dist/quickbase.esm.js +3 -3
  18. package/dist/quickbase.esm.js.map +3 -3
  19. package/dist/quickbase.min.js +3 -3
  20. package/dist/quickbase.min.js.map +3 -3
  21. package/package.json +16 -13
  22. package/src/auth/index.ts +55 -0
  23. package/src/auth/sso-token.ts +143 -0
  24. package/src/auth/temp-token.ts +160 -0
  25. package/src/auth/ticket.ts +329 -0
  26. package/src/auth/token-cache.ts +97 -0
  27. package/src/auth/types.ts +50 -0
  28. package/src/auth/user-token.ts +38 -0
  29. package/src/cli/schema.ts +512 -0
  30. package/src/client/index.ts +36 -0
  31. package/src/client/pagination.ts +337 -0
  32. package/src/client/request.ts +544 -0
  33. package/src/client/retry.ts +169 -0
  34. package/src/client/throttle.ts +105 -0
  35. package/src/core/config.ts +160 -0
  36. package/src/core/dates.ts +63 -0
  37. package/src/core/errors.ts +349 -0
  38. package/src/core/index.ts +50 -0
  39. package/src/core/logger.ts +113 -0
  40. package/src/core/schema.ts +213 -0
  41. package/src/core/transform.ts +295 -0
  42. package/src/core/types.ts +337 -0
  43. package/src/generated/client.ts +1785 -0
  44. package/src/generated/operations.json +672 -0
  45. package/src/generated/types.ts +4884 -0
  46. package/src/index.ts +259 -0
  47. package/src/xml/codepages.ts +119 -0
  48. package/src/xml/dbvars.ts +75 -0
  49. package/src/xml/errors.ts +133 -0
  50. package/src/xml/fields.ts +121 -0
  51. package/src/xml/groups.ts +560 -0
  52. package/src/xml/html.ts +152 -0
  53. package/src/xml/index.ts +1196 -0
  54. package/src/xml/metadata.ts +111 -0
  55. package/src/xml/records.ts +301 -0
  56. package/src/xml/request.ts +238 -0
  57. package/src/xml/roles.ts +321 -0
  58. package/src/xml/schema.ts +436 -0
  59. package/src/xml/transform.ts +381 -0
  60. package/src/xml/types.ts +602 -0
  61. package/src/xml/users.ts +221 -0
  62. 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
+ }