quickbase-js 0.1.0-beta.7 → 0.1.0-beta.8

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 (60) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/esm/quickbase.d.ts +589 -125
  3. package/dist/esm/quickbase.js +484 -145
  4. package/dist/esm/quickbase.js.map +1 -1
  5. package/dist/esm/quickbase.min.js +1 -1
  6. package/dist/esm/quickbase.min.js.map +1 -1
  7. package/dist/umd/quickbase.umd.js +492 -144
  8. package/dist/umd/quickbase.umd.js.map +1 -1
  9. package/dist/umd/quickbase.umd.min.js +1 -1
  10. package/dist/umd/quickbase.umd.min.js.map +1 -1
  11. package/open-api/fix-spec-main.ts +69 -68
  12. package/open-api/generate-override-report.ts +214 -0
  13. package/open-api/generate-unified-interface.ts +73 -94
  14. package/open-api/output/override-report.html +11 -0
  15. package/open-api/output/override-report.json +14395 -58
  16. package/open-api/output/quickbase-fixed.json +583 -2
  17. package/open-api/schema/tags/fields.ts +331 -16
  18. package/open-api/schema/tags/index.ts +2 -4
  19. package/open-api/types/spec.ts +2 -1
  20. package/package.json +31 -33
  21. package/src/RateLimitError.ts +12 -0
  22. package/src/ThrottleBucket.ts +36 -0
  23. package/src/generated-unified/QuickbaseClient.ts +119 -119
  24. package/src/invokeMethod.ts +357 -0
  25. package/src/quickbaseClient.ts +51 -209
  26. package/tests/setup.ts +2 -0
  27. package/tests/vitest/qb/apps/getApp.test.ts +6 -2
  28. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  29. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  30. package/tests/vitest/qb/auth/retryUserToken401.test.ts +56 -0
  31. package/tests/vitest/qb/fields/createField.test.ts +1 -2
  32. package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
  33. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  34. package/tests/vitest/qb/index.ts +1 -0
  35. package/tests/vitest/qb/tables/updateRelationship.test.ts +277 -0
  36. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  37. package/tests/vitest/unit/apps/updateApp.test.ts +381 -0
  38. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +6 -26
  39. package/tests/vitest/unit/auth/{retryOn401.test.ts → retryTempToken401.test.ts} +3 -6
  40. package/tests/vitest/unit/auth/{two401s.test.ts → retryTempTokenTwo401.test.ts} +4 -9
  41. package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -0
  42. package/tests/vitest/unit/fields/createField.test.ts +8 -12
  43. package/tests/vitest/unit/fields/getFieldUsage.test.ts +33 -14
  44. package/tests/vitest/unit/fields/updateField.test.ts +280 -0
  45. package/tests/vitest/unit/rateLimit.test.ts +189 -0
  46. package/tests/vitest/unit/tables/updateRelationship.test.ts +305 -0
  47. package/tests/vitest/unit/tokenLifespan.test.ts +138 -0
  48. package/tools/log-gen.ts +8 -4
  49. package/tools/log-gen.yaml +3004 -1848
  50. package/tsconfig.json +5 -2
  51. package/vitest.config.ts +2 -2
  52. package/notes.txt +0 -158
  53. package/tools/generate-types.ts +0 -29
  54. package/tools/openapi-schema-validator.ts +0 -38
  55. package/tools/spec-analyzer.ts +0 -163
  56. package/tools/split-spec.ts +0 -148
  57. package/tools/test-import.ts +0 -3
  58. package/tools/validate-spec.ts +0 -40
  59. /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
  60. /package/tests/playwright/qb/auth/{tokenRenewal.test.ts → tempTokenRenewal.test.ts} +0 -0
@@ -1,94 +1,95 @@
1
1
  #!/usr/bin/env node
2
-
3
2
  import { promises as fs } from "fs";
4
3
  import path from "path";
5
4
  import { fileURLToPath } from "url";
6
5
  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
6
  import { Spec } from "./types/spec.ts";
11
7
  import { toCamelCase } from "./utils/naming.ts";
