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.
Files changed (123) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/LICENSE +21 -0
  3. package/README.md +204 -0
  4. package/build-common.js +131 -0
  5. package/build-esm.js +71 -0
  6. package/build-umd.js +69 -0
  7. package/dist/esm/quickbase.d.ts +7594 -0
  8. package/dist/esm/quickbase.js +9752 -0
  9. package/dist/esm/quickbase.js.map +1 -0
  10. package/dist/esm/quickbase.min.js +2 -0
  11. package/dist/esm/quickbase.min.js.map +1 -0
  12. package/dist/umd/quickbase.umd.js +10502 -0
  13. package/dist/umd/quickbase.umd.js.map +1 -0
  14. package/dist/umd/quickbase.umd.min.js +2 -0
  15. package/dist/umd/quickbase.umd.min.js.map +1 -0
  16. package/examples/quicbase-jsCDN.html +36 -0
  17. package/examples/react.js +48 -0
  18. package/examples/vanillajs.html +47 -0
  19. package/notes.txt +158 -0
  20. package/open-api/fix-spec-main.ts +96 -0
  21. package/open-api/generate-unified-interface.ts +277 -0
  22. package/open-api/output/override-report.json +58 -0
  23. package/open-api/output/quickbase-fixed.json +10610 -0
  24. package/open-api/regenerate-client.ts +116 -0
  25. package/open-api/schema/enhance-general.ts +100 -0
  26. package/open-api/schema/fix-arrays.ts +69 -0
  27. package/open-api/schema/tags/apps.ts +90 -0
  28. package/open-api/schema/tags/fields.ts +156 -0
  29. package/open-api/schema/tags/groups.ts +51 -0
  30. package/open-api/schema/tags/index.ts +68 -0
  31. package/open-api/schema/tags/records.ts +115 -0
  32. package/open-api/schema/tags/tables.ts +211 -0
  33. package/open-api/schema/tags/users.ts +47 -0
  34. package/open-api/types/spec.ts +41 -0
  35. package/open-api/utils/infer-schema.ts +40 -0
  36. package/open-api/utils/naming.ts +10 -0
  37. package/package.json +91 -0
  38. package/playwright.config.ts +37 -0
  39. package/rollup.config.js +114 -0
  40. package/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
  41. package/specs/split/apps.json +1315 -0
  42. package/specs/split/auth.json +191 -0
  43. package/specs/split/fields.json +3869 -0
  44. package/specs/split/records.json +754 -0
  45. package/specs/split/reports.json +619 -0
  46. package/specs/split/tables.json +1605 -0
  47. package/src/generated-unified/QuickbaseClient.ts +359 -0
  48. package/src/generated-unified/missing-types-report.json +3 -0
  49. package/src/index.ts +1 -0
  50. package/src/quickbaseClient.ts +402 -0
  51. package/src/tokenCache.ts +51 -0
  52. package/src/utils.ts +7 -0
  53. package/test-esm.js +1 -0
  54. package/test-results.json +2050 -0
  55. package/test-umd.js +1 -0
  56. package/test.ts +1 -0
  57. package/tests/playwright/qb/auth/cacheSwitching.test.ts +159 -0
  58. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +219 -0
  59. package/tests/playwright/qb/auth/getAppWithTempToken.test.ts +162 -0
  60. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +153 -0
  61. package/tests/playwright/qb/auth/getTempToken.test.ts +152 -0
  62. package/tests/playwright/qb/auth/tokenPrefetch.test.ts +179 -0
  63. package/tests/playwright/qb/auth/tokenRenewal.test.ts +133 -0
  64. package/tests/setup.ts +51 -0
  65. package/tests/vitest/qb/apps/copyApp.test.ts +76 -0
  66. package/tests/vitest/qb/apps/createApp.test.ts +64 -0
  67. package/tests/vitest/qb/apps/deleteApp.test.ts +76 -0
  68. package/tests/vitest/qb/apps/getApp.test.ts +40 -0
  69. package/tests/vitest/qb/fields/createField.test.ts +78 -0
  70. package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
  71. package/tests/vitest/qb/fields/getField.test.ts +85 -0
  72. package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
  73. package/tests/vitest/qb/fields/getFields.test.ts +232 -0
  74. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
  75. package/tests/vitest/qb/index.ts +28 -0
  76. package/tests/vitest/qb/records/deleteRecords.test.ts +110 -0
  77. package/tests/vitest/qb/records/runQuery.test.ts +146 -0
  78. package/tests/vitest/qb/records/upsert.test.ts +102 -0
  79. package/tests/vitest/qb/tables/createTable.test.ts +55 -0
  80. package/tests/vitest/qb/tables/deleteTable.test.ts +50 -0
  81. package/tests/vitest/qb/tables/getAppTables.test.ts +46 -0
  82. package/tests/vitest/qb/tables/getRelationships.test.ts +80 -0
  83. package/tests/vitest/qb/tables/getTable.test.ts +26 -0
  84. package/tests/vitest/qb/tables/updateTable.test.ts +48 -0
  85. package/tests/vitest/unit/apps/copyApp.test.ts +488 -0
  86. package/tests/vitest/unit/apps/createApp.test.ts +186 -0
  87. package/tests/vitest/unit/apps/deleteApp.test.ts +216 -0
  88. package/tests/vitest/unit/apps/getApp.test.ts +128 -0
  89. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +82 -0
  90. package/tests/vitest/unit/auth/getTempToken.test.ts +95 -0
  91. package/tests/vitest/unit/auth/retryOn401.test.ts +116 -0
  92. package/tests/vitest/unit/auth/two401s.test.ts +86 -0
  93. package/tests/vitest/unit/fields/createField.test.ts +391 -0
  94. package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
  95. package/tests/vitest/unit/fields/getField.test.ts +189 -0
  96. package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
  97. package/tests/vitest/unit/fields/getFields.test.ts +122 -0
  98. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
  99. package/tests/vitest/unit/index.ts +33 -0
  100. package/tests/vitest/unit/records/deleteRecords.test.ts +112 -0
  101. package/tests/vitest/unit/records/runQuery.test.ts +102 -0
  102. package/tests/vitest/unit/records/upsert.test.ts +234 -0
  103. package/tests/vitest/unit/tables/createTable.test.ts +70 -0
  104. package/tests/vitest/unit/tables/deleteTable.test.ts +77 -0
  105. package/tests/vitest/unit/tables/getAppTables.test.ts +85 -0
  106. package/tests/vitest/unit/tables/getRelationships.test.ts +259 -0
  107. package/tests/vitest/unit/tables/getTable.test.ts +233 -0
  108. package/tests/vitest/unit/tables/updateTable.test.ts +80 -0
  109. package/tests/vitest/unit/umd.test.ts +8 -0
  110. package/text.txt +104 -0
  111. package/tools/generate-types.ts +29 -0
  112. package/tools/log-gen.ts +231 -0
  113. package/tools/log-gen.yaml +3826 -0
  114. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  115. package/tools/openapi-schema-validator.ts +38 -0
  116. package/tools/spec-analyzer.ts +163 -0
  117. package/tools/split-spec.ts +148 -0
  118. package/tools/test-import.ts +3 -0
  119. package/tools/validate-spec.ts +40 -0
  120. package/tsconfig.build.json +17 -0
  121. package/tsconfig.json +29 -0
  122. package/versionrc.json +9 -0
  123. 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
+ }