quickbase-js 0.1.0-beta.12 → 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 (168) hide show
  1. package/README.md +48 -18
  2. package/dist/esm/quickbase.d.ts +13 -3
  3. package/dist/esm/quickbase.js +416 -49
  4. package/dist/esm/quickbase.js.map +1 -1
  5. package/dist/esm/quickbase.min.js +1 -1
  6. package/dist/esm/quickbase.min.js.map +1 -1
  7. package/dist/umd/quickbase.umd.js +416 -49
  8. package/dist/umd/quickbase.umd.js.map +1 -1
  9. package/dist/umd/quickbase.umd.min.js +1 -1
  10. package/dist/umd/quickbase.umd.min.js.map +1 -1
  11. package/package.json +11 -4
  12. package/CHANGELOG.md +0 -227
  13. package/build-common.js +0 -131
  14. package/build-esm.js +0 -71
  15. package/build-umd.js +0 -69
  16. package/docs/.vitepress/cache/deps/@theme_index.js +0 -275
  17. package/docs/.vitepress/cache/deps/@theme_index.js.map +0 -7
  18. package/docs/.vitepress/cache/deps/_metadata.json +0 -40
  19. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js +0 -12542
  20. package/docs/.vitepress/cache/deps/chunk-5A67QEFC.js.map +0 -7
  21. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js +0 -9719
  22. package/docs/.vitepress/cache/deps/chunk-7D4P4MMP.js.map +0 -7
  23. package/docs/.vitepress/cache/deps/package.json +0 -3
  24. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js +0 -4504
  25. package/docs/.vitepress/cache/deps/vitepress___@vue_devtools-api.js.map +0 -7
  26. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js +0 -583
  27. package/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map +0 -7
  28. package/docs/.vitepress/cache/deps/vue.js +0 -343
  29. package/docs/.vitepress/cache/deps/vue.js.map +0 -7
  30. package/docs/.vitepress/config.mts +0 -26
  31. package/docs/api-examples.md +0 -49
  32. package/docs/index.md +0 -59
  33. package/docs/markdown-examples.md +0 -85
  34. package/docs-data/api-docs.json +0 -4143
  35. package/examples/quicbase-jsCDN.html +0 -36
  36. package/examples/react.js +0 -48
  37. package/examples/vanillajs.html +0 -47
  38. package/index.md +0 -25
  39. package/open-api/fix-spec-main.ts +0 -133
  40. package/open-api/generate-unified-interface.ts +0 -104
  41. package/open-api/generateDocsJson.ts +0 -24
  42. package/open-api/output/quickbase-fixed.json +0 -11191
  43. package/open-api/post-process-generated.ts +0 -226
  44. package/open-api/regenerate-client.ts +0 -117
  45. package/open-api/schema/enhance-general.ts +0 -111
  46. package/open-api/schema/fix-arrays.ts +0 -68
  47. package/open-api/schema/tags/apps.ts +0 -90
  48. package/open-api/schema/tags/fields.ts +0 -471
  49. package/open-api/schema/tags/groups.ts +0 -51
  50. package/open-api/schema/tags/index.ts +0 -66
  51. package/open-api/schema/tags/records.ts +0 -115
  52. package/open-api/schema/tags/tables.ts +0 -211
  53. package/open-api/schema/tags/users.ts +0 -47
  54. package/open-api/types/spec.ts +0 -42
  55. package/open-api/utils/common.ts +0 -29
  56. package/open-api/utils/generateJsDoc.ts +0 -74
  57. package/open-api/utils/infer-schema.ts +0 -40
  58. package/open-api/utils/naming.ts +0 -10
  59. package/open-api/utils/sharedUtils.ts +0 -352
  60. package/playwright.config.ts +0 -37
  61. package/rollup.config.js +0 -114
  62. package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +0 -46878
  63. package/specs/split/apps.json +0 -1315
  64. package/specs/split/auth.json +0 -191
  65. package/specs/split/fields.json +0 -3869
  66. package/specs/split/records.json +0 -754
  67. package/specs/split/reports.json +0 -619
  68. package/specs/split/tables.json +0 -1605
  69. package/src/RateLimitError.ts +0 -12
  70. package/src/ThrottleBucket.ts +0 -77
  71. package/src/authorizationStrategy.ts +0 -310
  72. package/src/generated-unified/QuickbaseClient.ts +0 -1135
  73. package/src/generated-unified/missing-types-report.json +0 -3
  74. package/src/index.ts +0 -1
  75. package/src/invokeMethod.ts +0 -212
  76. package/src/quickbaseClient.ts +0 -199
  77. package/src/rateLimiter.ts +0 -30
  78. package/src/tokenCache.ts +0 -39
  79. package/src/utils.ts +0 -50
  80. package/tests/playwright/qb/auth/cacheSwitching.test.ts +0 -159
  81. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +0 -219
  82. package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +0 -162
  83. package/tests/playwright/qb/auth/getAppWithTempTokenConcurrency.test.ts +0 -208
  84. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +0 -153
  85. package/tests/playwright/qb/auth/getTempToken.test.ts +0 -152
  86. package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +0 -179
  87. package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +0 -133
  88. package/tests/setup.ts +0 -53
  89. package/tests/vitest/qb/ThrottleBucket.test.ts +0 -77
  90. package/tests/vitest/qb/apps/copyApp.test.ts +0 -76
  91. package/tests/vitest/qb/apps/createApp.test.ts +0 -64
  92. package/tests/vitest/qb/apps/deleteApp.test.ts +0 -76
  93. package/tests/vitest/qb/apps/getApp.test.ts +0 -44
  94. package/tests/vitest/qb/apps/getAppEvents.test.ts +0 -67
  95. package/tests/vitest/qb/apps/updateApp.test.ts +0 -58
  96. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +0 -60
  97. package/tests/vitest/qb/auth/retryUserToken401.test.ts +0 -73
  98. package/tests/vitest/qb/auth/ssoTokenConcurrency.test.ts +0 -68
  99. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +0 -65
  100. package/tests/vitest/qb/auth/userTokenConcurrency.test.ts +0 -63
  101. package/tests/vitest/qb/fields/createField.test.ts +0 -77
  102. package/tests/vitest/qb/fields/deleteFields.test.ts +0 -83
  103. package/tests/vitest/qb/fields/getField.test.ts +0 -85
  104. package/tests/vitest/qb/fields/getFieldUsage.test.ts +0 -82
  105. package/tests/vitest/qb/fields/getFields.test.ts +0 -232
  106. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +0 -77
  107. package/tests/vitest/qb/fields/updateField.test.ts +0 -93
  108. package/tests/vitest/qb/formulas/runFormula.test.ts +0 -96
  109. package/tests/vitest/qb/index.ts +0 -29
  110. package/tests/vitest/qb/records/deleteRecords.test.ts +0 -110
  111. package/tests/vitest/qb/records/runQuery.test.ts +0 -146
  112. package/tests/vitest/qb/records/upsert.test.ts +0 -102
  113. package/tests/vitest/qb/tables/createRelationship.test.ts +0 -102
  114. package/tests/vitest/qb/tables/createTable.test.ts +0 -55
  115. package/tests/vitest/qb/tables/deleteRelationship.test.ts +0 -143
  116. package/tests/vitest/qb/tables/deleteTable.test.ts +0 -50
  117. package/tests/vitest/qb/tables/getAppTables.test.ts +0 -46
  118. package/tests/vitest/qb/tables/getRelationships.test.ts +0 -80
  119. package/tests/vitest/qb/tables/getTable.test.ts +0 -26
  120. package/tests/vitest/qb/tables/updateRelationship.test.ts +0 -250
  121. package/tests/vitest/qb/tables/updateTable.test.ts +0 -48
  122. package/tests/vitest/unit/ThrottleBucket.test.ts +0 -101
  123. package/tests/vitest/unit/apps/copyApp.test.ts +0 -581
  124. package/tests/vitest/unit/apps/createApp.test.ts +0 -186
  125. package/tests/vitest/unit/apps/deleteApp.test.ts +0 -216
  126. package/tests/vitest/unit/apps/getApp.test.ts +0 -128
  127. package/tests/vitest/unit/apps/getAppConcurrency.test.ts +0 -151
  128. package/tests/vitest/unit/apps/getAppEvents.test.ts +0 -193
  129. package/tests/vitest/unit/apps/updateApp.test.ts +0 -412
  130. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +0 -81
  131. package/tests/vitest/unit/auth/getTempToken.test.ts +0 -119
  132. package/tests/vitest/unit/auth/retryTempToken401.test.ts +0 -95
  133. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +0 -206
  134. package/tests/vitest/unit/auth/retryUserToken401.test.ts +0 -69
  135. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +0 -325
  136. package/tests/vitest/unit/auth/userTokenConcurrency.test.ts +0 -62
  137. package/tests/vitest/unit/fields/createField.test.ts +0 -451
  138. package/tests/vitest/unit/fields/deleteFields.test.ts +0 -141
  139. package/tests/vitest/unit/fields/getField.test.ts +0 -189
  140. package/tests/vitest/unit/fields/getFieldUsage.test.ts +0 -180
  141. package/tests/vitest/unit/fields/getFields.test.ts +0 -122
  142. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +0 -160
  143. package/tests/vitest/unit/fields/updateField.test.ts +0 -285
  144. package/tests/vitest/unit/formulas/runFormula.test.ts +0 -205
  145. package/tests/vitest/unit/index.ts +0 -34
  146. package/tests/vitest/unit/quickbaseClientHttpMethods.test.ts +0 -139
  147. package/tests/vitest/unit/rateLimit.test.ts +0 -191
  148. package/tests/vitest/unit/records/deleteRecords.test.ts +0 -112
  149. package/tests/vitest/unit/records/runQuery.test.ts +0 -102
  150. package/tests/vitest/unit/records/upsert.test.ts +0 -234
  151. package/tests/vitest/unit/tables/createRelationship.test.ts +0 -212
  152. package/tests/vitest/unit/tables/createTable.test.ts +0 -70
  153. package/tests/vitest/unit/tables/deleteRelationship.test.ts +0 -97
  154. package/tests/vitest/unit/tables/deleteTable.test.ts +0 -77
  155. package/tests/vitest/unit/tables/getAppTables.test.ts +0 -85
  156. package/tests/vitest/unit/tables/getRelationships.test.ts +0 -259
  157. package/tests/vitest/unit/tables/getTable.test.ts +0 -233
  158. package/tests/vitest/unit/tables/updateRelationship.test.ts +0 -307
  159. package/tests/vitest/unit/tables/updateTable.test.ts +0 -80
  160. package/tests/vitest/unit/tokenLifespan.test.ts +0 -204
  161. package/tests/vitest/unit/umd.test.ts +0 -8
  162. package/tools/log-gen.ts +0 -251
  163. package/tools/log-gen.yaml +0 -4829
  164. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  165. package/tsconfig.build.json +0 -17
  166. package/tsconfig.json +0 -32
  167. package/versionrc.json +0 -9
  168. package/vitest.config.ts +0 -27
