quickbase-js 1.0.1-beta.0 → 1.1.0-beta.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/CHANGELOG.md +24 -0
- package/dist/{quickbaseClient.d.ts → quickbase.d.ts} +344 -339
- package/dist/{quickbaseClient.js → quickbase.js} +350 -266
- package/dist/quickbase.js.map +1 -0
- package/log-gen.yaml +3565 -0
- package/package.json +11 -7
- package/rollup.config.js +2 -2
- package/src/code-generation/fix-spec-definitions.ts +417 -68
- package/src/code-generation/fix-spec-paths.ts +207 -2
- package/src/code-generation/output/quickbase-fixed.json +646 -1122
- package/src/generated-unified/QuickbaseClient.ts +9 -8
- package/src/log-gen.ts +82 -35
- package/src/quickbaseClient.ts +39 -42
- package/test-artifacts/test-results.json +173 -1145
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +2 -2
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +2 -2
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +2 -2
- package/tests/playwright/qb/auth/getTempToken.test.ts +2 -2
- package/tests/playwright/qb/auth/tokenPrefetch.test.ts +2 -2
- package/tests/playwright/qb/auth/tokenRenewal.test.ts +2 -2
- package/tests/setup.ts +16 -8
- package/tests/vitest/qb/apps/getApp.test.ts +37 -43
- package/tests/vitest/qb/records/deleteRecords.test.ts +78 -0
- package/tests/vitest/qb/records/upsert.test.ts +72 -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 +43 -64
- 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/getApp.test.ts +1 -1
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +4 -3
- package/tests/vitest/unit/auth/getTempToken.test.ts +8 -9
- package/tests/vitest/unit/auth/retryOn401.test.ts +1 -1
- package/tests/vitest/unit/auth/two401s.test.ts +1 -1
- package/tests/vitest/unit/fields/getFields.test.ts +1 -1
- package/tests/vitest/unit/records/deleteRecords.test.ts +118 -0
- package/tests/vitest/unit/records/upsert.test.ts +228 -0
- package/tests/vitest/unit/tables/createTable.test.ts +62 -0
- package/tests/vitest/unit/tables/deleteTable.test.ts +68 -0
- package/tests/vitest/unit/tables/getAppTables.test.ts +1 -1
- package/tests/vitest/unit/tables/getTable.test.ts +212 -0
- package/tests/vitest/unit/tables/updateTable.test.ts +74 -0
- package/tsconfig.json +3 -2
- package/vitest.config.ts +3 -2
- package/dist/quickbaseClient.js.map +0 -1
- package/logGen.yaml +0 -1820
- package/src/utils.ts +0 -5
package/logGen.yaml
DELETED
|
@@ -1,1820 +0,0 @@
|
|
|
1
|
-
project: quickbase-js
|
|
2
|
-
date: 3/9/2025
|
|
3
|
-
root: /home/drew/Projects/quickbase-js
|
|
4
|
-
goals:
|
|
5
|
-
- Implement a QuickBase API client in TypeScript.
|
|
6
|
-
- Generate client methods from the QuickBase RESTful API spec.
|
|
7
|
-
- Provide a simple and intuitive interface for interacting with QuickBase.
|
|
8
|
-
- Support both Node.js and browser environments.
|
|
9
|
-
- 'Use case: JS frameworks like React, Vue, and Angular hosted in QuickBase code pages.'
|
|
10
|
-
- Node.js is supported using User Tokens
|
|
11
|
-
- Use OpenAPI to generate the TypeScript types and methods.
|
|
12
|
-
- Use the latest ES6+ features and TypeScript features.
|
|
13
|
-
- '#'
|
|
14
|
-
- No manual updating methods when the QuickBase API changes.
|
|
15
|
-
- The proxy magic dynamically maps generated methods.
|
|
16
|
-
- No manual endpoint definitions—endpoints come from the generated apis (e.g., AppsApi.getApp).
|
|
17
|
-
- '#'
|
|
18
|
-
- 'Per-Instance Store: tokenCache is defined per quickbaseClient call, so each instance has its own isolated TokenCache.'
|
|
19
|
-
- 'No Singleton: No shared state—tokens are fetched and cached within the current instance.'
|
|
20
|
-
tree:
|
|
21
|
-
name: .
|
|
22
|
-
type: directory
|
|
23
|
-
children:
|
|
24
|
-
- name: .env
|
|
25
|
-
type: file
|
|
26
|
-
- name: .gitignore
|
|
27
|
-
type: file
|
|
28
|
-
- name: LICENSE
|
|
29
|
-
type: file
|
|
30
|
-
- name: README.md
|
|
31
|
-
type: file
|
|
32
|
-
- name: build.js
|
|
33
|
-
type: file
|
|
34
|
-
- name: dist
|
|
35
|
-
type: directory
|
|
36
|
-
children:
|
|
37
|
-
- name: quickbaseClient.js
|
|
38
|
-
type: file
|
|
39
|
-
- name: quickbaseClient.js.map
|
|
40
|
-
type: file
|
|
41
|
-
- name: examples
|
|
42
|
-
type: directory
|
|
43
|
-
children:
|
|
44
|
-
- name: react.js
|
|
45
|
-
type: file
|
|
46
|
-
- name: vanillajs.html
|
|
47
|
-
type: file
|
|
48
|
-
- name: logGen.yaml
|
|
49
|
-
type: file
|
|
50
|
-
- name: openapitools.json
|
|
51
|
-
type: file
|
|
52
|
-
- name: package-lock.json
|
|
53
|
-
type: file
|
|
54
|
-
- name: package.json
|
|
55
|
-
type: file
|
|
56
|
-
contents: |
|
|
57
|
-
{
|
|
58
|
-
"name": "quickbase-js",
|
|
59
|
-
"main": "dist/quickbaseClient.js",
|
|
60
|
-
"types": "dist/quickbaseClient.d.ts",
|
|
61
|
-
"version": "1.0.0",
|
|
62
|
-
"description": "QuickBase JavaScript client for frameworks and vanilla JS",
|
|
63
|
-
"module": "dist/quickbaseClient.js",
|
|
64
|
-
"type": "module",
|
|
65
|
-
"exports": {
|
|
66
|
-
".": {
|
|
67
|
-
"import": "./dist/quickbaseClient.js",
|
|
68
|
-
"types": "./dist/quickbaseClient.d.ts"
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"scripts": {
|
|
72
|
-
"fix-spec": "node --loader ts-node/esm src/code-generation/fix-spec-main.ts",
|
|
73
|
-
"regenerate": "node --loader ts-node/esm src/code-generation/regenerate-client.ts",
|
|
74
|
-
"generate-unified": "node --loader ts-node/esm src/code-generation/generate-unified-interface.ts",
|
|
75
|
-
"log-gen": "node --loader ts-node/esm src/log-gen.ts",
|
|
76
|
-
"build": "rimraf dist && rollup -c",
|
|
77
|
-
"build:prod": "rm -rf dist/* && NODE_ENV=production rollup -c",
|
|
78
|
-
"test": "vitest run tests/vitest/**/**/*.test.ts",
|
|
79
|
-
"test:pw:qb:all": "playwright test tests/playwright/qb --config playwright.config.ts",
|
|
80
|
-
"test:vitest:qb": "vitest run tests/vitest/qb/**/*.test.ts",
|
|
81
|
-
"test:vitest:qb:apps": "vitest run tests/vitest/qb/apps/**/*.test.ts",
|
|
82
|
-
"test:vitest:qb:auth": "vitest run tests/vitest/qb/auth/**/*.test.ts",
|
|
83
|
-
"test:vitest:qb:fields": "vitest run tests/vitest/qb/fields/**/*.test.ts",
|
|
84
|
-
"test:vitest:qb:tables": "vitest run tests/vitest/qb/tables/**/*.test.ts",
|
|
85
|
-
"test:pw:qb:auth": "playwright test tests/playwright/qb/auth",
|
|
86
|
-
"test::pw:qb:auth:temptoken": "playwright test tests/playwright/qb/auth/getTempToken.test.ts",
|
|
87
|
-
"test:pw:qb:auth:renewal": "playwright test tests/playwright/qb/auth/tokenRenewal.test.ts",
|
|
88
|
-
"test:pw:qb:auth:cache": "playwright test tests/playwright/qb/auth/cacheSwitching.test.ts",
|
|
89
|
-
"test:pw:qb:auth:temp401": "playwright test tests/playwright/qb/auth/fetchTempToken401.test.ts",
|
|
90
|
-
"test:pw:qb:auth:prefetch": "playwright test tests/playwright/qb/auth/tokenPrefetch.test.ts",
|
|
91
|
-
"test:pw:qb:auth:debug": "playwright test tests/playwright/qb/auth --headed --debug",
|
|
92
|
-
"test:pw:qb:auth:temp:debug": "playwright test tests/playwright/qb/auth/getTempToken.test.ts --headed --debug",
|
|
93
|
-
"test:pw:qb:auth:renewal:debug": "playwright test tests/playwright/qb/auth/tokenRenewal.test.ts --headed --debug --config=playwright.config.ts",
|
|
94
|
-
"test:pw:qb:auth:cache:debug": "playwright test tests/playwright/qb/auth/cacheSwitching.test.ts --headed --debug"
|
|
95
|
-
},
|
|
96
|
-
"dependencies": {
|
|
97
|
-
"typescript": "^5.8.2"
|
|
98
|
-
},
|
|
99
|
-
"devDependencies": {
|
|
100
|
-
"@openapitools/openapi-generator-cli": "^2.17.0",
|
|
101
|
-
"@playwright/test": "^1.51.0",
|
|
102
|
-
"@rollup/plugin-commonjs": "^28.0.3",
|
|
103
|
-
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
104
|
-
"@rollup/plugin-terser": "^0.4.4",
|
|
105
|
-
"@rollup/plugin-typescript": "^12.1.2",
|
|
106
|
-
"@types/node": "^20.17.24",
|
|
107
|
-
"dotenv": "^16.4.7",
|
|
108
|
-
"glob": "^11.0.1",
|
|
109
|
-
"js-yaml": "^4.1.0",
|
|
110
|
-
"jsdom": "^24.1.3",
|
|
111
|
-
"node-fetch": "^3.3.2",
|
|
112
|
-
"openapi-types": "^12.1.3",
|
|
113
|
-
"rimraf": "^5.0.10",
|
|
114
|
-
"rollup": "^4.34.9",
|
|
115
|
-
"rollup-plugin-dts": "^6.1.1",
|
|
116
|
-
"ts-morph": "^23.0.0",
|
|
117
|
-
"ts-node": "^10.9.2",
|
|
118
|
-
"tslib": "^2.8.1",
|
|
119
|
-
"vitest": "^1.6.0"
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
- name: playwright.config.ts
|
|
123
|
-
type: file
|
|
124
|
-
- name: rollup.config.js
|
|
125
|
-
type: file
|
|
126
|
-
contents: |
|
|
127
|
-
import typescript from "@rollup/plugin-typescript";
|
|
128
|
-
import terser from "@rollup/plugin-terser";
|
|
129
|
-
import nodeResolve from "@rollup/plugin-node-resolve";
|
|
130
|
-
import commonjs from "@rollup/plugin-commonjs";
|
|
131
|
-
import dts from "rollup-plugin-dts";
|
|
132
|
-
|
|
133
|
-
const isProd = process.env.NODE_ENV === "production";
|
|
134
|
-
|
|
135
|
-
export default [
|
|
136
|
-
{
|
|
137
|
-
input: "src/quickbaseClient.ts",
|
|
138
|
-
output: {
|
|
139
|
-
dir: "dist",
|
|
140
|
-
format: "esm",
|
|
141
|
-
sourcemap: true,
|
|
142
|
-
compact: isProd,
|
|
143
|
-
entryFileNames: "quickbaseClient.js",
|
|
144
|
-
},
|
|
145
|
-
external: [
|
|
146
|
-
"node:http",
|
|
147
|
-
"node:https",
|
|
148
|
-
"node:zlib",
|
|
149
|
-
"node:stream",
|
|
150
|
-
"node:buffer",
|
|
151
|
-
"node:util",
|
|
152
|
-
"node:url",
|
|
153
|
-
"node:net",
|
|
154
|
-
"node:fs",
|
|
155
|
-
"node:path",
|
|
156
|
-
"node-fetch",
|
|
157
|
-
],
|
|
158
|
-
plugins: [
|
|
159
|
-
nodeResolve({ preferBuiltins: true }),
|
|
160
|
-
commonjs(),
|
|
161
|
-
typescript({
|
|
162
|
-
tsconfig: "./tsconfig.json", // Updated to single tsconfig
|
|
163
|
-
declaration: false, // Rollup handles this separately
|
|
164
|
-
}),
|
|
165
|
-
isProd && terser(),
|
|
166
|
-
],
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
input: "src/quickbaseClient.ts",
|
|
170
|
-
output: {
|
|
171
|
-
file: "dist/quickbaseClient.d.ts",
|
|
172
|
-
format: "esm",
|
|
173
|
-
},
|
|
174
|
-
plugins: [
|
|
175
|
-
dts({
|
|
176
|
-
tsconfig: "./tsconfig.json", // Updated to single tsconfig
|
|
177
|
-
}),
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
].map((config) => ({
|
|
181
|
-
...config,
|
|
182
|
-
onwarn(warning, warn) {
|
|
183
|
-
if (
|
|
184
|
-
warning.code === "PLUGIN_WARNING" &&
|
|
185
|
-
warning.plugin === "typescript" &&
|
|
186
|
-
warning.message.includes("TS5096")
|
|
187
|
-
) {
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
warn(warning);
|
|
191
|
-
},
|
|
192
|
-
}));
|
|
193
|
-
- name: src
|
|
194
|
-
type: directory
|
|
195
|
-
children:
|
|
196
|
-
- name: code-generation
|
|
197
|
-
type: directory
|
|
198
|
-
children:
|
|
199
|
-
- name: fix-spec-definitions.ts
|
|
200
|
-
type: file
|
|
201
|
-
contents: |
|
|
202
|
-
export const definitions = {
|
|
203
|
-
App: {
|
|
204
|
-
type: "object",
|
|
205
|
-
properties: {
|
|
206
|
-
id: { type: "string" },
|
|
207
|
-
name: { type: "string" },
|
|
208
|
-
created: { type: "string", format: "date-time" },
|
|
209
|
-
updated: { type: "string", format: "date-time" },
|
|
210
|
-
description: { type: "string" },
|
|
211
|
-
timeZone: { type: "string" },
|
|
212
|
-
dateFormat: { type: "string" },
|
|
213
|
-
hasEveryoneOnTheInternet: { type: "boolean" },
|
|
214
|
-
memoryInfo: {
|
|
215
|
-
type: "object",
|
|
216
|
-
properties: {
|
|
217
|
-
estMemory: { type: "number" },
|
|
218
|
-
estMemoryInclDependentApps: { type: "number" },
|
|
219
|
-
},
|
|
220
|
-
},
|
|
221
|
-
securityProperties: {
|
|
222
|
-
type: "object",
|
|
223
|
-
properties: {
|
|
224
|
-
allowClone: { type: "boolean" },
|
|
225
|
-
allowExport: { type: "boolean" },
|
|
226
|
-
enableAppTokens: { type: "boolean" },
|
|
227
|
-
hideFromPublic: { type: "boolean" },
|
|
228
|
-
mustBeRealmApproved: { type: "boolean" },
|
|
229
|
-
useIPFilter: { type: "boolean" },
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
required: ["id", "name"],
|
|
234
|
-
},
|
|
235
|
-
Field: {
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
id: { type: "number" },
|
|
239
|
-
label: { type: "string" },
|
|
240
|
-
fieldType: { type: "string" },
|
|
241
|
-
noWrap: { type: "boolean" },
|
|
242
|
-
bold: { type: "boolean" },
|
|
243
|
-
required: { type: "boolean" },
|
|
244
|
-
appearsByDefault: { type: "boolean" },
|
|
245
|
-
findEnabled: { type: "boolean" },
|
|
246
|
-
unique: { type: "boolean" },
|
|
247
|
-
doesDataCopy: { type: "boolean" },
|
|
248
|
-
fieldHelp: { type: "string" },
|
|
249
|
-
audited: { type: "boolean" },
|
|
250
|
-
properties: {
|
|
251
|
-
type: "object",
|
|
252
|
-
properties: {
|
|
253
|
-
primaryKey: { type: "boolean" },
|
|
254
|
-
foreignKey: { type: "boolean" },
|
|
255
|
-
numLines: { type: "number" },
|
|
256
|
-
maxLength: { type: "number" },
|
|
257
|
-
appendOnly: { type: "boolean" },
|
|
258
|
-
allowHTML: { type: "boolean" },
|
|
259
|
-
allowMentions: { type: "boolean" },
|
|
260
|
-
sortAsGiven: { type: "boolean" },
|
|
261
|
-
carryChoices: { type: "boolean" },
|
|
262
|
-
allowNewChoices: { type: "boolean" },
|
|
263
|
-
formula: { type: "string" },
|
|
264
|
-
defaultValue: { type: "string" },
|
|
265
|
-
},
|
|
266
|
-
},
|
|
267
|
-
permissions: {
|
|
268
|
-
type: "array",
|
|
269
|
-
items: {
|
|
270
|
-
type: "object",
|
|
271
|
-
properties: {
|
|
272
|
-
permissionType: { type: "string" },
|
|
273
|
-
role: { type: "string" },
|
|
274
|
-
roleId: { type: "number" },
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
},
|
|
278
|
-
},
|
|
279
|
-
required: ["id", "label", "fieldType"],
|
|
280
|
-
},
|
|
281
|
-
Table: {
|
|
282
|
-
type: "object",
|
|
283
|
-
properties: {
|
|
284
|
-
id: { type: "string" },
|
|
285
|
-
name: { type: "string" },
|
|
286
|
-
alias: { type: "string" },
|
|
287
|
-
created: { type: "string", format: "date-time" },
|
|
288
|
-
defaultSortFieldId: { type: "number" },
|
|
289
|
-
defaultSortOrder: { type: "string" },
|
|
290
|
-
description: { type: "string" },
|
|
291
|
-
keyFieldId: { type: "number" },
|
|
292
|
-
nextFieldId: { type: "number" },
|
|
293
|
-
nextRecordId: { type: "number" },
|
|
294
|
-
pluralRecordName: { type: "string" },
|
|
295
|
-
singleRecordName: { type: "string" },
|
|
296
|
-
sizeLimit: { type: "string" },
|
|
297
|
-
spaceRemaining: { type: "string" },
|
|
298
|
-
spaceUsed: { type: "string" },
|
|
299
|
-
updated: { type: "string", format: "date-time" },
|
|
300
|
-
},
|
|
301
|
-
required: ["id", "name"],
|
|
302
|
-
},
|
|
303
|
-
Record: {
|
|
304
|
-
type: "object",
|
|
305
|
-
properties: {
|
|
306
|
-
id: { type: "number" },
|
|
307
|
-
fields: {
|
|
308
|
-
type: "object",
|
|
309
|
-
additionalProperties: { type: "string" },
|
|
310
|
-
},
|
|
311
|
-
},
|
|
312
|
-
required: ["id"],
|
|
313
|
-
},
|
|
314
|
-
Upsert200Response: {
|
|
315
|
-
type: "object",
|
|
316
|
-
properties: {
|
|
317
|
-
id: { type: "number" },
|
|
318
|
-
status: { type: "string" },
|
|
319
|
-
},
|
|
320
|
-
required: ["id"],
|
|
321
|
-
},
|
|
322
|
-
ReportRunResponse: {
|
|
323
|
-
type: "object",
|
|
324
|
-
properties: {
|
|
325
|
-
id: { type: "string" },
|
|
326
|
-
data: { type: "object" }, // Placeholder
|
|
327
|
-
},
|
|
328
|
-
required: ["id"],
|
|
329
|
-
},
|
|
330
|
-
GetTempTokenDBID200Response: {
|
|
331
|
-
type: "object",
|
|
332
|
-
properties: {
|
|
333
|
-
temporaryAuthorization: { type: "string" },
|
|
334
|
-
},
|
|
335
|
-
required: ["temporaryAuthorization"],
|
|
336
|
-
},
|
|
337
|
-
};
|
|
338
|
-
- name: fix-spec-main.ts
|
|
339
|
-
type: file
|
|
340
|
-
contents: |
|
|
341
|
-
#!/usr/bin/env node
|
|
342
|
-
import { promises as fs } from "fs";
|
|
343
|
-
import path from "path";
|
|
344
|
-
import { fileURLToPath } from "url";
|
|
345
|
-
import * as glob from "glob";
|
|
346
|
-
import { paths } from "./fix-spec-paths.ts";
|
|
347
|
-
import { definitions } from "./fix-spec-definitions.ts";
|
|
348
|
-
|
|
349
|
-
interface Parameter {
|
|
350
|
-
name: string;
|
|
351
|
-
in: string;
|
|
352
|
-
required?: boolean;
|
|
353
|
-
type?: string;
|
|
354
|
-
schema?: { type?: string; items?: any; $ref?: string; properties?: any };
|
|
355
|
-
example?: any;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
interface Operation {
|
|
359
|
-
parameters?: Parameter[];
|
|
360
|
-
responses?: Record<string, { description: string; schema?: any }>;
|
|
361
|
-
operationId?: string;
|
|
362
|
-
summary?: string;
|
|
363
|
-
tags?: string[];
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
interface Spec {
|
|
367
|
-
paths: Record<string, Record<string, Operation>>;
|
|
368
|
-
definitions?: Record<string, any>;
|
|
369
|
-
operations?: any;
|
|
370
|
-
groups?: any;
|
|
371
|
-
components?: any;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
function toCamelCase(str: string): string {
|
|
375
|
-
return str
|
|
376
|
-
.replace(/[-_](.)/g, (_, char) => char.toUpperCase())
|
|
377
|
-
.replace(/^./, (str) => str.toLowerCase());
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function fixArraySchemas(spec: Spec) {
|
|
381
|
-
for (const pathKey in spec.paths) {
|
|
382
|
-
for (const method in spec.paths[pathKey]) {
|
|
383
|
-
const operation = spec.paths[pathKey][method];
|
|
384
|
-
if (operation.parameters) {
|
|
385
|
-
operation.parameters.forEach((param: Parameter) => {
|
|
386
|
-
if (param.schema) {
|
|
387
|
-
if (param.schema.type === "array" && !param.schema.items) {
|
|
388
|
-
console.log(
|
|
389
|
-
`Fixing missing items in ${pathKey}(${method}).${param.name}`
|
|
390
|
-
);
|
|
391
|
-
param.schema.items =
|
|
392
|
-
pathKey === "/records" && param.name === "generated"
|
|
393
|
-
? { $ref: "#/definitions/Record" }
|
|
394
|
-
: { type: "string" };
|
|
395
|
-
}
|
|
396
|
-
if (param.schema.properties) {
|
|
397
|
-
for (const propKey in param.schema.properties) {
|
|
398
|
-
const prop = param.schema.properties[propKey];
|
|
399
|
-
if (prop.type === "array" && !prop.items) {
|
|
400
|
-
console.log(
|
|
401
|
-
`Fixing nested array in ${pathKey}(${method}).${param.name}.${propKey}`
|
|
402
|
-
);
|
|
403
|
-
prop.items =
|
|
404
|
-
propKey === "data" && pathKey === "/records"
|
|
405
|
-
? { $ref: "#/definitions/Record" }
|
|
406
|
-
: { type: "string" };
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
});
|
|
412
|
-
}
|
|
413
|
-
if (operation.responses) {
|
|
414
|
-
for (const status in operation.responses) {
|
|
415
|
-
const response = operation.responses[status];
|
|
416
|
-
if (response.schema) {
|
|
417
|
-
if (response.schema.type === "array" && !response.schema.items) {
|
|
418
|
-
console.log(
|
|
419
|
-
`Fixing missing items in ${pathKey}(${method}).responses.${status}`
|
|
420
|
-
);
|
|
421
|
-
response.schema.items =
|
|
422
|
-
pathKey === "/records"
|
|
423
|
-
? { $ref: "#/definitions/Upsert200Response" }
|
|
424
|
-
: { type: "string" };
|
|
425
|
-
}
|
|
426
|
-
if (response.schema.properties) {
|
|
427
|
-
for (const propKey in response.schema.properties) {
|
|
428
|
-
const prop = response.schema.properties[propKey];
|
|
429
|
-
if (prop.type === "array" && !prop.items) {
|
|
430
|
-
console.log(
|
|
431
|
-
`Fixing nested array in ${pathKey}(${method}).responses.${status}.${propKey}`
|
|
432
|
-
);
|
|
433
|
-
prop.items =
|
|
434
|
-
propKey === "data" && pathKey === "/records"
|
|
435
|
-
? { $ref: "#/definitions/Upsert200Response" }
|
|
436
|
-
: { type: "string" };
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
async function fixQuickBaseSpec(): Promise<void> {
|
|
448
|
-
try {
|
|
449
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
450
|
-
const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
|
|
451
|
-
const OUTPUT_DIR = path.join(CODEGEN_DIR, "output");
|
|
452
|
-
console.log("Finding latest QuickBase RESTful API spec...");
|
|
453
|
-
const specFiles = glob.sync(
|
|
454
|
-
path.join(SPECS_DIR, "QuickBase_RESTful_*.json")
|
|
455
|
-
);
|
|
456
|
-
if (specFiles.length === 0) {
|
|
457
|
-
console.error(
|
|
458
|
-
"No QuickBase_RESTful_*.json files found in specs/ folder."
|
|
459
|
-
);
|
|
460
|
-
process.exit(1);
|
|
461
|
-
}
|
|
462
|
-
const inputFile = specFiles.sort().pop() as string;
|
|
463
|
-
const outputFile = path.join(OUTPUT_DIR, "quickbase-fixed.json");
|
|
464
|
-
|
|
465
|
-
console.log(`Reading ${path.basename(inputFile)} from specs/...`);
|
|
466
|
-
const specContent = await fs.readFile(inputFile, "utf8");
|
|
467
|
-
const spec: Spec = JSON.parse(specContent);
|
|
468
|
-
|
|
469
|
-
console.log("Fixing parameters...");
|
|
470
|
-
for (const pathKey in spec.paths) {
|
|
471
|
-
for (const method in spec.paths[pathKey]) {
|
|
472
|
-
const operation = spec.paths[pathKey][method];
|
|
473
|
-
if (operation.parameters) {
|
|
474
|
-
operation.parameters = operation.parameters
|
|
475
|
-
.filter(
|
|
476
|
-
(param) =>
|
|
477
|
-
!["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
|
|
478
|
-
param.name
|
|
479
|
-
)
|
|
480
|
-
)
|
|
481
|
-
.map((param: Parameter) => {
|
|
482
|
-
param.name = toCamelCase(param.name);
|
|
483
|
-
if ("example" in param) delete param.example;
|
|
484
|
-
if ("schema" in param && param.in !== "body") delete param.schema;
|
|
485
|
-
if (!param.type && param.in !== "body") param.type = "string";
|
|
486
|
-
return param;
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
console.log("Applying endpoint fixes...");
|
|
493
|
-
spec.paths = { ...spec.paths, ...paths }; // Merge original and custom paths
|
|
494
|
-
|
|
495
|
-
console.log("Fixing array schemas...");
|
|
496
|
-
fixArraySchemas(spec);
|
|
497
|
-
|
|
498
|
-
console.log("Applying definitions...");
|
|
499
|
-
spec.definitions = { ...spec.definitions, ...definitions }; // Merge definitions
|
|
500
|
-
|
|
501
|
-
console.log("Removing unexpected top-level attributes...");
|
|
502
|
-
delete spec.operations;
|
|
503
|
-
delete spec.groups;
|
|
504
|
-
delete spec.components;
|
|
505
|
-
|
|
506
|
-
console.log(`Writing fixed spec to ${path.basename(outputFile)}...`);
|
|
507
|
-
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
508
|
-
await fs.writeFile(outputFile, JSON.stringify(spec, null, 2), "utf8");
|
|
509
|
-
console.log("Spec fixed successfully!");
|
|
510
|
-
} catch (error) {
|
|
511
|
-
console.error("Failed to fix spec:", error);
|
|
512
|
-
process.exit(1);
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
fixQuickBaseSpec();
|
|
517
|
-
- name: fix-spec-paths.ts
|
|
518
|
-
type: file
|
|
519
|
-
contents: |
|
|
520
|
-
export const paths = {
|
|
521
|
-
"/apps/{appId}": {
|
|
522
|
-
get: {
|
|
523
|
-
operationId: "getApp",
|
|
524
|
-
summary: "Get an app by ID",
|
|
525
|
-
tags: ["Apps"],
|
|
526
|
-
parameters: [
|
|
527
|
-
{
|
|
528
|
-
name: "appId",
|
|
529
|
-
in: "path",
|
|
530
|
-
required: true,
|
|
531
|
-
type: "string",
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
responses: {
|
|
535
|
-
200: {
|
|
536
|
-
description: "Success",
|
|
537
|
-
schema: { $ref: "#/definitions/App" },
|
|
538
|
-
},
|
|
539
|
-
},
|
|
540
|
-
},
|
|
541
|
-
},
|
|
542
|
-
"/fields": {
|
|
543
|
-
get: {
|
|
544
|
-
operationId: "getFields",
|
|
545
|
-
summary: "Get all fields for a table",
|
|
546
|
-
tags: ["Fields"],
|
|
547
|
-
parameters: [
|
|
548
|
-
{
|
|
549
|
-
name: "tableId",
|
|
550
|
-
in: "query",
|
|
551
|
-
required: true,
|
|
552
|
-
type: "string",
|
|
553
|
-
},
|
|
554
|
-
{
|
|
555
|
-
name: "includeFieldPerms",
|
|
556
|
-
in: "query",
|
|
557
|
-
required: false,
|
|
558
|
-
type: "boolean",
|
|
559
|
-
},
|
|
560
|
-
],
|
|
561
|
-
responses: {
|
|
562
|
-
200: {
|
|
563
|
-
description: "Success",
|
|
564
|
-
schema: {
|
|
565
|
-
type: "array",
|
|
566
|
-
items: { $ref: "#/definitions/Field" },
|
|
567
|
-
},
|
|
568
|
-
},
|
|
569
|
-
},
|
|
570
|
-
},
|
|
571
|
-
},
|
|
572
|
-
"/tables": {
|
|
573
|
-
get: {
|
|
574
|
-
operationId: "getAppTables",
|
|
575
|
-
summary: "Get all tables for an app",
|
|
576
|
-
tags: ["Tables"],
|
|
577
|
-
parameters: [
|
|
578
|
-
{
|
|
579
|
-
name: "appId",
|
|
580
|
-
in: "query",
|
|
581
|
-
required: true,
|
|
582
|
-
type: "string",
|
|
583
|
-
},
|
|
584
|
-
],
|
|
585
|
-
responses: {
|
|
586
|
-
200: {
|
|
587
|
-
description: "Success",
|
|
588
|
-
schema: {
|
|
589
|
-
type: "array",
|
|
590
|
-
items: { $ref: "#/definitions/Table" },
|
|
591
|
-
},
|
|
592
|
-
},
|
|
593
|
-
},
|
|
594
|
-
},
|
|
595
|
-
},
|
|
596
|
-
"/records/query": {
|
|
597
|
-
post: {
|
|
598
|
-
operationId: "upsertRecords",
|
|
599
|
-
summary: "Upsert records",
|
|
600
|
-
tags: ["Records"],
|
|
601
|
-
parameters: [
|
|
602
|
-
{
|
|
603
|
-
name: "generated",
|
|
604
|
-
in: "body",
|
|
605
|
-
required: true,
|
|
606
|
-
schema: {
|
|
607
|
-
type: "array",
|
|
608
|
-
items: { $ref: "#/definitions/Record" },
|
|
609
|
-
},
|
|
610
|
-
},
|
|
611
|
-
],
|
|
612
|
-
responses: {
|
|
613
|
-
200: {
|
|
614
|
-
description: "Success",
|
|
615
|
-
schema: {
|
|
616
|
-
type: "array",
|
|
617
|
-
items: { $ref: "#/definitions/Upsert200Response" },
|
|
618
|
-
},
|
|
619
|
-
},
|
|
620
|
-
207: {
|
|
621
|
-
description: "Multi-Status (partial success)",
|
|
622
|
-
schema: {
|
|
623
|
-
type: "array",
|
|
624
|
-
items: { $ref: "#/definitions/Upsert200Response" },
|
|
625
|
-
},
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
},
|
|
629
|
-
},
|
|
630
|
-
"/reports/{reportId}/run": {
|
|
631
|
-
post: {
|
|
632
|
-
operationId: "runReport",
|
|
633
|
-
summary: "Run a report",
|
|
634
|
-
tags: ["Reports"],
|
|
635
|
-
parameters: [
|
|
636
|
-
{
|
|
637
|
-
name: "reportId",
|
|
638
|
-
in: "path",
|
|
639
|
-
required: true,
|
|
640
|
-
type: "string",
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
name: "generated",
|
|
644
|
-
in: "body",
|
|
645
|
-
required: true,
|
|
646
|
-
schema: { type: "object" },
|
|
647
|
-
},
|
|
648
|
-
],
|
|
649
|
-
responses: {
|
|
650
|
-
200: {
|
|
651
|
-
description: "Success",
|
|
652
|
-
schema: {
|
|
653
|
-
type: "array",
|
|
654
|
-
items: { $ref: "#/definitions/ReportRunResponse" },
|
|
655
|
-
},
|
|
656
|
-
},
|
|
657
|
-
},
|
|
658
|
-
},
|
|
659
|
-
},
|
|
660
|
-
"/auth/temporary/{dbid}": {
|
|
661
|
-
get: {
|
|
662
|
-
operationId: "getTempTokenDBID",
|
|
663
|
-
summary: "Get a temporary authorization token for a specific dbid",
|
|
664
|
-
tags: ["Auth"],
|
|
665
|
-
parameters: [
|
|
666
|
-
{
|
|
667
|
-
name: "dbid",
|
|
668
|
-
in: "path",
|
|
669
|
-
required: true,
|
|
670
|
-
type: "string",
|
|
671
|
-
},
|
|
672
|
-
],
|
|
673
|
-
responses: {
|
|
674
|
-
200: {
|
|
675
|
-
description: "Success",
|
|
676
|
-
schema: { $ref: "#/definitions/GetTempTokenDBID200Response" },
|
|
677
|
-
},
|
|
678
|
-
},
|
|
679
|
-
},
|
|
680
|
-
},
|
|
681
|
-
};
|
|
682
|
-
- name: generate-unified-interface.ts
|
|
683
|
-
type: file
|
|
684
|
-
contents: |
|
|
685
|
-
// src/code-generation/generate-unified-interface.ts
|
|
686
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
687
|
-
import { join, dirname } from "path";
|
|
688
|
-
import { fileURLToPath } from "url";
|
|
689
|
-
import { simplifyName } from "../utils.ts";
|
|
690
|
-
import { OpenAPIV2 } from "openapi-types";
|
|
691
|
-
|
|
692
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
693
|
-
const SPEC_FILE = join(__dirname, "output", "quickbase-fixed.json");
|
|
694
|
-
const OUTPUT_DIR = join(__dirname, "..", "generated-unified");
|
|
695
|
-
const OUTPUT_FILE = join(OUTPUT_DIR, "QuickbaseClient.ts");
|
|
696
|
-
|
|
697
|
-
function generateInterface() {
|
|
698
|
-
if (!existsSync(SPEC_FILE)) {
|
|
699
|
-
console.error(
|
|
700
|
-
`Spec file ${SPEC_FILE} not found. Run 'npm run fix-spec' first.`
|
|
701
|
-
);
|
|
702
|
-
process.exit(1);
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
console.log("Generating unified QuickbaseClient interface...");
|
|
706
|
-
const spec = JSON.parse(
|
|
707
|
-
readFileSync(SPEC_FILE, "utf8")
|
|
708
|
-
) as OpenAPIV2.Document;
|
|
709
|
-
const { paths } = spec;
|
|
710
|
-
|
|
711
|
-
const modelImports = new Set<string>();
|
|
712
|
-
const methods: string[] = [];
|
|
713
|
-
|
|
714
|
-
for (const [path, methodsObj] of Object.entries(
|
|
715
|
-
paths as OpenAPIV2.PathsObject
|
|
716
|
-
)) {
|
|
717
|
-
if (!methodsObj) continue;
|
|
718
|
-
|
|
719
|
-
for (const [method, operation] of Object.entries(
|
|
720
|
-
methodsObj as OpenAPIV2.PathItemObject
|
|
721
|
-
)) {
|
|
722
|
-
const op = operation as OpenAPIV2.OperationObject | undefined;
|
|
723
|
-
if (!op || !op.operationId) continue;
|
|
724
|
-
|
|
725
|
-
const opId = op.operationId; // Use operationId directly, no simplification
|
|
726
|
-
const params = (op.parameters || [])
|
|
727
|
-
.filter((p) => {
|
|
728
|
-
const param = p as OpenAPIV2.ParameterObject;
|
|
729
|
-
return !["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
|
|
730
|
-
param.name
|
|
731
|
-
);
|
|
732
|
-
})
|
|
733
|
-
.map((p) => {
|
|
734
|
-
const param = p as OpenAPIV2.ParameterObject;
|
|
735
|
-
const type = param.type
|
|
736
|
-
? mapOpenApiTypeToTs(param.type)
|
|
737
|
-
: param.schema
|
|
738
|
-
? mapRefToType(param.schema, modelImports)
|
|
739
|
-
: "any";
|
|
740
|
-
return `${param.name}${param.required ? "" : "?"}: ${type}`;
|
|
741
|
-
})
|
|
742
|
-
.join("; ");
|
|
743
|
-
const response = op.responses?.["200"] as
|
|
744
|
-
| OpenAPIV2.ResponseObject
|
|
745
|
-
| undefined;
|
|
746
|
-
const returnType = response?.schema
|
|
747
|
-
? mapRefToType(response.schema, modelImports)
|
|
748
|
-
: "void";
|
|
749
|
-
methods.push(
|
|
750
|
-
` ${opId}: (params: { ${params} }) => Promise<${returnType}>;`
|
|
751
|
-
);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Generate import and export statements for all model types
|
|
756
|
-
const importExportLines = Array.from(modelImports)
|
|
757
|
-
.map((m) => `export { ${m} } from "../generated/models/${m}.ts";`)
|
|
758
|
-
.join("\n");
|
|
759
|
-
|
|
760
|
-
const interfaceContent = `// Generated on ${new Date().toISOString()}\n${importExportLines}\n\nexport interface QuickbaseClient {\n${methods.join(
|
|
761
|
-
"\n"
|
|
762
|
-
)}\n}\n`;
|
|
763
|
-
|
|
764
|
-
if (!existsSync(OUTPUT_DIR)) {
|
|
765
|
-
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
766
|
-
}
|
|
767
|
-
writeFileSync(OUTPUT_FILE, interfaceContent, "utf8");
|
|
768
|
-
console.log(`Generated ${OUTPUT_FILE}`);
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
function mapOpenApiTypeToTs(openApiType: string): string {
|
|
772
|
-
switch (openApiType.toLowerCase()) {
|
|
773
|
-
case "integer":
|
|
774
|
-
case "int":
|
|
775
|
-
return "number";
|
|
776
|
-
case "string":
|
|
777
|
-
return "string";
|
|
778
|
-
case "boolean":
|
|
779
|
-
return "boolean";
|
|
780
|
-
default:
|
|
781
|
-
return "any";
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
function mapRefToType(
|
|
786
|
-
schema: OpenAPIV2.SchemaObject | OpenAPIV2.ReferenceObject,
|
|
787
|
-
modelImports: Set<string>
|
|
788
|
-
): string {
|
|
789
|
-
if ("$ref" in schema) {
|
|
790
|
-
const model = schema.$ref.split("/").pop()!;
|
|
791
|
-
modelImports.add(model);
|
|
792
|
-
return model;
|
|
793
|
-
}
|
|
794
|
-
if (schema.type === "array" && schema.items) {
|
|
795
|
-
const itemType =
|
|
796
|
-
"$ref" in schema.items
|
|
797
|
-
? schema.items.$ref.split("/").pop()!
|
|
798
|
-
: mapOpenApiTypeToTs(schema.items.type || "any");
|
|
799
|
-
if ("$ref" in schema.items) modelImports.add(itemType);
|
|
800
|
-
return `${itemType}[]`;
|
|
801
|
-
}
|
|
802
|
-
return mapOpenApiTypeToTs(schema.type || "any");
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
try {
|
|
806
|
-
generateInterface();
|
|
807
|
-
} catch (error) {
|
|
808
|
-
console.error("Generation failed:", error);
|
|
809
|
-
process.exit(1);
|
|
810
|
-
}
|
|
811
|
-
- name: output
|
|
812
|
-
type: directory
|
|
813
|
-
children:
|
|
814
|
-
- name: quickbase-fixed.json
|
|
815
|
-
type: file
|
|
816
|
-
- name: regenerate-client.ts
|
|
817
|
-
type: file
|
|
818
|
-
contents: |
|
|
819
|
-
#!/usr/bin/env node
|
|
820
|
-
import { execSync } from "child_process";
|
|
821
|
-
import { existsSync } from "fs";
|
|
822
|
-
import { join } from "path";
|
|
823
|
-
import { fileURLToPath } from "url";
|
|
824
|
-
import fetch from "node-fetch";
|
|
825
|
-
import readline from "readline";
|
|
826
|
-
|
|
827
|
-
const CURRENT_JAR_VERSION = "7.12.0";
|
|
828
|
-
const MAVEN_METADATA_URL =
|
|
829
|
-
"https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/maven-metadata.xml";
|
|
830
|
-
const CODEGEN_DIR = join(fileURLToPath(import.meta.url), "..");
|
|
831
|
-
const JARS_DIR = join(CODEGEN_DIR, "..", "openapi-jars");
|
|
832
|
-
const SPEC_INPUT = join(CODEGEN_DIR, "output", "quickbase-fixed.json"); // Updated path
|
|
833
|
-
const OUTPUT_DIR = join(CODEGEN_DIR, "..", "generated");
|
|
834
|
-
|
|
835
|
-
async function getLatestVersion(): Promise<string> {
|
|
836
|
-
const response = await fetch(MAVEN_METADATA_URL);
|
|
837
|
-
if (!response.ok)
|
|
838
|
-
throw new Error(`Failed to fetch Maven metadata: ${response.statusText}`);
|
|
839
|
-
const text = await response.text();
|
|
840
|
-
const match = text.match(/<latest>(.*?)<\/latest>/);
|
|
841
|
-
if (!match)
|
|
842
|
-
throw new Error("Couldn’t parse latest version from Maven metadata");
|
|
843
|
-
return match[1];
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
async function checkAndPromptForUpdate(messages: string[]): Promise<string> {
|
|
847
|
-
const latestVersion = await getLatestVersion();
|
|
848
|
-
if (latestVersion === CURRENT_JAR_VERSION) {
|
|
849
|
-
messages.push(`Current version (${CURRENT_JAR_VERSION}) is the latest.`);
|
|
850
|
-
return CURRENT_JAR_VERSION;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
messages.push(
|
|
854
|
-
`Newer version available: ${latestVersion} (current: ${CURRENT_JAR_VERSION})`
|
|
855
|
-
);
|
|
856
|
-
const rl = readline.createInterface({
|
|
857
|
-
input: process.stdin,
|
|
858
|
-
output: process.stdout,
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
return new Promise((resolve) => {
|
|
862
|
-
const prompt = "Do you want to update to the latest version? (y/n): ";
|
|
863
|
-
messages.push(prompt);
|
|
864
|
-
rl.question(prompt, (answer) => {
|
|
865
|
-
rl.close();
|
|
866
|
-
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
867
|
-
messages.push(`Switching to version ${latestVersion}...`);
|
|
868
|
-
resolve(latestVersion);
|
|
869
|
-
} else {
|
|
870
|
-
messages.push(`Sticking with version ${CURRENT_JAR_VERSION}.`);
|
|
871
|
-
resolve(CURRENT_JAR_VERSION);
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
});
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
async function ensureJarExists(
|
|
878
|
-
version: string,
|
|
879
|
-
messages: string[]
|
|
880
|
-
): Promise<string> {
|
|
881
|
-
const jarPath = join(JARS_DIR, `openapi-generator-cli-${version}.jar`);
|
|
882
|
-
const jarUrl = `https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/${version}/openapi-generator-cli-${version}.jar`;
|
|
883
|
-
|
|
884
|
-
if (!existsSync(jarPath)) {
|
|
885
|
-
messages.push(`Downloading OpenAPI Generator CLI v${version}...`);
|
|
886
|
-
try {
|
|
887
|
-
execSync(`curl -L -o ${jarPath} ${jarUrl}`, { stdio: "inherit" });
|
|
888
|
-
messages.push(`Downloaded ${jarPath}`);
|
|
889
|
-
} catch (error) {
|
|
890
|
-
messages.push("Failed to download JAR.");
|
|
891
|
-
throw error;
|
|
892
|
-
}
|
|
893
|
-
} else {
|
|
894
|
-
messages.push(`Using existing ${jarPath}`);
|
|
895
|
-
}
|
|
896
|
-
return jarPath;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
function regenerateClient(jarPath: string, messages: string[]) {
|
|
900
|
-
messages.push("Regenerating client from spec...");
|
|
901
|
-
const command = `java -jar ${jarPath} generate -i ${SPEC_INPUT} -g typescript-fetch -o ${OUTPUT_DIR}`;
|
|
902
|
-
try {
|
|
903
|
-
execSync(command, { stdio: "inherit" });
|
|
904
|
-
messages.push("Client regeneration complete.");
|
|
905
|
-
} catch (error) {
|
|
906
|
-
messages.push("Regeneration failed.");
|
|
907
|
-
throw error;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
async function main() {
|
|
912
|
-
const messages: string[] = [];
|
|
913
|
-
try {
|
|
914
|
-
const versionToUse = await checkAndPromptForUpdate(messages);
|
|
915
|
-
const jarPath = await ensureJarExists(versionToUse, messages);
|
|
916
|
-
regenerateClient(jarPath, messages);
|
|
917
|
-
} catch (error) {
|
|
918
|
-
console.error("Error occurred during process:", error);
|
|
919
|
-
} finally {
|
|
920
|
-
console.log("\n--- Key Messages ---");
|
|
921
|
-
messages.forEach((msg) => console.log(msg));
|
|
922
|
-
}
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
main().catch((error) => {
|
|
926
|
-
console.error("Error in regeneration process:", error);
|
|
927
|
-
process.exit(1);
|
|
928
|
-
});
|
|
929
|
-
- name: generated
|
|
930
|
-
type: directory
|
|
931
|
-
children:
|
|
932
|
-
- name: .openapi-generator
|
|
933
|
-
type: directory
|
|
934
|
-
children:
|
|
935
|
-
- name: FILES
|
|
936
|
-
type: file
|
|
937
|
-
- name: VERSION
|
|
938
|
-
type: file
|
|
939
|
-
- name: .openapi-generator-ignore
|
|
940
|
-
type: file
|
|
941
|
-
- name: apis
|
|
942
|
-
type: directory
|
|
943
|
-
children:
|
|
944
|
-
- name: AppsApi.ts
|
|
945
|
-
type: file
|
|
946
|
-
- name: AuditApi.ts
|
|
947
|
-
type: file
|
|
948
|
-
- name: AuthApi.ts
|
|
949
|
-
type: file
|
|
950
|
-
- name: DocumentTemplatesApi.ts
|
|
951
|
-
type: file
|
|
952
|
-
- name: FieldsApi.ts
|
|
953
|
-
type: file
|
|
954
|
-
- name: FilesApi.ts
|
|
955
|
-
type: file
|
|
956
|
-
- name: FormulasApi.ts
|
|
957
|
-
type: file
|
|
958
|
-
- name: PlatformAnalyticsApi.ts
|
|
959
|
-
type: file
|
|
960
|
-
- name: RecordsApi.ts
|
|
961
|
-
type: file
|
|
962
|
-
- name: ReportsApi.ts
|
|
963
|
-
type: file
|
|
964
|
-
- name: SolutionsApi.ts
|
|
965
|
-
type: file
|
|
966
|
-
- name: TablesApi.ts
|
|
967
|
-
type: file
|
|
968
|
-
- name: UserTokenApi.ts
|
|
969
|
-
type: file
|
|
970
|
-
- name: UsersApi.ts
|
|
971
|
-
type: file
|
|
972
|
-
- name: index.ts
|
|
973
|
-
type: file
|
|
974
|
-
- name: index.ts
|
|
975
|
-
type: file
|
|
976
|
-
- name: models
|
|
977
|
-
type: directory
|
|
978
|
-
children:
|
|
979
|
-
- name: AddMembersToGroup200Response.ts
|
|
980
|
-
type: file
|
|
981
|
-
- name: AddSubgroupsToGroup200Response.ts
|
|
982
|
-
type: file
|
|
983
|
-
- name: App.ts
|
|
984
|
-
type: file
|
|
985
|
-
- name: AppMemoryInfo.ts
|
|
986
|
-
type: file
|
|
987
|
-
- name: AppSecurityProperties.ts
|
|
988
|
-
type: file
|
|
989
|
-
- name: Audit200Response.ts
|
|
990
|
-
type: file
|
|
991
|
-
- name: Audit202Response.ts
|
|
992
|
-
type: file
|
|
993
|
-
- name: AuditRequest.ts
|
|
994
|
-
type: file
|
|
995
|
-
- name: CloneUserToken200Response.ts
|
|
996
|
-
type: file
|
|
997
|
-
- name: CloneUserTokenRequest.ts
|
|
998
|
-
type: file
|
|
999
|
-
- name: CopyApp200Response.ts
|
|
1000
|
-
type: file
|
|
1001
|
-
- name: CopyAppRequest.ts
|
|
1002
|
-
type: file
|
|
1003
|
-
- name: CreateApp200Response.ts
|
|
1004
|
-
type: file
|
|
1005
|
-
- name: CreateAppRequest.ts
|
|
1006
|
-
type: file
|
|
1007
|
-
- name: CreateRelationship200Response.ts
|
|
1008
|
-
type: file
|
|
1009
|
-
- name: CreateRelationshipRequest.ts
|
|
1010
|
-
type: file
|
|
1011
|
-
- name: DeactivateUserToken200Response.ts
|
|
1012
|
-
type: file
|
|
1013
|
-
- name: DeleteFile200Response.ts
|
|
1014
|
-
type: file
|
|
1015
|
-
- name: DeleteRecords200Response.ts
|
|
1016
|
-
type: file
|
|
1017
|
-
- name: DeleteRecordsRequest.ts
|
|
1018
|
-
type: file
|
|
1019
|
-
- name: DeleteRelationship200Response.ts
|
|
1020
|
-
type: file
|
|
1021
|
-
- name: DeleteTable200Response.ts
|
|
1022
|
-
type: file
|
|
1023
|
-
- name: DenyUsers200Response.ts
|
|
1024
|
-
type: file
|
|
1025
|
-
- name: ExchangeSsoToken200Response.ts
|
|
1026
|
-
type: file
|
|
1027
|
-
- name: ExchangeSsoTokenRequest.ts
|
|
1028
|
-
type: file
|
|
1029
|
-
- name: Field.ts
|
|
1030
|
-
type: file
|
|
1031
|
-
- name: FieldPermissionsInner.ts
|
|
1032
|
-
type: file
|
|
1033
|
-
- name: FieldProperties.ts
|
|
1034
|
-
type: file
|
|
1035
|
-
- name: GenerateDocument200Response.ts
|
|
1036
|
-
type: file
|
|
1037
|
-
- name: GetField200Response.ts
|
|
1038
|
-
type: file
|
|
1039
|
-
- name: GetRelationships200Response.ts
|
|
1040
|
-
type: file
|
|
1041
|
-
- name: GetReport200Response.ts
|
|
1042
|
-
type: file
|
|
1043
|
-
- name: GetTable200Response.ts
|
|
1044
|
-
type: file
|
|
1045
|
-
- name: GetTempTokenDBID200Response.ts
|
|
1046
|
-
type: file
|
|
1047
|
-
- name: GetUsers200Response.ts
|
|
1048
|
-
type: file
|
|
1049
|
-
- name: GetUsersRequest.ts
|
|
1050
|
-
type: file
|
|
1051
|
-
- name: PlatformAnalyticEventSummaries200Response.ts
|
|
1052
|
-
type: file
|
|
1053
|
-
- name: PlatformAnalyticEventSummaries200ResponseMetadata.ts
|
|
1054
|
-
type: file
|
|
1055
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInner.ts
|
|
1056
|
-
type: file
|
|
1057
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInnerEventTypesInner.ts
|
|
1058
|
-
type: file
|
|
1059
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInnerTotals.ts
|
|
1060
|
-
type: file
|
|
1061
|
-
- name: PlatformAnalyticEventSummaries200ResponseTotals.ts
|
|
1062
|
-
type: file
|
|
1063
|
-
- name: PlatformAnalyticEventSummaries200ResponseWhereInner.ts
|
|
1064
|
-
type: file
|
|
1065
|
-
- name: PlatformAnalyticEventSummariesRequest.ts
|
|
1066
|
-
type: file
|
|
1067
|
-
- name: PlatformAnalyticEventSummariesRequestWhereInner.ts
|
|
1068
|
-
type: file
|
|
1069
|
-
- name: PlatformAnalyticReads200Response.ts
|
|
1070
|
-
type: file
|
|
1071
|
-
- name: Record.ts
|
|
1072
|
-
type: file
|
|
1073
|
-
- name: RemoveMembersFromGroup200Response.ts
|
|
1074
|
-
type: file
|
|
1075
|
-
- name: RemoveSubgroupsFromGroup200Response.ts
|
|
1076
|
-
type: file
|
|
1077
|
-
- name: ReportRunResponse.ts
|
|
1078
|
-
type: file
|
|
1079
|
-
- name: RunFormula200Response.ts
|
|
1080
|
-
type: file
|
|
1081
|
-
- name: RunFormulaRequest.ts
|
|
1082
|
-
type: file
|
|
1083
|
-
- name: Table.ts
|
|
1084
|
-
type: file
|
|
1085
|
-
- name: TransferUserToken200Response.ts
|
|
1086
|
-
type: file
|
|
1087
|
-
- name: TransferUserTokenRequest.ts
|
|
1088
|
-
type: file
|
|
1089
|
-
- name: UndenyUsers200Response.ts
|
|
1090
|
-
type: file
|
|
1091
|
-
- name: UpdateFieldRequest.ts
|
|
1092
|
-
type: file
|
|
1093
|
-
- name: UpdateRelationshipRequest.ts
|
|
1094
|
-
type: file
|
|
1095
|
-
- name: UpdateTableRequest.ts
|
|
1096
|
-
type: file
|
|
1097
|
-
- name: Upsert200Response.ts
|
|
1098
|
-
type: file
|
|
1099
|
-
- name: Upsert207Response.ts
|
|
1100
|
-
type: file
|
|
1101
|
-
- name: Upsert400Response.ts
|
|
1102
|
-
type: file
|
|
1103
|
-
- name: UpsertRequest.ts
|
|
1104
|
-
type: file
|
|
1105
|
-
- name: index.ts
|
|
1106
|
-
type: file
|
|
1107
|
-
- name: runtime.ts
|
|
1108
|
-
type: file
|
|
1109
|
-
- name: generated-old
|
|
1110
|
-
type: directory
|
|
1111
|
-
children:
|
|
1112
|
-
- name: .openapi-generator
|
|
1113
|
-
type: directory
|
|
1114
|
-
children:
|
|
1115
|
-
- name: FILES
|
|
1116
|
-
type: file
|
|
1117
|
-
- name: VERSION
|
|
1118
|
-
type: file
|
|
1119
|
-
- name: .openapi-generator-ignore
|
|
1120
|
-
type: file
|
|
1121
|
-
- name: apis
|
|
1122
|
-
type: directory
|
|
1123
|
-
children:
|
|
1124
|
-
- name: AppsApi.ts
|
|
1125
|
-
type: file
|
|
1126
|
-
- name: AuditApi.ts
|
|
1127
|
-
type: file
|
|
1128
|
-
- name: AuthApi.ts
|
|
1129
|
-
type: file
|
|
1130
|
-
- name: DocumentTemplatesApi.ts
|
|
1131
|
-
type: file
|
|
1132
|
-
- name: FieldsApi.ts
|
|
1133
|
-
type: file
|
|
1134
|
-
- name: FilesApi.ts
|
|
1135
|
-
type: file
|
|
1136
|
-
- name: FormulasApi.ts
|
|
1137
|
-
type: file
|
|
1138
|
-
- name: PlatformAnalyticsApi.ts
|
|
1139
|
-
type: file
|
|
1140
|
-
- name: RecordsApi.ts
|
|
1141
|
-
type: file
|
|
1142
|
-
- name: ReportsApi.ts
|
|
1143
|
-
type: file
|
|
1144
|
-
- name: SolutionsApi.ts
|
|
1145
|
-
type: file
|
|
1146
|
-
- name: TablesApi.ts
|
|
1147
|
-
type: file
|
|
1148
|
-
- name: UserTokenApi.ts
|
|
1149
|
-
type: file
|
|
1150
|
-
- name: UsersApi.ts
|
|
1151
|
-
type: file
|
|
1152
|
-
- name: index.ts
|
|
1153
|
-
type: file
|
|
1154
|
-
- name: index.ts
|
|
1155
|
-
type: file
|
|
1156
|
-
- name: models
|
|
1157
|
-
type: directory
|
|
1158
|
-
children:
|
|
1159
|
-
- name: AddMembersToGroup200Response.ts
|
|
1160
|
-
type: file
|
|
1161
|
-
- name: AddSubgroupsToGroup200Response.ts
|
|
1162
|
-
type: file
|
|
1163
|
-
- name: App.ts
|
|
1164
|
-
type: file
|
|
1165
|
-
- name: AppMemoryInfo.ts
|
|
1166
|
-
type: file
|
|
1167
|
-
- name: AppSecurityProperties.ts
|
|
1168
|
-
type: file
|
|
1169
|
-
- name: Audit200Response.ts
|
|
1170
|
-
type: file
|
|
1171
|
-
- name: Audit202Response.ts
|
|
1172
|
-
type: file
|
|
1173
|
-
- name: AuditRequest.ts
|
|
1174
|
-
type: file
|
|
1175
|
-
- name: CloneUserToken200Response.ts
|
|
1176
|
-
type: file
|
|
1177
|
-
- name: CloneUserTokenRequest.ts
|
|
1178
|
-
type: file
|
|
1179
|
-
- name: CopyApp200Response.ts
|
|
1180
|
-
type: file
|
|
1181
|
-
- name: CopyAppRequest.ts
|
|
1182
|
-
type: file
|
|
1183
|
-
- name: CreateApp200Response.ts
|
|
1184
|
-
type: file
|
|
1185
|
-
- name: CreateAppRequest.ts
|
|
1186
|
-
type: file
|
|
1187
|
-
- name: CreateField200Response.ts
|
|
1188
|
-
type: file
|
|
1189
|
-
- name: CreateRelationship200Response.ts
|
|
1190
|
-
type: file
|
|
1191
|
-
- name: CreateRelationshipRequest.ts
|
|
1192
|
-
type: file
|
|
1193
|
-
- name: DeactivateUserToken200Response.ts
|
|
1194
|
-
type: file
|
|
1195
|
-
- name: DeleteApp200Response.ts
|
|
1196
|
-
type: file
|
|
1197
|
-
- name: DeleteAppRequest.ts
|
|
1198
|
-
type: file
|
|
1199
|
-
- name: DeleteFields200Response.ts
|
|
1200
|
-
type: file
|
|
1201
|
-
- name: DeleteFile200Response.ts
|
|
1202
|
-
type: file
|
|
1203
|
-
- name: DeleteRelationship200Response.ts
|
|
1204
|
-
type: file
|
|
1205
|
-
- name: DenyUsers200Response.ts
|
|
1206
|
-
type: file
|
|
1207
|
-
- name: ExchangeSsoToken200Response.ts
|
|
1208
|
-
type: file
|
|
1209
|
-
- name: ExchangeSsoTokenRequest.ts
|
|
1210
|
-
type: file
|
|
1211
|
-
- name: Field.ts
|
|
1212
|
-
type: file
|
|
1213
|
-
- name: FieldPermissionsInner.ts
|
|
1214
|
-
type: file
|
|
1215
|
-
- name: FieldProperties.ts
|
|
1216
|
-
type: file
|
|
1217
|
-
- name: GenerateDocument200Response.ts
|
|
1218
|
-
type: file
|
|
1219
|
-
- name: GetField200Response.ts
|
|
1220
|
-
type: file
|
|
1221
|
-
- name: GetRelationships200Response.ts
|
|
1222
|
-
type: file
|
|
1223
|
-
- name: GetReport200Response.ts
|
|
1224
|
-
type: file
|
|
1225
|
-
- name: GetTempTokenDBID200Response.ts
|
|
1226
|
-
type: file
|
|
1227
|
-
- name: GetUsers200Response.ts
|
|
1228
|
-
type: file
|
|
1229
|
-
- name: GetUsersRequest.ts
|
|
1230
|
-
type: file
|
|
1231
|
-
- name: PlatformAnalyticEventSummaries200Response.ts
|
|
1232
|
-
type: file
|
|
1233
|
-
- name: PlatformAnalyticEventSummaries200ResponseMetadata.ts
|
|
1234
|
-
type: file
|
|
1235
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInner.ts
|
|
1236
|
-
type: file
|
|
1237
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInnerEventTypesInner.ts
|
|
1238
|
-
type: file
|
|
1239
|
-
- name: PlatformAnalyticEventSummaries200ResponseResultsInnerTotals.ts
|
|
1240
|
-
type: file
|
|
1241
|
-
- name: PlatformAnalyticEventSummaries200ResponseTotals.ts
|
|
1242
|
-
type: file
|
|
1243
|
-
- name: PlatformAnalyticEventSummaries200ResponseWhereInner.ts
|
|
1244
|
-
type: file
|
|
1245
|
-
- name: PlatformAnalyticEventSummariesRequest.ts
|
|
1246
|
-
type: file
|
|
1247
|
-
- name: PlatformAnalyticEventSummariesRequestWhereInner.ts
|
|
1248
|
-
type: file
|
|
1249
|
-
- name: PlatformAnalyticReads200Response.ts
|
|
1250
|
-
type: file
|
|
1251
|
-
- name: RemoveMembersFromGroup200Response.ts
|
|
1252
|
-
type: file
|
|
1253
|
-
- name: RemoveSubgroupsFromGroup200Response.ts
|
|
1254
|
-
type: file
|
|
1255
|
-
- name: RunFormula200Response.ts
|
|
1256
|
-
type: file
|
|
1257
|
-
- name: RunFormulaRequest.ts
|
|
1258
|
-
type: file
|
|
1259
|
-
- name: RunQuery200Response.ts
|
|
1260
|
-
type: file
|
|
1261
|
-
- name: RunQueryRequest.ts
|
|
1262
|
-
type: file
|
|
1263
|
-
- name: RunReport200Response.ts
|
|
1264
|
-
type: file
|
|
1265
|
-
- name: Table.ts
|
|
1266
|
-
type: file
|
|
1267
|
-
- name: TransferUserToken200Response.ts
|
|
1268
|
-
type: file
|
|
1269
|
-
- name: TransferUserTokenRequest.ts
|
|
1270
|
-
type: file
|
|
1271
|
-
- name: UndenyUsers200Response.ts
|
|
1272
|
-
type: file
|
|
1273
|
-
- name: UpdateApp200Response.ts
|
|
1274
|
-
type: file
|
|
1275
|
-
- name: UpdateAppRequest.ts
|
|
1276
|
-
type: file
|
|
1277
|
-
- name: UpdateFieldRequest.ts
|
|
1278
|
-
type: file
|
|
1279
|
-
- name: UpdateRelationshipRequest.ts
|
|
1280
|
-
type: file
|
|
1281
|
-
- name: Upsert200Response.ts
|
|
1282
|
-
type: file
|
|
1283
|
-
- name: index.ts
|
|
1284
|
-
type: file
|
|
1285
|
-
- name: runtime.ts
|
|
1286
|
-
type: file
|
|
1287
|
-
- name: generated-unified
|
|
1288
|
-
type: directory
|
|
1289
|
-
children:
|
|
1290
|
-
- name: QuickbaseClient.ts
|
|
1291
|
-
type: file
|
|
1292
|
-
- name: index.ts
|
|
1293
|
-
type: file
|
|
1294
|
-
- name: log-gen.ts
|
|
1295
|
-
type: file
|
|
1296
|
-
- name: openapi-jars
|
|
1297
|
-
type: directory
|
|
1298
|
-
children:
|
|
1299
|
-
- name: openapi-generator-cli-7.12.0.jar
|
|
1300
|
-
type: file
|
|
1301
|
-
- name: quickbaseClient.ts
|
|
1302
|
-
type: file
|
|
1303
|
-
contents: |
|
|
1304
|
-
// src/quickbaseClient.ts
|
|
1305
|
-
import { QuickbaseClient } from "./generated-unified/QuickbaseClient.ts";
|
|
1306
|
-
import {
|
|
1307
|
-
Configuration,
|
|
1308
|
-
HTTPHeaders,
|
|
1309
|
-
ResponseError,
|
|
1310
|
-
} from "./generated/runtime.ts";
|
|
1311
|
-
import * as apis from "./generated/apis/index.ts";
|
|
1312
|
-
import { simplifyName } from "./utils.ts";
|
|
1313
|
-
import { TokenCache } from "./tokenCache.ts";
|
|
1314
|
-
|
|
1315
|
-
export interface QuickbaseConfig {
|
|
1316
|
-
realm: string;
|
|
1317
|
-
userToken?: string;
|
|
1318
|
-
tempToken?: string;
|
|
1319
|
-
useTempTokens?: boolean;
|
|
1320
|
-
debug?: boolean;
|
|
1321
|
-
fetchApi?: typeof fetch;
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
export interface TempTokenParams {
|
|
1325
|
-
appId?: string;
|
|
1326
|
-
tableId?: string;
|
|
1327
|
-
dbid?: string;
|
|
1328
|
-
}
|
|
1329
|
-
|
|
1330
|
-
type ApiMethod<K extends keyof QuickbaseClient> = (
|
|
1331
|
-
requestParameters: Parameters<QuickbaseClient[K]>[0],
|
|
1332
|
-
initOverrides?: RequestInit
|
|
1333
|
-
) => Promise<ReturnType<QuickbaseClient[K]>>;
|
|
1334
|
-
|
|
1335
|
-
interface MethodInfo<K extends keyof QuickbaseClient> {
|
|
1336
|
-
api: any;
|
|
1337
|
-
method: ApiMethod<K>;
|
|
1338
|
-
paramMap: string[];
|
|
1339
|
-
}
|
|
1340
|
-
|
|
1341
|
-
type MethodMap = { [K in keyof QuickbaseClient]: MethodInfo<K> };
|
|
1342
|
-
|
|
1343
|
-
const getParamNames = (fn: (...args: any[]) => any): string[] =>
|
|
1344
|
-
fn
|
|
1345
|
-
.toString()
|
|
1346
|
-
.slice(fn.toString().indexOf("(") + 1, fn.toString().indexOf(")"))
|
|
1347
|
-
.split(",")
|
|
1348
|
-
.map((p) => p.trim().split("=")[0].trim())
|
|
1349
|
-
.filter((p) => p && !p.match(/^\{/) && p !== "options");
|
|
1350
|
-
|
|
1351
|
-
// New utility function to extract dbid
|
|
1352
|
-
const extractDbid = (
|
|
1353
|
-
params: Partial<TempTokenParams>,
|
|
1354
|
-
errorMessage: string
|
|
1355
|
-
): string => {
|
|
1356
|
-
const dbid = params.appId || params.tableId || params.dbid;
|
|
1357
|
-
if (!dbid) {
|
|
1358
|
-
throw new Error(errorMessage);
|
|
1359
|
-
}
|
|
1360
|
-
return dbid;
|
|
1361
|
-
};
|
|
1362
|
-
|
|
1363
|
-
export function quickbaseClient(config: QuickbaseConfig): QuickbaseClient {
|
|
1364
|
-
const {
|
|
1365
|
-
realm,
|
|
1366
|
-
userToken,
|
|
1367
|
-
tempToken: initialTempToken,
|
|
1368
|
-
useTempTokens,
|
|
1369
|
-
fetchApi,
|
|
1370
|
-
debug,
|
|
1371
|
-
} = config;
|
|
1372
|
-
const baseUrl = `https://api.quickbase.com/v1`;
|
|
1373
|
-
|
|
1374
|
-
const tokenCache = new TokenCache();
|
|
1375
|
-
|
|
1376
|
-
const headers: HTTPHeaders = {
|
|
1377
|
-
"QB-Realm-Hostname": `${realm}.quickbase.com`,
|
|
1378
|
-
"Content-Type": "application/json",
|
|
1379
|
-
};
|
|
1380
|
-
|
|
1381
|
-
if (initialTempToken) {
|
|
1382
|
-
headers["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
|
|
1383
|
-
} else if (userToken && !useTempTokens) {
|
|
1384
|
-
headers["Authorization"] = `QB-USER-TOKEN ${userToken}`;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
const defaultFetch =
|
|
1388
|
-
typeof window !== "undefined" ? window.fetch.bind(window) : undefined;
|
|
1389
|
-
const configuration = new Configuration({
|
|
1390
|
-
basePath: baseUrl,
|
|
1391
|
-
headers: { ...headers },
|
|
1392
|
-
fetchApi: fetchApi || defaultFetch,
|
|
1393
|
-
credentials: useTempTokens ? "include" : "omit",
|
|
1394
|
-
});
|
|
1395
|
-
|
|
1396
|
-
if (!configuration.fetchApi && typeof window === "undefined") {
|
|
1397
|
-
throw new Error(
|
|
1398
|
-
"fetchApi must be provided in non-browser environments (e.g., Node.js)"
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
const apiInstances = Object.fromEntries(
|
|
1403
|
-
Object.entries(apis)
|
|
1404
|
-
.filter(([name]) => name.endsWith("Api"))
|
|
1405
|
-
.map(([name, ApiClass]) => [
|
|
1406
|
-
name.replace("Api", "").toLowerCase(),
|
|
1407
|
-
new ApiClass(configuration),
|
|
1408
|
-
])
|
|
1409
|
-
);
|
|
1410
|
-
|
|
1411
|
-
const methodMap = buildMethodMap();
|
|
1412
|
-
|
|
1413
|
-
function buildMethodMap(): MethodMap {
|
|
1414
|
-
const methodMap: Partial<MethodMap> = {};
|
|
1415
|
-
const isValidMethod = (name: string) =>
|
|
1416
|
-
!name.startsWith("_") &&
|
|
1417
|
-
name !== "constructor" &&
|
|
1418
|
-
!["Middleware", "Pre", "Post", "Raw"].some((s) => name.includes(s));
|
|
1419
|
-
|
|
1420
|
-
for (const [apiName, api] of Object.entries(apiInstances)) {
|
|
1421
|
-
Object.getOwnPropertyNames(Object.getPrototypeOf(api))
|
|
1422
|
-
.filter(
|
|
1423
|
-
(name) =>
|
|
1424
|
-
isValidMethod(name) &&
|
|
1425
|
-
typeof api[name as keyof typeof api] === "function"
|
|
1426
|
-
)
|
|
1427
|
-
.forEach((rawMethodName) => {
|
|
1428
|
-
const simplifiedName = simplifyName(
|
|
1429
|
-
rawMethodName
|
|
1430
|
-
) as keyof QuickbaseClient;
|
|
1431
|
-
const method = api[rawMethodName as keyof typeof api];
|
|
1432
|
-
const boundMethod = method.bind(api) as unknown;
|
|
1433
|
-
if (typeof boundMethod === "function" && boundMethod.length <= 2) {
|
|
1434
|
-
methodMap[simplifiedName] = {
|
|
1435
|
-
api,
|
|
1436
|
-
method: boundMethod as ApiMethod<typeof simplifiedName>,
|
|
1437
|
-
paramMap: getParamNames(method),
|
|
1438
|
-
};
|
|
1439
|
-
if (debug)
|
|
1440
|
-
console.log(`Mapped ${rawMethodName} to ${simplifiedName}`);
|
|
1441
|
-
}
|
|
1442
|
-
});
|
|
1443
|
-
}
|
|
1444
|
-
return methodMap as MethodMap;
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
const fetchTempToken = async (dbid: string): Promise<string> => {
|
|
1448
|
-
const effectiveFetch = fetchApi || defaultFetch;
|
|
1449
|
-
if (!effectiveFetch) {
|
|
1450
|
-
throw new Error(
|
|
1451
|
-
"No fetch implementation available for fetching temp token"
|
|
1452
|
-
);
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
|
-
const response = await effectiveFetch(
|
|
1456
|
-
`https://api.quickbase.com/v1/auth/temporary/${dbid}`,
|
|
1457
|
-
{
|
|
1458
|
-
method: "GET",
|
|
1459
|
-
headers: { ...headers },
|
|
1460
|
-
credentials: "include",
|
|
1461
|
-
}
|
|
1462
|
-
);
|
|
1463
|
-
|
|
1464
|
-
if (!response.ok) {
|
|
1465
|
-
const errorBody = await response.text();
|
|
1466
|
-
throw new Error(`API Error: ${errorBody} (Status: ${response.status})`);
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
const tokenResult = await response.json();
|
|
1470
|
-
const token = tokenResult.temporaryAuthorization;
|
|
1471
|
-
if (!token) {
|
|
1472
|
-
throw new Error("No temporary token returned from API");
|
|
1473
|
-
}
|
|
1474
|
-
tokenCache.set(dbid, token);
|
|
1475
|
-
if (debug) {
|
|
1476
|
-
console.log(
|
|
1477
|
-
`Fetched and cached new token for dbid: ${dbid}`,
|
|
1478
|
-
token,
|
|
1479
|
-
`Expires at: ${new Date(
|
|
1480
|
-
Date.now() + (4 * 60 + 50) * 1000
|
|
1481
|
-
).toISOString()}`
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1484
|
-
return token;
|
|
1485
|
-
};
|
|
1486
|
-
|
|
1487
|
-
const invokeMethod = async <K extends keyof QuickbaseClient>(
|
|
1488
|
-
methodName: K,
|
|
1489
|
-
params: Parameters<QuickbaseClient[K]>[0] & Partial<TempTokenParams>,
|
|
1490
|
-
retryCount: number = 0
|
|
1491
|
-
): Promise<ReturnType<QuickbaseClient[K]>> => {
|
|
1492
|
-
const methodInfo = methodMap[methodName];
|
|
1493
|
-
if (!methodInfo) {
|
|
1494
|
-
console.error(`Method ${methodName} not found in methodMap`, methodMap);
|
|
1495
|
-
throw new Error(`Method ${methodName} not found`);
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
let token =
|
|
1499
|
-
initialTempToken || (userToken && !useTempTokens ? userToken : undefined);
|
|
1500
|
-
let initOverrides: RequestInit = {};
|
|
1501
|
-
|
|
1502
|
-
// Early return for getTempTokenDBID if cached token exists
|
|
1503
|
-
if (methodName === "getTempTokenDBID" && useTempTokens) {
|
|
1504
|
-
const dbid = extractDbid(params, "No dbid provided for getTempTokenDBID");
|
|
1505
|
-
const cachedToken = tokenCache.get(dbid);
|
|
1506
|
-
if (cachedToken) {
|
|
1507
|
-
return { temporaryAuthorization: cachedToken } as ReturnType<
|
|
1508
|
-
QuickbaseClient[K]
|
|
1509
|
-
>;
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
if (useTempTokens && !token) {
|
|
1514
|
-
const dbid = extractDbid(
|
|
1515
|
-
params,
|
|
1516
|
-
`No dbid found in params for ${methodName} to fetch temp token`
|
|
1517
|
-
);
|
|
1518
|
-
if (debug)
|
|
1519
|
-
console.log(`Cache state before fetch for ${dbid}:`, tokenCache.dump());
|
|
1520
|
-
const cachedToken = tokenCache.get(dbid);
|
|
1521
|
-
if (cachedToken) {
|
|
1522
|
-
token = cachedToken;
|
|
1523
|
-
if (debug) console.log(`Reusing cached token for dbid: ${dbid}`, token);
|
|
1524
|
-
} else {
|
|
1525
|
-
if (typeof window === "undefined" && !fetchApi) {
|
|
1526
|
-
throw new Error(
|
|
1527
|
-
"Temporary tokens require a browser environment or a custom fetchApi with browser-like session support"
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
token = await fetchTempToken(dbid);
|
|
1531
|
-
// Return immediately for getTempTokenDBID after fetching
|
|
1532
|
-
if (methodName === "getTempTokenDBID") {
|
|
1533
|
-
return { temporaryAuthorization: token } as ReturnType<
|
|
1534
|
-
QuickbaseClient[K]
|
|
1535
|
-
>;
|
|
1536
|
-
}
|
|
1537
|
-
}
|
|
1538
|
-
initOverrides.headers = {
|
|
1539
|
-
...headers,
|
|
1540
|
-
Authorization: `QB-TEMP-TOKEN ${token}`,
|
|
1541
|
-
};
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
if (debug) console.log(`Invoking ${methodName} with params:`, params);
|
|
1545
|
-
if (debug)
|
|
1546
|
-
console.log(`Calling method with args:`, [params, initOverrides]);
|
|
1547
|
-
const args: [any, RequestInit | undefined] =
|
|
1548
|
-
methodInfo.paramMap.length === 1 &&
|
|
1549
|
-
methodInfo.paramMap[0] === "requestParameters"
|
|
1550
|
-
? [params, initOverrides]
|
|
1551
|
-
: [params, initOverrides];
|
|
1552
|
-
|
|
1553
|
-
try {
|
|
1554
|
-
const response = await methodInfo.method(...args);
|
|
1555
|
-
if (debug) console.log(`Response from ${methodName}:`, response);
|
|
1556
|
-
return response;
|
|
1557
|
-
} catch (error) {
|
|
1558
|
-
if (
|
|
1559
|
-
error instanceof ResponseError &&
|
|
1560
|
-
error.response.status === 401 &&
|
|
1561
|
-
retryCount < 1
|
|
1562
|
-
) {
|
|
1563
|
-
if (debug)
|
|
1564
|
-
console.log(
|
|
1565
|
-
`Authorization error for ${methodName}, refreshing token:`,
|
|
1566
|
-
error.message
|
|
1567
|
-
);
|
|
1568
|
-
const dbid = extractDbid(
|
|
1569
|
-
params,
|
|
1570
|
-
`No dbid to refresh token after authorization error`
|
|
1571
|
-
);
|
|
1572
|
-
token = await fetchTempToken(dbid);
|
|
1573
|
-
initOverrides.headers = {
|
|
1574
|
-
...headers,
|
|
1575
|
-
Authorization: `QB-TEMP-TOKEN ${token}`,
|
|
1576
|
-
};
|
|
1577
|
-
if (debug) console.log(`Retrying ${methodName} with new token`);
|
|
1578
|
-
return invokeMethod(methodName, params, retryCount + 1); // Retry once
|
|
1579
|
-
}
|
|
1580
|
-
if (error instanceof ResponseError) {
|
|
1581
|
-
let errorMessage = error.message;
|
|
1582
|
-
try {
|
|
1583
|
-
const errorBody = await error.response.json();
|
|
1584
|
-
console.log(`Error response body for ${methodName}:`, errorBody);
|
|
1585
|
-
errorMessage = errorBody.message || errorMessage;
|
|
1586
|
-
} catch (e) {
|
|
1587
|
-
console.log(
|
|
1588
|
-
`Failed to parse error response for ${methodName}:`,
|
|
1589
|
-
error.message
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1592
|
-
throw new Error(
|
|
1593
|
-
`API Error: ${errorMessage} (Status: ${error.response.status})`
|
|
1594
|
-
);
|
|
1595
|
-
}
|
|
1596
|
-
throw error;
|
|
1597
|
-
}
|
|
1598
|
-
};
|
|
1599
|
-
|
|
1600
|
-
return new Proxy<QuickbaseClient>({} as QuickbaseClient, {
|
|
1601
|
-
get: (_, prop: string): ((params: any) => Promise<any>) | undefined => {
|
|
1602
|
-
if (prop in methodMap) {
|
|
1603
|
-
const methodName = prop as keyof QuickbaseClient;
|
|
1604
|
-
return (params: Parameters<QuickbaseClient[typeof methodName]>[0]) => {
|
|
1605
|
-
if (debug) console.log(`Proxy called ${methodName} with:`, params);
|
|
1606
|
-
return invokeMethod(methodName, params);
|
|
1607
|
-
};
|
|
1608
|
-
}
|
|
1609
|
-
console.warn(`Method ${prop} not found in methodMap`);
|
|
1610
|
-
return undefined;
|
|
1611
|
-
},
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
- name: specs
|
|
1615
|
-
type: directory
|
|
1616
|
-
children:
|
|
1617
|
-
- name: QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json
|
|
1618
|
-
type: file
|
|
1619
|
-
- name: tokenCache.ts
|
|
1620
|
-
type: file
|
|
1621
|
-
contents: |
|
|
1622
|
-
interface CachedToken {
|
|
1623
|
-
token: string;
|
|
1624
|
-
expiresAt: number; // Timestamp in milliseconds
|
|
1625
|
-
}
|
|
1626
|
-
|
|
1627
|
-
export class TokenCache {
|
|
1628
|
-
private cache: Map<string, CachedToken>;
|
|
1629
|
-
private readonly lifespan: number; // Token lifespan in milliseconds
|
|
1630
|
-
|
|
1631
|
-
constructor(lifespan: number = 4 * 60 * 1000 + 50 * 1000) {
|
|
1632
|
-
// Default 4:50
|
|
1633
|
-
this.cache = new Map<string, CachedToken>();
|
|
1634
|
-
this.lifespan = lifespan;
|
|
1635
|
-
}
|
|
1636
|
-
|
|
1637
|
-
get(dbid: string): string | undefined {
|
|
1638
|
-
const entry = this.cache.get(dbid);
|
|
1639
|
-
const now = Date.now();
|
|
1640
|
-
if (entry && entry.expiresAt > now) {
|
|
1641
|
-
return entry.token;
|
|
1642
|
-
}
|
|
1643
|
-
return undefined; // Expired or not found
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
// New method to get full entry
|
|
1647
|
-
getEntry(dbid: string): CachedToken | undefined {
|
|
1648
|
-
const entry = this.cache.get(dbid);
|
|
1649
|
-
const now = Date.now();
|
|
1650
|
-
if (entry && entry.expiresAt > now) {
|
|
1651
|
-
return entry;
|
|
1652
|
-
}
|
|
1653
|
-
return undefined; // Expired or not found
|
|
1654
|
-
}
|
|
1655
|
-
|
|
1656
|
-
set(dbid: string, token: string): void {
|
|
1657
|
-
const now = Date.now();
|
|
1658
|
-
this.cache.set(dbid, {
|
|
1659
|
-
token,
|
|
1660
|
-
expiresAt: now + this.lifespan,
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
dump(): [string, CachedToken][] {
|
|
1665
|
-
return Array.from(this.cache.entries());
|
|
1666
|
-
}
|
|
1667
|
-
|
|
1668
|
-
// New method to clear the cache
|
|
1669
|
-
clear(): void {
|
|
1670
|
-
this.cache.clear();
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
- name: utils.ts
|
|
1674
|
-
type: file
|
|
1675
|
-
- name: test-artifacts
|
|
1676
|
-
type: directory
|
|
1677
|
-
children:
|
|
1678
|
-
- name: .last-run.json
|
|
1679
|
-
type: file
|
|
1680
|
-
- name: test-results.json
|
|
1681
|
-
type: file
|
|
1682
|
-
- name: test-results.json
|
|
1683
|
-
type: file
|
|
1684
|
-
- name: tests
|
|
1685
|
-
type: directory
|
|
1686
|
-
children:
|
|
1687
|
-
- name: playwright
|
|
1688
|
-
type: directory
|
|
1689
|
-
children:
|
|
1690
|
-
- name: qb
|
|
1691
|
-
type: directory
|
|
1692
|
-
children:
|
|
1693
|
-
- name: auth
|
|
1694
|
-
type: directory
|
|
1695
|
-
children:
|
|
1696
|
-
- name: cacheSwitching.test.ts
|
|
1697
|
-
type: file
|
|
1698
|
-
- name: fetchTempToken401.test.ts
|
|
1699
|
-
type: file
|
|
1700
|
-
- name: getAppWithUserToken.test.ts
|
|
1701
|
-
type: file
|
|
1702
|
-
- name: getTempToken.test.ts
|
|
1703
|
-
type: file
|
|
1704
|
-
- name: tokenPrefetch.test.ts
|
|
1705
|
-
type: file
|
|
1706
|
-
- name: tokenRenewal.test.ts
|
|
1707
|
-
type: file
|
|
1708
|
-
- name: setup.ts
|
|
1709
|
-
type: file
|
|
1710
|
-
- name: vitest
|
|
1711
|
-
type: directory
|
|
1712
|
-
children:
|
|
1713
|
-
- name: qb
|
|
1714
|
-
type: directory
|
|
1715
|
-
children:
|
|
1716
|
-
- name: apps
|
|
1717
|
-
type: directory
|
|
1718
|
-
children:
|
|
1719
|
-
- name: getApp.test.ts
|
|
1720
|
-
type: file
|
|
1721
|
-
- name: fields
|
|
1722
|
-
type: directory
|
|
1723
|
-
children:
|
|
1724
|
-
- name: getFields.test.ts
|
|
1725
|
-
type: file
|
|
1726
|
-
- name: index.ts
|
|
1727
|
-
type: file
|
|
1728
|
-
- name: tables
|
|
1729
|
-
type: directory
|
|
1730
|
-
children:
|
|
1731
|
-
- name: getAppTables.test.ts
|
|
1732
|
-
type: file
|
|
1733
|
-
- name: unit
|
|
1734
|
-
type: directory
|
|
1735
|
-
children:
|
|
1736
|
-
- name: apps
|
|
1737
|
-
type: directory
|
|
1738
|
-
children:
|
|
1739
|
-
- name: getApp.test.ts
|
|
1740
|
-
type: file
|
|
1741
|
-
- name: auth
|
|
1742
|
-
type: directory
|
|
1743
|
-
children:
|
|
1744
|
-
- name: fetchTempToken401.test.ts
|
|
1745
|
-
type: file
|
|
1746
|
-
- name: getTempToken.test.ts
|
|
1747
|
-
type: file
|
|
1748
|
-
- name: retryOn401.test.ts
|
|
1749
|
-
type: file
|
|
1750
|
-
- name: two401s.test.ts
|
|
1751
|
-
type: file
|
|
1752
|
-
- name: fields
|
|
1753
|
-
type: directory
|
|
1754
|
-
children:
|
|
1755
|
-
- name: getFields.test.ts
|
|
1756
|
-
type: file
|
|
1757
|
-
- name: index.ts
|
|
1758
|
-
type: file
|
|
1759
|
-
- name: tables
|
|
1760
|
-
type: directory
|
|
1761
|
-
children:
|
|
1762
|
-
- name: getAppTables.test.ts
|
|
1763
|
-
type: file
|
|
1764
|
-
- name: text.txt
|
|
1765
|
-
type: file
|
|
1766
|
-
- name: tsconfig.json
|
|
1767
|
-
type: file
|
|
1768
|
-
contents: |-
|
|
1769
|
-
{
|
|
1770
|
-
"compilerOptions": {
|
|
1771
|
-
"target": "ESNext",
|
|
1772
|
-
"module": "ESNext", // Changed from NodeNext to match bundler
|
|
1773
|
-
"moduleResolution": "bundler",
|
|
1774
|
-
"strict": true,
|
|
1775
|
-
"lib": ["ESNext", "DOM"],
|
|
1776
|
-
"types": ["node", "playwright/test"],
|
|
1777
|
-
"esModuleInterop": true,
|
|
1778
|
-
"resolveJsonModule": true,
|
|
1779
|
-
"allowImportingTsExtensions": true,
|
|
1780
|
-
"noEmit": true,
|
|
1781
|
-
"outDir": "dist",
|
|
1782
|
-
"rootDir": ".", // Includes src/ and tests/
|
|
1783
|
-
"declaration": true,
|
|
1784
|
-
"sourceMap": true,
|
|
1785
|
-
"baseUrl": ".", // Added to set the root for path resolution
|
|
1786
|
-
"paths": {
|
|
1787
|
-
"@/*": ["./*"] // Maps '@/*' to root directory
|
|
1788
|
-
}
|
|
1789
|
-
},
|
|
1790
|
-
"include": ["src/**/*", "tests/**/*"],
|
|
1791
|
-
"exclude": ["node_modules", "dist", "src/generated-old"],
|
|
1792
|
-
"ts-node": {
|
|
1793
|
-
"esm": true,
|
|
1794
|
-
"transpileOnly": true
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
- name: vitest.config.ts
|
|
1798
|
-
type: file
|
|
1799
|
-
contents: |
|
|
1800
|
-
import { defineConfig } from "vitest/config";
|
|
1801
|
-
import path from "path";
|
|
1802
|
-
|
|
1803
|
-
export default defineConfig({
|
|
1804
|
-
test: {
|
|
1805
|
-
environment: "jsdom",
|
|
1806
|
-
include: ["tests/vitest/**/**/*.test.ts"],
|
|
1807
|
-
setupFiles: ["tests/setup.ts"],
|
|
1808
|
-
pool: "forks",
|
|
1809
|
-
poolOptions: {
|
|
1810
|
-
forks: {
|
|
1811
|
-
singleFork: true, // Run tests sequentially
|
|
1812
|
-
},
|
|
1813
|
-
},
|
|
1814
|
-
},
|
|
1815
|
-
resolve: {
|
|
1816
|
-
alias: {
|
|
1817
|
-
"@": path.resolve(__dirname, "./"),
|
|
1818
|
-
},
|
|
1819
|
-
},
|
|
1820
|
-
});
|