quickbase-js 0.1.0-beta.10

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 (165) hide show
  1. package/CHANGELOG.md +211 -0
  2. package/LICENSE +21 -0
  3. package/README.md +179 -0
  4. package/api-examples.md +49 -0
  5. package/build-common.js +131 -0
  6. package/build-esm.js +71 -0
  7. package/build-umd.js +69 -0
  8. package/dist/esm/quickbase.d.ts +8081 -0
  9. package/dist/esm/quickbase.js +7928 -0
  10. package/dist/esm/quickbase.js.map +1 -0
  11. package/dist/esm/quickbase.min.js +2 -0
  12. package/dist/esm/quickbase.min.js.map +1 -0
  13. package/dist/umd/quickbase.umd.js +8504 -0
  14. package/dist/umd/quickbase.umd.js.map +1 -0
  15. package/dist/umd/quickbase.umd.min.js +2 -0
  16. package/dist/umd/quickbase.umd.min.js.map +1 -0
  17. package/docs/.vitepress/cache/deps/@theme_index.js +275 -0
  18. package/docs/.vitepress/cache/deps/@theme_index.js.map +7 -0
  19. package/docs/.vitepress/cache/deps/_metadata.json +40 -0
  20. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js +12542 -0
  21. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js.map +7 -0
  22. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js +9719 -0
  23. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js.map +7 -0
  24. package/docs/.vitepress/cache/deps/package.json +3 -0
  25. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +4504 -0
  26. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +7 -0
  27. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +583 -0
  28. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +7 -0
  29. package/docs/.vitepress/cache/deps/vue.js +343 -0
  30. package/docs/.vitepress/cache/deps/vue.js.map +7 -0
  31. package/docs/.vitepress/config.mts +26 -0
  32. package/docs/api-examples.md +49 -0
  33. package/docs/index.md +59 -0
  34. package/docs/markdown-examples.md +85 -0
  35. package/docs-data/api-docs.json +4143 -0
  36. package/examples/quicbase-jsCDN.html +36 -0
  37. package/examples/react.js +48 -0
  38. package/examples/vanillajs.html +47 -0
  39. package/index.md +25 -0
  40. package/markdown-examples.md +85 -0
  41. package/open-api/fix-spec-main.ts +133 -0
  42. package/open-api/generate-unified-interface.ts +104 -0
  43. package/open-api/generateDocsJson.ts +24 -0
  44. package/open-api/output/quickbase-fixed.json +11191 -0
  45. package/open-api/post-process-generated.ts +226 -0
  46. package/open-api/regenerate-client.ts +117 -0
  47. package/open-api/schema/enhance-general.ts +111 -0
  48. package/open-api/schema/fix-arrays.ts +68 -0
  49. package/open-api/schema/tags/apps.ts +90 -0
  50. package/open-api/schema/tags/fields.ts +471 -0
  51. package/open-api/schema/tags/groups.ts +51 -0
  52. package/open-api/schema/tags/index.ts +66 -0
  53. package/open-api/schema/tags/records.ts +115 -0
  54. package/open-api/schema/tags/tables.ts +211 -0
  55. package/open-api/schema/tags/users.ts +47 -0
  56. package/open-api/types/spec.ts +42 -0
  57. package/open-api/utils/common.ts +29 -0
  58. package/open-api/utils/generateJsDoc.ts +74 -0
  59. package/open-api/utils/infer-schema.ts +40 -0
  60. package/open-api/utils/naming.ts +10 -0
  61. package/open-api/utils/sharedUtils.ts +352 -0
  62. package/package.json +96 -0
  63. package/playwright.config.ts +37 -0
  64. package/rollup.config.js +114 -0
  65. package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
  66. package/specs/split/apps.json +1315 -0
  67. package/specs/split/auth.json +191 -0
  68. package/specs/split/fields.json +3869 -0
  69. package/specs/split/records.json +754 -0
  70. package/specs/split/reports.json +619 -0
  71. package/specs/split/tables.json +1605 -0
  72. package/src/RateLimitError.ts +12 -0
  73. package/src/ThrottleBucket.ts +41 -0
  74. package/src/authorizationStrategy.ts +283 -0
  75. package/src/generated-unified/QuickbaseClient.ts +1135 -0
  76. package/src/generated-unified/missing-types-report.json +3 -0
  77. package/src/index.ts +1 -0
  78. package/src/invokeMethod.ts +245 -0
  79. package/src/quickbaseClient.ts +194 -0
  80. package/src/rateLimiter.ts +25 -0
  81. package/src/tokenCache.ts +35 -0
  82. package/src/utils.ts +50 -0
  83. package/tests/playwright/qb/auth/cacheSwitching.test.ts +159 -0
  84. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +219 -0
  85. package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +162 -0
  86. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +153 -0
  87. package/tests/playwright/qb/auth/getTempToken.test.ts +152 -0
  88. package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +179 -0
  89. package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +133 -0
  90. package/tests/setup.ts +53 -0
  91. package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
  92. package/tests/vitest/qb/apps/createApp.test.ts +64 -0
  93. package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
  94. package/tests/vitest/qb/apps/getApp.test.ts +44 -0
  95. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  96. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  97. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
  98. package/tests/vitest/qb/auth/retryUserToken401.test.ts +73 -0
  99. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
  100. package/tests/vitest/qb/fields/createField.test.ts +77 -0
  101. package/tests/vitest/qb/fields/deleteFields.test.ts +83 -0
  102. package/tests/vitest/qb/fields/getField.test.ts +85 -0
  103. package/tests/vitest/qb/fields/getFieldUsage.test.ts +82 -0
  104. package/tests/vitest/qb/fields/getFields.test.ts +232 -0
  105. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
  106. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  107. package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
  108. package/tests/vitest/qb/index.ts +29 -0
  109. package/tests/vitest/qb/records/deleteRecords.test.ts +110 -0
  110. package/tests/vitest/qb/records/runQuery.test.ts +146 -0
  111. package/tests/vitest/qb/records/upsert.test.ts +102 -0
  112. package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
  113. package/tests/vitest/qb/tables/createTable.test.ts +55 -0
  114. package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
  115. package/tests/vitest/qb/tables/deleteTable.test.ts +50 -0
  116. package/tests/vitest/qb/tables/getAppTables.test.ts +46 -0
  117. package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
  118. package/tests/vitest/qb/tables/getTable.test.ts +26 -0
  119. package/tests/vitest/qb/tables/updateRelationship.test.ts +250 -0
  120. package/tests/vitest/qb/tables/updateTable.test.ts +48 -0
  121. package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
  122. package/tests/vitest/unit/apps/copyApp.test.ts +581 -0
  123. package/tests/vitest/unit/apps/createApp.test.ts +186 -0
  124. package/tests/vitest/unit/apps/deleteApp.test.ts +216 -0
  125. package/tests/vitest/unit/apps/getApp.test.ts +128 -0
  126. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  127. package/tests/vitest/unit/apps/updateApp.test.ts +412 -0
  128. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +63 -0
  129. package/tests/vitest/unit/auth/getTempToken.test.ts +119 -0
  130. package/tests/vitest/unit/auth/retryTempToken401.test.ts +95 -0
  131. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +206 -0
  132. package/tests/vitest/unit/auth/retryUserToken401.test.ts +69 -0
  133. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
  134. package/tests/vitest/unit/fields/createField.test.ts +451 -0
  135. package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
  136. package/tests/vitest/unit/fields/getField.test.ts +189 -0
  137. package/tests/vitest/unit/fields/getFieldUsage.test.ts +180 -0
  138. package/tests/vitest/unit/fields/getFields.test.ts +122 -0
  139. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
  140. package/tests/vitest/unit/fields/updateField.test.ts +285 -0
  141. package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
  142. package/tests/vitest/unit/index.ts +34 -0
  143. package/tests/vitest/unit/quickbaseClientHttpMethods.test.ts +139 -0
  144. package/tests/vitest/unit/rateLimit.test.ts +191 -0
  145. package/tests/vitest/unit/records/deleteRecords.test.ts +112 -0
  146. package/tests/vitest/unit/records/runQuery.test.ts +102 -0
  147. package/tests/vitest/unit/records/upsert.test.ts +234 -0
  148. package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
  149. package/tests/vitest/unit/tables/createTable.test.ts +70 -0
  150. package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
  151. package/tests/vitest/unit/tables/deleteTable.test.ts +77 -0
  152. package/tests/vitest/unit/tables/getAppTables.test.ts +85 -0
  153. package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
  154. package/tests/vitest/unit/tables/getTable.test.ts +233 -0
  155. package/tests/vitest/unit/tables/updateRelationship.test.ts +307 -0
  156. package/tests/vitest/unit/tables/updateTable.test.ts +80 -0
  157. package/tests/vitest/unit/tokenLifespan.test.ts +204 -0
  158. package/tests/vitest/unit/umd.test.ts +8 -0
  159. package/tools/log-gen.ts +251 -0
  160. package/tools/log-gen.yaml +4777 -0
  161. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  162. package/tsconfig.build.json +17 -0
  163. package/tsconfig.json +32 -0
  164. package/versionrc.json +9 -0
  165. package/vitest.config.ts +27 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ "missingTypes": []
