quickbase-js 0.1.0-beta.13 → 0.1.0-beta.14

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 (167) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/quickbase.d.ts +2 -1
  3. package/package.json +9 -4
  4. package/CHANGELOG.md +0 -239
  5. package/build-common.js +0 -131
  6. package/build-esm.js +0 -71
  7. package/build-umd.js +0 -69
  8. package/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  9. package/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  10. package/docs/.vitepress/cache/deps/_metadata.json +0 -40
  11. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js +0 -12542
  12. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js.map +0 -7
  13. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js +0 -9719
  14. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js.map +0 -7
  15. package/docs/.vitepress/cache/deps/package.json +0 -3
  16. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4504
  17. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  18. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  19. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  20. package/docs/.vitepress/cache/deps/vue.js +0 -343
  21. package/docs/.vitepress/cache/deps/vue.js.map +0 -7
  22. package/docs/.vitepress/config.mts +0 -26
  23. package/docs/api-examples.md +0 -49
  24. package/docs/index.md +0 -59
  25. package/docs/markdown-examples.md +0 -85
  26. package/docs-data/api-docs.json +0 -4143
  27. package/examples/quicbase-jsCDN.html +0 -36
  28. package/examples/react.js +0 -48
  29. package/examples/vanillajs.html +0 -47
  30. package/index.md +0 -25
  31. package/open-api/fix-spec-main.ts +0 -133
  32. package/open-api/generate-unified-interface.ts +0 -104
  33. package/open-api/generateDocsJson.ts +0 -24
  34. package/open-api/output/quickbase-fixed.json +0 -11191
  35. package/open-api/post-process-generated.ts +0 -226
  36. package/open-api/regenerate-client.ts +0 -117
  37. package/open-api/schema/enhance-general.ts +0 -111
  38. package/open-api/schema/fix-arrays.ts +0 -68
  39. package/open-api/schema/tags/apps.ts +0 -90
  40. package/open-api/schema/tags/fields.ts +0 -471
  41. package/open-api/schema/tags/groups.ts +0 -51
  42. package/open-api/schema/tags/index.ts +0 -66
  43. package/open-api/schema/tags/records.ts +0 -115
  44. package/open-api/schema/tags/tables.ts +0 -211
  45. package/open-api/schema/tags/users.ts +0 -47
  46. package/open-api/types/spec.ts +0 -42
  47. package/open-api/utils/common.ts +0 -29
  48. package/open-api/utils/generateJsDoc.ts +0 -74
  49. package/open-api/utils/infer-schema.ts +0 -40
  50. package/open-api/utils/naming.ts +0 -10
  51. package/open-api/utils/sharedUtils.ts +0 -352
  52. package/playwright.config.ts +0 -37
  53. package/rollup.config.js +0 -114
  54. package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +0 -46878
  55. package/specs/split/apps.json +0 -1315
  56. package/specs/split/auth.json +0 -191
  57. package/specs/split/fields.json +0 -3869
  58. package/specs/split/records.json +0 -754
  59. package/specs/split/reports.json +0 -619
  60. package/specs/split/tables.json +0 -1605
  61. package/src/BurstAwareThrottleBucket.ts +0 -57
  62. package/src/FlowThrottleBucket.ts +0 -47
  63. package/src/RateLimitError.ts +0 -12
  64. package/src/RateThrottleBucket.ts +0 -5
  65. package/src/Semaphore.ts +0 -33
  66. package/src/authorizationStrategy.ts +0 -321
  67. package/src/generated-unified/QuickbaseClient.ts +0 -1135
  68. package/src/generated-unified/missing-types-report.json +0 -3
  69. package/src/index.ts +0 -1
  70. package/src/invokeMethod.ts +0 -290
  71. package/src/pagination.ts +0 -429
  72. package/src/quickbaseClient.ts +0 -227
  73. package/src/rateLimiter.ts +0 -30
  74. package/src/tokenCache.ts +0 -39
  75. package/src/utils.ts +0 -50
  76. package/tests/playwright/qb/auth/cacheSwitching.test.ts +0 -159
  77. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +0 -219
  78. package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +0 -162
  79. package/tests/playwright/qb/auth/getAppWithTempTokenConcurrency.test.ts +0 -208
  80. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +0 -153
  81. package/tests/playwright/qb/auth/getTempToken.test.ts +0 -152
  82. package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +0 -179
  83. package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +0 -133
  84. package/tests/setup.ts +0 -53
  85. package/tests/vitest/qb/apps/copyApp.test.ts +0 -76
  86. package/tests/vitest/qb/apps/createApp.test.ts +0 -64
  87. package/tests/vitest/qb/apps/deleteApp.test.ts +0 -76
  88. package/tests/vitest/qb/apps/getApp.test.ts +0 -44
  89. package/tests/vitest/qb/apps/getAppEvents.test.ts +0 -67
  90. package/tests/vitest/qb/apps/updateApp.test.ts +0 -58
  91. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +0 -60
  92. package/tests/vitest/qb/auth/retryUserToken401.test.ts +0 -73
  93. package/tests/vitest/qb/auth/ssoTokenConcurrency.test.ts +0 -68
  94. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +0 -65
  95. package/tests/vitest/qb/auth/userTokenConcurrency.test.ts +0 -63
  96. package/tests/vitest/qb/fields/createField.test.ts +0 -77
  97. package/tests/vitest/qb/fields/deleteFields.test.ts +0 -83
  98. package/tests/vitest/qb/fields/getField.test.ts +0 -85
  99. package/tests/vitest/qb/fields/getFieldUsage.test.ts +0 -82
  100. package/tests/vitest/qb/fields/getFields.test.ts +0 -232
  101. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +0 -77
  102. package/tests/vitest/qb/fields/updateField.test.ts +0 -93
  103. package/tests/vitest/qb/formulas/runFormula.test.ts +0 -96
  104. package/tests/vitest/qb/index.ts +0 -29
  105. package/tests/vitest/qb/quickbase-js/BurstAwareThrottleBucket.test.ts +0 -125
  106. package/tests/vitest/qb/quickbase-js/FlowBucket.test.ts +0 -77
  107. package/tests/vitest/qb/quickbase-js/paginationSkip.test.ts +0 -111
  108. package/tests/vitest/qb/records/deleteRecords.test.ts +0 -110
  109. package/tests/vitest/qb/records/runQuery.test.ts +0 -145
  110. package/tests/vitest/qb/records/upsert.test.ts +0 -102
  111. package/tests/vitest/qb/tables/createRelationship.test.ts +0 -102
  112. package/tests/vitest/qb/tables/createTable.test.ts +0 -55
  113. package/tests/vitest/qb/tables/deleteRelationship.test.ts +0 -143
  114. package/tests/vitest/qb/tables/deleteTable.test.ts +0 -50
  115. package/tests/vitest/qb/tables/getAppTables.test.ts +0 -46
  116. package/tests/vitest/qb/tables/getRelationships.test.ts +0 -80
  117. package/tests/vitest/qb/tables/getTable.test.ts +0 -26
  118. package/tests/vitest/qb/tables/updateRelationship.test.ts +0 -250
  119. package/tests/vitest/qb/tables/updateTable.test.ts +0 -48
  120. package/tests/vitest/unit/apps/copyApp.test.ts +0 -581
  121. package/tests/vitest/unit/apps/createApp.test.ts +0 -186
  122. package/tests/vitest/unit/apps/deleteApp.test.ts +0 -216
  123. package/tests/vitest/unit/apps/getApp.test.ts +0 -128
  124. package/tests/vitest/unit/apps/getAppConcurrency.test.ts +0 -151
  125. package/tests/vitest/unit/apps/getAppEvents.test.ts +0 -193
  126. package/tests/vitest/unit/apps/updateApp.test.ts +0 -412
  127. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +0 -81
  128. package/tests/vitest/unit/auth/getTempToken.test.ts +0 -119
  129. package/tests/vitest/unit/auth/retryTempToken401.test.ts +0 -95
  130. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +0 -206
  131. package/tests/vitest/unit/auth/retryUserToken401.test.ts +0 -66
  132. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +0 -325
  133. package/tests/vitest/unit/auth/userTokenConcurrency.test.ts +0 -62
  134. package/tests/vitest/unit/fields/createField.test.ts +0 -451
  135. package/tests/vitest/unit/fields/deleteFields.test.ts +0 -141
  136. package/tests/vitest/unit/fields/getField.test.ts +0 -189
  137. package/tests/vitest/unit/fields/getFieldUsage.test.ts +0 -180
  138. package/tests/vitest/unit/fields/getFields.test.ts +0 -122
  139. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +0 -160
  140. package/tests/vitest/unit/fields/updateField.test.ts +0 -285
  141. package/tests/vitest/unit/formulas/runFormula.test.ts +0 -205
  142. package/tests/vitest/unit/index.ts +0 -34
  143. package/tests/vitest/unit/quickbase-js/BurstAwareThrottleBucket.test.ts +0 -108
  144. package/tests/vitest/unit/quickbase-js/FlowBucket.test.ts +0 -101
  145. package/tests/vitest/unit/quickbase-js/pagination.test.ts +0 -251
  146. package/tests/vitest/unit/quickbase-js/quickbaseClientHttpMethods.test.ts +0 -141
  147. package/tests/vitest/unit/quickbase-js/rateLimit.test.ts +0 -191
  148. package/tests/vitest/unit/quickbase-js/tokenLifespan.test.ts +0 -204
  149. package/tests/vitest/unit/records/deleteRecords.test.ts +0 -112
  150. package/tests/vitest/unit/records/runQuery.test.ts +0 -101
  151. package/tests/vitest/unit/records/upsert.test.ts +0 -234
  152. package/tests/vitest/unit/tables/createRelationship.test.ts +0 -212
  153. package/tests/vitest/unit/tables/createTable.test.ts +0 -70
  154. package/tests/vitest/unit/tables/deleteRelationship.test.ts +0 -97
  155. package/tests/vitest/unit/tables/deleteTable.test.ts +0 -77
  156. package/tests/vitest/unit/tables/getAppTables.test.ts +0 -85
  157. package/tests/vitest/unit/tables/getRelationships.test.ts +0 -259
  158. package/tests/vitest/unit/tables/getTable.test.ts +0 -233
  159. package/tests/vitest/unit/tables/updateRelationship.test.ts +0 -307
  160. package/tests/vitest/unit/tables/updateTable.test.ts +0 -80
  161. package/tools/log-gen.ts +0 -251
  162. package/tools/log-gen.yaml +0 -4829
  163. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  164. package/tsconfig.build.json +0 -17
  165. package/tsconfig.json +0 -32
  166. package/versionrc.json +0 -9
  167. package/vitest.config.ts +0 -27
