quickbase-js 1.0.1-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 +7 -0
- package/LICENSE +21 -0
- package/README.md +102 -0
- package/build.js +66 -0
- package/dist/quickbaseClient.d.ts +3888 -0
- package/dist/quickbaseClient.js +5812 -0
- package/dist/quickbaseClient.js.map +1 -0
- package/examples/react.js +22 -0
- package/examples/vanillajs.html +23 -0
- package/logGen.yaml +1820 -0
- package/package.json +72 -0
- package/playwright.config.ts +37 -0
- package/rollup.config.js +70 -0
- package/src/code-generation/fix-spec-definitions.ts +136 -0
- package/src/code-generation/fix-spec-main.ts +176 -0
- package/src/code-generation/fix-spec-paths.ts +163 -0
- package/src/code-generation/generate-unified-interface.ts +152 -0
- package/src/code-generation/output/quickbase-fixed.json +8435 -0
- package/src/code-generation/regenerate-client.ts +110 -0
- package/src/generated-unified/QuickbaseClient.ts +59 -0
- package/src/index.ts +1 -0
- package/src/log-gen.ts +122 -0
- package/src/quickbaseClient.ts +320 -0
- package/src/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
- package/src/tokenCache.ts +51 -0
- package/src/utils.ts +5 -0
- package/test-artifacts/.last-run.json +4 -0
- package/test-artifacts/test-results.json +1801 -0
- package/test-results.json +2050 -0
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +189 -0
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +217 -0
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +151 -0
- package/tests/playwright/qb/auth/getTempToken.test.ts +180 -0
- package/tests/playwright/qb/auth/tokenPrefetch.test.ts +192 -0
- package/tests/playwright/qb/auth/tokenRenewal.test.ts +110 -0
- package/tests/setup.ts +19 -0
- package/tests/vitest/qb/apps/getApp.test.ts +46 -0
- package/tests/vitest/qb/fields/getFields.test.ts +234 -0
- package/tests/vitest/qb/index.ts +4 -0
- package/tests/vitest/qb/tables/getAppTables.test.ts +67 -0
- package/tests/vitest/unit/apps/getApp.test.ts +81 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +59 -0
- package/tests/vitest/unit/auth/getTempToken.test.ts +83 -0
- package/tests/vitest/unit/auth/retryOn401.test.ts +72 -0
- package/tests/vitest/unit/auth/two401s.test.ts +77 -0
- package/tests/vitest/unit/fields/getFields.test.ts +115 -0
- package/tests/vitest/unit/index.ts +4 -0
- package/tests/vitest/unit/tables/getAppTables.test.ts +72 -0
- package/text.txt +104 -0
- package/tsconfig.json +28 -0
- package/versionrc.json +9 -0
- package/vitest.config.ts +21 -0
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "quickbase-js",
|
|
3
|
+
"main": "dist/quickbaseClient.js",
|
|
4
|
+
"types": "dist/quickbaseClient.d.ts",
|
|
5
|
+
"version": "1.0.1-beta.0",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"description": "QuickBase JavaScript client for frameworks and vanilla JS",
|
|
10
|
+
"module": "dist/quickbaseClient.js",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/quickbaseClient.js",
|
|
15
|
+
"types": "./dist/quickbaseClient.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"release": "standard-version",
|
|
20
|
+
"release:beta": "standard-version --prerelease beta",
|
|
21
|
+
"release:alpha": "standard-version --prerelease alpha",
|
|
22
|
+
"fix-spec": "node --loader ts-node/esm src/code-generation/fix-spec-main.ts",
|
|
23
|
+
"regenerate": "node --loader ts-node/esm src/code-generation/regenerate-client.ts",
|
|
24
|
+
"generate-unified": "node --loader ts-node/esm src/code-generation/generate-unified-interface.ts",
|
|
25
|
+
"log-gen": "node --loader ts-node/esm src/log-gen.ts",
|
|
26
|
+
"build": "rimraf dist && rollup -c",
|
|
27
|
+
"build:prod": "rm -rf dist/* && NODE_ENV=production rollup -c",
|
|
28
|
+
"test": "vitest run tests/vitest/**/**/*.test.ts",
|
|
29
|
+
"test:pw:qb:all": "playwright test tests/playwright/qb --config playwright.config.ts",
|
|
30
|
+
"test:vitest:qb": "vitest run tests/vitest/qb/**/*.test.ts",
|
|
31
|
+
"test:vitest:qb:apps": "vitest run tests/vitest/qb/apps/**/*.test.ts",
|
|
32
|
+
"test:vitest:qb:auth": "vitest run tests/vitest/qb/auth/**/*.test.ts",
|
|
33
|
+
"test:vitest:qb:fields": "vitest run tests/vitest/qb/fields/**/*.test.ts",
|
|
34
|
+
"test:vitest:qb:tables": "vitest run tests/vitest/qb/tables/**/*.test.ts",
|
|
35
|
+
"test:pw:qb:auth": "playwright test tests/playwright/qb/auth",
|
|
36
|
+
"test::pw:qb:auth:temptoken": "playwright test tests/playwright/qb/auth/getTempToken.test.ts",
|
|
37
|
+
"test:pw:qb:auth:renewal": "playwright test tests/playwright/qb/auth/tokenRenewal.test.ts",
|
|
38
|
+
"test:pw:qb:auth:cache": "playwright test tests/playwright/qb/auth/cacheSwitching.test.ts",
|
|
39
|
+
"test:pw:qb:auth:temp401": "playwright test tests/playwright/qb/auth/fetchTempToken401.test.ts",
|
|
40
|
+
"test:pw:qb:auth:prefetch": "playwright test tests/playwright/qb/auth/tokenPrefetch.test.ts",
|
|
41
|
+
"test:pw:qb:auth:debug": "playwright test tests/playwright/qb/auth --headed --debug",
|
|
42
|
+
"test:pw:qb:auth:temp:debug": "playwright test tests/playwright/qb/auth/getTempToken.test.ts --headed --debug",
|
|
43
|
+
"test:pw:qb:auth:renewal:debug": "playwright test tests/playwright/qb/auth/tokenRenewal.test.ts --headed --debug --config=playwright.config.ts",
|
|
44
|
+
"test:pw:qb:auth:cache:debug": "playwright test tests/playwright/qb/auth/cacheSwitching.test.ts --headed --debug"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"typescript": "^5.8.2"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@openapitools/openapi-generator-cli": "^2.17.0",
|
|
51
|
+
"@playwright/test": "^1.51.0",
|
|
52
|
+
"@rollup/plugin-commonjs": "^28.0.3",
|
|
53
|
+
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
54
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
55
|
+
"@rollup/plugin-typescript": "^12.1.2",
|
|
56
|
+
"@types/node": "^22.13.10",
|
|
57
|
+
"dotenv": "^16.4.7",
|
|
58
|
+
"glob": "^11.0.1",
|
|
59
|
+
"js-yaml": "^4.1.0",
|
|
60
|
+
"jsdom": "^26.0.0",
|
|
61
|
+
"node-fetch": "^3.3.2",
|
|
62
|
+
"openapi-types": "^12.1.3",
|
|
63
|
+
"rimraf": "^5.0.10",
|
|
64
|
+
"rollup": "^4.35.0",
|
|
65
|
+
"rollup-plugin-dts": "^6.1.1",
|
|
66
|
+
"standard-version": "^9.5.0",
|
|
67
|
+
"ts-morph": "^25.0.1",
|
|
68
|
+
"ts-node": "^10.9.2",
|
|
69
|
+
"tslib": "^2.8.1",
|
|
70
|
+
"vitest": "^3.0.8"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// playwright.config.ts
|
|
2
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
3
|
+
|
|
4
|
+
const config = defineConfig({
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
workers: undefined, // Default: half of CPU cores (e.g., 2 on a 4-core machine)
|
|
7
|
+
// workers: 2, // Or explicitly set to 2 for controlled testing
|
|
8
|
+
testDir: "./tests/playwright",
|
|
9
|
+
timeout: 600000, // 10 minutes
|
|
10
|
+
retries: 2,
|
|
11
|
+
reporter: [
|
|
12
|
+
["list"],
|
|
13
|
+
["json", { outputFile: "test-artifacts/test-results.json" }],
|
|
14
|
+
],
|
|
15
|
+
use: {
|
|
16
|
+
baseURL: "https://api.quickbase.com",
|
|
17
|
+
headless: true,
|
|
18
|
+
viewport: { width: 1280, height: 720 },
|
|
19
|
+
actionTimeout: 10000,
|
|
20
|
+
screenshot: "off",
|
|
21
|
+
},
|
|
22
|
+
outputDir: "test-artifacts",
|
|
23
|
+
projects: [
|
|
24
|
+
{
|
|
25
|
+
name: "chromium",
|
|
26
|
+
use: { ...devices["Desktop Chrome"] },
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(
|
|
32
|
+
`Loading Playwright config with timeout: ${config.timeout}ms, workers: ${
|
|
33
|
+
config.workers || "default (half CPU cores)"
|
|
34
|
+
}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
export default config;
|
package/rollup.config.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import typescript from "@rollup/plugin-typescript";
|
|
2
|
+
import terser from "@rollup/plugin-terser";
|
|
3
|
+
import nodeResolve from "@rollup/plugin-node-resolve";
|
|
4
|
+
import commonjs from "@rollup/plugin-commonjs";
|
|
5
|
+
import dts from "rollup-plugin-dts";
|
|
6
|
+
|
|
7
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
8
|
+
|
|
9
|
+
export default [
|
|
10
|
+
{
|
|
11
|
+
input: "src/quickbaseClient.ts",
|
|
12
|
+
output: {
|
|
13
|
+
dir: "dist",
|
|
14
|
+
format: "esm",
|
|
15
|
+
sourcemap: true,
|
|
16
|
+
compact: isProd,
|
|
17
|
+
entryFileNames: "quickbaseClient.js",
|
|
18
|
+
},
|
|
19
|
+
external: [
|
|
20
|
+
"node:http",
|
|
21
|
+
"node:https",
|
|
22
|
+
"node:zlib",
|
|
23
|
+
"node:stream",
|
|
24
|
+
"node:buffer",
|
|
25
|
+
"node:util",
|
|
26
|
+
"node:url",
|
|
27
|
+
"node:net",
|
|
28
|
+
"node:fs",
|
|
29
|
+
"node:path",
|
|
30
|
+
"node-fetch",
|
|
31
|
+
],
|
|
32
|
+
plugins: [
|
|
33
|
+
nodeResolve({ preferBuiltins: true }),
|
|
34
|
+
commonjs(),
|
|
35
|
+
typescript({
|
|
36
|
+
tsconfig: "./tsconfig.json",
|
|
37
|
+
declaration: false, // No .d.ts here, handled by dts plugin
|
|
38
|
+
}),
|
|
39
|
+
isProd && terser(),
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
input: "src/quickbaseClient.ts",
|
|
44
|
+
output: {
|
|
45
|
+
file: "dist/quickbaseClient.d.ts",
|
|
46
|
+
format: "esm",
|
|
47
|
+
// Optional: sourcemap for .d.ts if needed for debugging
|
|
48
|
+
// sourcemap: true,
|
|
49
|
+
},
|
|
50
|
+
plugins: [
|
|
51
|
+
dts({
|
|
52
|
+
tsconfig: "./tsconfig.json",
|
|
53
|
+
// Ensure all referenced files are included
|
|
54
|
+
respectExternal: true,
|
|
55
|
+
}),
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
].map((config) => ({
|
|
59
|
+
...config,
|
|
60
|
+
onwarn(warning, warn) {
|
|
61
|
+
if (
|
|
62
|
+
warning.code === "PLUGIN_WARNING" &&
|
|
63
|
+
warning.plugin === "typescript" &&
|
|
64
|
+
warning.message.includes("TS5096")
|
|
65
|
+
) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
warn(warning);
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export const definitions = {
|
|
2
|
+
App: {
|
|
3
|
+
type: "object",
|
|
4
|
+
properties: {
|
|
5
|
+
id: { type: "string" },
|
|
6
|
+
name: { type: "string" },
|
|
7
|
+
created: { type: "string", format: "date-time" },
|
|
8
|
+
updated: { type: "string", format: "date-time" },
|
|
9
|
+
description: { type: "string" },
|
|
10
|
+
timeZone: { type: "string" },
|
|
11
|
+
dateFormat: { type: "string" },
|
|
12
|
+
hasEveryoneOnTheInternet: { type: "boolean" },
|
|
13
|
+
memoryInfo: {
|
|
14
|
+
type: "object",
|
|
15
|
+
properties: {
|
|
16
|
+
estMemory: { type: "number" },
|
|
17
|
+
estMemoryInclDependentApps: { type: "number" },
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
securityProperties: {
|
|
21
|
+
type: "object",
|
|
22
|
+
properties: {
|
|
23
|
+
allowClone: { type: "boolean" },
|
|
24
|
+
allowExport: { type: "boolean" },
|
|
25
|
+
enableAppTokens: { type: "boolean" },
|
|
26
|
+
hideFromPublic: { type: "boolean" },
|
|
27
|
+
mustBeRealmApproved: { type: "boolean" },
|
|
28
|
+
useIPFilter: { type: "boolean" },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
required: ["id", "name"],
|
|
33
|
+
},
|
|
34
|
+
Field: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
id: { type: "number" },
|
|
38
|
+
label: { type: "string" },
|
|
39
|
+
fieldType: { type: "string" },
|
|
40
|
+
noWrap: { type: "boolean" },
|
|
41
|
+
bold: { type: "boolean" },
|
|
42
|
+
required: { type: "boolean" },
|
|
43
|
+
appearsByDefault: { type: "boolean" },
|
|
44
|
+
findEnabled: { type: "boolean" },
|
|
45
|
+
unique: { type: "boolean" },
|
|
46
|
+
doesDataCopy: { type: "boolean" },
|
|
47
|
+
fieldHelp: { type: "string" },
|
|
48
|
+
audited: { type: "boolean" },
|
|
49
|
+
properties: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
primaryKey: { type: "boolean" },
|
|
53
|
+
foreignKey: { type: "boolean" },
|
|
54
|
+
numLines: { type: "number" },
|
|
55
|
+
maxLength: { type: "number" },
|
|
56
|
+
appendOnly: { type: "boolean" },
|
|
57
|
+
allowHTML: { type: "boolean" },
|
|
58
|
+
allowMentions: { type: "boolean" },
|
|
59
|
+
sortAsGiven: { type: "boolean" },
|
|
60
|
+
carryChoices: { type: "boolean" },
|
|
61
|
+
allowNewChoices: { type: "boolean" },
|
|
62
|
+
formula: { type: "string" },
|
|
63
|
+
defaultValue: { type: "string" },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
permissions: {
|
|
67
|
+
type: "array",
|
|
68
|
+
items: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
permissionType: { type: "string" },
|
|
72
|
+
role: { type: "string" },
|
|
73
|
+
roleId: { type: "number" },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["id", "label", "fieldType"],
|
|
79
|
+
},
|
|
80
|
+
Table: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
id: { type: "string" },
|
|
84
|
+
name: { type: "string" },
|
|
85
|
+
alias: { type: "string" },
|
|
86
|
+
created: { type: "string", format: "date-time" },
|
|
87
|
+
defaultSortFieldId: { type: "number" },
|
|
88
|
+
defaultSortOrder: { type: "string" },
|
|
89
|
+
description: { type: "string" },
|
|
90
|
+
keyFieldId: { type: "number" },
|
|
91
|
+
nextFieldId: { type: "number" },
|
|
92
|
+
nextRecordId: { type: "number" },
|
|
93
|
+
pluralRecordName: { type: "string" },
|
|
94
|
+
singleRecordName: { type: "string" },
|
|
95
|
+
sizeLimit: { type: "string" },
|
|
96
|
+
spaceRemaining: { type: "string" },
|
|
97
|
+
spaceUsed: { type: "string" },
|
|
98
|
+
updated: { type: "string", format: "date-time" },
|
|
99
|
+
},
|
|
100
|
+
required: ["id", "name"],
|
|
101
|
+
},
|
|
102
|
+
Record: {
|
|
103
|
+
type: "object",
|
|
104
|
+
properties: {
|
|
105
|
+
id: { type: "number" },
|
|
106
|
+
fields: {
|
|
107
|
+
type: "object",
|
|
108
|
+
additionalProperties: { type: "string" },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ["id"],
|
|
112
|
+
},
|
|
113
|
+
Upsert200Response: {
|
|
114
|
+
type: "object",
|
|
115
|
+
properties: {
|
|
116
|
+
id: { type: "number" },
|
|
117
|
+
status: { type: "string" },
|
|
118
|
+
},
|
|
119
|
+
required: ["id"],
|
|
120
|
+
},
|
|
121
|
+
ReportRunResponse: {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
id: { type: "string" },
|
|
125
|
+
data: { type: "object" }, // Placeholder
|
|
126
|
+
},
|
|
127
|
+
required: ["id"],
|
|
128
|
+
},
|
|
129
|
+
GetTempTokenDBID200Response: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
temporaryAuthorization: { type: "string" },
|
|
133
|
+
},
|
|
134
|
+
required: ["temporaryAuthorization"],
|
|
135
|
+
},
|
|
136
|
+
};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { promises as fs } from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import * as glob from "glob";
|
|
6
|
+
import { paths } from "./fix-spec-paths.ts";
|
|
7
|
+
import { definitions } from "./fix-spec-definitions.ts";
|
|
8
|
+
|
|
9
|
+
interface Parameter {
|
|
10
|
+
name: string;
|
|
11
|
+
in: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
type?: string;
|
|
14
|
+
schema?: { type?: string; items?: any; $ref?: string; properties?: any };
|
|
15
|
+
example?: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Operation {
|
|
19
|
+
parameters?: Parameter[];
|
|
20
|
+
responses?: Record<string, { description: string; schema?: any }>;
|
|
21
|
+
operationId?: string;
|
|
22
|
+
summary?: string;
|
|
23
|
+
tags?: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Spec {
|
|
27
|
+
paths: Record<string, Record<string, Operation>>;
|
|
28
|
+
definitions?: Record<string, any>;
|
|
29
|
+
operations?: any;
|
|
30
|
+
groups?: any;
|
|
31
|
+
components?: any;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function toCamelCase(str: string): string {
|
|
35
|
+
return str
|
|
36
|
+
.replace(/[-_](.)/g, (_, char) => char.toUpperCase())
|
|
37
|
+
.replace(/^./, (str) => str.toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function fixArraySchemas(spec: Spec) {
|
|
41
|
+
for (const pathKey in spec.paths) {
|
|
42
|
+
for (const method in spec.paths[pathKey]) {
|
|
43
|
+
const operation = spec.paths[pathKey][method];
|
|
44
|
+
if (operation.parameters) {
|
|
45
|
+
operation.parameters.forEach((param: Parameter) => {
|
|
46
|
+
if (param.schema) {
|
|
47
|
+
if (param.schema.type === "array" && !param.schema.items) {
|
|
48
|
+
console.log(
|
|
49
|
+
`Fixing missing items in ${pathKey}(${method}).${param.name}`
|
|
50
|
+
);
|
|
51
|
+
param.schema.items =
|
|
52
|
+
pathKey === "/records" && param.name === "generated"
|
|
53
|
+
? { $ref: "#/definitions/Record" }
|
|
54
|
+
: { type: "string" };
|
|
55
|
+
}
|
|
56
|
+
if (param.schema.properties) {
|
|
57
|
+
for (const propKey in param.schema.properties) {
|
|
58
|
+
const prop = param.schema.properties[propKey];
|
|
59
|
+
if (prop.type === "array" && !prop.items) {
|
|
60
|
+
console.log(
|
|
61
|
+
`Fixing nested array in ${pathKey}(${method}).${param.name}.${propKey}`
|
|
62
|
+
);
|
|
63
|
+
prop.items =
|
|
64
|
+
propKey === "data" && pathKey === "/records"
|
|
65
|
+
? { $ref: "#/definitions/Record" }
|
|
66
|
+
: { type: "string" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
if (operation.responses) {
|
|
74
|
+
for (const status in operation.responses) {
|
|
75
|
+
const response = operation.responses[status];
|
|
76
|
+
if (response.schema) {
|
|
77
|
+
if (response.schema.type === "array" && !response.schema.items) {
|
|
78
|
+
console.log(
|
|
79
|
+
`Fixing missing items in ${pathKey}(${method}).responses.${status}`
|
|
80
|
+
);
|
|
81
|
+
response.schema.items =
|
|
82
|
+
pathKey === "/records"
|
|
83
|
+
? { $ref: "#/definitions/Upsert200Response" }
|
|
84
|
+
: { type: "string" };
|
|
85
|
+
}
|
|
86
|
+
if (response.schema.properties) {
|
|
87
|
+
for (const propKey in response.schema.properties) {
|
|
88
|
+
const prop = response.schema.properties[propKey];
|
|
89
|
+
if (prop.type === "array" && !prop.items) {
|
|
90
|
+
console.log(
|
|
91
|
+
`Fixing nested array in ${pathKey}(${method}).responses.${status}.${propKey}`
|
|
92
|
+
);
|
|
93
|
+
prop.items =
|
|
94
|
+
propKey === "data" && pathKey === "/records"
|
|
95
|
+
? { $ref: "#/definitions/Upsert200Response" }
|
|
96
|
+
: { type: "string" };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function fixQuickBaseSpec(): Promise<void> {
|
|
108
|
+
try {
|
|
109
|
+
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
110
|
+
const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
|
|
111
|
+
const OUTPUT_DIR = path.join(CODEGEN_DIR, "output");
|
|
112
|
+
console.log("Finding latest QuickBase RESTful API spec...");
|
|
113
|
+
const specFiles = glob.sync(
|
|
114
|
+
path.join(SPECS_DIR, "QuickBase_RESTful_*.json")
|
|
115
|
+
);
|
|
116
|
+
if (specFiles.length === 0) {
|
|
117
|
+
console.error(
|
|
118
|
+
"No QuickBase_RESTful_*.json files found in specs/ folder."
|
|
119
|
+
);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const inputFile = specFiles.sort().pop() as string;
|
|
123
|
+
const outputFile = path.join(OUTPUT_DIR, "quickbase-fixed.json");
|
|
124
|
+
|
|
125
|
+
console.log(`Reading ${path.basename(inputFile)} from specs/...`);
|
|
126
|
+
const specContent = await fs.readFile(inputFile, "utf8");
|
|
127
|
+
const spec: Spec = JSON.parse(specContent);
|
|
128
|
+
|
|
129
|
+
console.log("Fixing parameters...");
|
|
130
|
+
for (const pathKey in spec.paths) {
|
|
131
|
+
for (const method in spec.paths[pathKey]) {
|
|
132
|
+
const operation = spec.paths[pathKey][method];
|
|
133
|
+
if (operation.parameters) {
|
|
134
|
+
operation.parameters = operation.parameters
|
|
135
|
+
.filter(
|
|
136
|
+
(param) =>
|
|
137
|
+
!["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
|
|
138
|
+
param.name
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
.map((param: Parameter) => {
|
|
142
|
+
param.name = toCamelCase(param.name);
|
|
143
|
+
if ("example" in param) delete param.example;
|
|
144
|
+
if ("schema" in param && param.in !== "body") delete param.schema;
|
|
145
|
+
if (!param.type && param.in !== "body") param.type = "string";
|
|
146
|
+
return param;
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log("Applying endpoint fixes...");
|
|
153
|
+
spec.paths = { ...spec.paths, ...paths }; // Merge original and custom paths
|
|
154
|
+
|
|
155
|
+
console.log("Fixing array schemas...");
|
|
156
|
+
fixArraySchemas(spec);
|
|
157
|
+
|
|
158
|
+
console.log("Applying definitions...");
|
|
159
|
+
spec.definitions = { ...spec.definitions, ...definitions }; // Merge definitions
|
|
160
|
+
|
|
161
|
+
console.log("Removing unexpected top-level attributes...");
|
|
162
|
+
delete spec.operations;
|
|
163
|
+
delete spec.groups;
|
|
164
|
+
delete spec.components;
|
|
165
|
+
|
|
166
|
+
console.log(`Writing fixed spec to ${path.basename(outputFile)}...`);
|
|
167
|
+
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
168
|
+
await fs.writeFile(outputFile, JSON.stringify(spec, null, 2), "utf8");
|
|
169
|
+
console.log("Spec fixed successfully!");
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error("Failed to fix spec:", error);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fixQuickBaseSpec();
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// src/code-generation/fix-spec-paths.ts
|
|
2
|
+
export const paths = {
|
|
3
|
+
"/apps/{appId}": {
|
|
4
|
+
get: {
|
|
5
|
+
operationId: "getApp",
|
|
6
|
+
summary: "Get an app by ID",
|
|
7
|
+
tags: ["Apps"],
|
|
8
|
+
parameters: [
|
|
9
|
+
{
|
|
10
|
+
name: "appId",
|
|
11
|
+
in: "path",
|
|
12
|
+
required: true,
|
|
13
|
+
type: "string",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
responses: {
|
|
17
|
+
200: {
|
|
18
|
+
description: "Success",
|
|
19
|
+
schema: { $ref: "#/definitions/App" },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
"/fields": {
|
|
25
|
+
get: {
|
|
26
|
+
operationId: "getFields",
|
|
27
|
+
summary: "Get all fields for a table",
|
|
28
|
+
tags: ["Fields"],
|
|
29
|
+
parameters: [
|
|
30
|
+
{
|
|
31
|
+
name: "tableId",
|
|
32
|
+
in: "query",
|
|
33
|
+
required: true,
|
|
34
|
+
type: "string",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "includeFieldPerms",
|
|
38
|
+
in: "query",
|
|
39
|
+
required: false,
|
|
40
|
+
type: "boolean",
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
responses: {
|
|
44
|
+
200: {
|
|
45
|
+
description: "Success",
|
|
46
|
+
schema: {
|
|
47
|
+
type: "array",
|
|
48
|
+
items: { $ref: "#/definitions/Field" },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
"/tables": {
|
|
55
|
+
get: {
|
|
56
|
+
operationId: "getAppTables",
|
|
57
|
+
summary: "Get all tables for an app",
|
|
58
|
+
tags: ["Tables"],
|
|
59
|
+
parameters: [
|
|
60
|
+
{
|
|
61
|
+
name: "appId",
|
|
62
|
+
in: "query",
|
|
63
|
+
required: true,
|
|
64
|
+
type: "string",
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
responses: {
|
|
68
|
+
200: {
|
|
69
|
+
description: "Success",
|
|
70
|
+
schema: {
|
|
71
|
+
type: "array",
|
|
72
|
+
items: { $ref: "#/definitions/Table" },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
"/records/query": {
|
|
79
|
+
post: {
|
|
80
|
+
operationId: "upsertRecords",
|
|
81
|
+
summary: "Upsert records",
|
|
82
|
+
tags: ["Records"],
|
|
83
|
+
parameters: [
|
|
84
|
+
{
|
|
85
|
+
name: "generated",
|
|
86
|
+
in: "body",
|
|
87
|
+
required: true,
|
|
88
|
+
schema: {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: { $ref: "#/definitions/Record" },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
responses: {
|
|
95
|
+
200: {
|
|
96
|
+
description: "Success",
|
|
97
|
+
schema: {
|
|
98
|
+
type: "array",
|
|
99
|
+
items: { $ref: "#/definitions/Upsert200Response" },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
207: {
|
|
103
|
+
description: "Multi-Status (partial success)",
|
|
104
|
+
schema: {
|
|
105
|
+
type: "array",
|
|
106
|
+
items: { $ref: "#/definitions/Upsert207Response" }, // Corrected from Upsert200Response
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
"/reports/{reportId}/run": {
|
|
113
|
+
post: {
|
|
114
|
+
operationId: "runReport",
|
|
115
|
+
summary: "Run a report",
|
|
116
|
+
tags: ["Reports"],
|
|
117
|
+
parameters: [
|
|
118
|
+
{
|
|
119
|
+
name: "reportId",
|
|
120
|
+
in: "path",
|
|
121
|
+
required: true,
|
|
122
|
+
type: "string",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "generated",
|
|
126
|
+
in: "body",
|
|
127
|
+
required: true,
|
|
128
|
+
schema: { type: "object" },
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
responses: {
|
|
132
|
+
200: {
|
|
133
|
+
description: "Success",
|
|
134
|
+
schema: {
|
|
135
|
+
type: "array",
|
|
136
|
+
items: { $ref: "#/definitions/ReportRunResponse" },
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
"/auth/temporary/{dbid}": {
|
|
143
|
+
get: {
|
|
144
|
+
operationId: "getTempTokenDBID",
|
|
145
|
+
summary: "Get a temporary authorization token for a specific dbid",
|
|
146
|
+
tags: ["Auth"],
|
|
147
|
+
parameters: [
|
|
148
|
+
{
|
|
149
|
+
name: "dbid",
|
|
150
|
+
in: "path",
|
|
151
|
+
required: true,
|
|
152
|
+
type: "string",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
responses: {
|
|
156
|
+
200: {
|
|
157
|
+
description: "Success",
|
|
158
|
+
schema: { $ref: "#/definitions/GetTempTokenDBID200Response" },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
};
|