3
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./quickbaseClient.ts";
@@ -0,0 +1,245 @@
1
+ // src/invokeMethod.ts
2
+
3
+ import { AuthorizationStrategy, extractDbid } from "./authorizationStrategy";
4
+ import { RateLimiter } from "./rateLimiter";
5
+ import { RateLimitError } from "./RateLimitError";
6
+ import { ResponseError } from "./generated/runtime";
7
+ import { QuickbaseClient } from "./quickbaseClient";
8
+
9
+ export type ApiMethod<K extends keyof QuickbaseClient> = (
10
+ requestParameters: Parameters<QuickbaseClient[K]>[0],
11
+ initOverrides?: RequestInit
12
+ ) => Promise<ReturnType<QuickbaseClient[K]>>;
13
+
14
+ export interface MethodInfo<K extends keyof QuickbaseClient> {
15
+ api: any;
16
+ method: ApiMethod<K>;
17
+ paramMap: string[];
18
+ httpMethod: string;
19
+ }
20
+
21
+ export async function invokeMethod<K extends keyof QuickbaseClient>(
22
+ methodName: K,
23
+ params: Parameters<QuickbaseClient[K]>[0] & {
24
+ dbid?: string;
25
+ tableId?: string;
26
+ appId?: string;
27
+ },
28
+ methodMap: { [P in keyof QuickbaseClient]: MethodInfo<P> },
29
+ baseHeaders: Record<string, string>,
30
+ authStrategy: AuthorizationStrategy,
31
+ rateLimiter: RateLimiter,
32
+ transformDates: (obj: any, convertStringsToDates: boolean) => any,
33
+ debug: boolean | undefined,
34
+ convertDates: boolean
35
+ ): Promise<ReturnType<QuickbaseClient[K]>> {
36
+ console.log("[invokeMethod] Starting for method:", methodName);
37
+
38
+ const methodInfo = methodMap[methodName];
39
+ if (!methodInfo) throw new Error(`Method ${methodName} not found`);
40
+
41
+ const hasBody = "body" in params && params.body !== undefined;
42
+ const body = hasBody ? params.body : undefined;
43
+ const restParams: any = hasBody
44
+ ? Object.fromEntries(
45
+ Object.entries(params).filter(([key]) => key !== "body")
46
+ )
47
+ : { ...params };
48
+ const requestParameters: any = {
49
+ ...restParams,
50
+ ...(hasBody ? { generated: body } : {}),
51
+ };
52
+
53
+ const requestOptions: RequestInit = {
54
+ credentials: methodName === "getTempTokenDBID" ? "include" : "omit",
55
+ method: methodInfo.httpMethod,
56
+ };
57
+
58
+ let dbid: string | undefined = extractDbid(params);
59
+ console.log("[invokeMethod] Extracted dbid:", dbid);
60
+
61
+ let token = dbid
62
+ ? await authStrategy.getToken(dbid)
63
+ : await authStrategy.getToken("");
64
+ console.log("[invokeMethod] Initial token:", token);
65
+
66
+ if (token) {
67
+ authStrategy.applyHeaders(baseHeaders, token);
68
+ requestOptions.headers = { ...baseHeaders };
69
+ console.log(
70
+ "[invokeMethod] Token found, headers set:",
71
+ requestOptions.headers
72
+ );
73
+ if (methodName === "getTempTokenDBID") {
74
+ console.log("[invokeMethod] Using cached token:", token);
75
+ return { temporaryAuthorization: token } as ReturnType<
76
+ QuickbaseClient[K]
77
+ >;
78
+ }
79
+ }
80
+
81
+ if (debug) {
82
+ console.log(`[${methodName}] requestParameters:`, requestParameters);
83
+ console.log(`[${methodName}] requestOptions:`, requestOptions);
84
+ }
85
+
86
+ async function processResponse(
87
+ rawResponse: any
88
+ ): Promise<ReturnType<QuickbaseClient[K]>> {
89
+ if (debug) console.log(`[${methodName}] rawResponse:`, rawResponse);
90
+ if (rawResponse instanceof Response) {
91
+ const contentType = rawResponse.headers
92
+ .get("Content-Type")
93
+ ?.toLowerCase();
94
+ if (debug) console.log(`[${methodName}] contentType:`, contentType);
95
+ if (contentType?.includes("application/json")) {
96
+ const jsonResponse = await rawResponse.json();
97
+ return transformDates(jsonResponse, convertDates) as ReturnType<
98
+ QuickbaseClient[K]
99
+ >;
100
+ }
101
+ return rawResponse as ReturnType<QuickbaseClient[K]>;
102
+ }
103
+ if (rawResponse && typeof rawResponse.value === "function") {
104
+ const response = await rawResponse.value();
105
+ if (debug)
106
+ console.log(`[${methodName}] Resolved JSONApiResponse:`, response);
107
+ return transformDates(response, convertDates) as ReturnType<
108
+ QuickbaseClient[K]
109
+ >;
110
+ }
111
+ return transformDates(rawResponse, convertDates) as ReturnType<
112
+ QuickbaseClient[K]
113
+ >;
114
+ }
115
+
116
+ async function parseErrorResponse(
117
+ response: Response
118
+ ): Promise<{ message: string; status: number }> {
119
+ let message = "Unknown error";
120
+ let status = response.status || 500;
121
+ try {
122
+ const contentType =
123
+ response.headers?.get("Content-Type")?.toLowerCase() ||
124
+ "application/json";
125
+ if (
126
+ contentType.includes("application/json") &&
127
+ typeof response.json === "function"
128
+ ) {
129
+ const errorBody = await response.json();
130
+ if (
131
+ errorBody &&
132
+ typeof errorBody === "object" &&
133
+ "message" in errorBody
134
+ ) {
135
+ message = errorBody.message;
136
+ } else {
137
+ message = "Invalid error response format";
138
+ }
139
+ } else if (typeof response.text === "function") {
140
+ message = (await response.text()) || message;
141
+ }
142
+ } catch (e) {
143
+ if (debug) console.log(`[${methodName}] Error parsing response body:`, e);
144
+ message = "Failed to parse error response";
145
+ }
146
+ if (debug)
147
+ console.log(
148
+ "[invokeMethod] Parsed error - status:",
149
+ status,
150
+ "message:",
151
+ message
152
+ );
153
+ return { message, status };
154
+ }
155
+
156
+ let attempt = 0;
157
+ const maxAttempts = rateLimiter.maxRetries + 1;
158
+
159
+ while (attempt < maxAttempts) {
160
+ console.log("[invokeMethod] Attempt:", attempt + 1, "of", maxAttempts);
161
+ try {
162
+ await rateLimiter.throttle();
163
+ console.log("[invokeMethod] About to call API for method:", methodName);
164
+ const response = await methodInfo.method(
165
+ requestParameters,
166
+ requestOptions
167
+ );
168
+ console.log("[invokeMethod] API call completed for method:", methodName);
169
+ return await processResponse(response);
170
+ } catch (error: unknown) {
171
+ if (debug) console.log("[invokeMethod] Caught error:", error);
172
+
173
+ let status: number;
174
+ let message: string;
175
+ let response: Response | undefined;
176
+
177
+ if (error instanceof ResponseError && error.response) {
178
+ response = error.response;
179
+ ({ message, status } = await parseErrorResponse(response));
180
+ } else if (error instanceof Response) {
181
+ response = error;
182
+ ({ message, status } = await parseErrorResponse(response));
183
+ } else {
184
+ if (debug) console.log(`[${methodName}] Unexpected error:`, error);
185
+ // Attempt to handle as a fetch error with a response
186
+ if (error instanceof Error && "response" in error) {
187
+ response = (error as any).response;
188
+ if (response instanceof Response) {
189
+ ({ message, status } = await parseErrorResponse(response));
190
+ } else {
191
+ throw error; // Rethrow if no response
192
+ }
193
+ } else {
194
+ throw error; // Rethrow truly unexpected errors
195
+ }
196
+ }
197
+
198
+ if (debug)
199
+ console.log("[invokeMethod] Handling error with status:", status);
200
+
201
+ if (status === 429) {
202
+ if (!(error instanceof ResponseError)) {
203
+ throw new Error("Expected ResponseError for 429 handling");
204
+ }
205
+ const delay = await rateLimiter.handle429(error, attempt + 1);
206
+ if (debug) console.log(`[${methodName}] 429 delay: ${delay}ms`);
207
+ if (attempt + 1 === maxAttempts) {
208
+ throw new RateLimitError(
209
+ `API Error: ${message} (Status: ${status})`,
210
+ status,
211
+ response?.headers.get("Retry-After")
212
+ ? parseInt(response.headers.get("Retry-After")!, 10)
213
+ : undefined
214
+ );
215
+ }
216
+ await new Promise((resolve) => setTimeout(resolve, delay));
217
+ attempt++;
218
+ continue;
219
+ }
220
+
221
+ const newToken = await authStrategy.handleError(
222
+ status,
223
+ params,
224
+ attempt,
225
+ maxAttempts,
226
+ debug,
227
+ methodName
228
+ );
229
+ if (newToken) {
230
+ token = newToken;
231
+ authStrategy.applyHeaders(baseHeaders, token);
232
+ requestOptions.headers = { ...baseHeaders };
233
+ attempt++;
234
+ if (debug)
235
+ console.log(
236
+ `[${methodName}] Retrying with token: ${token.substring(0, 10)}...`
237
+ );
238
+ continue;
239
+ }
240
+
241
+ throw new Error(`API Error: ${message} (Status: ${status})`);
242
+ }
243
+ }
244
+ throw new Error(`API Error: Exhausted retries after ${maxAttempts} attempts`);
245
+ }
@@ -0,0 +1,194 @@
1
+ // src/quickbaseClient.ts
2
+
3
+ import { QuickbaseClient as IQuickbaseClient } from "./generated-unified/QuickbaseClient";
4
+ import { Configuration, HTTPHeaders } from "./generated/runtime";
5
+ import * as apis from "./generated/apis";
6
+ import { TokenCache } from "./tokenCache";
7
+ import {
8
+ simplifyName,
9
+ getParamNames,
10
+ transformDates,
11
+ extractHttpMethod,
12
+ } from "./utils"; // Updated import
13
+ import { invokeMethod, MethodInfo } from "./invokeMethod";
14
+ import {
15
+ TempTokenStrategy,
16
+ UserTokenStrategy,
17
+ AuthorizationStrategy,
18
+ SsoTokenStrategy,
19
+ } from "./authorizationStrategy";
20
+ import { ThrottleBucket } from "./ThrottleBucket";
21
+ import { RateLimiter } from "./rateLimiter";
22
+
23
+ export * from "./generated/models/index";
24
+
25
+ export interface QuickbaseClient extends IQuickbaseClient {}
26
+
27
+ export interface QuickbaseConfig {
28
+ realm: string;
29
+ userToken?: string;
30
+ tempToken?: string;
31
+ useTempTokens?: boolean;
32
+ useSso?: boolean;
33
+ samlToken?: string;
34
+ debug?: boolean;
35
+ fetchApi?: typeof fetch;
36
+ convertDates?: boolean;
37
+ tempTokenLifespan?: number;
38
+ throttle?: { rate: number; burst: number };
39
+ maxRetries?: number;
40
+ retryDelay?: number;
41
+ tokenCache?: TokenCache;
42
+ baseUrl?: string;
43
+ }
44
+
45
+ type MethodMap = {
46
+ [K in keyof QuickbaseClient]: MethodInfo<K>;
47
+ };
48
+
49
+ export function quickbase(config: QuickbaseConfig): QuickbaseClient {
50
+ const {
51
+ realm,
52
+ userToken,
53
+ tempToken,
54
+ useTempTokens,
55
+ useSso,
56
+ samlToken,
57
+ fetchApi,
58
+ debug,
59
+ convertDates = true,
60
+ tempTokenLifespan = 290000,
61
+ throttle = { rate: 10, burst: 10 },
62
+ maxRetries = 3,
63
+ retryDelay = 1000,
64
+ tokenCache: providedTokenCache,
65
+ baseUrl = "https://api.quickbase.com/v1",
66
+ } = config;
67
+
68
+ const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
69
+ const throttleBucket = throttle
70
+ ? new ThrottleBucket(throttle.rate, throttle.burst)
71
+ : null;
72
+ const rateLimiter = new RateLimiter(throttleBucket, maxRetries, retryDelay);
73
+
74
+ const defaultFetch: typeof fetch | undefined =
75
+ typeof globalThis.window !== "undefined"
76
+ ? globalThis.window.fetch.bind(globalThis.window)
77
+ : undefined;
78
+ const effectiveFetch = fetchApi || defaultFetch;
79
+
80
+ if (!effectiveFetch) {
81
+ throw new Error("No fetch implementation available");
82
+ }
83
+
84
+ const authStrategy: AuthorizationStrategy = useSso
85
+ ? new SsoTokenStrategy(samlToken || "", realm, effectiveFetch, debug)
86
+ : useTempTokens
87
+ ? new TempTokenStrategy(
88
+ tokenCache,
89
+ tempToken,
90
+ effectiveFetch,
91
+ realm,
92
+ baseUrl
93
+ )
94
+ : new UserTokenStrategy(userToken || "");
95
+
96
+ const baseHeaders: HTTPHeaders = {
97
+ "QB-Realm-Hostname": `${realm}.quickbase.com`,
98
+ "Content-Type": "application/json",
99
+ };
100
+
101
+ const configuration = new Configuration({
102
+ basePath: baseUrl,
103
+ headers: { ...baseHeaders },
104
+ fetchApi: effectiveFetch,
105
+ credentials: "omit",
106
+ });
107
+
108
+ const apiInstances = Object.fromEntries(
109
+ Object.entries(apis)
110
+ .filter(([name]) => name.endsWith("Api"))
111
+ .map(([name, ApiClass]) => [
112
+ name.replace("Api", "").toLowerCase(),
113
+ new ApiClass(configuration),
114
+ ])
115
+ );
116
+
117
+ function buildMethodMap(): MethodMap {
118
+ const methodMap: Partial<MethodMap> = {};
119
+ const isValidMethod = (name: string) =>
120
+ !name.startsWith("_") &&
121
+ name !== "constructor" &&
122
+ !["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
123
+
124
+ for (const [apiName, api] of Object.entries(apiInstances)) {
125
+ Object.getOwnPropertyNames(Object.getPrototypeOf(api))
126
+ .filter(
127
+ (name) =>
128
+ isValidMethod(name) &&
129
+ typeof api[name as keyof typeof api] === "function"
130
+ )
131
+ .forEach((rawMethodName) => {
132
+ const simplifiedName = simplifyName(
133
+ rawMethodName
134
+ ) as keyof QuickbaseClient;
135
+ const rawMethodKey = `${rawMethodName}Raw` as keyof typeof api;
136
+ const method =
137
+ api[rawMethodKey] || api[rawMethodName as keyof typeof api];
138
+ const boundMethod = method.bind(api as any) as unknown;
139
+ if (typeof boundMethod === "function" && boundMethod.length <= 2) {
140
+ const httpMethod = extractHttpMethod(method);
141
+ methodMap[simplifiedName] = {
142
+ api,
143
+ method: boundMethod as any,
144
+ paramMap: getParamNames(method),
145
+ httpMethod,
146
+ };
147
+ }
148
+ });
149
+ }
150
+ if (debug) {
151
+ console.log("[buildMethodMap] Methods:", Object.keys(methodMap));
152
+ }
153
+ return methodMap as MethodMap;
154
+ }
155
+
156
+ const methodMap = buildMethodMap();
157
+
158
+ const proxyHandler: ProxyHandler<QuickbaseClient> = {
159
+ get: (_, prop: string) => {
160
+ console.log(
161
+ "[proxy] Accessing:",
162
+ prop,
163
+ "in methodMap:",
164
+ prop in methodMap
165
+ );
166
+ if (prop in methodMap) {
167
+ const methodName = prop as keyof QuickbaseClient;
168
+ return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) =>
169
+ invokeMethod(
170
+ methodName,
171
+ params,
172
+ methodMap,
173
+ baseHeaders,
174
+ authStrategy,
175
+ rateLimiter,
176
+ transformDates,
177
+ debug,
178
+ convertDates
179
+ );
180
+ }
181
+ console.log("[proxy] Method not found:", prop);
182
+ return undefined;
183
+ },
184
+ };
185
+
186
+ const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, proxyHandler);
187
+
188
+ if (debug) {
189
+ console.log("[createClient] Config:", config);
190
+ console.log("[createClient] Proxy created:", proxy);
191
+ }
192
+
193
+ return proxy;
194
+ }
@@ -0,0 +1,25 @@
1
+ // src/rateLimiter.ts
2
+ import { ThrottleBucket } from "./ThrottleBucket";
3
+ import { ResponseError } from "./generated/runtime";
4
+
5
+ export class RateLimiter {
6
+ constructor(
7
+ private throttleBucket: ThrottleBucket | 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
+ async handle429(error: ResponseError, attempt: number): Promise<number> {
19
+ // Remove the throw here; let invokeMethod handle exhaustion
20
+ const retryAfter = error.response.headers.get("Retry-After");
21
+ return retryAfter
22
+ ? parseInt(retryAfter, 10) * 1000
23
+ : this.retryDelay * Math.pow(2, attempt - 1);
24
+ }
25
+ }
@@ -0,0 +1,35 @@
1
+ // src/tokenCache.ts
2
+ interface CachedToken {
3
+ token: string;
4
+ expiresAt: number;
5
+ }
6
+
7
+ export class TokenCache {
8
+ private cache: Map<string, CachedToken>;
9
+ private readonly tempTokenLifespan: number;
10
+
11
+ constructor(tempTokenLifespan: number = 4 * 60 * 1000 + 50 * 1000) {
12
+ this.cache = new Map<string, CachedToken>();
13
+ this.tempTokenLifespan = tempTokenLifespan;
14
+ }
15
+
16
+ get(dbid: string): string | undefined {
17
+ const entry = this.cache.get(dbid);
18
+ const now = Date.now();
19
+ if (entry && entry.expiresAt > now) return entry.token;
20
+ if (entry) this.cache.delete(dbid); // Clean up expired temp tokens
21
+ return undefined;
22
+ }
23
+
24
+ set(dbid: string, token: string, lifespan?: number): void {
25
+ const now = Date.now();
26
+ this.cache.set(dbid, {
27
+ token,
28
+ expiresAt: now + (lifespan || this.tempTokenLifespan),
29
+ });
30
+ }
31
+
32
+ clear(): void {
33
+ this.cache.clear();
34
+ }
35
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,50 @@
1
+ // src/utils.ts
2
+
3
+ export function simplifyName(methodName: string): string {
4
+ // Assuming this removes 'Raw' or similar suffixes as per your original logic
5
+ return methodName
6
+ .replace(/Raw$/, "")
7
+ .replace(/^./, (str) => str.toLowerCase());
8
+ }
9
+
10
+ export function getParamNames(func: Function): string[] {
11
+ // Your existing logic to extract parameter names from a function
12
+ const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/gm;
13
+ const ARGUMENT_NAMES = /([^\s,]+)/g;
14
+ const fnStr = func.toString().replace(STRIP_COMMENTS, "");
15
+ const result = fnStr
16
+ .slice(fnStr.indexOf("(") + 1, fnStr.indexOf(")"))
17
+ .match(ARGUMENT_NAMES);
18
+ return result || [];
19
+ }
20
+
21
+ export function transformDates(obj: any, convertStringsToDates: boolean): any {
22
+ // Your existing logic to transform dates
23
+ if (!obj || typeof obj !== "object") return obj;
24
+ if (Array.isArray(obj))
25
+ return obj.map((item) => transformDates(item, convertStringsToDates));
26
+ const result: any = {};
27
+ for (const [key, value] of Object.entries(obj)) {
28
+ if (
29
+ typeof value === "string" &&
30
+ convertStringsToDates &&
31
+ /^\d{4}-\d{2}-\d{2}/.test(value)
32
+ ) {
33
+ result[key] = new Date(value);
34
+ } else if (value instanceof Date) {
35
+ result[key] = value;
36
+ } else if (typeof value === "object") {
37
+ result[key] = transformDates(value, convertStringsToDates);
38
+ } else {
39
+ result[key] = value;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+
45
+ export function extractHttpMethod(method: Function): string {
46
+ const source = method.toString();
47
+ // Match the `method: 'SOME_METHOD'` part of the `request` call
48
+ const match = source.match(/method:\s*['"](\w+)['"]/i);
49
+ return match ? match[1].toUpperCase() : "GET"; // Fallback to GET if no match
50
+ }