quickbase-js 0.1.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +252 -126
- package/dist/esm/quickbase.d.ts +2725 -2689
- package/dist/esm/quickbase.js +1763 -3524
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +1762 -3706
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/package.json +27 -12
- package/CHANGELOG.md +0 -155
- package/build-common.js +0 -131
- package/build-esm.js +0 -71
- package/build-umd.js +0 -69
- package/examples/quicbase-jsCDN.html +0 -36
- package/examples/react.js +0 -48
- package/examples/vanillajs.html +0 -47
- package/open-api/fix-spec-main.ts +0 -97
- package/open-api/generate-override-report.ts +0 -214
- package/open-api/generate-unified-interface.ts +0 -256
- package/open-api/output/override-report.html +0 -11
- package/open-api/output/override-report.json +0 -14395
- package/open-api/output/quickbase-fixed.json +0 -11191
- package/open-api/regenerate-client.ts +0 -116
- package/open-api/schema/enhance-general.ts +0 -100
- package/open-api/schema/fix-arrays.ts +0 -69
- package/open-api/schema/tags/apps.ts +0 -90
- package/open-api/schema/tags/fields.ts +0 -471
- package/open-api/schema/tags/groups.ts +0 -51
- package/open-api/schema/tags/index.ts +0 -66
- package/open-api/schema/tags/records.ts +0 -115
- package/open-api/schema/tags/tables.ts +0 -211
- package/open-api/schema/tags/users.ts +0 -47
- package/open-api/types/spec.ts +0 -42
- package/open-api/utils/infer-schema.ts +0 -40
- package/open-api/utils/naming.ts +0 -10
- package/playwright.config.ts +0 -37
- package/rollup.config.js +0 -114
- package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +0 -46878
- package/specs/split/apps.json +0 -1315
- package/specs/split/auth.json +0 -191
- package/specs/split/fields.json +0 -3869
- package/specs/split/records.json +0 -754
- package/specs/split/reports.json +0 -619
- package/specs/split/tables.json +0 -1605
- package/src/RateLimitError.ts +0 -12
- package/src/ThrottleBucket.ts +0 -36
- package/src/generated-unified/QuickbaseClient.ts +0 -359
- package/src/generated-unified/missing-types-report.json +0 -3
- package/src/index.ts +0 -1
- package/src/invokeMethod.ts +0 -357
- package/src/quickbaseClient.ts +0 -244
- package/src/tokenCache.ts +0 -51
- package/src/utils.ts +0 -7
- package/test-esm.js +0 -1
- package/test-results.json +0 -2050
- package/test-umd.js +0 -1
- package/test.ts +0 -1
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +0 -159
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +0 -219
- package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +0 -162
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +0 -153
- package/tests/playwright/qb/auth/getTempToken.test.ts +0 -152
- package/tests/playwright/qb/auth/tempTokenPrefetch.test.ts +0 -179
- package/tests/playwright/qb/auth/tempTokenRenewal.test.ts +0 -133
- package/tests/setup.ts +0 -53
- package/tests/vitest/qb/apps/copyApp.test.ts +0 -76
- package/tests/vitest/qb/apps/createApp.test.ts +0 -64
- package/tests/vitest/qb/apps/deleteApp.test.ts +0 -76
- package/tests/vitest/qb/apps/getApp.test.ts +0 -44
- package/tests/vitest/qb/apps/getAppEvents.test.ts +0 -67
- package/tests/vitest/qb/apps/updateApp.test.ts +0 -58
- package/tests/vitest/qb/auth/retryUserToken401.test.ts +0 -56
- package/tests/vitest/qb/fields/createField.test.ts +0 -77
- package/tests/vitest/qb/fields/deleteFields.test.ts +0 -83
- package/tests/vitest/qb/fields/getField.test.ts +0 -85
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +0 -174
- package/tests/vitest/qb/fields/getFields.test.ts +0 -232
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +0 -77
- package/tests/vitest/qb/fields/updateField.test.ts +0 -93
- package/tests/vitest/qb/index.ts +0 -29
- package/tests/vitest/qb/records/deleteRecords.test.ts +0 -110
- package/tests/vitest/qb/records/runQuery.test.ts +0 -146
- package/tests/vitest/qb/records/upsert.test.ts +0 -102
- package/tests/vitest/qb/tables/createTable.test.ts +0 -55
- package/tests/vitest/qb/tables/deleteTable.test.ts +0 -50
- package/tests/vitest/qb/tables/getAppTables.test.ts +0 -46
- package/tests/vitest/qb/tables/getRelationships.test.ts +0 -80
- package/tests/vitest/qb/tables/getTable.test.ts +0 -26
- package/tests/vitest/qb/tables/updateRelationship.test.ts +0 -277
- package/tests/vitest/qb/tables/updateTable.test.ts +0 -48
- package/tests/vitest/unit/apps/copyApp.test.ts +0 -488
- package/tests/vitest/unit/apps/createApp.test.ts +0 -186
- package/tests/vitest/unit/apps/deleteApp.test.ts +0 -216
- package/tests/vitest/unit/apps/getApp.test.ts +0 -128
- package/tests/vitest/unit/apps/getAppEvents.test.ts +0 -193
- package/tests/vitest/unit/apps/updateApp.test.ts +0 -381
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +0 -62
- package/tests/vitest/unit/auth/getTempToken.test.ts +0 -95
- package/tests/vitest/unit/auth/retryTempToken401.test.ts +0 -113
- package/tests/vitest/unit/auth/retryTempTokenTwo401.test.ts +0 -81
- package/tests/vitest/unit/auth/retryUserToken401.test.ts +0 -56
- package/tests/vitest/unit/fields/createField.test.ts +0 -387
- package/tests/vitest/unit/fields/deleteFields.test.ts +0 -141
- package/tests/vitest/unit/fields/getField.test.ts +0 -189
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +0 -166
- package/tests/vitest/unit/fields/getFields.test.ts +0 -122
- package/tests/vitest/unit/fields/getFieldsUsage.test.ts +0 -160
- package/tests/vitest/unit/fields/updateField.test.ts +0 -280
- package/tests/vitest/unit/index.ts +0 -33
- package/tests/vitest/unit/rateLimit.test.ts +0 -189
- package/tests/vitest/unit/records/deleteRecords.test.ts +0 -112
- package/tests/vitest/unit/records/runQuery.test.ts +0 -102
- package/tests/vitest/unit/records/upsert.test.ts +0 -234
- package/tests/vitest/unit/tables/createTable.test.ts +0 -70
- package/tests/vitest/unit/tables/deleteTable.test.ts +0 -77
- package/tests/vitest/unit/tables/getAppTables.test.ts +0 -85
- package/tests/vitest/unit/tables/getRelationships.test.ts +0 -259
- package/tests/vitest/unit/tables/getTable.test.ts +0 -233
- package/tests/vitest/unit/tables/updateRelationship.test.ts +0 -305
- package/tests/vitest/unit/tables/updateTable.test.ts +0 -80
- package/tests/vitest/unit/tokenLifespan.test.ts +0 -138
- package/tests/vitest/unit/umd.test.ts +0 -8
- package/text.txt +0 -104
- package/tools/log-gen.ts +0 -235
- package/tools/log-gen.yaml +0 -4982
- package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
- package/tsconfig.build.json +0 -17
- package/tsconfig.json +0 -32
- package/versionrc.json +0 -9
- package/vitest.config.ts +0 -27
package/src/invokeMethod.ts
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
// src/invokeMethod.ts
|
|
2
|
-
import { QuickbaseClient } from "./quickbaseClient";
|
|
3
|
-
import { ResponseError } from "./generated/runtime";
|
|
4
|
-
import { ThrottleBucket } from "./ThrottleBucket";
|
|
5
|
-
import { RateLimitError } from "./RateLimitError";
|
|
6
|
-
import { GetTempTokenDBID200Response } from "./generated/models";
|
|
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 interface TempTokenParams {
|
|
21
|
-
appId?: string;
|
|
22
|
-
tableId?: string;
|
|
23
|
-
dbid?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface TokenCache {
|
|
27
|
-
get: (dbid: string) => string | undefined;
|
|
28
|
-
set: (dbid: string, token: string) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export async function invokeMethod<K extends keyof QuickbaseClient>(
|
|
32
|
-
methodName: K,
|
|
33
|
-
params: Parameters<QuickbaseClient[K]>[0] & Partial<TempTokenParams>,
|
|
34
|
-
methodMap: { [P in keyof QuickbaseClient]: MethodInfo<P> },
|
|
35
|
-
baseHeaders: Record<string, string>,
|
|
36
|
-
tokenCache: TokenCache,
|
|
37
|
-
fetchTempToken: (dbid: string) => Promise<string>,
|
|
38
|
-
transformDates: (obj: any, convertStringsToDates: boolean) => any,
|
|
39
|
-
initialTempToken: string | undefined,
|
|
40
|
-
userToken: string | undefined,
|
|
41
|
-
useTempTokens: boolean | undefined,
|
|
42
|
-
debug: boolean | undefined,
|
|
43
|
-
convertDates: boolean,
|
|
44
|
-
retryCount: number = 0,
|
|
45
|
-
throttleBucket: ThrottleBucket | null = null,
|
|
46
|
-
maxRetries: number = 3,
|
|
47
|
-
retryDelay: number = 1000
|
|
48
|
-
): Promise<ReturnType<QuickbaseClient[K]>> {
|
|
49
|
-
const methodInfo = methodMap[methodName];
|
|
50
|
-
if (!methodInfo) throw new Error(`Method ${methodName} not found`);
|
|
51
|
-
|
|
52
|
-
const hasBody = "body" in params && params.body !== undefined;
|
|
53
|
-
const body = hasBody ? (params as any).body : undefined;
|
|
54
|
-
const restParams: any = hasBody
|
|
55
|
-
? Object.fromEntries(
|
|
56
|
-
Object.entries(params).filter(([key]) => key !== "body")
|
|
57
|
-
)
|
|
58
|
-
: { ...params };
|
|
59
|
-
|
|
60
|
-
const requestParameters: any = {
|
|
61
|
-
...restParams,
|
|
62
|
-
...(hasBody ? { generated: body } : {}),
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
let requestOptions: RequestInit = {
|
|
66
|
-
credentials: "omit",
|
|
67
|
-
method: methodInfo.httpMethod,
|
|
68
|
-
...(hasBody ? { body } : {}),
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const selectedToken =
|
|
72
|
-
initialTempToken || (userToken && !useTempTokens ? userToken : undefined);
|
|
73
|
-
let authorizationToken = selectedToken;
|
|
74
|
-
|
|
75
|
-
if (methodName === "getTempTokenDBID" && useTempTokens) {
|
|
76
|
-
const dbid = extractDbid(params, "No dbid provided for getTempTokenDBID");
|
|
77
|
-
const cachedToken = tokenCache.get(dbid);
|
|
78
|
-
if (cachedToken) {
|
|
79
|
-
return Promise.resolve({
|
|
80
|
-
temporaryAuthorization: cachedToken,
|
|
81
|
-
} as GetTempTokenDBID200Response as ReturnType<QuickbaseClient[K]>);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (useTempTokens && !authorizationToken) {
|
|
86
|
-
const dbid = extractDbid(
|
|
87
|
-
params,
|
|
88
|
-
`No dbid found in params for ${methodName} to fetch temp token`
|
|
89
|
-
);
|
|
90
|
-
const cachedToken = tokenCache.get(dbid);
|
|
91
|
-
authorizationToken = cachedToken || (await fetchTempToken(dbid));
|
|
92
|
-
if (methodName === "getTempTokenDBID") {
|
|
93
|
-
return Promise.resolve({
|
|
94
|
-
temporaryAuthorization: authorizationToken,
|
|
95
|
-
} as GetTempTokenDBID200Response as ReturnType<QuickbaseClient[K]>);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
requestOptions.headers = authorizationToken
|
|
99
|
-
? {
|
|
100
|
-
...baseHeaders,
|
|
101
|
-
Authorization: useTempTokens
|
|
102
|
-
? `QB-TEMP-TOKEN ${authorizationToken}`
|
|
103
|
-
: `QB-USER-TOKEN ${authorizationToken}`,
|
|
104
|
-
}
|
|
105
|
-
: baseHeaders;
|
|
106
|
-
|
|
107
|
-
if (debug) {
|
|
108
|
-
console.log(`[${methodName}] requestParameters:`, requestParameters);
|
|
109
|
-
console.log(`[${methodName}] requestOptions:`, requestOptions);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function processResponse(
|
|
113
|
-
rawResponse: any
|
|
114
|
-
): Promise<ReturnType<QuickbaseClient[K]>> {
|
|
115
|
-
if (debug) console.log(`[${methodName}] rawResponse:`, rawResponse);
|
|
116
|
-
|
|
117
|
-
if (rawResponse instanceof Response) {
|
|
118
|
-
const contentType = rawResponse.headers
|
|
119
|
-
.get("Content-Type")
|
|
120
|
-
?.toLowerCase();
|
|
121
|
-
if (debug) console.log(`[${methodName}] contentType:`, contentType);
|
|
122
|
-
|
|
123
|
-
if (contentType?.includes("application/octet-stream")) {
|
|
124
|
-
return (await rawResponse.arrayBuffer()) as ReturnType<
|
|
125
|
-
QuickbaseClient[K]
|
|
126
|
-
>;
|
|
127
|
-
}
|
|
128
|
-
if (
|
|
129
|
-
contentType?.includes("application/x-yaml") ||
|
|
130
|
-
contentType?.includes("text/yaml")
|
|
131
|
-
) {
|
|
132
|
-
return (await rawResponse.text()) as ReturnType<QuickbaseClient[K]>;
|
|
133
|
-
}
|
|
134
|
-
if (contentType?.includes("application/json")) {
|
|
135
|
-
const jsonResponse = await rawResponse.json();
|
|
136
|
-
return transformDates(jsonResponse, convertDates) as ReturnType<
|
|
137
|
-
QuickbaseClient[K]
|
|
138
|
-
>;
|
|
139
|
-
}
|
|
140
|
-
return rawResponse as ReturnType<QuickbaseClient[K]>;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (rawResponse && typeof rawResponse.value === "function") {
|
|
144
|
-
const response = await rawResponse.value();
|
|
145
|
-
if (debug)
|
|
146
|
-
console.log(`[${methodName}] Resolved JSONApiResponse:`, response);
|
|
147
|
-
return transformDates(response, convertDates) as ReturnType<
|
|
148
|
-
QuickbaseClient[K]
|
|
149
|
-
>;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const transformed = transformDates(rawResponse, convertDates);
|
|
153
|
-
if (debug)
|
|
154
|
-
console.log(`[${methodName}] Transformed non-Response:`, transformed);
|
|
155
|
-
return transformed as ReturnType<QuickbaseClient[K]>;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function withRetries(
|
|
159
|
-
fn: () => Promise<ReturnType<QuickbaseClient[K]>>,
|
|
160
|
-
options: {
|
|
161
|
-
maxAttempts: number;
|
|
162
|
-
shouldRetry: (error: any) => boolean;
|
|
163
|
-
onRetry?: (error: any, attempt: number) => Promise<void>;
|
|
164
|
-
delay?: (attempt: number, error: any) => number;
|
|
165
|
-
}
|
|
166
|
-
): Promise<ReturnType<QuickbaseClient[K]>> {
|
|
167
|
-
let attempt = 0;
|
|
168
|
-
while (true) {
|
|
169
|
-
try {
|
|
170
|
-
if (throttleBucket) {
|
|
171
|
-
if (debug) console.log(`[${methodName}] Awaiting throttle bucket`);
|
|
172
|
-
await throttleBucket.acquire();
|
|
173
|
-
if (debug) console.log(`[${methodName}] Throttle bucket acquired`);
|
|
174
|
-
}
|
|
175
|
-
return await fn();
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.log(`[${methodName}] Caught error:`, error);
|
|
178
|
-
console.log(
|
|
179
|
-
`[${methodName}] instanceof ResponseError:`,
|
|
180
|
-
error instanceof ResponseError
|
|
181
|
-
);
|
|
182
|
-
console.log(
|
|
183
|
-
`[${methodName}] Response status:`,
|
|
184
|
-
(error as any).response?.status
|
|
185
|
-
);
|
|
186
|
-
const effectiveError =
|
|
187
|
-
error instanceof Object &&
|
|
188
|
-
"cause" in error &&
|
|
189
|
-
error.cause instanceof ResponseError
|
|
190
|
-
? error.cause
|
|
191
|
-
: error instanceof ResponseError
|
|
192
|
-
? error
|
|
193
|
-
: error;
|
|
194
|
-
console.log(`[${methodName}] Effective error:`, effectiveError);
|
|
195
|
-
console.log(
|
|
196
|
-
`[${methodName}] Effective instanceof ResponseError:`,
|
|
197
|
-
effectiveError instanceof ResponseError
|
|
198
|
-
);
|
|
199
|
-
console.log(
|
|
200
|
-
`[${methodName}] Effective response status:`,
|
|
201
|
-
(effectiveError as any).response?.status
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
attempt++;
|
|
205
|
-
if (
|
|
206
|
-
!options.shouldRetry(effectiveError) ||
|
|
207
|
-
attempt >= options.maxAttempts
|
|
208
|
-
) {
|
|
209
|
-
let errorMessage =
|
|
210
|
-
effectiveError instanceof ResponseError
|
|
211
|
-
? effectiveError.message
|
|
212
|
-
: String(error);
|
|
213
|
-
console.log(`[${methodName}] No retry, throwing:`, errorMessage);
|
|
214
|
-
if (
|
|
215
|
-
effectiveError instanceof ResponseError &&
|
|
216
|
-
effectiveError.response
|
|
217
|
-
) {
|
|
218
|
-
try {
|
|
219
|
-
const errorBody = await effectiveError.response.json();
|
|
220
|
-
if (debug)
|
|
221
|
-
console.log(`[${methodName}] Error response body:`, errorBody);
|
|
222
|
-
errorMessage = errorBody?.message || errorMessage;
|
|
223
|
-
} catch (e) {
|
|
224
|
-
if (debug)
|
|
225
|
-
console.log(`[${methodName}] Failed to parse error body:`, e);
|
|
226
|
-
}
|
|
227
|
-
throw new Error(
|
|
228
|
-
`API Error: ${errorMessage} (Status: ${effectiveError.response.status})`
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
throw new Error(`API Error: ${errorMessage} (Status: unknown)`);
|
|
232
|
-
}
|
|
233
|
-
if (options.onRetry) {
|
|
234
|
-
console.log(`[${methodName}] Retrying, attempt:`, attempt);
|
|
235
|
-
await options.onRetry(effectiveError, attempt);
|
|
236
|
-
}
|
|
237
|
-
const delayMs = options.delay
|
|
238
|
-
? options.delay(attempt, effectiveError)
|
|
239
|
-
: retryDelay * Math.pow(2, attempt - 1);
|
|
240
|
-
if (debug)
|
|
241
|
-
console.log(
|
|
242
|
-
`[${methodName}] Retrying after ${delayMs}ms (attempt ${attempt}/${
|
|
243
|
-
options.maxAttempts - 1
|
|
244
|
-
})`
|
|
245
|
-
);
|
|
246
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const mainPromise = withRetries(
|
|
252
|
-
() =>
|
|
253
|
-
methodInfo
|
|
254
|
-
.method(requestParameters, requestOptions)
|
|
255
|
-
.then(processResponse),
|
|
256
|
-
{
|
|
257
|
-
maxAttempts: (useTempTokens ?? false) || userToken ? 2 : 1,
|
|
258
|
-
shouldRetry: (error) =>
|
|
259
|
-
error instanceof ResponseError &&
|
|
260
|
-
error.response?.status === 401 &&
|
|
261
|
-
((useTempTokens ?? false) ||
|
|
262
|
-
(!!userToken && !(useTempTokens ?? false))),
|
|
263
|
-
onRetry: async (error) => {
|
|
264
|
-
if (error instanceof ResponseError && error.response?.status === 401) {
|
|
265
|
-
if (useTempTokens ?? false) {
|
|
266
|
-
if (debug)
|
|
267
|
-
console.log(
|
|
268
|
-
`Authorization error for ${methodName} (temp token), refreshing token:`,
|
|
269
|
-
error.message
|
|
270
|
-
);
|
|
271
|
-
const dbid = extractDbid(
|
|
272
|
-
params,
|
|
273
|
-
`No dbid to refresh token after authorization error`
|
|
274
|
-
);
|
|
275
|
-
try {
|
|
276
|
-
authorizationToken = await fetchTempToken(dbid);
|
|
277
|
-
} catch (fetchError) {
|
|
278
|
-
const fetchErrorMessage =
|
|
279
|
-
fetchError instanceof ResponseError && fetchError.response
|
|
280
|
-
? (await fetchError.response.json())?.message ||
|
|
281
|
-
"Unauthorized"
|
|
282
|
-
: String(fetchError);
|
|
283
|
-
throw new Error(
|
|
284
|
-
`API Error: ${fetchErrorMessage} (Status: ${
|
|
285
|
-
fetchError instanceof ResponseError &&
|
|
286
|
-
fetchError.response?.status
|
|
287
|
-
? fetchError.response.status
|
|
288
|
-
: "unknown"
|
|
289
|
-
})`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
requestOptions.headers = {
|
|
293
|
-
...baseHeaders,
|
|
294
|
-
Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
|
|
295
|
-
};
|
|
296
|
-
} else if (userToken) {
|
|
297
|
-
if (debug)
|
|
298
|
-
console.log(
|
|
299
|
-
`Authorization error for ${methodName} (user token), retrying with same token:`,
|
|
300
|
-
error.message
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
if (debug)
|
|
304
|
-
console.log(
|
|
305
|
-
`Retrying ${methodName} with ${
|
|
306
|
-
useTempTokens ? "temp" : "user"
|
|
307
|
-
} token`
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
|
-
}
|
|
312
|
-
);
|
|
313
|
-
|
|
314
|
-
return mainPromise.catch((error: unknown) => {
|
|
315
|
-
let effectiveError: unknown = error;
|
|
316
|
-
if (
|
|
317
|
-
error instanceof Object &&
|
|
318
|
-
"cause" in error &&
|
|
319
|
-
error.cause instanceof ResponseError
|
|
320
|
-
) {
|
|
321
|
-
effectiveError = error.cause;
|
|
322
|
-
} else if (error instanceof ResponseError) {
|
|
323
|
-
effectiveError = error;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
effectiveError instanceof ResponseError &&
|
|
328
|
-
effectiveError.response?.status === 429
|
|
329
|
-
) {
|
|
330
|
-
return withRetries(() => Promise.reject(error), {
|
|
331
|
-
maxAttempts: maxRetries + 1,
|
|
332
|
-
shouldRetry: (err) =>
|
|
333
|
-
err instanceof ResponseError && err.response?.status === 429,
|
|
334
|
-
delay: (attempt, err) => {
|
|
335
|
-
const headers =
|
|
336
|
-
err instanceof ResponseError && err.response
|
|
337
|
-
? err.response.headers
|
|
338
|
-
: new Headers();
|
|
339
|
-
const retryAfter = headers.get("Retry-After");
|
|
340
|
-
return retryAfter
|
|
341
|
-
? parseInt(retryAfter, 10) * 1000
|
|
342
|
-
: retryDelay * Math.pow(2, attempt - 1);
|
|
343
|
-
},
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
throw effectiveError;
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
function extractDbid(
|
|
351
|
-
params: Partial<TempTokenParams>,
|
|
352
|
-
errorMessage: string
|
|
353
|
-
): string {
|
|
354
|
-
const dbid = params.dbid || params.tableId || params.appId;
|
|
355
|
-
if (!dbid) throw new Error(errorMessage);
|
|
356
|
-
return dbid;
|
|
357
|
-
}
|
package/src/quickbaseClient.ts
DELETED
|
@@ -1,244 +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 { simplifyName } from "./utils";
|
|
7
|
-
import { invokeMethod, MethodInfo } from "./invokeMethod"; // Added import
|
|
8
|
-
import { ThrottleBucket } from "./ThrottleBucket";
|
|
9
|
-
|
|
10
|
-
export * from "./generated/models/index";
|
|
11
|
-
|
|
12
|
-
export interface QuickbaseClient extends IQuickbaseClient {}
|
|
13
|
-
|
|
14
|
-
export interface QuickbaseConfig {
|
|
15
|
-
realm: string;
|
|
16
|
-
userToken?: string;
|
|
17
|
-
tempToken?: string;
|
|
18
|
-
useTempTokens?: boolean;
|
|
19
|
-
debug?: boolean;
|
|
20
|
-
fetchApi?: typeof fetch;
|
|
21
|
-
convertDates?: boolean;
|
|
22
|
-
tokenLifespan?: number;
|
|
23
|
-
throttle?: { rate: number; burst: number };
|
|
24
|
-
maxRetries?: number;
|
|
25
|
-
retryDelay?: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type MethodMap = {
|
|
29
|
-
[K in keyof QuickbaseClient]: MethodInfo<K>;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const getParamNames = (fn: (...args: any[]) => any): string[] =>
|
|
33
|
-
fn
|
|
34
|
-
.toString()
|
|
35
|
-
.slice(fn.toString().indexOf("(") + 1, fn.toString().indexOf(")"))
|
|
36
|
-
.split(",")
|
|
37
|
-
.map((p) => p.trim().split("=")[0]?.trim())
|
|
38
|
-
.filter((p) => p && !p.match(/^\{/) && p !== "options");
|
|
39
|
-
|
|
40
|
-
function transformDates(obj: any, convertStringsToDates: boolean = true): any {
|
|
41
|
-
if (obj === null || obj === undefined) return obj;
|
|
42
|
-
if (obj instanceof Date) return obj;
|
|
43
|
-
if (
|
|
44
|
-
convertStringsToDates &&
|
|
45
|
-
typeof obj === "string" &&
|
|
46
|
-
/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?/.test(
|
|
47
|
-
obj
|
|
48
|
-
)
|
|
49
|
-
) {
|
|
50
|
-
return new Date(obj);
|
|
51
|
-
}
|
|
52
|
-
if (Array.isArray(obj)) {
|
|
53
|
-
return obj.map((item) => transformDates(item, convertStringsToDates));
|
|
54
|
-
}
|
|
55
|
-
if (typeof obj === "object") {
|
|
56
|
-
return Object.fromEntries(
|
|
57
|
-
Object.entries(obj).map(([key, value]) => [
|
|
58
|
-
key,
|
|
59
|
-
transformDates(value, convertStringsToDates),
|
|
60
|
-
])
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
return obj;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function inferHttpMethod(methodSource: string, debug?: boolean): string {
|
|
67
|
-
const methodMatch = methodSource.match(/method:\s*['"]?(\w+)['"]?/i);
|
|
68
|
-
const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
|
|
69
|
-
if (debug) {
|
|
70
|
-
// console.log(`[inferHttpMethod] Source:`, methodSource);
|
|
71
|
-
// console.log(`[inferHttpMethod] Extracted method:`, method);
|
|
72
|
-
}
|
|
73
|
-
return method;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
77
|
-
const {
|
|
78
|
-
realm,
|
|
79
|
-
userToken,
|
|
80
|
-
tempToken: initialTempToken,
|
|
81
|
-
useTempTokens,
|
|
82
|
-
fetchApi,
|
|
83
|
-
debug,
|
|
84
|
-
convertDates = true,
|
|
85
|
-
tokenLifespan,
|
|
86
|
-
throttle = { rate: 10, burst: 10 },
|
|
87
|
-
maxRetries = 3,
|
|
88
|
-
retryDelay = 1000,
|
|
89
|
-
} = config;
|
|
90
|
-
const baseUrl = `https://api.quickbase.com/v1`;
|
|
91
|
-
|
|
92
|
-
const tokenCache = new TokenCache(tokenLifespan);
|
|
93
|
-
const throttleBucket = throttle
|
|
94
|
-
? new ThrottleBucket(throttle.rate, throttle.burst)
|
|
95
|
-
: null;
|
|
96
|
-
|
|
97
|
-
const baseHeaders: HTTPHeaders = {
|
|
98
|
-
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
99
|
-
"Content-Type": "application/json",
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
if (initialTempToken) {
|
|
103
|
-
baseHeaders["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
|
|
104
|
-
} else if (userToken && !useTempTokens) {
|
|
105
|
-
baseHeaders["Authorization"] = `QB-USER-TOKEN ${userToken}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const defaultFetch: typeof fetch | undefined =
|
|
109
|
-
typeof globalThis.window !== "undefined"
|
|
110
|
-
? globalThis.window.fetch.bind(globalThis.window)
|
|
111
|
-
: undefined;
|
|
112
|
-
const configuration = new Configuration({
|
|
113
|
-
basePath: baseUrl,
|
|
114
|
-
headers: { ...baseHeaders },
|
|
115
|
-
fetchApi: fetchApi || defaultFetch,
|
|
116
|
-
credentials: "omit",
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
if (!configuration.fetchApi && typeof globalThis.window === "undefined") {
|
|
120
|
-
throw new Error(
|
|
121
|
-
"fetchApi must be provided in non-browser environments (e.g., Node.js)"
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const apiInstances = Object.fromEntries(
|
|
126
|
-
Object.entries(apis)
|
|
127
|
-
.filter(([name]) => name.endsWith("Api"))
|
|
128
|
-
.map(([name, ApiClass]) => [
|
|
129
|
-
name.replace("Api", "").toLowerCase(),
|
|
130
|
-
new ApiClass(configuration),
|
|
131
|
-
])
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
const methodMap = buildMethodMap();
|
|
135
|
-
|
|
136
|
-
function buildMethodMap(): MethodMap {
|
|
137
|
-
const methodMap: Partial<MethodMap> = {};
|
|
138
|
-
const isValidMethod = (name: string) =>
|
|
139
|
-
!name.startsWith("_") &&
|
|
140
|
-
name !== "constructor" &&
|
|
141
|
-
!["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
|
|
142
|
-
|
|
143
|
-
for (const [apiName, api] of Object.entries(apiInstances)) {
|
|
144
|
-
Object.getOwnPropertyNames(Object.getPrototypeOf(api))
|
|
145
|
-
.filter(
|
|
146
|
-
(name) =>
|
|
147
|
-
isValidMethod(name) &&
|
|
148
|
-
typeof api[name as keyof typeof api] === "function"
|
|
149
|
-
)
|
|
150
|
-
.forEach((rawMethodName) => {
|
|
151
|
-
const simplifiedName = simplifyName(
|
|
152
|
-
rawMethodName
|
|
153
|
-
) as keyof QuickbaseClient;
|
|
154
|
-
const rawMethodKey = `${rawMethodName}Raw` as keyof typeof api;
|
|
155
|
-
const method =
|
|
156
|
-
api[rawMethodKey] || api[rawMethodName as keyof typeof api];
|
|
157
|
-
const boundMethod = method.bind(api as any) as unknown;
|
|
158
|
-
if (typeof boundMethod === "function" && boundMethod.length <= 2) {
|
|
159
|
-
const methodSource = method.toString();
|
|
160
|
-
methodMap[simplifiedName] = {
|
|
161
|
-
api,
|
|
162
|
-
method: boundMethod as any, // Adjust typing as needed
|
|
163
|
-
paramMap: getParamNames(method),
|
|
164
|
-
httpMethod: inferHttpMethod(methodSource, debug),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
return methodMap as MethodMap;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const fetchTempToken = async (dbid: string): Promise<string> => {
|
|
173
|
-
const effectiveFetch = fetchApi || defaultFetch;
|
|
174
|
-
if (!effectiveFetch) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
"No fetch implementation available for fetching temp token"
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const response = await effectiveFetch(
|
|
181
|
-
`https://api.quickbase.com/v1/auth/temporary/${dbid}`,
|
|
182
|
-
{
|
|
183
|
-
method: "GET",
|
|
184
|
-
headers: { ...baseHeaders },
|
|
185
|
-
credentials: "include",
|
|
186
|
-
}
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (!response.ok) {
|
|
190
|
-
const errorBody: { message?: string } = await response.json();
|
|
191
|
-
throw new Error(
|
|
192
|
-
`API Error: ${errorBody.message || "Unknown error"} (Status: ${
|
|
193
|
-
response.status
|
|
194
|
-
})`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const tokenResult = await response.json();
|
|
199
|
-
const token = tokenResult.temporaryAuthorization;
|
|
200
|
-
if (!token) {
|
|
201
|
-
throw new Error("No temporary token returned from API");
|
|
202
|
-
}
|
|
203
|
-
tokenCache.set(dbid, token);
|
|
204
|
-
if (debug) {
|
|
205
|
-
console.log(`Fetched and cached new token for dbid: ${dbid}`, token);
|
|
206
|
-
}
|
|
207
|
-
return token;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, {
|
|
211
|
-
get: (_, prop: string): ((params: any) => Promise<any>) | undefined => {
|
|
212
|
-
if (prop in methodMap) {
|
|
213
|
-
const methodName = prop as keyof QuickbaseClient;
|
|
214
|
-
return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) =>
|
|
215
|
-
invokeMethod(
|
|
216
|
-
methodName,
|
|
217
|
-
params,
|
|
218
|
-
methodMap,
|
|
219
|
-
baseHeaders,
|
|
220
|
-
tokenCache,
|
|
221
|
-
fetchTempToken,
|
|
222
|
-
transformDates,
|
|
223
|
-
initialTempToken,
|
|
224
|
-
userToken,
|
|
225
|
-
useTempTokens,
|
|
226
|
-
debug,
|
|
227
|
-
convertDates,
|
|
228
|
-
0, // retryCount
|
|
229
|
-
throttleBucket,
|
|
230
|
-
maxRetries,
|
|
231
|
-
retryDelay
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
return undefined;
|
|
235
|
-
},
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
if (debug) {
|
|
239
|
-
console.log("[createClient] Config:", config);
|
|
240
|
-
console.log("[createClient] Returning:", proxy);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return proxy;
|
|
244
|
-
}
|
package/src/tokenCache.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
interface CachedToken {
|
|
2
|
-
token: string;
|
|
3
|
-
expiresAt: number; // Timestamp in milliseconds
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
export class TokenCache {
|
|
7
|
-
private cache: Map<string, CachedToken>;
|
|
8
|
-
private readonly lifespan: number; // Token lifespan in milliseconds
|
|
9
|
-
|
|
10
|
-
constructor(lifespan: number = 4 * 60 * 1000 + 50 * 1000) {
|
|
11
|
-
// Default 4:50
|
|
12
|
-
this.cache = new Map<string, CachedToken>();
|
|
13
|
-
this.lifespan = lifespan;
|
|
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) {
|
|
20
|
-
return entry.token;
|
|
21
|
-
}
|
|
22
|
-
return undefined; // Expired or not found
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// New method to get full entry
|
|
26
|
-
getEntry(dbid: string): CachedToken | undefined {
|
|
27
|
-
const entry = this.cache.get(dbid);
|
|
28
|
-
const now = Date.now();
|
|
29
|
-
if (entry && entry.expiresAt > now) {
|
|
30
|
-
return entry;
|
|
31
|
-
}
|
|
32
|
-
return undefined; // Expired or not found
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
set(dbid: string, token: string): void {
|
|
36
|
-
const now = Date.now();
|
|
37
|
-
this.cache.set(dbid, {
|
|
38
|
-
token,
|
|
39
|
-
expiresAt: now + this.lifespan,
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
dump(): [string, CachedToken][] {
|
|
44
|
-
return Array.from(this.cache.entries());
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// New method to clear the cache
|
|
48
|
-
clear(): void {
|
|
49
|
-
this.cache.clear();
|
|
50
|
-
}
|
|
51
|
-
}
|
package/src/utils.ts
DELETED
package/test-esm.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import { quickbase } from './dist/esm/quickbase.js'; const qb = quickbase({ realm: 'example', userToken: 'your-token' }); console.log(qb);
|