quickbase-js 0.1.0-beta.7
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/CHANGELOG.md +129 -0
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/build-common.js +131 -0
- package/build-esm.js +71 -0
- package/build-umd.js +69 -0
- package/dist/esm/quickbase.d.ts +7594 -0
- package/dist/esm/quickbase.js +9752 -0
- package/dist/esm/quickbase.js.map +1 -0
- package/dist/esm/quickbase.min.js +2 -0
- package/dist/esm/quickbase.min.js.map +1 -0
- package/dist/umd/quickbase.umd.js +10502 -0
- package/dist/umd/quickbase.umd.js.map +1 -0
- package/dist/umd/quickbase.umd.min.js +2 -0
- package/dist/umd/quickbase.umd.min.js.map +1 -0
- package/examples/quicbase-jsCDN.html +36 -0
- package/examples/react.js +48 -0
- package/examples/vanillajs.html +47 -0
- package/notes.txt +158 -0
- package/open-api/fix-spec-main.ts +96 -0
- package/open-api/generate-unified-interface.ts +277 -0
- package/open-api/output/override-report.json +58 -0
- package/open-api/output/quickbase-fixed.json +10610 -0
- package/open-api/regenerate-client.ts +116 -0
- package/open-api/schema/enhance-general.ts +100 -0
- package/open-api/schema/fix-arrays.ts +69 -0
- package/open-api/schema/tags/apps.ts +90 -0
- package/open-api/schema/tags/fields.ts +156 -0
- package/open-api/schema/tags/groups.ts +51 -0
- package/open-api/schema/tags/index.ts +68 -0
- package/open-api/schema/tags/records.ts +115 -0
- package/open-api/schema/tags/tables.ts +211 -0
- package/open-api/schema/tags/users.ts +47 -0
- package/open-api/types/spec.ts +41 -0
- package/open-api/utils/infer-schema.ts +40 -0
- package/open-api/utils/naming.ts +10 -0
- package/package.json +91 -0
- package/playwright.config.ts +37 -0
- package/rollup.config.js +114 -0
- package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
- package/specs/split/apps.json +1315 -0
- package/specs/split/auth.json +191 -0
- package/specs/split/fields.json +3869 -0
- package/specs/split/records.json +754 -0
- package/specs/split/reports.json +619 -0
- package/specs/split/tables.json +1605 -0
- package/src/generated-unified/QuickbaseClient.ts +359 -0
- package/src/generated-unified/missing-types-report.json +3 -0
- package/src/index.ts +1 -0
- package/src/quickbaseClient.ts +402 -0
- package/src/tokenCache.ts +51 -0
- package/src/utils.ts +7 -0
- package/test-esm.js +1 -0
- package/test-results.json +2050 -0
- package/test-umd.js +1 -0
- package/test.ts +1 -0
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +159 -0
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +219 -0
- package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +162 -0
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +153 -0
- package/tests/playwright/qb/auth/getTempToken.test.ts +152 -0
- package/tests/playwright/qb/auth/tokenPrefetch.test.ts +179 -0
- package/tests/playwright/qb/auth/tokenRenewal.test.ts +133 -0
- package/tests/setup.ts +51 -0
- package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
- package/tests/vitest/qb/apps/createApp.test.ts +64 -0
- package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
- package/tests/vitest/qb/apps/getApp.test.ts +40 -0
- package/tests/vitest/qb/fields/createField.test.ts +78 -0
- package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
- package/tests/vitest/qb/fields/getField.test.ts +85 -0
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
- package/tests/vitest/qb/fields/getFields.test.ts +232 -0
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
- package/tests/vitest/qb/index.ts +28 -0
- package/tests/vitest/qb/records/deleteRecords.test.ts +110 -0
- package/tests/vitest/qb/records/runQuery.test.ts +146 -0
- package/tests/vitest/qb/records/upsert.test.ts +102 -0
- package/tests/vitest/qb/tables/createTable.test.ts +55 -0
- package/tests/vitest/qb/tables/deleteTable.test.ts +50 -0
- package/tests/vitest/qb/tables/getAppTables.test.ts +46 -0
- package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
- package/tests/vitest/qb/tables/getTable.test.ts +26 -0
- package/tests/vitest/qb/tables/updateTable.test.ts +48 -0
- package/tests/vitest/unit/apps/copyApp.test.ts +488 -0
- package/tests/vitest/unit/apps/createApp.test.ts +186 -0
- package/tests/vitest/unit/apps/deleteApp.test.ts +216 -0
- package/tests/vitest/unit/apps/getApp.test.ts +128 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +82 -0
- package/tests/vitest/unit/auth/getTempToken.test.ts +95 -0
- package/tests/vitest/unit/auth/retryOn401.test.ts +116 -0
- package/tests/vitest/unit/auth/two401s.test.ts +86 -0
- package/tests/vitest/unit/fields/createField.test.ts +391 -0
- package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
- package/tests/vitest/unit/fields/getField.test.ts +189 -0
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
- package/tests/vitest/unit/fields/getFields.test.ts +122 -0
- package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
- package/tests/vitest/unit/index.ts +33 -0
- package/tests/vitest/unit/records/deleteRecords.test.ts +112 -0
- package/tests/vitest/unit/records/runQuery.test.ts +102 -0
- package/tests/vitest/unit/records/upsert.test.ts +234 -0
- package/tests/vitest/unit/tables/createTable.test.ts +70 -0
- package/tests/vitest/unit/tables/deleteTable.test.ts +77 -0
- package/tests/vitest/unit/tables/getAppTables.test.ts +85 -0
- package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
- package/tests/vitest/unit/tables/getTable.test.ts +233 -0
- package/tests/vitest/unit/tables/updateTable.test.ts +80 -0
- package/tests/vitest/unit/umd.test.ts +8 -0
- package/text.txt +104 -0
- package/tools/generate-types.ts +29 -0
- package/tools/log-gen.ts +231 -0
- package/tools/log-gen.yaml +3826 -0
- package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
- package/tools/openapi-schema-validator.ts +38 -0
- package/tools/spec-analyzer.ts +163 -0
- package/tools/split-spec.ts +148 -0
- package/tools/test-import.ts +3 -0
- package/tools/validate-spec.ts +40 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +29 -0
- package/versionrc.json +9 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
// src/quickbaseClient.ts
|
|
2
|
+
import { QuickbaseClient as IQuickbaseClient } from "./generated-unified/QuickbaseClient";
|
|
3
|
+
import { Configuration, HTTPHeaders, ResponseError } from "./generated/runtime";
|
|
4
|
+
import * as apis from "./generated/apis";
|
|
5
|
+
import { TokenCache } from "./tokenCache";
|
|
6
|
+
import { simplifyName } from "./utils";
|
|
7
|
+
|
|
8
|
+
export * from "./generated/models/index";
|
|
9
|
+
|
|
10
|
+
export interface QuickbaseClient extends IQuickbaseClient {}
|
|
11
|
+
|
|
12
|
+
export interface QuickbaseConfig {
|
|
13
|
+
realm: string;
|
|
14
|
+
userToken?: string;
|
|
15
|
+
tempToken?: string;
|
|
16
|
+
useTempTokens?: boolean;
|
|
17
|
+
debug?: boolean;
|
|
18
|
+
fetchApi?: typeof fetch;
|
|
19
|
+
convertDates?: boolean; // New flag to control date string conversion, defaults to true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TempTokenParams {
|
|
23
|
+
appId?: string;
|
|
24
|
+
tableId?: string;
|
|
25
|
+
dbid?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type ApiMethod<K extends keyof QuickbaseClient> = (
|
|
29
|
+
requestParameters: Parameters<QuickbaseClient[K]>[0],
|
|
30
|
+
initOverrides?: RequestInit
|
|
31
|
+
) => Promise<ReturnType<QuickbaseClient[K]>>;
|
|
32
|
+
|
|
33
|
+
interface MethodInfo<K extends keyof QuickbaseClient> {
|
|
34
|
+
api: any;
|
|
35
|
+
method: ApiMethod<K>;
|
|
36
|
+
paramMap: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type MethodMap = {
|
|
40
|
+
[K in keyof QuickbaseClient]: MethodInfo<K>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const getParamNames = (fn: (...args: any[]) => any): string[] =>
|
|
44
|
+
fn
|
|
45
|
+
.toString()
|
|
46
|
+
.slice(fn.toString().indexOf("(") + 1, fn.toString().indexOf(")"))
|
|
47
|
+
.split(",")
|
|
48
|
+
.map((p) => p.trim().split("=")[0]?.trim())
|
|
49
|
+
.filter((p) => p && !p.match(/^\{/) && p !== "options");
|
|
50
|
+
|
|
51
|
+
const extractDbid = (
|
|
52
|
+
params: Partial<TempTokenParams>,
|
|
53
|
+
errorMessage: string
|
|
54
|
+
): string => {
|
|
55
|
+
const dbid = params.dbid || params.tableId || params.appId;
|
|
56
|
+
if (!dbid) {
|
|
57
|
+
throw new Error(errorMessage);
|
|
58
|
+
}
|
|
59
|
+
return dbid;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Utility to convert ISO date strings to Date objects recursively, with optional conversion
|
|
63
|
+
function transformDates(obj: any, convertStringsToDates: boolean = true): any {
|
|
64
|
+
if (obj === null || obj === undefined) return obj;
|
|
65
|
+
if (obj instanceof Date) return obj;
|
|
66
|
+
if (
|
|
67
|
+
convertStringsToDates &&
|
|
68
|
+
typeof obj === "string" &&
|
|
69
|
+
/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?/.test(
|
|
70
|
+
obj
|
|
71
|
+
)
|
|
72
|
+
) {
|
|
73
|
+
return new Date(obj);
|
|
74
|
+
}
|
|
75
|
+
if (Array.isArray(obj)) {
|
|
76
|
+
return obj.map((item) => transformDates(item, convertStringsToDates));
|
|
77
|
+
}
|
|
78
|
+
if (typeof obj === "object") {
|
|
79
|
+
return Object.fromEntries(
|
|
80
|
+
Object.entries(obj).map(([key, value]) => [
|
|
81
|
+
key,
|
|
82
|
+
transformDates(value, convertStringsToDates),
|
|
83
|
+
])
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return obj;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function quickbase(config: QuickbaseConfig): QuickbaseClient {
|
|
90
|
+
const {
|
|
91
|
+
realm,
|
|
92
|
+
userToken,
|
|
93
|
+
tempToken: initialTempToken,
|
|
94
|
+
useTempTokens,
|
|
95
|
+
fetchApi,
|
|
96
|
+
debug,
|
|
97
|
+
convertDates = true, // Default to true for backward compatibility
|
|
98
|
+
} = config;
|
|
99
|
+
const baseUrl = `https://api.quickbase.com/v1`;
|
|
100
|
+
|
|
101
|
+
const tokenCache = new TokenCache();
|
|
102
|
+
|
|
103
|
+
const baseHeaders: HTTPHeaders = {
|
|
104
|
+
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
if (initialTempToken) {
|
|
109
|
+
baseHeaders["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
|
|
110
|
+
} else if (userToken && !useTempTokens) {
|
|
111
|
+
baseHeaders["Authorization"] = `QB-USER-TOKEN ${userToken}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const defaultFetch: typeof fetch | undefined =
|
|
115
|
+
typeof globalThis.window !== "undefined"
|
|
116
|
+
? globalThis.window.fetch.bind(globalThis.window)
|
|
117
|
+
: undefined;
|
|
118
|
+
const configuration = new Configuration({
|
|
119
|
+
basePath: baseUrl,
|
|
120
|
+
headers: { ...baseHeaders },
|
|
121
|
+
fetchApi: fetchApi || defaultFetch,
|
|
122
|
+
credentials: "omit",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (!configuration.fetchApi && typeof globalThis.window === "undefined") {
|
|
126
|
+
throw new Error(
|
|
127
|
+
"fetchApi must be provided in non-browser environments (e.g., Node.js)"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const apiInstances = Object.fromEntries(
|
|
132
|
+
Object.entries(apis)
|
|
133
|
+
.filter(([name]) => name.endsWith("Api"))
|
|
134
|
+
.map(([name, ApiClass]) => [
|
|
135
|
+
name.replace("Api", "").toLowerCase(),
|
|
136
|
+
new ApiClass(configuration),
|
|
137
|
+
])
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const methodMap = buildMethodMap();
|
|
141
|
+
|
|
142
|
+
function buildMethodMap(): MethodMap {
|
|
143
|
+
const methodMap: Partial<MethodMap> = {};
|
|
144
|
+
const isValidMethod = (name: string) =>
|
|
145
|
+
!name.startsWith("_") &&
|
|
146
|
+
name !== "constructor" &&
|
|
147
|
+
!["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
|
|
148
|
+
|
|
149
|
+
for (const [apiName, api] of Object.entries(apiInstances)) {
|
|
150
|
+
Object.getOwnPropertyNames(Object.getPrototypeOf(api))
|
|
151
|
+
.filter(
|
|
152
|
+
(name) =>
|
|
153
|
+
isValidMethod(name) &&
|
|
154
|
+
typeof api[name as keyof typeof api] === "function"
|
|
155
|
+
)
|
|
156
|
+
.forEach((rawMethodName) => {
|
|
157
|
+
const simplifiedName = simplifyName(
|
|
158
|
+
rawMethodName
|
|
159
|
+
) as keyof QuickbaseClient;
|
|
160
|
+
const method = api[rawMethodName as keyof typeof api];
|
|
161
|
+
const boundMethod = method.bind(api as any) as unknown;
|
|
162
|
+
if (typeof boundMethod === "function" && boundMethod.length <= 2) {
|
|
163
|
+
methodMap[simplifiedName] = {
|
|
164
|
+
api,
|
|
165
|
+
method: boundMethod as ApiMethod<typeof simplifiedName>,
|
|
166
|
+
paramMap: getParamNames(method),
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return methodMap as MethodMap;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fetchTempToken = async (dbid: string): Promise<string> => {
|
|
175
|
+
const effectiveFetch = fetchApi || defaultFetch;
|
|
176
|
+
if (!effectiveFetch) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
"No fetch implementation available for fetching temp token"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const response = await effectiveFetch(
|
|
183
|
+
`https://api.quickbase.com/v1/auth/temporary/${dbid}`,
|
|
184
|
+
{
|
|
185
|
+
method: "GET",
|
|
186
|
+
headers: { ...baseHeaders },
|
|
187
|
+
credentials: "include",
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const errorBody: { message?: string } = await response.json();
|
|
193
|
+
throw new Error(
|
|
194
|
+
`API Error: ${errorBody.message || "Unknown error"} (Status: ${
|
|
195
|
+
response.status
|
|
196
|
+
})`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const tokenResult = await response.json();
|
|
201
|
+
const token = tokenResult.temporaryAuthorization;
|
|
202
|
+
if (!token) {
|
|
203
|
+
throw new Error("No temporary token returned from API");
|
|
204
|
+
}
|
|
205
|
+
tokenCache.set(dbid, token);
|
|
206
|
+
if (debug) {
|
|
207
|
+
console.log(`Fetched and cached new token for dbid: ${dbid}`, token);
|
|
208
|
+
}
|
|
209
|
+
return token;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
async function invokeMethod<K extends keyof QuickbaseClient>(
|
|
213
|
+
methodName: K,
|
|
214
|
+
params: Parameters<QuickbaseClient[K]>[0] & Partial<TempTokenParams>,
|
|
215
|
+
retryCount: number = 0
|
|
216
|
+
): Promise<ReturnType<QuickbaseClient[K]>> {
|
|
217
|
+
const methodInfo = methodMap[methodName];
|
|
218
|
+
if (!methodInfo) {
|
|
219
|
+
throw new Error(`Method ${methodName} not found`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Safely handle body extraction
|
|
223
|
+
const hasBody = "body" in params && params.body !== undefined;
|
|
224
|
+
const body = hasBody ? (params as any).body : undefined;
|
|
225
|
+
const restParams: any = hasBody
|
|
226
|
+
? Object.fromEntries(
|
|
227
|
+
Object.entries(params).filter(([key]) => key !== "body")
|
|
228
|
+
)
|
|
229
|
+
: { ...params };
|
|
230
|
+
|
|
231
|
+
// Construct requestParameters with 'generated' for body
|
|
232
|
+
const requestParameters: any = {
|
|
233
|
+
...restParams,
|
|
234
|
+
...(body ? { generated: { ...body } } : {}),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
let requestOptions: RequestInit = {
|
|
238
|
+
credentials: "omit",
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const selectedToken =
|
|
242
|
+
initialTempToken || (userToken && !useTempTokens ? userToken : undefined);
|
|
243
|
+
|
|
244
|
+
if (methodName === "getTempTokenDBID" && useTempTokens) {
|
|
245
|
+
const dbid = extractDbid(params, "No dbid provided for getTempTokenDBID");
|
|
246
|
+
const cachedToken = tokenCache.get(dbid);
|
|
247
|
+
if (cachedToken) {
|
|
248
|
+
return { temporaryAuthorization: cachedToken } as ReturnType<
|
|
249
|
+
QuickbaseClient[K]
|
|
250
|
+
>;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
let authorizationToken = selectedToken;
|
|
255
|
+
if (useTempTokens && !authorizationToken) {
|
|
256
|
+
const dbid = extractDbid(
|
|
257
|
+
params,
|
|
258
|
+
`No dbid found in params for ${methodName} to fetch temp token`
|
|
259
|
+
);
|
|
260
|
+
const cachedToken = tokenCache.get(dbid);
|
|
261
|
+
authorizationToken = cachedToken || (await fetchTempToken(dbid));
|
|
262
|
+
if (methodName === "getTempTokenDBID") {
|
|
263
|
+
return { temporaryAuthorization: authorizationToken } as ReturnType<
|
|
264
|
+
QuickbaseClient[K]
|
|
265
|
+
>;
|
|
266
|
+
}
|
|
267
|
+
requestOptions.headers = {
|
|
268
|
+
...baseHeaders,
|
|
269
|
+
Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
|
|
270
|
+
};
|
|
271
|
+
} else if (authorizationToken) {
|
|
272
|
+
requestOptions.headers = {
|
|
273
|
+
...baseHeaders,
|
|
274
|
+
Authorization: `QB-USER-TOKEN ${authorizationToken}`,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (debug) {
|
|
279
|
+
console.log(`[${methodName}] requestParameters:`, requestParameters);
|
|
280
|
+
console.log(`[${methodName}] requestOptions:`, requestOptions);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const response = await methodInfo.method(
|
|
285
|
+
requestParameters,
|
|
286
|
+
requestOptions
|
|
287
|
+
);
|
|
288
|
+
if (debug) {
|
|
289
|
+
console.log(`[${methodName}] rawResponse:`, response);
|
|
290
|
+
}
|
|
291
|
+
if (response instanceof Response) {
|
|
292
|
+
const contentType = response.headers.get("Content-Type")?.toLowerCase();
|
|
293
|
+
if (debug) {
|
|
294
|
+
console.log(`[${methodName}] contentType:`, contentType);
|
|
295
|
+
}
|
|
296
|
+
if (contentType?.includes("application/octet-stream")) {
|
|
297
|
+
return (await response.arrayBuffer()) as ReturnType<
|
|
298
|
+
QuickbaseClient[K]
|
|
299
|
+
>;
|
|
300
|
+
} else if (
|
|
301
|
+
contentType?.includes("application/x-yaml") ||
|
|
302
|
+
contentType?.includes("text/yaml")
|
|
303
|
+
) {
|
|
304
|
+
return (await response.text()) as ReturnType<QuickbaseClient[K]>;
|
|
305
|
+
} else if (contentType?.includes("application/json")) {
|
|
306
|
+
const jsonResponse = await response.json();
|
|
307
|
+
if (debug) {
|
|
308
|
+
console.log(`[${methodName}] jsonResponse:`, jsonResponse);
|
|
309
|
+
}
|
|
310
|
+
const transformedResponse = transformDates(
|
|
311
|
+
jsonResponse,
|
|
312
|
+
convertDates
|
|
313
|
+
);
|
|
314
|
+
if (debug) {
|
|
315
|
+
console.log(
|
|
316
|
+
`[${methodName}] transformedResponse:`,
|
|
317
|
+
transformedResponse
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
return transformedResponse as ReturnType<QuickbaseClient[K]>;
|
|
321
|
+
}
|
|
322
|
+
return response as ReturnType<QuickbaseClient[K]>;
|
|
323
|
+
} else {
|
|
324
|
+
if (debug) {
|
|
325
|
+
console.log(
|
|
326
|
+
`[${methodName}] non-Response return, applying transform:`,
|
|
327
|
+
response
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
const transformedResponse = transformDates(response, convertDates);
|
|
331
|
+
if (debug) {
|
|
332
|
+
console.log(
|
|
333
|
+
`[${methodName}] transformedNonResponse:`,
|
|
334
|
+
transformedResponse
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
return transformedResponse as ReturnType<QuickbaseClient[K]>;
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
if (
|
|
341
|
+
error instanceof ResponseError &&
|
|
342
|
+
error.response.status === 401 &&
|
|
343
|
+
retryCount < 1 &&
|
|
344
|
+
useTempTokens
|
|
345
|
+
) {
|
|
346
|
+
if (debug) {
|
|
347
|
+
console.log(
|
|
348
|
+
`Authorization error for ${methodName}, refreshing token:`,
|
|
349
|
+
error.message
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
const dbid = extractDbid(
|
|
353
|
+
params,
|
|
354
|
+
`No dbid to refresh token after authorization error`
|
|
355
|
+
);
|
|
356
|
+
authorizationToken = await fetchTempToken(dbid);
|
|
357
|
+
requestOptions.headers = {
|
|
358
|
+
...baseHeaders,
|
|
359
|
+
Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
|
|
360
|
+
};
|
|
361
|
+
if (debug) {
|
|
362
|
+
console.log(`Retrying ${methodName} with new token`);
|
|
363
|
+
}
|
|
364
|
+
return invokeMethod(methodName, params, retryCount + 1);
|
|
365
|
+
}
|
|
366
|
+
if (error instanceof ResponseError) {
|
|
367
|
+
let errorMessage = error.message;
|
|
368
|
+
try {
|
|
369
|
+
const errorBody: { message?: string } = await error.response.json();
|
|
370
|
+
if (debug) {
|
|
371
|
+
console.log(`Error response body for ${methodName}:`, errorBody);
|
|
372
|
+
}
|
|
373
|
+
errorMessage = errorBody.message || errorMessage;
|
|
374
|
+
} catch (e) {
|
|
375
|
+
// Silent fail on parse error
|
|
376
|
+
}
|
|
377
|
+
throw new Error(
|
|
378
|
+
`API Error: ${errorMessage} (Status: ${error.response.status})`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, {
|
|
386
|
+
get: (_, prop: string): ((params: any) => Promise<any>) | undefined => {
|
|
387
|
+
if (prop in methodMap) {
|
|
388
|
+
const methodName = prop as keyof QuickbaseClient;
|
|
389
|
+
return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) =>
|
|
390
|
+
invokeMethod(methodName, params);
|
|
391
|
+
}
|
|
392
|
+
return undefined;
|
|
393
|
+
},
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
if (debug) {
|
|
397
|
+
console.log("[createClient] Config:", config);
|
|
398
|
+
console.log("[createClient] Returning:", proxy);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return proxy;
|
|
402
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
ADDED
package/test-esm.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import { quickbase } from './dist/esm/quickbase.js'; const qb = quickbase({ realm: 'example', userToken: 'your-token' }); console.log(qb);
|