12
8
 
13
- // Removed FixSpecConfig since applyOverrides is no longer needed
14
9
  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")
10
+ const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
11
+ const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
12
+ const OUTPUT_DIR = path.join(CODEGEN_DIR, "output");
13
+ console.log("Finding latest QuickBase RESTful API spec...");
14
+ const specFiles = glob.sync(path.join(SPECS_DIR, "QuickBase_RESTful_*.json"));
15
+ console.log("Spec files:", specFiles);
16
+
17
+ if (specFiles.length === 0) {
18
+ throw new Error(
19
+ "No QuickBase_RESTful_*.json files found in specs/ folder."
22
20
  );
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");
21
+ }
22
+
23
+ const inputFile = specFiles.sort().pop() as string;
24
+ const outputFile = path.join(OUTPUT_DIR, "quickbase-fixed.json");
30
25
 
31
- console.log(`Reading ${path.basename(inputFile)} from specs/...`);
32
- const specContent = await fs.readFile(inputFile, "utf8");
33
- const spec: Spec = JSON.parse(specContent);
26
+ console.log(`Reading ${path.basename(inputFile)} from specs/...`);
27
+ const specContent = await fs.readFile(inputFile, "utf8");
28
+ console.log("File read successfully");
29
+ const spec: Spec = JSON.parse(specContent);
30
+ console.log("JSON parsed successfully");
34
31
 
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
- }
32
+ if (!spec.definitions || typeof spec.definitions !== "object") {
33
+ spec.definitions = {};
34
+ }
35
+ console.log("Spec definitions after initialization:", spec.definitions);
36
+
37
+ console.log("Fixing parameters...");
38
+ for (const pathKey in spec.paths) {
39
+ for (const method in spec.paths[pathKey]) {
40
+ const operation = spec.paths[pathKey][method];
41
+ if (operation.parameters) {
42
+ operation.parameters = operation.parameters
43
+ .filter(
44
+ (param) =>
45
+ !["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
46
+ param.name
47
+ )
48
+ )
49
+ .map((param) => {
50
+ param.name = toCamelCase(param.name);
51
+ if ("example" in param) delete param.example;
52
+ if ("schema" in param && param.in !== "body") delete param.schema;
53
+ if (!param.type && param.in !== "body") {
54
+ param.type = param.name.includes("Id") ? "string" : "string";
55
+ console.log(
56
+ `Set default type 'string' for ${pathKey}(${method}).${param.name}`
57
+ );
58
+ }
59
+ return param;
60
+ });
60
61
  }
61
62
  }
63
+ }
62
64
 
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);
65
+ console.log("Spec definitions before enhanceTags:", spec.definitions);
66
+ console.log("Enhancing raw spec with tags...");
67
+ const { enhanceTags } = await import("./schema/tags/index.ts"); // Updated to index.ts
68
+ enhanceTags(spec);
69
+ console.log("Enhancing raw spec with general enhancements...");
70
+ const { enhanceGeneral } = await import("./schema/enhance-general.ts");
71
+ enhanceGeneral(spec);
72
+ console.log("Fixing array schemas...");
73
+ const { fixArrays } = await import("./schema/fix-arrays.ts");
74
+ fixArrays(spec);
71
75
 
72
- console.log("Removing unexpected top-level attributes...");
73
- delete spec.operations;
74
- delete spec.groups;
75
- delete spec.components;
76
+ console.log("Removing unexpected top-level attributes...");
77
+ delete spec.operations;
78
+ delete spec.groups;
79
+ delete spec.components;
76
80
 
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
- }
81
+ console.log(`Writing fixed spec to ${path.basename(outputFile)}...`);
82
+ await fs.mkdir(OUTPUT_DIR, { recursive: true });
83
+ await fs.writeFile(outputFile, JSON.stringify(spec, null, 2), "utf8");
84
+ console.log("Spec fixed successfully! Output written to:", outputFile);
85
85
  }
86
86
 
