quickbase-js 0.1.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -0
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/build-common.js +131 -0
- package/build-esm.js +71 -0
- package/build-umd.js +69 -0
- package/dist/esm/quickbase.d.ts +7594 -0
- package/dist/esm/quickbase.js +9752 -0
- package/dist/esm/quickbase.js.map +1 -0
- package/dist/esm/quickbase.min.js +2 -0
- package/dist/esm/quickbase.min.js.map +1 -0
- package/dist/umd/quickbase.umd.js +10502 -0
- package/dist/umd/quickbase.umd.js.map +1 -0
- package/dist/umd/quickbase.umd.min.js +2 -0
- package/dist/umd/quickbase.umd.min.js.map +1 -0
- package/examples/quicbase-jsCDN.html +36 -0
- package/examples/react.js +48 -0
- package/examples/vanillajs.html +47 -0
- package/notes.txt +158 -0
- package/open-api/fix-spec-main.ts +96 -0
- package/open-api/generate-unified-interface.ts +277 -0
- package/open-api/output/override-report.json +58 -0
- package/open-api/output/quickbase-fixed.json +10610 -0
- package/open-api/regenerate-client.ts +116 -0
- package/open-api/schema/enhance-general.ts +100 -0
- package/open-api/schema/fix-arrays.ts +69 -0
- package/open-api/schema/tags/apps.ts +90 -0
- package/open-api/schema/tags/fields.ts +156 -0
- package/open-api/schema/tags/groups.ts +51 -0
- package/open-api/schema/tags/index.ts +68 -0
- package/open-api/schema/tags/records.ts +115 -0
- package/open-api/schema/tags/tables.ts +211 -0
- package/open-api/schema/tags/users.ts +47 -0
- package/open-api/types/spec.ts +41 -0
- package/open-api/utils/infer-schema.ts +40 -0
- package/open-api/utils/naming.ts +10 -0
- package/package.json +91 -0
- package/playwright.config.ts +37 -0
- package/rollup.config.js +114 -0
- package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
- package/specs/split/apps.json +1315 -0
- package/specs/split/auth.json +191 -0
- package/specs/split/fields.json +3869 -0
- package/specs/split/records.json +754 -0
- package/specs/split/reports.json +619 -0
- package/specs/split/tables.json +1605 -0
- package/src/generated-unified/QuickbaseClient.ts +359 -0
- package/src/generated-unified/missing-types-report.json +3 -0
- package/src/index.ts +1 -0
- package/src/quickbaseClient.ts +402 -0
- package/src/tokenCache.ts +51 -0
- package/src/utils.ts +7 -0
- package/test-esm.js +1 -0
- package/test-results.json +2050 -0
- package/test-umd.js +1 -0
- package/test.ts +1 -0
- package/tests/playwright/qb/auth/cacheSwitching.test.ts +159 -0
- package/tests/playwright/qb/auth/fetchTempToken401.test.ts +219 -0
- package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +162 -0
- package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +153 -0
- package/tests/playwright/qb/auth/getTempToken.test.ts +152 -0
- package/tests/playwright/qb/auth/tokenPrefetch.test.ts +179 -0
- package/tests/playwright/qb/auth/tokenRenewal.test.ts +133 -0
- package/tests/setup.ts +51 -0
- package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
- package/tests/vitest/qb/apps/createApp.test.ts +64 -0
- package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
- package/tests/vitest/qb/apps/getApp.test.ts +40 -0
- package/tests/vitest/qb/fields/createField.test.ts +78 -0
- package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
- package/tests/vitest/qb/fields/getField.test.ts +85 -0
- package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
- package/tests/vitest/qb/fields/getFields.test.ts +232 -0
- package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
- package/tests/vitest/qb/index.ts +28 -0
- package/tests/vitest/qb/records/deleteRecords.test.ts +110 -0
- package/tests/vitest/qb/records/runQuery.test.ts +146 -0
- package/tests/vitest/qb/records/upsert.test.ts +102 -0
- package/tests/vitest/qb/tables/createTable.test.ts +55 -0
- package/tests/vitest/qb/tables/deleteTable.test.ts +50 -0
- package/tests/vitest/qb/tables/getAppTables.test.ts +46 -0
- package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
- package/tests/vitest/qb/tables/getTable.test.ts +26 -0
- package/tests/vitest/qb/tables/updateTable.test.ts +48 -0
- package/tests/vitest/unit/apps/copyApp.test.ts +488 -0
- package/tests/vitest/unit/apps/createApp.test.ts +186 -0
- package/tests/vitest/unit/apps/deleteApp.test.ts +216 -0
- package/tests/vitest/unit/apps/getApp.test.ts +128 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +82 -0
- package/tests/vitest/unit/auth/getTempToken.test.ts +95 -0
- package/tests/vitest/unit/auth/retryOn401.test.ts +116 -0
- package/tests/vitest/unit/auth/two401s.test.ts +86 -0
- package/tests/vitest/unit/fields/createField.test.ts +391 -0
- package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
- package/tests/vitest/unit/fields/getField.test.ts +189 -0
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
- package/tests/vitest/unit/fields/getFields.test.ts +122 -0
- package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
- package/tests/vitest/unit/index.ts +33 -0
- package/tests/vitest/unit/records/deleteRecords.test.ts +112 -0
- package/tests/vitest/unit/records/runQuery.test.ts +102 -0
- package/tests/vitest/unit/records/upsert.test.ts +234 -0
- package/tests/vitest/unit/tables/createTable.test.ts +70 -0
- package/tests/vitest/unit/tables/deleteTable.test.ts +77 -0
- package/tests/vitest/unit/tables/getAppTables.test.ts +85 -0
- package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
- package/tests/vitest/unit/tables/getTable.test.ts +233 -0
- package/tests/vitest/unit/tables/updateTable.test.ts +80 -0
- package/tests/vitest/unit/umd.test.ts +8 -0
- package/text.txt +104 -0
- package/tools/generate-types.ts +29 -0
- package/tools/log-gen.ts +231 -0
- package/tools/log-gen.yaml +3826 -0
- package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
- package/tools/openapi-schema-validator.ts +38 -0
- package/tools/spec-analyzer.ts +163 -0
- package/tools/split-spec.ts +148 -0
- package/tools/test-import.ts +3 -0
- package/tools/validate-spec.ts +40 -0
- package/tsconfig.build.json +17 -0
- package/tsconfig.json +29 -0
- package/versionrc.json +9 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<title>Quickbase</title>
|
|
6
|
+
<script src="https://cdn.jsdelivr.net/npm/quickbase-js@1.1.0-beta.6/dist/umd/quickbase.umd.min.js"></script>
|
|
7
|
+
</head>
|
|
8
|
+
|
|
9
|
+
<body>
|
|
10
|
+
<div>
|
|
11
|
+
<h1>Quickbase-js CDN Example</h1>
|
|
12
|
+
<p id="result">Loading...</p>
|
|
13
|
+
</div>
|
|
14
|
+
<script>
|
|
15
|
+
var realm = "xxxxxxxxxxxxxxxxxxxx"; // replace with your realm
|
|
16
|
+
var appId = "xxxxxxxxxxxxxxxxxxxx"; // replace with your app ID
|
|
17
|
+
if (typeof QuickbaseJS === "undefined") {
|
|
18
|
+
document.getElementById("result").textContent = "Error: Failed to load QuickbaseJS library.";
|
|
19
|
+
} else {
|
|
20
|
+
var qbClient = QuickbaseJS.quickbase({
|
|
21
|
+
realm,
|
|
22
|
+
useTempTokens: true
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
qbClient.getApp({ appId })
|
|
26
|
+
.then(function (app) {
|
|
27
|
+
document.getElementById("result").textContent = "App Name: " + app.name;
|
|
28
|
+
})
|
|
29
|
+
.catch(function (err) {
|
|
30
|
+
document.getElementById("result").textContent = "Error: " + (err.message || "Failed to load app");
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
</body>
|
|
35
|
+
|
|
36
|
+
</html>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import { quickbase } from "quickbase-js";
|
|
3
|
+
|
|
4
|
+
const App = () => {
|
|
5
|
+
const [appName, setAppName] = useState < string > "Loading...";
|
|
6
|
+
const [error, setError] = (useState < string) | (null > null);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// Initialize the QuickBase client
|
|
10
|
+
const qb = quickbase({
|
|
11
|
+
realm: "your-realm", // Replace with actual QuickBase realm
|
|
12
|
+
// ## ------------------------------
|
|
13
|
+
// ## Authentication Options
|
|
14
|
+
// ## ------------------------------
|
|
15
|
+
// ## OPTION 1: User Token Authentication
|
|
16
|
+
// ## - Use this if you have a QuickBase user token (get it from "My Profile" > "Manage User Tokens")
|
|
17
|
+
// ## - Works in Node.js or browsers, ideal for standalone apps or testing outside QuickBase
|
|
18
|
+
// ## - Use the line below and replace with your token; comment out 'useTempTokens'
|
|
19
|
+
// userToken: "your-user-token",
|
|
20
|
+
|
|
21
|
+
// ## ------------------------------
|
|
22
|
+
// ## OPTION 2: Temporary Token Authentication
|
|
23
|
+
// ## - Use this for QuickBase code pages, leveraging the browser’s authenticated session
|
|
24
|
+
// ## - No user token needed; requires running in a QuickBase browser context
|
|
25
|
+
// ## - Uncomment the line below and comment out 'userToken' if using this option
|
|
26
|
+
// useTempTokens: true,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Fetch the app
|
|
30
|
+
qb.getApp({ appId: "your-app-id" }) // Replace with actual app ID
|
|
31
|
+
.then((app) => {
|
|
32
|
+
setAppName(app.name);
|
|
33
|
+
})
|
|
34
|
+
.catch((err) => {
|
|
35
|
+
console.error("Error fetching app:", err);
|
|
36
|
+
setError("Failed to load app");
|
|
37
|
+
});
|
|
38
|
+
}, []); // Empty dependency array to run once on mount
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div>
|
|
42
|
+
<h1>Quickbase Test</h1>
|
|
43
|
+
{error ? <p>{error}</p> : <p>App Name: {appName}</p>}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default App;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<title>Quickbase Test</title>
|
|
6
|
+
</head>
|
|
7
|
+
|
|
8
|
+
<body>
|
|
9
|
+
<div>
|
|
10
|
+
<h1>Quickbase Test</h1>
|
|
11
|
+
<p id="result">Loading...</p>
|
|
12
|
+
</div>
|
|
13
|
+
<script type="module">
|
|
14
|
+
import { quickbase } from 'quickbase-js';
|
|
15
|
+
// Initalize the QuickBase client
|
|
16
|
+
const qb = quickbase({
|
|
17
|
+
realm: 'your-realm', // Replace with actual QuickBase realm
|
|
18
|
+
// ## ------------------------------
|
|
19
|
+
// ## Authentication Options
|
|
20
|
+
// ## ----------------------------
|
|
21
|
+
// ## OPTION 1: User Token Authentication
|
|
22
|
+
// ## - Use this if you have a QuickBase user token (get it from "My Profile" > "Manage User Tokens")
|
|
23
|
+
// ## - Works in Node.js or browsers, ideal for standalone apps or testing outside QuickBase
|
|
24
|
+
// ## - Uncomment the line below and replace with your token; comment out 'useTempTokens'
|
|
25
|
+
// userToken: "your-user-token",
|
|
26
|
+
|
|
27
|
+
// ## ----------------------------
|
|
28
|
+
// ## OPTION 2: Temporary Token Authentication
|
|
29
|
+
// ## - Use this for QuickBase code pages, leveraging the browser’s authenticated session
|
|
30
|
+
// ## - No user token needed; requires running in a QuickBase browser context
|
|
31
|
+
// ## - Uncomment the line below and comment out 'userToken' if using this option
|
|
32
|
+
// useTempTokens: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Fetch the app
|
|
36
|
+
qb.getApp({ appId: 'your-app-id' }) // Replace with actual app ID
|
|
37
|
+
.then((app) => {
|
|
38
|
+
document.getElementById('result').textContent = `App Name: ${app.name}`;
|
|
39
|
+
})
|
|
40
|
+
.catch((err) => {
|
|
41
|
+
console.error('Error fetching app:', err);
|
|
42
|
+
document.getElementById('result').textContent = err.message || 'Failed to load app';
|
|
43
|
+
});
|
|
44
|
+
</script>
|
|
45
|
+
</body>
|
|
46
|
+
|
|
47
|
+
</html>
|
package/notes.txt
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
At the core of my challenge is to update the QuickbaseClient.ts file with the correct types signatures, removing "any" types.Right now the way I have been doing that is by adding spec overrides to the raw specs/QuickBase_RESTful_API_*.json file by creating open-api/paths/* and open-api/definitions/* files based on the quickbase documentation that override the raw specs file. I think this has been working okay, its fairly manual, and I feel like I am duplicating a lot of work that exists in the raw specs file. The reason I'm doing this is because the raw specs file has been difficult to work with in the past.Please give me a detailed summary of how the QuickbaseClient.ts is generated,
|
|
2
|
+
|
|
3
|
+
Yes, I understand your concern, and you're correct to point out that enhancing raw spec processing represents a shift from your current strategy of using path/* and definitions/* folder overrides. Let's clarify the relationship between these approaches and why enhancing raw spec processing could be a complementary or alternative strategy.
|
|
4
|
+
Current Strategy (Path/* and Definitions/* Overrides):
|
|
5
|
+
Manual overrides in open-api/paths/* and open-api/definitions/*
|
|
6
|
+
|
|
7
|
+
Directly modifies specific endpoints and types
|
|
8
|
+
|
|
9
|
+
Pros:
|
|
10
|
+
Precise control over problematic endpoints
|
|
11
|
+
|
|
12
|
+
Immediate fixes for known issues
|
|
13
|
+
|
|
14
|
+
Cons:
|
|
15
|
+
Manual and time-consuming
|
|
16
|
+
|
|
17
|
+
Duplicates information from raw spec
|
|
18
|
+
|
|
19
|
+
Doesn't scale well for large APIs
|
|
20
|
+
|
|
21
|
+
Maintenance burden as API evolves
|
|
22
|
+
|
|
23
|
+
Proposed Strategy (Enhanced Raw Spec Processing):
|
|
24
|
+
Focuses on improving the initial processing of QuickBase_RESTful_API_*.json
|
|
25
|
+
|
|
26
|
+
Aims to extract better type information from the raw spec
|
|
27
|
+
|
|
28
|
+
Pros:
|
|
29
|
+
Addresses root causes of "any" types
|
|
30
|
+
|
|
31
|
+
Scales better for large APIs
|
|
32
|
+
|
|
33
|
+
Reduces need for manual overrides
|
|
34
|
+
|
|
35
|
+
More maintainable long-term
|
|
36
|
+
|
|
37
|
+
Cons:
|
|
38
|
+
Requires more upfront development
|
|
39
|
+
|
|
40
|
+
May need validation to ensure accuracy
|
|
41
|
+
|
|
42
|
+
Still might require some manual overrides for edge cases
|
|
43
|
+
|
|
44
|
+
Relationship Between Strategies:
|
|
45
|
+
Complementary Nature:
|
|
46
|
+
Enhanced raw spec processing can reduce the need for manual overrides
|
|
47
|
+
|
|
48
|
+
Overrides can still be used for edge cases or QuickBase-specific quirks
|
|
49
|
+
|
|
50
|
+
Better raw spec processing makes overrides more targeted and efficient
|
|
51
|
+
|
|
52
|
+
Evolution, Not Replacement:
|
|
53
|
+
We're not abandoning the override system entirely
|
|
54
|
+
|
|
55
|
+
Instead, we're adding a new layer of processing to make overrides less necessary
|
|
56
|
+
|
|
57
|
+
The goal is to shift from heavy reliance on manual overrides to automated type extraction
|
|
58
|
+
|
|
59
|
+
Strategic Shift:
|
|
60
|
+
Moving from reactive (fixing issues after they're found) to proactive (preventing issues at the source)
|
|
61
|
+
|
|
62
|
+
Investing in better raw spec processing now will reduce maintenance costs later
|
|
63
|
+
|
|
64
|
+
Allows for more systematic type improvements across the entire API
|
|
65
|
+
|
|
66
|
+
Implementation Plan for Enhanced Raw Spec Processing:
|
|
67
|
+
Spec Analysis Tool:
|
|
68
|
+
Develop a tool to analyze QuickBase_RESTful_API_*.json
|
|
69
|
+
|
|
70
|
+
Identify:
|
|
71
|
+
Endpoints with incomplete type information
|
|
72
|
+
|
|
73
|
+
Patterns leading to "any" types
|
|
74
|
+
|
|
75
|
+
Common schema structures
|
|
76
|
+
|
|
77
|
+
Output a type inventory report
|
|
78
|
+
|
|
79
|
+
Enhanced fix-spec-main.ts:
|
|
80
|
+
Implement partial spec loading for efficiency
|
|
81
|
+
|
|
82
|
+
Add type validation and inference:
|
|
83
|
+
Detect missing schema information
|
|
84
|
+
|
|
85
|
+
Infer types from examples or patterns
|
|
86
|
+
|
|
87
|
+
Flag areas needing manual review
|
|
88
|
+
|
|
89
|
+
Enhance array and object schema processing
|
|
90
|
+
|
|
91
|
+
Type Mapping Improvements:
|
|
92
|
+
Extend mapOpenApiTypeToTs in generate-unified-interface.ts:
|
|
93
|
+
Handle more OpenAPI type variations
|
|
94
|
+
|
|
95
|
+
Add fallback type inference
|
|
96
|
+
|
|
97
|
+
Improve mapRefToType:
|
|
98
|
+
Handle complex schema compositions
|
|
99
|
+
|
|
100
|
+
Better reference resolution
|
|
101
|
+
|
|
102
|
+
Validation and Reporting:
|
|
103
|
+
Add type quality validation in generate-unified-interface.ts
|
|
104
|
+
|
|
105
|
+
Generate reports on:
|
|
106
|
+
"any" type occurrences
|
|
107
|
+
|
|
108
|
+
Missing type information
|
|
109
|
+
|
|
110
|
+
Suggested manual overrides
|
|
111
|
+
|
|
112
|
+
Integration with Existing Overrides:
|
|
113
|
+
Modify fix-spec-main.ts to:
|
|
114
|
+
Apply automated fixes first
|
|
115
|
+
|
|
116
|
+
Then merge path/* and definitions/* overrides
|
|
117
|
+
|
|
118
|
+
Create a report comparing automated vs. manual type definitions
|
|
119
|
+
|
|
120
|
+
Transition Strategy:
|
|
121
|
+
Parallel Development:
|
|
122
|
+
Continue using current overrides while developing new processing
|
|
123
|
+
|
|
124
|
+
Run both systems in parallel for validation
|
|
125
|
+
|
|
126
|
+
Gradual Migration:
|
|
127
|
+
Start with automated processing for new endpoints
|
|
128
|
+
|
|
129
|
+
Gradually reduce manual overrides as confidence grows
|
|
130
|
+
|
|
131
|
+
Maintain overrides for edge cases
|
|
132
|
+
|
|
133
|
+
Validation and Feedback:
|
|
134
|
+
Compare generated types from both approaches
|
|
135
|
+
|
|
136
|
+
Identify areas where overrides are still necessary
|
|
137
|
+
|
|
138
|
+
Refine automated processing based on findings
|
|
139
|
+
|
|
140
|
+
Next Steps:
|
|
141
|
+
Given that this is a strategic shift, I recommend:
|
|
142
|
+
Starting with the spec analysis tool to understand raw spec patterns
|
|
143
|
+
|
|
144
|
+
Implementing basic enhancements to fix-spec-main.ts
|
|
145
|
+
|
|
146
|
+
Running parallel generation (old vs. new) for comparison
|
|
147
|
+
|
|
148
|
+
Gradually reducing reliance on manual overrides
|
|
149
|
+
|
|
150
|
+
Would you like to:
|
|
151
|
+
See a detailed implementation plan for the spec analysis tool?
|
|
152
|
+
|
|
153
|
+
Start with specific enhancements to fix-spec-main.ts?
|
|
154
|
+
|
|
155
|
+
Develop a validation process to compare old vs. new approaches?
|
|
156
|
+
|
|
157
|
+
Discuss how to maintain both strategies during transition?
|
|
158
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import * as glob from "glob";
|
|
7
|
+
import { enhanceTags } from "./schema/tags/index.ts";
|
|
8
|
+
import { enhanceGeneral } from "./schema/enhance-general.ts";
|
|
9
|
+
import { fixArrays } from "./schema/fix-arrays.ts";
|
|
10
|
+
import { Spec } from "./types/spec.ts";
|
|
11
|
+
import { toCamelCase } from "./utils/naming.ts";
|
|
12
|
+
|
|
13
|
+
// Removed FixSpecConfig since applyOverrides is no longer needed
|
|
14
|
+
async function fixQuickBaseSpec(): Promise<void> {
|
|
15
|
+
try {
|
|
16
|
+
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
|
|
18
|
+
const OUTPUT_DIR = path.join(CODEGEN_DIR, "output");
|
|
19
|
+
console.log("Finding latest QuickBase RESTful API spec...");
|
|
20
|
+
const specFiles = glob.sync(
|
|
21
|
+
path.join(SPECS_DIR, "QuickBase_RESTful_*.json")
|
|
22
|
+
);
|
|
23
|
+
if (specFiles.length === 0) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
"No QuickBase_RESTful_*.json files found in specs/ folder."
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
const inputFile = specFiles.sort().pop() as string;
|
|
29
|
+
const outputFile = path.join(OUTPUT_DIR, "quickbase-fixed.json");
|
|
30
|
+
|
|
31
|
+
console.log(`Reading ${path.basename(inputFile)} from specs/...`);
|
|
32
|
+
const specContent = await fs.readFile(inputFile, "utf8");
|
|
33
|
+
const spec: Spec = JSON.parse(specContent);
|
|
34
|
+
|
|
35
|
+
console.log("Fixing parameters...");
|
|
36
|
+
for (const pathKey in spec.paths) {
|
|
37
|
+
for (const method in spec.paths[pathKey]) {
|
|
38
|
+
const operation = spec.paths[pathKey][method];
|
|
39
|
+
if (operation.parameters) {
|
|
40
|
+
operation.parameters = operation.parameters
|
|
41
|
+
.filter(
|
|
42
|
+
(param) =>
|
|
43
|
+
!["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
|
|
44
|
+
param.name
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
.map((param) => {
|
|
48
|
+
param.name = toCamelCase(param.name);
|
|
49
|
+
if ("example" in param) delete param.example;
|
|
50
|
+
if ("schema" in param && param.in !== "body") delete param.schema;
|
|
51
|
+
if (!param.type && param.in !== "body") {
|
|
52
|
+
param.type = param.name.includes("Id") ? "string" : "string";
|
|
53
|
+
console.log(
|
|
54
|
+
`Set default type 'string' for ${pathKey}(${method}).${param.name} in initial pass`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return param;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Removed applyOverrides conditional logic
|
|
64
|
+
console.log("Enhancing raw spec with tags...");
|
|
65
|
+
enhanceTags(spec);
|
|
66
|
+
console.log("Enhancing raw spec with general enhancements...");
|
|
67
|
+
enhanceGeneral(spec);
|
|
68
|
+
|
|
69
|
+
console.log("Fixing array schemas...");
|
|
70
|
+
fixArrays(spec);
|
|
71
|
+
|
|
72
|
+
console.log("Removing unexpected top-level attributes...");
|
|
73
|
+
delete spec.operations;
|
|
74
|
+
delete spec.groups;
|
|
75
|
+
delete spec.components;
|
|
76
|
+
|
|
77
|
+
console.log(`Writing fixed spec to ${path.basename(outputFile)}...`);
|
|
78
|
+
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
79
|
+
await fs.writeFile(outputFile, JSON.stringify(spec, null, 2), "utf8");
|
|
80
|
+
console.log("Spec fixed successfully! Output written to:", outputFile);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error("Failed to fix spec:", error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main() {
|
|
88
|
+
try {
|
|
89
|
+
await fixQuickBaseSpec();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("Fatal error in script execution:", error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
// open-api/generate-unified-interface.ts
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
readFileSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
} from "fs";
|
|
10
|
+
import { join, dirname } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { OpenAPIV2 } from "openapi-types";
|
|
13
|
+
import { simplifyName } from "../src/utils.ts";
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const SPEC_FILE = join(__dirname, "output", "quickbase-fixed.json");
|
|
17
|
+
const OUTPUT_DIR = join(__dirname, "..", "src", "generated-unified");
|
|
18
|
+
const OUTPUT_FILE = join(OUTPUT_DIR, "QuickbaseClient.ts");
|
|
19
|
+
const MODELS_DIR = join(__dirname, "..", "src", "generated", "models");
|
|
20
|
+
|
|
21
|
+
function mapOpenApiTypeToTs(
|
|
22
|
+
openApiType: string | string[] | undefined
|
|
23
|
+
): string {
|
|
24
|
+
const type = Array.isArray(openApiType)
|
|
25
|
+
? openApiType[0]
|
|
26
|
+
: openApiType || "any";
|
|
27
|
+
switch (type.toLowerCase()) {
|
|
28
|
+
case "integer":
|
|
29
|
+
case "int":
|
|
30
|
+
case "number":
|
|
31
|
+
return "number";
|
|
32
|
+
case "string":
|
|
33
|
+
return "string";
|
|
34
|
+
case "boolean":
|
|
35
|
+
return "boolean";
|
|
36
|
+
default:
|
|
37
|
+
return "any";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function mapRefToType(
|
|
42
|
+
schema: OpenAPIV2.SchemaObject | OpenAPIV2.ReferenceObject | undefined,
|
|
43
|
+
modelImports: Set<string>,
|
|
44
|
+
spec: OpenAPIV2.Document,
|
|
45
|
+
depth: number = 0,
|
|
46
|
+
availableModels: string[],
|
|
47
|
+
missingTypes: Set<string>
|
|
48
|
+
): string {
|
|
49
|
+
if (!schema) return "any";
|
|
50
|
+
|
|
51
|
+
if ("$ref" in schema && schema.$ref) {
|
|
52
|
+
const refParts = schema.$ref.split("/");
|
|
53
|
+
const model = refParts[refParts.length - 1];
|
|
54
|
+
const pascalModel = model.charAt(0).toUpperCase() + model.slice(1);
|
|
55
|
+
const camelModel = model.charAt(0).toLowerCase() + model.slice(1);
|
|
56
|
+
if (availableModels.includes(pascalModel)) {
|
|
57
|
+
modelImports.add(pascalModel);
|
|
58
|
+
return pascalModel;
|
|
59
|
+
} else if (availableModels.includes(camelModel)) {
|
|
60
|
+
modelImports.add(camelModel);
|
|
61
|
+
return camelModel;
|
|
62
|
+
}
|
|
63
|
+
missingTypes.add(pascalModel);
|
|
64
|
+
console.warn(
|
|
65
|
+
`Type ${pascalModel} not found in /generated/models, defaulting to 'any'`
|
|
66
|
+
);
|
|
67
|
+
return "any";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if ("type" in schema) {
|
|
71
|
+
if (schema.type === "object" && schema.properties) {
|
|
72
|
+
const props = schema.properties;
|
|
73
|
+
if (props.data && props.data.format === "binary") {
|
|
74
|
+
return "ArrayBuffer"; // Map binary object to ArrayBuffer
|
|
75
|
+
}
|
|
76
|
+
if (props.content && props.content.format === "yaml") {
|
|
77
|
+
return "string"; // Map YAML object to string
|
|
78
|
+
}
|
|
79
|
+
const propTypes = Object.entries(props).map(([key, prop]) => {
|
|
80
|
+
const propSchema = prop as OpenAPIV2.SchemaObject;
|
|
81
|
+
const propType = mapRefToType(
|
|
82
|
+
propSchema,
|
|
83
|
+
modelImports,
|
|
84
|
+
spec,
|
|
85
|
+
depth + 1,
|
|
86
|
+
availableModels,
|
|
87
|
+
missingTypes
|
|
88
|
+
);
|
|
89
|
+
return `${key}${propSchema.required ? "" : "?"}: ${propType}`;
|
|
90
|
+
});
|
|
91
|
+
return `{ ${propTypes.join("; ")} }`;
|
|
92
|
+
}
|
|
93
|
+
if (schema.type === "array" && schema.items) {
|
|
94
|
+
const items = schema.items as
|
|
95
|
+
| OpenAPIV2.SchemaObject
|
|
96
|
+
| OpenAPIV2.ReferenceObject;
|
|
97
|
+
const itemType = mapRefToType(
|
|
98
|
+
items,
|
|
99
|
+
modelImports,
|
|
100
|
+
spec,
|
|
101
|
+
depth + 1,
|
|
102
|
+
availableModels,
|
|
103
|
+
missingTypes
|
|
104
|
+
);
|
|
105
|
+
return `${itemType}[]`;
|
|
106
|
+
}
|
|
107
|
+
if (schema.type === "object" && schema.additionalProperties) {
|
|
108
|
+
const additionalProps = schema.additionalProperties as
|
|
109
|
+
| OpenAPIV2.SchemaObject
|
|
110
|
+
| OpenAPIV2.ReferenceObject
|
|
111
|
+
| boolean;
|
|
112
|
+
if (typeof additionalProps === "boolean") {
|
|
113
|
+
return "{ [key: string]: any }";
|
|
114
|
+
}
|
|
115
|
+
const valueType = mapRefToType(
|
|
116
|
+
additionalProps,
|
|
117
|
+
modelImports,
|
|
118
|
+
spec,
|
|
119
|
+
depth + 1,
|
|
120
|
+
availableModels,
|
|
121
|
+
missingTypes
|
|
122
|
+
);
|
|
123
|
+
return `{ [key: string]: ${valueType} }`;
|
|
124
|
+
}
|
|
125
|
+
return mapOpenApiTypeToTs(schema.type);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return "any";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function generateInterface() {
|
|
132
|
+
if (!existsSync(SPEC_FILE)) {
|
|
133
|
+
console.error(
|
|
134
|
+
`Spec file ${SPEC_FILE} not found. Run 'npm run fix-spec' first.`
|
|
135
|
+
);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log("Generating unified QuickbaseClient interface...");
|
|
140
|
+
const spec = JSON.parse(
|
|
141
|
+
readFileSync(SPEC_FILE, "utf8")
|
|
142
|
+
) as OpenAPIV2.Document;
|
|
143
|
+
const { paths } = spec;
|
|
144
|
+
|
|
145
|
+
const availableModels = readdirSync(MODELS_DIR)
|
|
146
|
+
.filter((file) => file.endsWith(".ts") && !file.startsWith("index"))
|
|
147
|
+
.map((file) => file.replace(".ts", ""));
|
|
148
|
+
console.log("Available models:", availableModels);
|
|
149
|
+
|
|
150
|
+
const modelImports = new Set<string>();
|
|
151
|
+
const missingTypes = new Set<string>();
|
|
152
|
+
const methods: string[] = [];
|
|
153
|
+
|
|
154
|
+
for (const [path, methodsObj] of Object.entries(
|
|
155
|
+
paths as OpenAPIV2.PathsObject
|
|
156
|
+
)) {
|
|
157
|
+
if (!methodsObj) continue;
|
|
158
|
+
|
|
159
|
+
for (const [method, operation] of Object.entries(
|
|
160
|
+
methodsObj as OpenAPIV2.PathItemObject
|
|
161
|
+
)) {
|
|
162
|
+
const op = operation as OpenAPIV2.OperationObject | undefined;
|
|
163
|
+
if (!op || !op.operationId) continue;
|
|
164
|
+
|
|
165
|
+
const opId = simplifyName(op.operationId);
|
|
166
|
+
const summary = op.summary || "No description available.";
|
|
167
|
+
const params = (op.parameters || [])
|
|
168
|
+
.filter((p) => {
|
|
169
|
+
const param = p as OpenAPIV2.Parameter;
|
|
170
|
+
return !["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
|
|
171
|
+
"name" in param ? param.name : ""
|
|
172
|
+
);
|
|
173
|
+
})
|
|
174
|
+
.map((param: OpenAPIV2.Parameter) => {
|
|
175
|
+
if (!("name" in param)) return "";
|
|
176
|
+
let type: string;
|
|
177
|
+
if ("schema" in param && param.schema) {
|
|
178
|
+
type = mapRefToType(
|
|
179
|
+
param.schema,
|
|
180
|
+
modelImports,
|
|
181
|
+
spec,
|
|
182
|
+
1,
|
|
183
|
+
availableModels,
|
|
184
|
+
missingTypes
|
|
185
|
+
);
|
|
186
|
+
} else if ("type" in param && param.type) {
|
|
187
|
+
type = mapOpenApiTypeToTs(param.type);
|
|
188
|
+
} else {
|
|
189
|
+
console.warn(
|
|
190
|
+
`Parameter ${param.name} in ${opId} has no type or schema, defaulting to 'any'`
|
|
191
|
+
);
|
|
192
|
+
type = "any";
|
|
193
|
+
}
|
|
194
|
+
const paramName = param.in === "body" ? "body" : param.name;
|
|
195
|
+
return `${paramName}${param.required ? "" : "?"}: ${type}`;
|
|
196
|
+
})
|
|
197
|
+
.filter((param) => param !== "")
|
|
198
|
+
.join("; ");
|
|
199
|
+
|
|
200
|
+
const successResponses = ["200", "207"]
|
|
201
|
+
.map((code) => ({
|
|
202
|
+
code,
|
|
203
|
+
response: op.responses?.[code] as
|
|
204
|
+
| OpenAPIV2.ResponseObject
|
|
205
|
+
| undefined,
|
|
206
|
+
}))
|
|
207
|
+
.filter(({ response }) => response?.schema);
|
|
208
|
+
const returnTypes = successResponses.map(({ response }) =>
|
|
209
|
+
mapRefToType(
|
|
210
|
+
response!.schema,
|
|
211
|
+
modelImports,
|
|
212
|
+
spec,
|
|
213
|
+
1,
|
|
214
|
+
availableModels,
|
|
215
|
+
missingTypes
|
|
216
|
+
)
|
|
217
|
+
);
|
|
218
|
+
const uniqueReturnTypes = [...new Set(returnTypes)];
|
|
219
|
+
const returnType =
|
|
220
|
+
uniqueReturnTypes.length > 1
|
|
221
|
+
? uniqueReturnTypes.join(" | ")
|
|
222
|
+
: uniqueReturnTypes[0] || "void";
|
|
223
|
+
|
|
224
|
+
const jsDoc = [
|
|
225
|
+
` /**`,
|
|
226
|
+
` * ${summary}`,
|
|
227
|
+
` * @param params - Parameters for the ${opId} operation`,
|
|
228
|
+
` * @returns A promise resolving to the ${opId} response`,
|
|
229
|
+
` */`,
|
|
230
|
+
].join("\n");
|
|
231
|
+
|
|
232
|
+
methods.push(
|
|
233
|
+
`${jsDoc}\n ${opId}: (params: { ${params} }) => Promise<${returnType}>;`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Always write the missing-types-report.json to reflect the current state
|
|
239
|
+
if (missingTypes.size > 0) {
|
|
240
|
+
console.log(
|
|
241
|
+
"Missing types detected (defaulted to 'any'):",
|
|
242
|
+
Array.from(missingTypes)
|
|
243
|
+
);
|
|
244
|
+
} else {
|
|
245
|
+
console.log("No missing types detected.");
|
|
246
|
+
}
|
|
247
|
+
writeFileSync(
|
|
248
|
+
join(OUTPUT_DIR, "missing-types-report.json"),
|
|
249
|
+
JSON.stringify({ missingTypes: Array.from(missingTypes) }, null, 2)
|
|
250
|
+
);
|
|
251
|
+
console.log("Missing types report saved to missing-types-report.json");
|
|
252
|
+
|
|
253
|
+
const importStatement =
|
|
254
|
+
modelImports.size > 0
|
|
255
|
+
? `import { ${Array.from(modelImports)
|
|
256
|
+
.sort()
|
|
257
|
+
.join(", ")} } from "../generated/models";`
|
|
258
|
+
: "";
|
|
259
|
+
console.log(`Imports: ${Array.from(modelImports).join(", ")}`);
|
|
260
|
+
|
|
261
|
+
const interfaceContent = `// Generated on ${new Date().toISOString()}\n${importStatement}\n\nexport interface QuickbaseClient {\n${methods.join(
|
|
262
|
+
"\n"
|
|
263
|
+
)}\n}\n`;
|
|
264
|
+
|
|
265
|
+
if (!existsSync(OUTPUT_DIR)) {
|
|
266
|
+
mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
267
|
+
}
|
|
268
|
+
writeFileSync(OUTPUT_FILE, interfaceContent, "utf8");
|
|
269
|
+
console.log(`Generated ${OUTPUT_FILE}`);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
generateInterface();
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.error("Generation failed:", error);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|