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.
- package/CHANGELOG.md +26 -0
- package/dist/esm/quickbase.d.ts +589 -125
- package/dist/esm/quickbase.js +484 -145
- package/dist/esm/quickbase.js.map +1 -1
- package/dist/esm/quickbase.min.js +1 -1
- package/dist/esm/quickbase.min.js.map +1 -1
- package/dist/umd/quickbase.umd.js +492 -144
- package/dist/umd/quickbase.umd.js.map +1 -1
- package/dist/umd/quickbase.umd.min.js +1 -1
- package/dist/umd/quickbase.umd.min.js.map +1 -1
- package/open-api/fix-spec-main.ts +69 -68
- package/open-api/generate-override-report.ts +214 -0
- package/open-api/generate-unified-interface.ts +73 -94
- package/open-api/output/override-report.html +11 -0
- package/open-api/output/override-report.json +14395 -58
- package/open-api/output/quickbase-fixed.json +583 -2
- package/open-api/schema/tags/fields.ts +331 -16
- package/open-api/schema/tags/index.ts +2 -4
- package/open-api/types/spec.ts +2 -1
- package/package.json +31 -33
- package/src/RateLimitError.ts +12 -0
- package/src/ThrottleBucket.ts +36 -0
- package/src/generated-unified/QuickbaseClient.ts +119 -119
- package/src/invokeMethod.ts +357 -0
- package/src/quickbaseClient.ts +51 -209
- package/tests/setup.ts +2 -0
- package/tests/vitest/qb/apps/getApp.test.ts +6 -2
- package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
- package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
- package/tests/vitest/qb/auth/retryUserToken401.test.ts +56 -0
- package/tests/vitest/qb/fields/createField.test.ts +1 -2
- package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
- package/tests/vitest/qb/fields/updateField.test.ts +93 -0
- package/tests/vitest/qb/index.ts +1 -0
- package/tests/vitest/qb/tables/updateRelationship.test.ts +277 -0
- package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
- package/tests/vitest/unit/apps/updateApp.test.ts +381 -0
- package/tests/vitest/unit/auth/fetchTempToken401.test.ts +6 -26
- package/tests/vitest/unit/auth/{retryOn401.test.ts → retryTempToken401.test.ts} +3 -6
- package/tests/vitest/unit/auth/{two401s.test.ts → retryTempTokenTwo401.test.ts} +4 -9
- package/tests/vitest/unit/auth/retryUserToken401.test.ts +56 -0
- package/tests/vitest/unit/fields/createField.test.ts +8 -12
- package/tests/vitest/unit/fields/getFieldUsage.test.ts +33 -14
- package/tests/vitest/unit/fields/updateField.test.ts +280 -0
- package/tests/vitest/unit/rateLimit.test.ts +189 -0
- package/tests/vitest/unit/tables/updateRelationship.test.ts +305 -0
- package/tests/vitest/unit/tokenLifespan.test.ts +138 -0
- package/tools/log-gen.ts +8 -4
- package/tools/log-gen.yaml +3004 -1848
- package/tsconfig.json +5 -2
- package/vitest.config.ts +2 -2
- package/notes.txt +0 -158
- package/tools/generate-types.ts +0 -29
- package/tools/openapi-schema-validator.ts +0 -38
- package/tools/spec-analyzer.ts +0 -163
- package/tools/split-spec.ts +0 -148
- package/tools/test-import.ts +0 -3
- package/tools/validate-spec.ts +0 -40
- /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
- /package/tests/playwright/qb/auth/{tokenRenewal.test.ts → tempTokenRenewal.test.ts} +0 -0
package/tsconfig.json
CHANGED
|
@@ -17,13 +17,16 @@
|
|
|
17
17
|
"baseUrl": ".",
|
|
18
18
|
"paths": {
|
|
19
19
|
"@/*": ["src/*"],
|
|
20
|
-
"@tests/*": ["tests/*"]
|
|
20
|
+
"@tests/*": ["tests/*"],
|
|
21
|
+
"undici-types": ["node_modules/undici-types/index.d.ts"],
|
|
22
|
+
"openapi-types": ["node_modules/openapi-types/index.d.ts"],
|
|
23
|
+
"jsondiffpatch": ["node_modules/jsondiffpatch/lib/index.d.ts"]
|
|
21
24
|
}
|
|
22
25
|
},
|
|
23
26
|
"include": ["src/**/*", "tests/**/*", "open-api/**/*", "tools/**/*"],
|
|
24
27
|
"exclude": ["node_modules", "dist/**/*", "generated/**/*", "specs"],
|
|
25
28
|
"ts-node": {
|
|
26
29
|
"esm": true,
|
|
27
|
-
"
|
|
30
|
+
"experimentalResolver": true
|
|
28
31
|
}
|
|
29
32
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
// vitest.config.ts
|
|
2
1
|
import { defineConfig } from "vitest/config";
|
|
3
2
|
import path from "path";
|
|
4
|
-
import dotenv from "dotenv";
|
|
3
|
+
import dotenv from "dotenv";
|
|
5
4
|
|
|
6
5
|
// Load .env variables before tests run
|
|
7
6
|
dotenv.config({ path: path.resolve(__dirname, ".env") });
|
|
@@ -17,6 +16,7 @@ export default defineConfig({
|
|
|
17
16
|
singleFork: true, // Run tests sequentially
|
|
18
17
|
},
|
|
19
18
|
},
|
|
19
|
+
testTimeout: 10000, // Global timeout of 10 seconds for all tests
|
|
20
20
|
},
|
|
21
21
|
resolve: {
|
|
22
22
|
alias: {
|
package/notes.txt
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
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
|
-
|
package/tools/generate-types.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// tools/generate-types.ts
|
|
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 { execSync } from "child_process";
|
|
7
|
-
|
|
8
|
-
async function generateTypes(): Promise<void> {
|
|
9
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const SPLIT_DIR = path.join(CODEGEN_DIR, "..", "specs", "split");
|
|
11
|
-
const OUTPUT_DIR = path.join(CODEGEN_DIR, "..", "src", "generated-types");
|
|
12
|
-
|
|
13
|
-
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
14
|
-
|
|
15
|
-
const specFiles = glob.sync(path.join(SPLIT_DIR, "*.json"));
|
|
16
|
-
for (const specFile of specFiles) {
|
|
17
|
-
const category = path.basename(specFile, ".json");
|
|
18
|
-
const outputFile = path.join(OUTPUT_DIR, `${category}.ts`);
|
|
19
|
-
console.log(`Generating types for ${category}...`);
|
|
20
|
-
execSync(`npx openapi-typescript ${specFile} --output ${outputFile}`, {
|
|
21
|
-
stdio: "inherit",
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
generateTypes().catch((error) => {
|
|
27
|
-
console.error("Failed to generate types:", error);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
// tools/validate-spec.ts
|
|
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 OpenAPISchemaValidator from "openapi-schema-validator";
|
|
7
|
-
|
|
8
|
-
async function validateSpecs(): Promise<void> {
|
|
9
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
const SPLIT_DIR = path.join(CODEGEN_DIR, "..", "specs", "split");
|
|
11
|
-
|
|
12
|
-
const specFiles = glob.sync(path.join(SPLIT_DIR, "*.json"));
|
|
13
|
-
if (specFiles.length === 0) {
|
|
14
|
-
console.error("No split spec files found in specs/split/ folder.");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const validator = new OpenAPISchemaValidator({ version: 2 }); // Adjust based on spec version
|
|
19
|
-
|
|
20
|
-
for (const specFile of specFiles) {
|
|
21
|
-
console.log(`Validating ${path.basename(specFile)}...`);
|
|
22
|
-
const specContent = await fs.readFile(specFile, "utf8");
|
|
23
|
-
const spec = JSON.parse(specContent);
|
|
24
|
-
|
|
25
|
-
const result = validator.validate(spec);
|
|
26
|
-
if (result.errors.length > 0) {
|
|
27
|
-
console.error(`Validation errors in ${specFile}:`, result.errors);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
} else {
|
|
30
|
-
console.log(`${specFile} is valid.`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
validateSpecs().catch((error) => {
|
|
36
|
-
console.error("Failed to validate specs:", error);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
});
|
package/tools/spec-analyzer.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
import * as glob from "glob";
|
|
5
|
-
|
|
6
|
-
interface Parameter {
|
|
7
|
-
name: string;
|
|
8
|
-
in: string;
|
|
9
|
-
required?: boolean;
|
|
10
|
-
type?: string;
|
|
11
|
-
schema?: { type?: string; items?: any; $ref?: string; properties?: any };
|
|
12
|
-
example?: any;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface Operation {
|
|
16
|
-
parameters?: Parameter[];
|
|
17
|
-
responses?: Record<string, { description: string; schema?: any }>;
|
|
18
|
-
operationId?: string;
|
|
19
|
-
summary?: string;
|
|
20
|
-
tags?: string[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface Spec {
|
|
24
|
-
paths: Record<string, Record<string, Operation>>;
|
|
25
|
-
definitions?: Record<string, any>;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface TypeInference {
|
|
29
|
-
fieldId: string;
|
|
30
|
-
inferredType: string;
|
|
31
|
-
source: "schema" | "example";
|
|
32
|
-
confidence: number;
|
|
33
|
-
context: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
interface AnalysisReport {
|
|
37
|
-
requestTypes: TypeInference[];
|
|
38
|
-
responseTypes: { [status: string]: TypeInference[] };
|
|
39
|
-
dynamicFields: TypeInference[];
|
|
40
|
-
errors: string[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function inferTypeFromValue(value: any): string {
|
|
44
|
-
if (typeof value === "string") return "string";
|
|
45
|
-
if (typeof value === "number")
|
|
46
|
-
return Number.isInteger(value) ? "integer" : "number";
|
|
47
|
-
if (typeof value === "boolean") return "boolean";
|
|
48
|
-
if (Array.isArray(value)) {
|
|
49
|
-
if (value.length === 0) return "any[]";
|
|
50
|
-
const itemTypes = value.map(inferTypeFromValue);
|
|
51
|
-
const uniqueTypes = [...new Set(itemTypes)];
|
|
52
|
-
return uniqueTypes.length === 1 ? `${uniqueTypes[0]}[]` : "any[]";
|
|
53
|
-
}
|
|
54
|
-
if (typeof value === "object" && value !== null) {
|
|
55
|
-
return "object";
|
|
56
|
-
}
|
|
57
|
-
return "any";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function analyzeDynamicFields(
|
|
61
|
-
schema: any,
|
|
62
|
-
analysis: AnalysisReport,
|
|
63
|
-
context: string
|
|
64
|
-
) {
|
|
65
|
-
const examples = schema["x-amf-examples"];
|
|
66
|
-
if (examples) {
|
|
67
|
-
for (const [exampleName, example] of Object.entries(examples)) {
|
|
68
|
-
const data = example.value?.data;
|
|
69
|
-
if (Array.isArray(data)) {
|
|
70
|
-
data.forEach((record: any, index: number) => {
|
|
71
|
-
for (const [fieldId, fieldValue] of Object.entries(record)) {
|
|
72
|
-
const inferredType = inferTypeFromValue(fieldValue.value);
|
|
73
|
-
analysis.dynamicFields.push({
|
|
74
|
-
fieldId,
|
|
75
|
-
inferredType,
|
|
76
|
-
source: "example",
|
|
77
|
-
confidence: 0.8,
|
|
78
|
-
context: `${context}-example-${exampleName}-${index}`,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Add schema-based type extraction logic
|
|
87
|
-
if (schema.properties?.data?.items) {
|
|
88
|
-
// Add logic to extract types from schema
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function analyzeUpsertSpec(spec: Spec): AnalysisReport {
|
|
93
|
-
const upsertPath = spec.paths["/records"]?.post;
|
|
94
|
-
if (!upsertPath)
|
|
95
|
-
return {
|
|
96
|
-
errors: ["Upsert endpoint not found"],
|
|
97
|
-
requestTypes: [],
|
|
98
|
-
responseTypes: {},
|
|
99
|
-
dynamicFields: [],
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const analysis: AnalysisReport = {
|
|
103
|
-
requestTypes: [],
|
|
104
|
-
responseTypes: { "200": [], "207": [] },
|
|
105
|
-
dynamicFields: [],
|
|
106
|
-
errors: [],
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const requestSchema = upsertPath.parameters?.find(
|
|
110
|
-
(p) => p.in === "body"
|
|
111
|
-
)?.schema;
|
|
112
|
-
if (requestSchema) {
|
|
113
|
-
analyzeDynamicFields(requestSchema, analysis, "request");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (const status of ["200", "207"]) {
|
|
117
|
-
const responseSchema = upsertPath.responses?.[status]?.schema;
|
|
118
|
-
if (responseSchema) {
|
|
119
|
-
analyzeDynamicFields(responseSchema, analysis, `response-${status}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return analysis;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function runAnalysis() {
|
|
127
|
-
try {
|
|
128
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
129
|
-
const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
|
|
130
|
-
const OUTPUT_DIR = path.join(CODEGEN_DIR, "..", "tools", "analysis");
|
|
131
|
-
console.log("Finding latest QuickBase RESTful API spec...");
|
|
132
|
-
const specFiles = glob.sync(
|
|
133
|
-
path.join(SPECS_DIR, "QuickBase_RESTful_*.json")
|
|
134
|
-
);
|
|
135
|
-
if (specFiles.length === 0) {
|
|
136
|
-
console.error(
|
|
137
|
-
"No QuickBase_RESTful_*.json files found in specs/ folder."
|
|
138
|
-
);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
const inputFile = specFiles.sort().pop() as string;
|
|
142
|
-
console.log(`Reading ${path.basename(inputFile)} from specs/...`);
|
|
143
|
-
const specContent = await fs.readFile(inputFile, "utf8");
|
|
144
|
-
const spec: Spec = JSON.parse(specContent);
|
|
145
|
-
|
|
146
|
-
const analysis = analyzeUpsertSpec(spec);
|
|
147
|
-
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
148
|
-
await fs.writeFile(
|
|
149
|
-
path.join(OUTPUT_DIR, "upsert-analysis.json"),
|
|
150
|
-
JSON.stringify(analysis, null, 2),
|
|
151
|
-
"utf8"
|
|
152
|
-
);
|
|
153
|
-
console.log(
|
|
154
|
-
"Analysis complete. Output written to:",
|
|
155
|
-
path.join(OUTPUT_DIR, "upsert-analysis.json")
|
|
156
|
-
);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error("Failed to analyze spec:", error);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
runAnalysis();
|
package/tools/split-spec.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
// tools/split-spec.ts
|
|
2
|
-
import { promises as fs } from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import * as glob from "glob";
|
|
6
|
-
|
|
7
|
-
interface Parameter {
|
|
8
|
-
name: string;
|
|
9
|
-
in: string;
|
|
10
|
-
required?: boolean;
|
|
11
|
-
type?: string;
|
|
12
|
-
schema?: any;
|
|
13
|
-
example?: any;
|
|
14
|
-
description?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface Operation {
|
|
18
|
-
parameters?: Parameter[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface Spec {
|
|
22
|
-
swagger?: string;
|
|
23
|
-
openapi?: string;
|
|
24
|
-
info: any;
|
|
25
|
-
paths: Record<string, Record<string, Operation>>;
|
|
26
|
-
definitions?: Record<string, any>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function normalizeParameters(parameters: Parameter[] = []): Parameter[] {
|
|
30
|
-
return parameters.map((param) => {
|
|
31
|
-
const normalized: Parameter = {
|
|
32
|
-
name: param.name,
|
|
33
|
-
in: param.in,
|
|
34
|
-
required: param.required,
|
|
35
|
-
description: param.description,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// OpenAPI v2: 'body' parameters use 'schema', others use 'type'
|
|
39
|
-
if (param.in === "body") {
|
|
40
|
-
if (param.schema) {
|
|
41
|
-
normalized.schema = param.schema;
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
normalized.type = param.type || "string";
|
|
45
|
-
if (param.example !== undefined) {
|
|
46
|
-
delete param.example; // Remove 'example' as it’s not allowed in v2
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Ensure 'in' is valid
|
|
51
|
-
if (
|
|
52
|
-
!["query", "header", "path", "formData", "body"].includes(normalized.in)
|
|
53
|
-
) {
|
|
54
|
-
console.warn(
|
|
55
|
-
`Invalid 'in' value '${normalized.in}' for parameter '${normalized.name}', defaulting to 'query'`
|
|
56
|
-
);
|
|
57
|
-
normalized.in = "query";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return normalized;
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function splitSpec(): Promise<void> {
|
|
65
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
66
|
-
const SPECS_DIR = path.join(CODEGEN_DIR, "..", "specs");
|
|
67
|
-
const OUTPUT_DIR = path.join(SPECS_DIR, "split");
|
|
68
|
-
|
|
69
|
-
// Find the latest spec file
|
|
70
|
-
const specFiles = glob.sync(path.join(SPECS_DIR, "QuickBase_RESTful_*.json"));
|
|
71
|
-
if (specFiles.length === 0) {
|
|
72
|
-
console.error("No QuickBase_RESTful_*.json files found in specs/ folder.");
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
const inputFile = specFiles.sort().pop() as string;
|
|
76
|
-
const specContent = await fs.readFile(inputFile, "utf8");
|
|
77
|
-
const spec: Spec = JSON.parse(specContent);
|
|
78
|
-
|
|
79
|
-
// Define categories based on endpoint paths
|
|
80
|
-
const categories: Record<string, string[]> = {
|
|
81
|
-
apps: Object.keys(spec.paths).filter((p) => p.startsWith("/apps")),
|
|
82
|
-
auth: Object.keys(spec.paths).filter((p) => p.startsWith("/auth")),
|
|
83
|
-
fields: Object.keys(spec.paths).filter((p) => p.startsWith("/fields")),
|
|
84
|
-
records: Object.keys(spec.paths).filter((p) => p.startsWith("/records")),
|
|
85
|
-
tables: Object.keys(spec.paths).filter((p) => p.startsWith("/tables")),
|
|
86
|
-
reports: Object.keys(spec.paths).filter((p) => p.startsWith("/reports")),
|
|
87
|
-
relationships: Object.keys(spec.paths).filter((p) =>
|
|
88
|
-
p.startsWith("/relationships")
|
|
89
|
-
),
|
|
90
|
-
events: Object.keys(spec.paths).filter((p) => p.startsWith("/events")),
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
await fs.mkdir(OUTPUT_DIR, { recursive: true });
|
|
94
|
-
|
|
95
|
-
// Split spec by category
|
|
96
|
-
for (const [category, paths] of Object.entries(categories)) {
|
|
97
|
-
if (paths.length === 0) continue;
|
|
98
|
-
|
|
99
|
-
const categorySpec: Spec = {
|
|
100
|
-
swagger: "2.0", // OpenAPI v2
|
|
101
|
-
info: spec.info || { title: `${category} API`, version: "1.0.0" },
|
|
102
|
-
paths: {},
|
|
103
|
-
definitions: {},
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Add relevant paths and normalize parameters
|
|
107
|
-
for (const p of paths) {
|
|
108
|
-
const pathObj = { ...spec.paths[p] };
|
|
109
|
-
for (const method in pathObj) {
|
|
110
|
-
if (pathObj[method].parameters) {
|
|
111
|
-
pathObj[method].parameters = normalizeParameters(
|
|
112
|
-
pathObj[method].parameters
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
categorySpec.paths[p] = pathObj;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Add relevant definitions
|
|
120
|
-
const refs = new Set<string>();
|
|
121
|
-
const refRegex = /#\/definitions\/([^"]+)/g;
|
|
122
|
-
const categorySpecString = JSON.stringify(categorySpec);
|
|
123
|
-
let match;
|
|
124
|
-
while ((match = refRegex.exec(categorySpecString))) {
|
|
125
|
-
refs.add(match[1]);
|
|
126
|
-
}
|
|
127
|
-
if (spec.definitions) {
|
|
128
|
-
for (const ref of refs) {
|
|
129
|
-
if (spec.definitions[ref]) {
|
|
130
|
-
categorySpec.definitions![ref] = spec.definitions[ref];
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const outputFile = path.join(OUTPUT_DIR, `${category}.json`);
|
|
136
|
-
await fs.writeFile(
|
|
137
|
-
outputFile,
|
|
138
|
-
JSON.stringify(categorySpec, null, 2),
|
|
139
|
-
"utf8"
|
|
140
|
-
);
|
|
141
|
-
console.log(`Wrote ${outputFile}`);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
splitSpec().catch((error) => {
|
|
146
|
-
console.error("Failed to split spec:", error);
|
|
147
|
-
process.exit(1);
|
|
148
|
-
});
|
package/tools/test-import.ts
DELETED
package/tools/validate-spec.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// tools/validate-spec.ts
|
|
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 OpenAPISchemaValidatorModule from "openapi-schema-validator";
|
|
7
|
-
|
|
8
|
-
const OpenAPISchemaValidator = OpenAPISchemaValidatorModule.default;
|
|
9
|
-
|
|
10
|
-
async function validateSpecs(): Promise<void> {
|
|
11
|
-
const CODEGEN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const SPLIT_DIR = path.join(CODEGEN_DIR, "..", "specs", "split");
|
|
13
|
-
|
|
14
|
-
const specFiles = glob.sync(path.join(SPLIT_DIR, "*.json"));
|
|
15
|
-
if (specFiles.length === 0) {
|
|
16
|
-
console.error("No split spec files found in specs/split/ folder.");
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const validator = new OpenAPISchemaValidator({ version: 2 });
|
|
21
|
-
|
|
22
|
-
for (const specFile of specFiles) {
|
|
23
|
-
console.log(`Validating ${path.basename(specFile)}...`);
|
|
24
|
-
const specContent = await fs.readFile(specFile, "utf8");
|
|
25
|
-
const spec = JSON.parse(specContent);
|
|
26
|
-
|
|
27
|
-
const result = validator.validate(spec);
|
|
28
|
-
if (result.errors.length > 0) {
|
|
29
|
-
console.error(`Validation errors in ${specFile}:`, result.errors);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
} else {
|
|
32
|
-
console.log(`${specFile} is valid.`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
validateSpecs().catch((error) => {
|
|
38
|
-
console.error("Failed to validate specs:", error);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
});
|
|
File without changes
|
|
File without changes
|