package/src/pagination.ts DELETED
@@ -1,429 +0,0 @@
1
- import { QuickbaseClient } from "./quickbaseClient";
2
- import { invokeMethod, MethodInfo } from "./invokeMethod";
3
- import { AuthorizationStrategy } from "./authorizationStrategy";
4
- import { RateLimiter } from "./rateLimiter";
5
-
6
- /**
7
- * Represents a paginated response from the Quick Base API.
8
- * @template T - The type of items in the data array.
9
- */
10
- interface PaginatedResponse<T> {
11
- /** The key containing the array of records (e.g., "data" or "users") and additional metadata. */
12
- [key: string]: T[] | any;
13
- /** Optional array of field definitions for the records. */
14
- fields?: { id: number; label: string; type: string }[];
15
- /** Metadata describing the pagination state. */
16
- metadata: {
17
- /** Total number of records available (skip-based pagination). */
18
- totalRecords?: number;
19
- /** Number of records in the current page. */
20
- numRecords?: number;
21
- /** Number of fields in the records. */
22
- numFields?: number;
23
- /** Number of records skipped in the current page (skip-based pagination). */
24
- skip?: number;
25
- /** Maximum number of records per page (skip-based pagination). */
26
- top?: number;
27
- /** Token for the next page (token-based pagination). */
28
- nextPageToken?: string;
29
- /** Alternative token for the next page (token-based pagination). */
30
- nextToken?: string;
31
- };
32
- }
33
-
34
- /**
35
- * Represents a request body that may include pagination options.
36
- */
37
- interface BodyWithOptions {
38
- /** Optional request body containing pagination parameters. */
39
- body?: {
40
- /** Optional pagination options for skip-based requests. */
41
- options?: {
42
- /** Sorting criteria for the records. */
43
- sortBy?: Array<{ fieldId: number; order: "ASC" | "DESC" }>;
44
- /** Number of records to skip. */
45
- skip?: number;
46
- /** Maximum number of records to return per page. */
47
- top?: number;
48
- /** Token for the next page (token-based pagination). */
49
- nextPageToken?: string;
50
- /** Alternative token for the next page (token-based pagination). */
51
- nextToken?: string;
52
- /** Additional arbitrary options. */
53
- [key: string]: any;
54
- };
55
- /** Token for the next page (token-based pagination, directly in body). */
56
- nextPageToken?: string;
57
- /** Alternative token for the next page (token-based pagination, directly in body). */
58
- nextToken?: string;
59
- /** Additional arbitrary body properties. */
60
- [key: string]: any;
61
- };
62
- }
63
-
64
- /**
65
- * Defines the continuation state for pagination, either skip-based or token-based.
66
- */
67
- type PaginationContinuation =
68
- | { type: "skip"; value: number } // Skip-based: number of records to skip next
69
- | { type: "token"; value: string; key: "nextPageToken" | "nextToken" }; // Token-based: token for the next page
70
-
71
- /**
72
- * Paginates records from a Quick Base API endpoint, handling both skip-based and token-based pagination.
73
- * @template K - The method name keyof QuickbaseClient.
74
- * @param methodName - The Quick Base API method to invoke (e.g., "runQuery", "getUsers").
75
- * @param params - Parameters for the API method, including optional skip and top values.
76
- * @param methodMap - Mapping of method names to their implementation details.
77
- * @param baseHeaders - Base HTTP headers for API requests.
78
- * @param authStrategy - Strategy for handling authentication tokens.
79
- * @param rateLimiter - Rate limiting mechanism to respect API limits.
80
- * @param transformDates - Function to transform date strings in the response.
81
- * @param debug - If true, logs detailed pagination information to the console.
82
- * @param convertDates - If true, converts date strings to Date objects in the response.
83
- * @param initialResponse - Optional initial response to start pagination from (used by invokeMethod).
84
- * @returns A promise resolving to the complete paginated response with all records.
85
- * @throws {Error} If no response is received during pagination.
86
- */
87
- export async function paginateRecords<K extends keyof QuickbaseClient>(
88
- methodName: K,
89
- params: Parameters<QuickbaseClient[K]>[0] & { skip?: number; top?: number },
90
- methodMap: { [P in keyof QuickbaseClient]: MethodInfo<P> },
91
- baseHeaders: Record<string, string>,
92
- authStrategy: AuthorizationStrategy,
93
- rateLimiter: RateLimiter,
94
- transformDates: (obj: any, convertStringsToDates: boolean) => any,
95
- debug: boolean | undefined,
96
- convertDates: boolean,
97
- initialResponse?: PaginatedResponse<any>
98
- ): Promise<ReturnType<QuickbaseClient[K]>> {
99
- if (debug) console.log("[paginateRecords] Starting with params:", params);
100
-
101
- /** Extracts the key containing the data array from the response (e.g., "data" or "users"). */
102
- const getDataKey = (response: PaginatedResponse<any>): string =>
103
- Object.keys(response).find((key) => Array.isArray(response[key])) || "data";
104
-
105
- const dataKey = initialResponse ? getDataKey(initialResponse) : "data";
106
- let allRecords: any[] = initialResponse ? initialResponse[dataKey] : [];
107
- let lastResponse: PaginatedResponse<any> | undefined = initialResponse;
108
- let requestCount = 0;
109
-
110
- /**
111
- * Determines the pagination strategy based on response metadata.
112
- * @param response - The API response to analyze.
113
- * @returns "skip" for skip-based, "token" for token-based, or "none" if no pagination is detected.
114
- */
115
- const determinePaginationType = (
116
- response: PaginatedResponse<any>
117
- ): "skip" | "token" | "none" => {
118
- if (
119
- response.metadata.totalRecords !== undefined &&
120
- response.metadata.skip !== undefined
121
- ) {
122
- if (debug)
123
- console.log("[paginateRecords] Detected skip-based pagination");
124
- return "skip";
125
- }
126
- if (
127
- response.metadata.nextPageToken !== undefined ||
128
- response.metadata.nextToken !== undefined
129
- ) {
130
- if (debug)
131
- console.log("[paginateRecords] Detected token-based pagination");
132
- return "token";
133
- }
134
- if (debug)
135
- console.log(
136
- "[paginateRecords] No clear pagination type detected, assuming single-page response"
137
- );
138
- return "none"; // No pagination indicators
139
- };
140
-
141
- let paginationType: "skip" | "token" | "none" = initialResponse
142
- ? determinePaginationType(initialResponse)
143
- : "none"; // Start with no pagination assumption
144
-
145
- let continuation: PaginationContinuation | null = initialResponse
146
- ? paginationType === "skip"
147
- ? {
148
- type: "skip",
149
- value:
150
- (initialResponse.metadata.skip ?? 0) +
151
- (initialResponse.metadata.numRecords || allRecords.length),
152
- }
153
- : paginationType === "token"
154
- ? {
155
- type: "token",
156
- value:
157
- initialResponse.metadata.nextPageToken ||
158
- initialResponse.metadata.nextToken ||
159
- "",
160
- key:
161
- initialResponse.metadata.nextPageToken !== undefined
162
- ? "nextPageToken"
163
- : "nextToken",
164
- }
165
- : null
166
- : null; // No continuation until type is determined
167
-
168
- if (debug) {
169
- console.log("[paginateRecords] Initial state:", {
170
- dataKey,
171
- paginationType,
172
- continuation,
173
- hasInitialResponse: !!initialResponse,
174
- totalRecordsSoFar: allRecords.length,
175
- });
176
- if (initialResponse && initialResponse[dataKey].length > 0) {
177
- console.log(
178
- "[paginateRecords] Initial response IDs: first:",
179
- initialResponse[dataKey][0]["3"]?.value,
180
- "last:",
181
- initialResponse[dataKey][initialResponse[dataKey].length - 1]["3"]
182
- ?.value
183
- );
184
- }
185
- }
186
-
187
- /** Fetches the next page of records using the provided parameters. */
188
- const fetchNextPage = async (
189
- paginatedParams: any
190
- ): Promise<PaginatedResponse<any>> => {
191
- if (debug)
192
- console.log("[paginateRecords] Fetching with params:", paginatedParams);
193
- const response = await invokeMethod(
194
- methodName,
195
- paginatedParams,
196
- methodMap,
197
- baseHeaders,
198
- authStrategy,
199
- rateLimiter,
200
- transformDates,
201
- debug,
202
- convertDates,
203
- false,
204
- 0,
205
- rateLimiter.maxRetries + 1,
206
- true
207
- );
208
- requestCount++;
209
- return response as PaginatedResponse<any>;
210
- };
211
-
212
- /** Constructs the parameters for the next API request based on the pagination type. */
213
- const getPaginatedParams = (): any => {
214
- const hasBody = "body" in params && params.body !== undefined;
215
- if (paginationType === "skip" && continuation?.type === "skip") {
216
- const paginatedOptions = {
217
- ...(hasBody && "options" in params.body! ? params.body!.options : {}),
218
- skip: continuation.value,
219
- };
220
- return hasBody
221
- ? {
222
- ...params,
223
- body: {
224
- ...(params as BodyWithOptions).body!,
225
- options: paginatedOptions,
226
- },
227
- }
228
- : { ...params, skip: continuation.value };
229
- } else if (paginationType === "token" && continuation?.type === "token") {
230
- if (continuation.value === "" && !lastResponse) {
231
- return { ...params }; // Initial call, no pagination fields
232
- }
233
- const tokenKey = continuation.key;
234
- return hasBody
235
- ? {
236
- ...params,
237
- body: {
238
- ...(params as BodyWithOptions).body!,
239
- [tokenKey]: continuation.value,
240
- },
241
- }
242
- : { ...params, [tokenKey]: continuation.value };
243
- }
244
- return params; // No pagination or initial call
245
- };
246
-
247
- if (!initialResponse) {
248
- const firstParams = getPaginatedParams();
249
- const firstResponse = await fetchNextPage(firstParams);
250
- const actualDataKey = getDataKey(firstResponse);
251
- allRecords = firstResponse[actualDataKey];
252
- lastResponse = firstResponse;
253
- paginationType = determinePaginationType(firstResponse);
254
- continuation =
255
- paginationType === "skip"
256
- ? {
257
- type: "skip",
258
- value:
259
- (firstResponse.metadata.skip ?? 0) +
260
- (firstResponse.metadata.numRecords || allRecords.length),
261
- }
262
- : paginationType === "token"
263
- ? {
264
- type: "token",
265
- value:
266
- firstResponse.metadata.nextPageToken ??
267
- firstResponse.metadata.nextToken ??
268
- "",
269
- key:
270
- firstResponse.metadata.nextPageToken !== undefined
271
- ? "nextPageToken"
272
- : "nextToken",
273
- }
274
- : null;
275
- }
276
-
277
- while (true) {
278
- const hasMore =
279
- (paginationType === "skip" &&
280
- continuation?.type === "skip" &&
281
- lastResponse!.metadata.totalRecords !== undefined &&
282
- continuation.value < lastResponse!.metadata.totalRecords &&
283
- allRecords.length < lastResponse!.metadata.totalRecords) ||
284
- (paginationType === "token" &&
285
- continuation?.type === "token" &&
286
- continuation.value !== "");
287
-
288
- if (!hasMore) {
289
- if (debug) {
290
- console.log(
291
- "[paginateRecords] Stopping:",
292
- paginationType === "skip"
293
- ? "All skip-based records fetched or no more data"
294
- : paginationType === "token"
295
- ? "Token exhausted"
296
- : "No pagination required"
297
- );
298
- }
299
- break;
300
- }
301
-
302
- const paginatedParams = getPaginatedParams();
303
- const response = await fetchNextPage(paginatedParams);
304
- const actualDataKey = getDataKey(response);
305
- allRecords = allRecords.concat(response[actualDataKey]);
306
- lastResponse = response;
307
-
308
- if (debug && response[actualDataKey].length > 0) {
309
- console.log(
310
- "[paginateRecords] Fetched records this iteration:",
311
- response[actualDataKey].length,
312
- "IDs: first:",
313
- response[actualDataKey][0]["3"]?.value,
314
- "last:",
315
- response[actualDataKey][response[actualDataKey].length - 1]["3"]?.value,
316
- "Continuation after fetch:",
317
- continuation
318
- );
319
- }
320
-
321
- if (paginationType === "skip") {
322
- const newSkip =
323
- continuation?.type === "skip"
324
- ? continuation.value + response[actualDataKey].length
325
- : (response.metadata.skip ?? 0) + response[actualDataKey].length;
326
- continuation = { type: "skip", value: newSkip };
327
- if (response[actualDataKey].length === 0) {
328
- if (
329
- debug &&
330
- lastResponse!.metadata.totalRecords &&
331
- newSkip < lastResponse!.metadata.totalRecords
332
- ) {
333
- console.warn(
334
- "[paginateRecords] Warning: Empty response received but records remain",
335
- { skip: newSkip, totalRecords: lastResponse!.metadata.totalRecords }
336
- );
337
- }
338
- break;
339
- }
340
- } else if (paginationType === "token") {
341
- const newToken =
342
- response.metadata.nextPageToken ?? response.metadata.nextToken ?? "";
343
- continuation = {
344
- type: "token",
345
- value: newToken,
346
- key:
347
- response.metadata.nextPageToken !== undefined
348
- ? "nextPageToken"
349
- : "nextToken",
350
- };
351
- if (continuation.value === "") {
352
- break;
353
- } else if (response[actualDataKey].length === 0 && newToken !== "") {
354
- if (debug)
355
- console.warn(
356
- "[paginateRecords] Warning: Empty response with non-empty token, possible API inconsistency",
357
- { token: newToken }
358
- );
359
- }
360
- }
361
- }
362
-
363
- if (!lastResponse) {
364
- throw new Error("[paginateRecords] No response received during pagination");
365
- }
366
-
367
- const finalDataKey = getDataKey(lastResponse);
368
- const finalResponse: PaginatedResponse<any> = {
369
- [finalDataKey]: allRecords,
370
- fields: lastResponse.fields,
371
- metadata: {
372
- totalRecords:
373
- paginationType === "skip"
374
- ? lastResponse.metadata.totalRecords
375
- : undefined,
376
- numRecords: allRecords.length,
377
- numFields: lastResponse.metadata.numFields,
378
- skip: paginationType === "skip" ? 0 : undefined,
379
- top: paginationType === "skip" ? undefined : undefined,
380
- ...(paginationType === "token" &&
381
- continuation?.type === "token" &&
382
- continuation.key
383
- ? { [continuation.key]: "" }
384
- : {}),
385
- },
386
- };
387
-
388
- if (debug) {
389
- console.log("[paginateRecords] Final response summary:", {
390
- totalRecords: finalResponse.metadata.totalRecords,
391
- numRecords: finalResponse.metadata.numRecords,
392
- numFields: finalResponse.metadata.numFields,
393
- skip: finalResponse.metadata.skip,
394
- top: finalResponse.metadata.top,
395
- ...(paginationType === "token" &&
396
- continuation?.type === "token" &&
397
- continuation.key
398
- ? { [continuation.key]: finalResponse.metadata[continuation.key] }
399
- : {}),
400
- });
401
- console.log("[paginateRecords] Pagination stats:", {
402
- totalRequests: requestCount,
403
- });
404
- }
405
-
406
- return finalResponse as ReturnType<QuickbaseClient[K]>;
407
- }
408
-
409
- /**
410
- * Checks if a response is paginatable based on its structure.
411
- * @param response - The response to check.
412
- * @returns True if the response has a data array and pagination metadata, false otherwise.
413
- */
414
- export function isPaginatable(
415
- response: any
416
- ): response is PaginatedResponse<any> {
417
- return (
418
- response &&
419
- typeof response === "object" &&
420
- Object.keys(response).some((key) => Array.isArray(response[key])) &&
421
- "metadata" in response &&
422
- typeof response.metadata === "object" &&
423
- (("totalRecords" in response.metadata &&
424
- "numRecords" in response.metadata &&
425
- "skip" in response.metadata) ||
426
- "nextPageToken" in response.metadata ||
427
- "nextToken" in response.metadata)
428
- );
429
- }
@@ -1,227 +0,0 @@
1
- import { QuickbaseClient as IQuickbaseClient } from "./generated-unified/QuickbaseClient";
2
- import { Configuration, HTTPHeaders } from "./generated/runtime";
3
- import * as apis from "./generated/apis";
4
- import { TokenCache } from "./tokenCache";
5
- import {
6
- simplifyName,
7
- getParamNames,
8
- transformDates,
9
- extractHttpMethod,
10
- } from "./utils";
11
- import { invokeMethod, MethodInfo } from "./invokeMethod";
12
- import {
13
- TempTokenStrategy,
14
- UserTokenStrategy,
15
- AuthorizationStrategy,
16
- SsoTokenStrategy,
17
- } from "./authorizationStrategy";
18
- import { FlowThrottleBucket } from "./FlowThrottleBucket";
19
- import { BurstAwareThrottleBucket } from "./BurstAwareThrottleBucket";
20
- import { RateLimiter } from "./rateLimiter";
21
-
22
- export * from "./generated/models/index";
23
-
24
- export interface QuickbaseClient extends IQuickbaseClient {}
25
-
26
- export interface QuickbaseConfig {
27
- realm: string;
28
- userToken?: string;
29
- tempToken?: string;
30
- useTempTokens?: boolean;
31
- useSso?: boolean;
32
- samlToken?: string;
33
- debug?: boolean;
34
- fetchApi?: typeof fetch;
35
- convertDates?: boolean;
36
- tempTokenLifespan?: number;
37
- throttle?: {
38
- type?: "flow" | "burst-aware";
39
- rate?: number;
40
- burst?: number;
41
- windowSeconds?: number;
42
- };
43
- maxRetries?: number;
44
- retryDelay?: number;
45
- tokenCache?: TokenCache;
46
- baseUrl?: string;
47
- autoPaginate?: boolean;
48
- }
49
-
50
- export type ThrottleOptions = {
51
- type?: "flow" | "burst-aware";
52
- rate?: number;
53
- burst?: number;
54
- windowSeconds?: number;
55
- };
56
-
57
- type MethodMap = {
58
- [K in keyof QuickbaseClient]: MethodInfo<K>;
59
- };
60
-
61
- export function quickbase(config: QuickbaseConfig): QuickbaseClient {
62
- const {
63
- realm,
64
- userToken,
65
- tempToken,
66
- useTempTokens = false,
67
- useSso = false,
68
- samlToken,
69
- fetchApi,
70
- debug,
71
- convertDates = true,
72
- tempTokenLifespan = 290000,
73
- throttle = { type: "flow", rate: 5, burst: 50 }, // Changed rate from 6 to 5
74
- maxRetries = 3,
75
- retryDelay = 1000,
76
- tokenCache: providedTokenCache,
77
- baseUrl = "https://api.quickbase.com/v1",
78
- autoPaginate = true,
79
- } = config;
80
-
81
- const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
82
-
83
- const throttleOptions = throttle as ThrottleOptions;
84
- const throttleBucket =
85
- throttleOptions.type === "burst-aware"
86
- ? new BurstAwareThrottleBucket({
87
- maxTokens: throttleOptions.burst || 100,
88
- windowSeconds: throttleOptions.windowSeconds || 10,
89
- })
90
- : new FlowThrottleBucket(
91
- throttleOptions.rate || 5, // Changed rate from 6 to 5
92
- throttleOptions.burst || 50
93
- );
94
-
95
- const rateLimiter = new RateLimiter(throttleBucket, maxRetries, retryDelay);
96
-
97
- const defaultFetch: typeof fetch =
98
- globalThis.fetch || globalThis.window?.fetch?.bind(globalThis.window);
99
- if (!defaultFetch && !fetchApi) {
100
- throw new Error(
101
- "No fetch implementation available. Please provide fetchApi in a Node.js environment without native fetch."
102
- );
103
- }
104
- const effectiveFetch = fetchApi || defaultFetch;
105
-
106
- const authStrategy: AuthorizationStrategy = useSso
107
- ? new SsoTokenStrategy(
108
- samlToken || "",
109
- realm,
110
- effectiveFetch,
111
- debug,
112
- baseUrl
113
- )
114
- : useTempTokens
115
- ? new TempTokenStrategy(
116
- tokenCache,
117
- tempToken,
118
- effectiveFetch,
119
- realm,
120
- baseUrl
121
- )
122
- : new UserTokenStrategy(userToken || "", baseUrl);
123
-
124
- const baseHeaders: HTTPHeaders = {
125
- "QB-Realm-Hostname": `${realm}.quickbase.com`,
126
- "Content-Type": "application/json",
127
- };
128
-
129
- const configuration = new Configuration({
130
- basePath: baseUrl,
131
- headers: { ...baseHeaders },
132
- fetchApi: effectiveFetch,
133
- credentials: "omit",
134
- });
135
-
136
- const apiInstances = Object.fromEntries(
137
- Object.entries(apis)
138
- .filter(([name]) => name.endsWith("Api"))
139
- .map(([name, ApiClass]) => [
140
- name.replace("Api", "").toLowerCase(),
141
- new ApiClass(configuration),
142
- ])
143
- );
144
-
145
- function buildMethodMap(): MethodMap {
146
- const methodMap: Partial<MethodMap> = {};
147
- const isValidMethod = (name: string) =>
148
- !name.startsWith("_") &&
149
- name !== "constructor" &&
150
- !["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
151
-
152
- for (const [apiName, api] of Object.entries(apiInstances)) {
153
- Object.getOwnPropertyNames(Object.getPrototypeOf(api))
154
- .filter(
155
- (name) =>
156
- isValidMethod(name) &&
157
- typeof api[name as keyof typeof api] === "function"
158
- )
159
- .forEach((rawMethodName) => {
160
- const simplifiedName = simplifyName(
161
- rawMethodName
162
- ) as keyof QuickbaseClient;
163
- const rawMethodKey = `${rawMethodName}Raw` as keyof typeof api;
164
- const method =
165
- api[rawMethodKey] || api[rawMethodName as keyof typeof api];
166
- const boundMethod = method.bind(api as any) as unknown;
167
- if (typeof boundMethod === "function" && boundMethod.length <= 2) {
168
- const httpMethod = extractHttpMethod(method);
169
- methodMap[simplifiedName] = {
170
- api,
171
- method: boundMethod as any,
172
- paramMap: getParamNames(method),
173
- httpMethod,
174
- };
175
- }
176
- });
177
- }
178
- if (debug) {
179
- console.log("[buildMethodMap] Methods:", Object.keys(methodMap));
180
- }
181
- return methodMap as MethodMap;
182
- }
183
-
184
- const methodMap = buildMethodMap();
185
-
186
- const proxyHandler: globalThis.ProxyHandler<QuickbaseClient> = {
187
- get: (_: QuickbaseClient, prop: string) => {
188
- if (debug) {
189
- console.log(
190
- "[proxy] Accessing:",
191
- prop,
192
- "in methodMap:",
193
- prop in methodMap
194
- );
195
- }
196
- if (prop in methodMap) {
197
- const methodName = prop as keyof QuickbaseClient;
198
- return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) =>
199
- invokeMethod(
200
- methodName,
201
- params,
202
- methodMap,
203
- baseHeaders,
204
- authStrategy,
205
- rateLimiter,
206
- transformDates,
207
- debug,
208
- convertDates,
209
- autoPaginate
210
- );
211
- }
212
- if (debug) {
213
- console.log("[proxy] Method not found:", prop);
214
- }
215
- return undefined;
216
- },
217
- };
218
-
219
- const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, proxyHandler);
220
-
221
- if (debug) {
222
- console.log("[createClient] Config:", config);
223
- console.log("[createClient] Returning proxy");
224
- }
225
-
226
- return proxy;
227
- }
@@ -1,30 +0,0 @@
1
- // src/RateLimiter.ts
2
- import { ResponseError } from "./generated/runtime";
3
- import { RateThrottleBucket } from "./RateThrottleBucket";
4
-
5
- export class RateLimiter {
6
- constructor(
7
- private throttleBucket: RateThrottleBucket | null,
8
- public maxRetries: number = 3,
9
- private retryDelay: number = 1000
10
- ) {}
11
-
12
- async throttle(): Promise<void> {
13
- if (this.throttleBucket) {
14
- await this.throttleBucket.acquire();
15
- }
16
- }
17
-
18
- release(): void {
19
- if (this.throttleBucket) {
20
- this.throttleBucket.release();
21
- }
22
- }
23
-
24
- async handle429(error: ResponseError, attempt: number): Promise<number> {
25
- const retryAfter = error.response.headers.get("Retry-After");
26
- return retryAfter
27
- ? parseInt(retryAfter, 10) * 1000
28
- : this.retryDelay * Math.pow(2, attempt - 1);
29
- }
30
- }