@@ -1,3 +0,0 @@
1
- {
2
- "missingTypes": []
3
- }
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./quickbaseClient.ts";
@@ -1,212 +0,0 @@
1
- // src/invokeMethod.ts
2
- import { AuthorizationStrategy, extractDbid } from "./authorizationStrategy";
3
- import { RateLimiter } from "./rateLimiter";
4
- import { RateLimitError } from "./RateLimitError";
5
- import { ResponseError } from "./generated/runtime";
6
- import { QuickbaseClient } from "./quickbaseClient";
7
-
8
- export type ApiMethod<K extends keyof QuickbaseClient> = (
9
- requestParameters: Parameters<QuickbaseClient[K]>[0],
10
- initOverrides?: RequestInit
11
- ) => Promise<ReturnType<QuickbaseClient[K]>>;
12
-
13
- export interface MethodInfo<K extends keyof QuickbaseClient> {
14
- api: any;
15
- method: ApiMethod<K>;
16
- paramMap: string[];
17
- httpMethod: string;
18
- }
19
-
20
- export async function invokeMethod<K extends keyof QuickbaseClient>(
21
- methodName: K,
22
- params: Parameters<QuickbaseClient[K]>[0] & {
23
- dbid?: string;
24
- tableId?: string;
25
- appId?: string;
26
- startTime?: number; // Added for testing post-throttle timing
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
- attempt: number = 0,
36
- maxAttempts: number = rateLimiter.maxRetries + 1
37
- ): Promise<ReturnType<QuickbaseClient[K]>> {
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(
46
- ([key]) => key !== "body" && key !== "startTime"
47
- )
48
- )
49
- : { ...params, startTime: undefined };
50
- const requestParameters: any = {
51
- ...restParams,
52
- ...(hasBody ? { generated: body } : {}),
53
- };
54
-
55
- const requestOptions: RequestInit = {
56
- credentials: methodName === "getTempTokenDBID" ? "include" : "omit",
57
- method: methodInfo.httpMethod,
58
- };
59
-
60
- let dbid: string | undefined = extractDbid(params);
61
- let token = dbid
62
- ? await authStrategy.getToken(dbid)
63
- : await authStrategy.getToken("");
64
-
65
- if (token) {
66
- authStrategy.applyHeaders(baseHeaders, token);
67
- requestOptions.headers = { ...baseHeaders };
68
- if (methodName === "getTempTokenDBID") {
69
- return { temporaryAuthorization: token } as ReturnType<
70
- QuickbaseClient[K]
71
- >;
72
- }
73
- }
74
-
75
- async function processResponse(
76
- rawResponse: any
77
- ): Promise<ReturnType<QuickbaseClient[K]>> {
78
- if (rawResponse instanceof Response) {
79
- const contentType = rawResponse.headers
80
- .get("Content-Type")
81
- ?.toLowerCase();
82
- if (contentType?.includes("application/json")) {
83
- const jsonResponse = await rawResponse.json();
84
- return transformDates(jsonResponse, convertDates) as ReturnType<
85
- QuickbaseClient[K]
86
- >;
87
- }
88
- return rawResponse as ReturnType<QuickbaseClient[K]>;
89
- }
90
- if (rawResponse && typeof rawResponse.value === "function") {
91
- const response = await rawResponse.value();
92
- return transformDates(response, convertDates) as ReturnType<
93
- QuickbaseClient[K]
94
- >;
95
- }
96
- return transformDates(rawResponse, convertDates) as ReturnType<
97
- QuickbaseClient[K]
98
- >;
99
- }
100
-
101
- async function parseErrorResponse(
102
- response: Response
103
- ): Promise<{ message: string; status: number }> {
104
- let message = "Unknown error";
105
- let status = response.status || 500;
106
- try {
107
- const contentType =
108
- response.headers?.get("Content-Type")?.toLowerCase() ||
109
- "application/json";
110
- if (
111
- contentType.includes("application/json") &&
112
- typeof response.json === "function"
113
- ) {
114
- const errorBody = await response.json();
115
- if (
116
- errorBody &&
117
- typeof errorBody === "object" &&
118
- "message" in errorBody
119
- ) {
120
- message = errorBody.message;
121
- } else {
122
- message = "Invalid error response format";
123
- }
124
- } else if (typeof response.text === "function") {
125
- message = (await response.text()) || message;
126
- }
127
- } catch (e) {
128
- message = "Failed to parse error response";
129
- }
130
- return { message, status };
131
- }
132
-
133
- while (attempt < maxAttempts) {
134
- let acquired = false;
135
- try {
136
- await rateLimiter.throttle();
137
- acquired = true;
138
- const postThrottleTime = Date.now(); // Capture post-throttle time
139
- if (params.startTime !== undefined) params.startTime = postThrottleTime; // Update startTime for testing
140
- const responsePromise = methodInfo.method(
141
- requestParameters,
142
- requestOptions
143
- );
144
- rateLimiter.release(); // Release slot immediately after starting fetch
145
- const response = await responsePromise;
146
- return await processResponse(response);
147
- } catch (error: unknown) {
148
- if (acquired) rateLimiter.release();
149
- let status: number;
150
- let message: string;
151
- let response: Response | undefined;
152
-
153
- if (error instanceof ResponseError && error.response) {
154
- response = error.response;
155
- ({ message, status } = await parseErrorResponse(response));
156
- } else if (error instanceof Response) {
157
- response = error;
158
- ({ message, status } = await parseErrorResponse(response));
159
- } else {
160
- throw error;
161
- }
162
-
163
- if (status === 429) {
164
- if (!(error instanceof ResponseError)) {
165
- throw new Error("Expected ResponseError for 429 handling");
166
- }
167
- const delay = await rateLimiter.handle429(error, attempt + 1);
168
- if (attempt + 1 === maxAttempts) {
169
- throw new RateLimitError(
170
- `API Error: ${message} (Status: ${status})`,
171
- status,
172
- response?.headers.get("Retry-After")
173
- ? parseInt(response.headers.get("Retry-After")!, 10)
174
- : undefined
175
- );
176
- }
177
- await new Promise((resolve) => setTimeout(resolve, delay));
178
- attempt++;
179
- continue;
180
- }
181
-
182
- let newToken: string | null;
183
- try {
184
- newToken = await authStrategy.handleError(
185
- status,
186
- params,
187
- attempt,
188
- maxAttempts,
189
- debug,
190
- methodName
191
- );
192
- } catch (authError) {
193
- throw authError;
194
- }
195
-
196
- if (newToken !== null) {
197
- token = newToken;
198
- authStrategy.applyHeaders(baseHeaders, token);
199
- requestOptions.headers = { ...baseHeaders };
200
- if (debug)
201
- console.log(
202
- `[${methodName}] Retrying with token: ${token.substring(0, 10)}...`
203
- );
204
- attempt++;
205
- continue;
206
- }
207
-
208
- throw new Error(`API Error: ${message} (Status: ${status})`);
209
- }
210
- }
211
- throw new Error(`API Error: Exhausted retries after ${maxAttempts} attempts`);
212
- }
@@ -1,199 +0,0 @@
1
- // src/quickbaseClient.ts
2
- import { QuickbaseClient as IQuickbaseClient } from "./generated-unified/QuickbaseClient";
3
- import { Configuration, HTTPHeaders } from "./generated/runtime";
4
- import * as apis from "./generated/apis";
5
- import { TokenCache } from "./tokenCache";
6
- import {
7
- simplifyName,
8
- getParamNames,
9
- transformDates,
10
- extractHttpMethod,
11
- } from "./utils";
12
- import { invokeMethod, MethodInfo } from "./invokeMethod";
13
- import {
14
- TempTokenStrategy,
15
- UserTokenStrategy,
16
- AuthorizationStrategy,
17
- SsoTokenStrategy,
18
- } from "./authorizationStrategy";
19
- import { ConcurrentThrottleBucket } from "./ThrottleBucket";
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?: { rate: number; burst: number };
38
- maxRetries?: number;
39
- retryDelay?: number;
40
- tokenCache?: TokenCache;
41
- baseUrl?: string;
42
- }
43
-
44
- type MethodMap = {
45
- [K in keyof QuickbaseClient]: MethodInfo<K>;
46
- };
47
-
48
- export function quickbase(config: QuickbaseConfig): QuickbaseClient {
49
- const {
50
- realm,
51
- userToken,
52
- tempToken,
53
- useTempTokens,
54
- useSso,
55
- samlToken,
56
- fetchApi,
57
- debug,
58
- convertDates = true,
59
- tempTokenLifespan = 290000,
60
- throttle = { rate: 5, burst: 3 }, // Updated default to match passing test
61
- maxRetries = 3,
62
- retryDelay = 1000,
63
- tokenCache: providedTokenCache,
64
- baseUrl = "https://api.quickbase.com/v1",
65
- } = config;
66
-
67
- const tokenCache = providedTokenCache || new TokenCache(tempTokenLifespan);
68
- const throttleBucket = throttle
69
- ? new ConcurrentThrottleBucket(throttle.rate, throttle.burst)
70
- : null;
71
- const rateLimiter = new RateLimiter(throttleBucket, maxRetries, retryDelay);
72
-
73
- const defaultFetch: typeof fetch | undefined =
74
- typeof globalThis.window !== "undefined"
75
- ? globalThis.window.fetch.bind(globalThis.window)
76
- : undefined;
77
- const effectiveFetch = fetchApi || defaultFetch;
78
-
79
- if (!effectiveFetch) {
80
- throw new Error("No fetch implementation available");
81
- }
82
-
83
- const authStrategy: AuthorizationStrategy = useSso
84
- ? new SsoTokenStrategy(
85
- samlToken || "",
86
- realm,
87
- effectiveFetch,
88
- debug,
89
- baseUrl
90
- )
91
- : useTempTokens
92
- ? new TempTokenStrategy(
93
- tokenCache,
94
- tempToken,
95
- effectiveFetch,
96
- realm,
97
- baseUrl
98
- )
99
- : new UserTokenStrategy(userToken || "", baseUrl);
100
-
101
- const baseHeaders: HTTPHeaders = {
102
- "QB-Realm-Hostname": `${realm}.quickbase.com`,
103
- "Content-Type": "application/json",
104
- };
105
-
106
- const configuration = new Configuration({
107
- basePath: baseUrl,
108
- headers: { ...baseHeaders },
109
- fetchApi: effectiveFetch,
110
- credentials: "omit",
111
- });
112
-
113
- const apiInstances = Object.fromEntries(
114
- Object.entries(apis)
115
- .filter(([name]) => name.endsWith("Api"))
116
- .map(([name, ApiClass]) => [
117
- name.replace("Api", "").toLowerCase(),
118
- new ApiClass(configuration),
119
- ])
120
- );
121
-
122
- function buildMethodMap(): MethodMap {
123
- const methodMap: Partial<MethodMap> = {};
124
- const isValidMethod = (name: string) =>
125
- !name.startsWith("_") &&
126
- name !== "constructor" &&
127
- !["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
128
-
129
- for (const [apiName, api] of Object.entries(apiInstances)) {
130
- Object.getOwnPropertyNames(Object.getPrototypeOf(api))
131
- .filter(
132
- (name) =>
133
- isValidMethod(name) &&
134
- typeof api[name as keyof typeof api] === "function"
135
- )
136
- .forEach((rawMethodName) => {
137
- const simplifiedName = simplifyName(
138
- rawMethodName
139
- ) as keyof QuickbaseClient;
140
- const rawMethodKey = `${rawMethodName}Raw` as keyof typeof api;
141
- const method =
142
- api[rawMethodKey] || api[rawMethodName as keyof typeof api];
143
- const boundMethod = method.bind(api as any) as unknown;
144
- if (typeof boundMethod === "function" && boundMethod.length <= 2) {
145
- const httpMethod = extractHttpMethod(method);
146
- methodMap[simplifiedName] = {
147
- api,
148
- method: boundMethod as any,
149
- paramMap: getParamNames(method),
150
- httpMethod,
151
- };
152
- }
153
- });
154
- }
155
- if (debug) {
156
- console.log("[buildMethodMap] Methods:", Object.keys(methodMap));
157
- }
158
- return methodMap as MethodMap;
159
- }
160
-
161
- const methodMap = buildMethodMap();
162
-
163
- const proxyHandler: ProxyHandler<QuickbaseClient> = {
164
- get: (_, prop: string) => {
165
- console.log(
166
- "[proxy] Accessing:",
167
- prop,
168
- "in methodMap:",
169
- prop in methodMap
170
- );
171
- if (prop in methodMap) {
172
- const methodName = prop as keyof QuickbaseClient;
173
- return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) =>
174
- invokeMethod(
175
- methodName,
176
- params,
177
- methodMap,
178
- baseHeaders,
179
- authStrategy,
180
- rateLimiter,
181
- transformDates,
182
- debug,
183
- convertDates
184
- );
185
- }
186
- console.log("[proxy] Method not found:", prop);
187
- return undefined;
188
- },
189
- };
190
-
191
- const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, proxyHandler);
192
-
193
- if (debug) {
194
- console.log("[createClient] Config:", config);
195
- console.log("[createClient] Proxy created:", proxy);
196
- }
197
-
198
- return proxy;
199
- }
@@ -1,30 +0,0 @@
1
- // src/rateLimiter.ts
2
- import { ConcurrentThrottleBucket } from "./ThrottleBucket"; // Updated import
3
- import { ResponseError } from "./generated/runtime";
4
-
5
- export class RateLimiter {
6
- constructor(
7
- private throttleBucket: ConcurrentThrottleBucket | null, // Updated type
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
- }
package/src/tokenCache.ts DELETED
@@ -1,39 +0,0 @@
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
- delete(dbid: string): void {
33
- this.cache.delete(dbid);
34
- }
35
-
36
- clear(): void {
37
- this.cache.clear();
38
- }
39
- }
package/src/utils.ts DELETED
@@ -1,50 +0,0 @@
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
- }
@@ -1,159 +0,0 @@
1
- import { test, expect } from "@playwright/test";
2
- import { quickbase, QuickbaseClient } from "../../../../src/quickbaseClient.ts";
3
- import dotenv from "dotenv";
4
-
5
- dotenv.config();
6
-
7
- const loginToQuickbase = async (
8
- page: import("@playwright/test").Page,
9
- quickbaseUrl: string,
10
- username: string,
11
- password: string
12
- ): Promise<boolean> => {
13
- console.log("Logging in to QuickBase");
14
- let loginSuccess = false;
15
- const maxLoginAttempts = 3;
16
- let loginAttempt = 0;
17
-
18
- while (!loginSuccess && loginAttempt < maxLoginAttempts) {
19
- try {
20
- loginAttempt++;
21
- if (loginAttempt > 1) console.log(`Login attempt ${loginAttempt}`);
22
-
23
- console.log("Navigating to login page");
24
- await page.goto(quickbaseUrl, { timeout: 30000 });
25
- console.log("Waiting for loginid selector");
26
- await page.waitForSelector("input[name='loginid']", { timeout: 30000 });
27
- await page.fill("input[name='loginid']", username);
28
- await page.fill("input[name='password']", password);
29
- console.log("Clicking signin");
30
- await page.click("#signin");
31
- console.log("Waiting for navigation");
32
- await page.waitForURL(`https://*.quickbase.com/**`, {
33
- waitUntil: "networkidle",
34
- timeout: 30000,
35
- });
36
-
37
- const loginError = await page.$(".login-error");
38
- if (loginError) {
39
- throw new Error("Login failed. Please check your credentials.");
40
- }
41
-
42
- console.log("Signed In to QuickBase.");
43
- loginSuccess = true;
44
- } catch (error) {
45
- console.error(
46
- `Login attempt ${loginAttempt} failed: ${(error as Error).message}`
47
- );
48
- if (loginAttempt >= maxLoginAttempts) {
49
- console.error("Max login attempts reached. Exiting.");
50
- await page.context().browser()!.close();
51
- return false;
52
- }
53
- await new Promise((resolve) => setTimeout(resolve, 5000));
54
- }
55
- }
56
- return loginSuccess;
57
- };
58
-
59
- test.describe("QuickbaseClient Integration - Cache Switching with Temp Tokens", () => {
60
- test("caches and switches tokens for different DBIDs with getApp → getFields → getApp sequence", async ({
61
- page,
62
- }) => {
63
- console.log("Starting cacheSwitching test");
64
- const realm = process.env.QB_REALM ?? "";
65
- const appId = process.env.QB_APP_ID ?? "";
66
- const tableId = process.env.QB_TABLE_ID_1 ?? "";
67
- const username = process.env.QB_USERNAME ?? "";
68
- const password = process.env.QB_PASSWORD ?? "";
69
-
70
- if (!realm || !appId || !tableId || !username || !password) {
71
- throw new Error(
72
- "Required QuickBase environment variables are not defined"
73
- );
74
- }
75
-
76
- const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
77
- const loginSuccess = await loginToQuickbase(
78
- page,
79
- quickbaseUrl,
80
- username,
81
- password
82
- );
83
- if (!loginSuccess) {
84
- throw new Error("Failed to log in to QuickBase after max attempts.");
85
- }
86
-
87
- await page.goto(`https://${realm}.quickbase.com/db/${appId}`, {
88
- waitUntil: "networkidle",
89
- timeout: 30000,
90
- });
91
- console.log("Post-login URL after app navigation:", page.url());
92
-
93
- const client = quickbase({
94
- realm,
95
- useTempTokens: true,
96
- debug: true,
97
- fetchApi: async (input: RequestInfo | URL, init?: RequestInit) => {
98
- const url = input.toString();
99
- console.log(`Fetching ${url} with init:`, init);
100
- const effectiveInit = init || {};
101
- const response = await page.evaluate(
102
- async ([fetchUrl, fetchInit]: [string, RequestInit]) => {
103
- const res = await fetch(fetchUrl, fetchInit); // Rely on quickbaseClient.ts for credentials
104
- const body = await res.text();
105
- const headersObj: Record<string, string> = {};
106
- res.headers.forEach((value, key) => {
107
- headersObj[key] = value;
108
- });
109
- return {
110
- ok: res.ok,
111
- status: res.status,
112
- statusText: res.statusText,
113
- headers: headersObj,
114
- body,
115
- };
116
- },
117
- [url, effectiveInit] as [string, RequestInit]
118
- );
119
-
120
- console.log(`Raw response from ${url}:`, {
121
- status: response.status,
122
- statusText: response.statusText,
123
- body: response.body,
124
- });
125
-
126
- const fetchResponse = new Response(response.body || null, {
127
- status: response.status,
128
- statusText: response.statusText,
129
- headers: new Headers(response.headers),
130
- });
131
-
132
- fetchResponse.json = async () => {
133
- if (!response.body) {
134
- throw new Error("Empty response body from API");
135
- }
136
- try {
137
- return JSON.parse(response.body);
138
- } catch (e) {
139
- throw new SyntaxError(`Invalid JSON response: ${response.body}`);
140
- }
141
- };
142
-
143
- return fetchResponse;
144
- },
145
- }) as QuickbaseClient;
146
-
147
- const appResult1 = await client.getApp({ appId });
148
- console.log("First getApp response:", appResult1);
149
- expect(appResult1).toHaveProperty("id", appId);
150
-
151
- const fieldsResult = await client.getFields({ tableId });
152
- console.log("getFields response:", fieldsResult);
153
- expect(fieldsResult.length).toBeGreaterThan(0);
154
-
155
- const appResult2 = await client.getApp({ appId });
156
- console.log("Second getApp response:", appResult2);
157
- expect(appResult2).toHaveProperty("id", appId);
158
- });
159
- });