87
87
  async function main() {
88
88
  try {
89
+ console.log("Starting...");
89
90
  await fixQuickBaseSpec();
90
91
  } catch (error) {
91
- console.error("Fatal error in script execution:", error);
92
+ console.error("Error:", error);
92
93
  process.exit(1);
93
94
  }
94
95
  }
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { promises as fs } from "fs";
4
+ import * as path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import fastJsonPatch, { AddOperation } from "fast-json-patch";
7
+ import { Spec } from "./types/spec";
8
+
9
+ const { compare, applyPatch } = fastJsonPatch;
10
+
11
+ // Basic CSS for readability
12
+ const HTML_STYLE = `
13
+ <style>
14
+ .jsondiffpatch-delta { font-family: monospace; }
15
+ .jsondiffpatch-added { background-color: #e6ffe6; color: #006400; }
16
+ .jsondiffpatch-deleted { background-color: #ffe6e6; color: #8b0000; text-decoration: line-through; }
17
+ .jsondiffpatch-modified { background-color: #fff3e6; }
18
+ .jsondiffpatch-unchanged { color: #666; }
19
+ .jsondiffpatch-key { font-weight: bold; }
20
+ .jsondiffpatch-tags { color: purple; font-style: italic; }
21
+ </style>
22
+ `;
23
+
24
+ // Helper to decode JSON Pointer paths
25
+ function decodeJsonPointer(jsonPointerSegment: string): string {
26
+ // Split the full path and decode each segment
27
+ const segments = jsonPointerSegment.split("/").filter(Boolean);
28
+ const decodedSegments = segments.map((seg) =>
29
+ seg.replace("~1", "/").replace("~0", "~")
30
+ );
31
+ // Remove "paths" prefix and join with a single leading slash
32
+ const pathIndex = decodedSegments.indexOf("paths");
33
+ const relevantSegments =
34
+ pathIndex !== -1 && pathIndex + 1 < decodedSegments.length
35
+ ? decodedSegments.slice(pathIndex + 1)
36
+ : decodedSegments;
37
+ return "/" + relevantSegments.join("/"); // Ensure single leading slash
38
+ }
39
+
40
+ // Enhanced HTML formatter with clean paths, tags, and old vs. new values
41
+ function formatPatchToHtml(patch: any[], spec: Spec): string {
42
+ let html = `<html><head><title>Override Report Diff</title>${HTML_STYLE}</head><body><h1>Override Report Diff</h1>`;
43
+ patch.forEach((op) => {
44
+ const pathParts = op.path.split("/").filter(Boolean);
45
+ const lastPart = pathParts[pathParts.length - 1];
46
+ let oldValue = "N/A";
47
+ let context = decodeJsonPointer(op.path);
48
+ let method = op.method ? op.method.toUpperCase() : "";
49
+
50
+ if (method) context = context.replace(`/${method}`, "") + ` [${method}]`;
51
+
52
+ if (op.op === "replace" || op.op === "remove") {
53
+ const reversePatch: AddOperation<any>[] = [
54
+ { op: "add", path: op.path, value: op.value },
55
+ ];
56
+ const tempSpec = JSON.parse(JSON.stringify(spec));
57
+ applyPatch(tempSpec, reversePatch);
58
+ const pathSegments = op.path.split("/").slice(1);
59
+ oldValue =
60
+ pathSegments.reduce((obj: any, key: string) => obj?.[key], tempSpec) ||
61
+ "N/A";
62
+ oldValue = JSON.stringify(oldValue);
63
+ }
64
+
65
+ const tagDisplay =
66
+ op.tags && op.tags.length > 0 ? ` (Tags: ${op.tags.join(", ")})` : "";
67
+
68
+ if (op.op === "replace") {
69
+ html += `<h2>Path: ${context}${tagDisplay}</h2><p>Modified: <span class="jsondiffpatch-deleted">${oldValue}</span> → <span class="jsondiffpatch-added">${JSON.stringify(
70
+ op.value
71
+ )}</span></p>`;
72
+ } else if (op.op === "add") {
73
+ html += `<h2>Path: ${context}${tagDisplay}</h2><p>Added: <span class="jsondiffpatch-added">${JSON.stringify(
74
+ op.value
75
+ )}</span></p>`;
76
+ } else if (op.op === "remove") {
77
+ html += `<h2>Path: ${context}${tagDisplay}</h2><p>Removed: <span class="jsondiffpatch-deleted">${oldValue}</span></p>`;
78
+ }
79
+ });
80
+ html += "</body></html>";
81
+ return html;
82
+ }
83
+
84
+ async function generateOverrideReport(): Promise<void> {
85
+ try {
86
+ const CODEGEN_DIR: string = path.dirname(fileURLToPath(import.meta.url));
87
+ const SPECS_DIR: string = path.join(CODEGEN_DIR, "..", "specs");
88
+ const OUTPUT_DIR: string = path.join(CODEGEN_DIR, "output");
89
+ const RAW_SPEC: string = path.join(
90
+ SPECS_DIR,
91
+ "QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json"
92
+ );
93
+ const FIXED_SPEC: string = path.join(OUTPUT_DIR, "quickbase-fixed.json");
94
+ const JSON_OUTPUT_FILE: string = path.join(
95
+ OUTPUT_DIR,
96
+ "override-report.json"
97
+ );
98
+ const HTML_OUTPUT_FILE: string = path.join(
99
+ OUTPUT_DIR,
100
+ "override-report.html"
101
+ );
102
+
103
+ console.log("Cleaning up old override report...");
104
+ if (
105
+ await fs
106
+ .stat(JSON_OUTPUT_FILE)
107
+ .then(() => true)
108
+ .catch(() => false)
109
+ ) {
110
+ await fs.unlink(JSON_OUTPUT_FILE);
111
+ console.log(`Removed ${JSON_OUTPUT_FILE}`);
112
+ }
113
+ if (
114
+ await fs
115
+ .stat(HTML_OUTPUT_FILE)
116
+ .then(() => true)
117
+ .catch(() => false)
118
+ ) {
119
+ await fs.unlink(HTML_OUTPUT_FILE);
120
+ console.log(`Removed ${HTML_OUTPUT_FILE}`);
121
+ } else {
122
+ console.log("No old override reports found to remove.");
123
+ }
124
+
125
+ if (
126
+ !(await fs
127
+ .stat(RAW_SPEC)
128
+ .then(() => true)
129
+ .catch(() => false))
130
+ ) {
131
+ console.error(`Raw spec ${RAW_SPEC} not found.`);
132
+ process.exit(1);
133
+ }
134
+ if (
135
+ !(await fs
136
+ .stat(FIXED_SPEC)
137
+ .then(() => true)
138
+ .catch(() => false))
139
+ ) {
140
+ console.error(
141
+ `Fixed spec ${FIXED_SPEC} not found. Run 'npm run fix-spec' first.`
142
+ );
143
+ process.exit(1);
144
+ }
145
+
146
+ console.log(`Reading raw spec from ${RAW_SPEC}...`);
147
+ const rawContent = await fs.readFile(RAW_SPEC, "utf8");
148
+ const rawSpec: Spec = JSON.parse(rawContent);
149
+
150
+ console.log(`Reading fixed spec from ${FIXED_SPEC}...`);
151
+ const fixedContent = await fs.readFile(FIXED_SPEC, "utf8");
152
+ const fixedSpec: Spec = JSON.parse(fixedContent);
153
+
154
+ console.log("Generating patch...");
155
+ const patch = compare(rawSpec, fixedSpec);
156
+
157
+ // Enhance patch with decoded paths and tags inferred from parent operation
158
+ const enhancedPatch = patch.map((op) => {
159
+ let decodedPath = decodeJsonPointer(op.path);
160
+ let tags: string[] = [];
161
+ let method: string = "";
162
+
163
+ if (op.path.startsWith("/paths")) {
164
+ const pathParts = op.path.split("/").filter(Boolean);
165
+ if (
166
+ pathParts.length >= 3 &&
167
+ ["get", "post", "put", "delete"].includes(pathParts[2].toLowerCase())
168
+ ) {
169
+ const fullPathSegment = "/paths/" + pathParts[1];
170
+ const basePath = decodeJsonPointer(fullPathSegment);
171
+ method = pathParts[2];
172
+ console.log(`Debug: basePath=${basePath}, method=${method}`); // Debug log
173
+ if (rawSpec.paths[basePath]?.[method]?.tags) {
174
+ tags = rawSpec.paths[basePath][method].tags || [];
175
+ } else if (rawSpec.paths[basePath]?.[method]) {
176
+ console.log(`No tags found for ${basePath} [${method}]`);
177
+ } else {
178
+ console.log(`Operation not found: ${basePath} [${method}]`);
179
+ }
180
+ }
181
+ }
182
+
183
+ return {
184
+ ...op,
185
+ decodedPath,
186
+ tags: tags.length > 0 ? tags : undefined,
187
+ method,
188
+ };
189
+ });
190
+
191
+ console.log("Writing JSON override report...");
192
+ await fs.mkdir(OUTPUT_DIR, { recursive: true });
193
+ await fs.writeFile(
194
+ JSON_OUTPUT_FILE,
195
+ JSON.stringify(enhancedPatch, null, 2),
196
+ "utf8"
197
+ );
198
+
199
+ console.log("Writing HTML override report...");
200
+ const htmlContent = formatPatchToHtml(enhancedPatch, rawSpec);
201
+ await fs.writeFile(HTML_OUTPUT_FILE, htmlContent, "utf8");
202
+
203
+ console.log("Override report generated successfully!");
204
+ } catch (error) {
205
+ console.error("Failed to generate override report:", error);
206
+ process.exit(1);
207
+ }
208
+ }
209
+
210
+ async function main() {
211
+ await generateOverrideReport();
212
+ }
213
+
214
+ main();
@@ -1,4 +1,5 @@
1
- // open-api/generate-unified-interface.ts
1
+ #!/usr/bin/env node
2
+ console.log("Script started");
2
3
 
3
4
  import {
4
5
  readFileSync,
@@ -7,12 +8,23 @@ import {
7
8
  existsSync,
8
9
  readdirSync,
9
10
  } from "fs";
11
+ console.log("FS modules imported");
12
+
10
13
  import { join, dirname } from "path";
14
+ console.log("Path modules imported");
15
+
11
16
  import { fileURLToPath } from "url";
12
- import { OpenAPIV2 } from "openapi-types";
13
- import { simplifyName } from "../src/utils.ts";
17
+ console.log("URL module imported");
18
+
19
+ import { OpenAPIV2 } from "openapi-types"; // Use module name, not relative path
20
+ console.log("openapi-types imported");
21
+
22
+ // Temporary simplifyName fallback
23
+ const simplifyName = (str: string) => str;
14
24
 
15
25
  const __dirname = dirname(fileURLToPath(import.meta.url));
26
+ console.log("__dirname set:", __dirname);
27
+
16
28
  const SPEC_FILE = join(__dirname, "output", "quickbase-fixed.json");
17
29
  const OUTPUT_DIR = join(__dirname, "..", "src", "generated-unified");
18
30
  const OUTPUT_FILE = join(OUTPUT_DIR, "QuickbaseClient.ts");
@@ -52,30 +64,18 @@ function mapRefToType(
52
64
  const refParts = schema.$ref.split("/");
53
65
  const model = refParts[refParts.length - 1];
54
66
  const pascalModel = model.charAt(0).toUpperCase() + model.slice(1);
55
- const camelModel = model.charAt(0).toLowerCase() + model.slice(1);
56
67
  if (availableModels.includes(pascalModel)) {
57
68
  modelImports.add(pascalModel);
58
69
  return pascalModel;
59
- } else if (availableModels.includes(camelModel)) {
60
- modelImports.add(camelModel);
61
- return camelModel;
62
70
  }
63
71
  missingTypes.add(pascalModel);
64
- console.warn(
65
- `Type ${pascalModel} not found in /generated/models, defaulting to 'any'`
66
- );
72
+ console.warn(`Type ${pascalModel} not found, defaulting to 'any'`);
67
73
  return "any";
68
74
  }
69
75
 
70
76
  if ("type" in schema) {
71
77
  if (schema.type === "object" && schema.properties) {
72
78
  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
79
  const propTypes = Object.entries(props).map(([key, prop]) => {
80
80
  const propSchema = prop as OpenAPIV2.SchemaObject;
81
81
  const propType = mapRefToType(
@@ -104,24 +104,6 @@ function mapRefToType(
104
104
  );
105
105
  return `${itemType}[]`;
106
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
107
  return mapOpenApiTypeToTs(schema.type);
126
108
  }
127
109
 
@@ -129,6 +111,7 @@ function mapRefToType(
129
111
  }
130
112
 
131
113
  function generateInterface() {
114
+ console.log("Checking spec file:", SPEC_FILE);
132
115
  if (!existsSync(SPEC_FILE)) {
133
116
  console.error(
134
117
  `Spec file ${SPEC_FILE} not found. Run 'npm run fix-spec' first.`
@@ -136,12 +119,24 @@ function generateInterface() {
136
119
  process.exit(1);
137
120
  }
138
121
 
139
- console.log("Generating unified QuickbaseClient interface...");
140
- const spec = JSON.parse(
141
- readFileSync(SPEC_FILE, "utf8")
142
- ) as OpenAPIV2.Document;
122
+ console.log("Reading spec file...");
123
+ const specContent = readFileSync(SPEC_FILE, "utf8");
124
+ const spec = JSON.parse(specContent) as OpenAPIV2.Document;
125
+ console.log("Spec parsed, keys:", Object.keys(spec));
126
+
143
127
  const { paths } = spec;
128
+ if (!paths) {
129
+ console.error("No 'paths' in spec");
130
+ process.exit(1);
131
+ }
144
132
 
133
+ console.log("Checking models directory:", MODELS_DIR);
134
+ if (!existsSync(MODELS_DIR)) {
135
+ console.error(
136
+ `Models directory ${MODELS_DIR} not found. Run 'npm run gen:openapi' first.`
137
+ );
138
+ process.exit(1);
139
+ }
145
140
  const availableModels = readdirSync(MODELS_DIR)
146
141
  .filter((file) => file.endsWith(".ts") && !file.startsWith("index"))
147
142
  .map((file) => file.replace(".ts", ""));
@@ -155,7 +150,6 @@ function generateInterface() {
155
150
  paths as OpenAPIV2.PathsObject
156
151
  )) {
157
152
  if (!methodsObj) continue;
158
-
159
153
  for (const [method, operation] of Object.entries(
160
154
  methodsObj as OpenAPIV2.PathItemObject
161
155
  )) {
@@ -163,69 +157,61 @@ function generateInterface() {
163
157
  if (!op || !op.operationId) continue;
164
158
 
165
159
  const opId = simplifyName(op.operationId);
166
- const summary = op.summary || "No description available.";
160
+ const summary = op.summary || "No description.";
167
161
  const params = (op.parameters || [])
168
- .filter((p) => {
169
- const param = p as OpenAPIV2.Parameter;
162
+ .filter((p: OpenAPIV2.ParameterObject | OpenAPIV2.ReferenceObject) => {
163
+ const param = p as OpenAPIV2.ParameterObject;
170
164
  return !["QB-Realm-Hostname", "Authorization", "User-Agent"].includes(
171
- "name" in param ? param.name : ""
165
+ param.name || ""
172
166
  );
173
167
  })
174
- .map((param: OpenAPIV2.Parameter) => {
175
- if (!("name" in param)) return "";
176
- let type: string;
177
- if ("schema" in param && param.schema) {
168
+ .map((param: OpenAPIV2.ParameterObject | OpenAPIV2.ReferenceObject) => {
169
+ const p = param as OpenAPIV2.ParameterObject;
170
+ if (!p.name) return "";
171
+ let type = "any";
172
+ if ("schema" in p && p.schema) {
178
173
  type = mapRefToType(
179
- param.schema,
174
+ p.schema,
180
175
  modelImports,
181
176
  spec,
182
177
  1,
183
178
  availableModels,
184
179
  missingTypes
185
180
  );
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";
181
+ } else if ("type" in p && p.type) {
182
+ type = mapOpenApiTypeToTs(p.type);
193
183
  }
194
- const paramName = param.in === "body" ? "body" : param.name;
195
- return `${paramName}${param.required ? "" : "?"}: ${type}`;
184
+ const paramName = p.in === "body" ? "body" : p.name;
185
+ return `${paramName}${p.required ? "" : "?"}: ${type}`;
196
186
  })
197
187
  .filter((param) => param !== "")
198
188
  .join("; ");
199
189
 
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
190
+ const returnTypes = ["200", "207"]
191
+ .map(
192
+ (code) => op.responses?.[code] as OpenAPIV2.ResponseObject | undefined
216
193
  )
217
- );
218
- const uniqueReturnTypes = [...new Set(returnTypes)];
194
+ .filter((response) => response?.schema)
195
+ .map((response) =>
196
+ mapRefToType(
197
+ response!.schema,
198
+ modelImports,
199
+ spec,
200
+ 1,
201
+ availableModels,
202
+ missingTypes
203
+ )
204
+ );
219
205
  const returnType =
220
- uniqueReturnTypes.length > 1
221
- ? uniqueReturnTypes.join(" | ")
222
- : uniqueReturnTypes[0] || "void";
206
+ returnTypes.length > 1
207
+ ? returnTypes.join(" | ")
208
+ : returnTypes[0] || "void";
223
209
 
224
210
  const jsDoc = [
225
211
  ` /**`,
226
212
  ` * ${summary}`,
227
- ` * @param params - Parameters for the ${opId} operation`,
228
- ` * @returns A promise resolving to the ${opId} response`,
213
+ ` * @param params - Parameters for ${opId}`,
214
+ ` * @returns Promise resolving to ${opId} response`,
229
215
  ` */`,
230
216
  ].join("\n");
231
217
 
@@ -235,20 +221,11 @@ function generateInterface() {
235
221
  }
236
222
  }
237
223
 
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
- }
224
+ console.log("Writing missing types report...");
247
225
  writeFileSync(
248
226
  join(OUTPUT_DIR, "missing-types-report.json"),
249
227
  JSON.stringify({ missingTypes: Array.from(missingTypes) }, null, 2)
250
228
  );
251
- console.log("Missing types report saved to missing-types-report.json");
252
229
 
253
230
  const importStatement =
254
231
  modelImports.size > 0
@@ -256,22 +233,24 @@ function generateInterface() {
256
233
  .sort()
257
234
  .join(", ")} } from "../generated/models";`
258
235
  : "";
259
- console.log(`Imports: ${Array.from(modelImports).join(", ")}`);
260
-
261
236
  const interfaceContent = `// Generated on ${new Date().toISOString()}\n${importStatement}\n\nexport interface QuickbaseClient {\n${methods.join(
262
237
  "\n"
263
238
  )}\n}\n`;
264
239
 
240
+ console.log("Ensuring output directory exists...");
265
241
  if (!existsSync(OUTPUT_DIR)) {
266
242
  mkdirSync(OUTPUT_DIR, { recursive: true });
267
243
  }
268
244
  writeFileSync(OUTPUT_FILE, interfaceContent, "utf8");
269
- console.log(`Generated ${OUTPUT_FILE}`);
245
+ console.log("Generated:", OUTPUT_FILE);
270
246
  }
271
247
 
248
+ console.log("Entering try block");
272
249
  try {
273
250
  generateInterface();
274
251
  } catch (error) {
275
- console.error("Generation failed:", error);
252
+ console.error("Error in generateInterface:", error);
276
253
  process.exit(1);
277
254
  }
255
+
256
+ console.log("Script completed");