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

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 (82) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +46 -130
  3. package/dist/esm/quickbase.d.ts +1292 -125
  4. package/dist/esm/quickbase.js +666 -190
  5. package/dist/esm/quickbase.js.map +1 -1
  6. package/dist/esm/quickbase.min.js +1 -1
  7. package/dist/esm/quickbase.min.js.map +1 -1
  8. package/dist/umd/quickbase.umd.js +674 -189
  9. package/dist/umd/quickbase.umd.js.map +1 -1
  10. package/dist/umd/quickbase.umd.min.js +1 -1
  11. package/dist/umd/quickbase.umd.min.js.map +1 -1
  12. package/open-api/fix-spec-main.ts +69 -68
  13. package/open-api/generate-override-report.ts +214 -0
  14. package/open-api/generate-unified-interface.ts +162 -103
  15. package/open-api/output/override-report.html +11 -0
  16. package/open-api/output/override-report.json +14395 -58
  17. package/open-api/output/quickbase-fixed.json +583 -2
  18. package/open-api/schema/tags/fields.ts +331 -16
  19. package/open-api/schema/tags/index.ts +2 -4
  20. package/open-api/types/spec.ts +2 -1
  21. package/open-api/utils/generateJsDoc.ts +96 -0
  22. package/package.json +33 -33
  23. package/src/RateLimitError.ts +12 -0
  24. package/src/ThrottleBucket.ts +41 -0
  25. package/src/authorizationStrategy.ts +246 -0
  26. package/src/generated-unified/QuickbaseClient.ts +809 -119
  27. package/src/invokeMethod.ts +242 -0
  28. package/src/quickbaseClient.ts +117 -248
  29. package/src/rateLimiter.ts +25 -0
  30. package/src/tokenCache.ts +10 -26
  31. package/tests/setup.ts +2 -0
  32. package/tests/vitest/qb/apps/getApp.test.ts +6 -2
  33. package/tests/vitest/qb/apps/getAppEvents.test.ts +67 -0
  34. package/tests/vitest/qb/apps/updateApp.test.ts +58 -0
  35. package/tests/vitest/qb/auth/mockQuickbaseServer.ts +60 -0
  36. package/tests/vitest/qb/auth/retryUserToken401.test.ts +73 -0
  37. package/tests/vitest/qb/auth/ssoTokenIntegrationWithServer.test.ts +65 -0
  38. package/tests/vitest/qb/fields/createField.test.ts +1 -2
  39. package/tests/vitest/qb/fields/deleteFields.test.ts +10 -16
  40. package/tests/vitest/qb/fields/getFieldUsage.test.ts +58 -150
  41. package/tests/vitest/qb/fields/updateField.test.ts +93 -0
  42. package/tests/vitest/qb/formulas/runFormula.test.ts +96 -0
  43. package/tests/vitest/qb/index.ts +1 -0
  44. package/tests/vitest/qb/tables/createRelationship.test.ts +102 -0
  45. package/tests/vitest/qb/tables/deleteRelationship.test.ts +143 -0
  46. package/tests/vitest/qb/tables/updateRelationship.test.ts +250 -0
  47. package/tests/vitest/unit/ThrottleBucket.test.ts +95 -0
  48. package/tests/vitest/unit/apps/copyApp.test.ts +133 -40
  49. package/tests/vitest/unit/apps/getAppEvents.test.ts +193 -0
  50. package/tests/vitest/unit/apps/updateApp.test.ts +412 -0
  51. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +7 -26
  52. package/tests/vitest/unit/auth/getTempToken.test.ts +42 -18
  53. package/tests/vitest/unit/auth/retryTempToken401.test.ts +95 -0
  54. package/tests/vitest/unit/auth/retryTempTokenMaxRetries.test.ts +235 -0
  55. package/tests/vitest/unit/auth/retryUserToken401.test.ts +69 -0
  56. package/tests/vitest/unit/auth/ssoTokenRefresh.test.ts +325 -0
  57. package/tests/vitest/unit/fields/createField.test.ts +180 -120
  58. package/tests/vitest/unit/fields/getFieldUsage.test.ts +47 -14
  59. package/tests/vitest/unit/fields/updateField.test.ts +285 -0
  60. package/tests/vitest/unit/formulas/runFormula.test.ts +205 -0
  61. package/tests/vitest/unit/index.ts +2 -1
  62. package/tests/vitest/unit/inferHttpMethod.test.ts +131 -0
  63. package/tests/vitest/unit/rateLimit.test.ts +191 -0
  64. package/tests/vitest/unit/tables/createRelationship.test.ts +212 -0
  65. package/tests/vitest/unit/tables/deleteRelationship.test.ts +97 -0
  66. package/tests/vitest/unit/tables/updateRelationship.test.ts +307 -0
  67. package/tests/vitest/unit/tokenLifespan.test.ts +204 -0
  68. package/tools/log-gen.ts +20 -7
  69. package/tools/log-gen.yaml +3446 -1848
  70. package/tsconfig.json +5 -2
  71. package/vitest.config.ts +2 -2
  72. package/notes.txt +0 -158
  73. package/tests/vitest/unit/auth/retryOn401.test.ts +0 -116
  74. package/tests/vitest/unit/auth/two401s.test.ts +0 -86
  75. package/tools/generate-types.ts +0 -29
  76. package/tools/openapi-schema-validator.ts +0 -38
  77. package/tools/spec-analyzer.ts +0 -163
  78. package/tools/split-spec.ts +0 -148
  79. package/tools/test-import.ts +0 -3
  80. package/tools/validate-spec.ts +0 -40
  81. /package/tests/playwright/qb/auth/{tokenPrefetch.test.ts → tempTokenPrefetch.test.ts} +0 -0
  82. /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
- "transpileOnly": true
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"; // Add this import
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
-
@@ -1,116 +0,0 @@
1
- // tests/vitest/unit/auth/retryOn401.test.ts
2
- import { describe, it, expect, beforeEach, vi } from "vitest";
3
- import {
4
- createClient,
5
- mockFetch,
6
- QB_REALM,
7
- QB_TABLE_ID_1,
8
- } from "@tests/setup.ts";
9
-
10
- describe("QuickbaseClient Unit - 401 Retry Creates New Token", () => {
11
- let client: ReturnType<typeof createClient>;
12
-
13
- beforeEach(() => {
14
- mockFetch.mockClear();
15
- client = createClient(mockFetch, { useTempTokens: true, debug: true });
16
- });
17
-
18
- it("creates a new token on 401 and retries successfully", async () => {
19
- const mockToken = "new_token_456";
20
- const mockFields = [{ id: 1, label: "Field1" }];
21
- let callCount = 0;
22
-
23
- // Mock fetch without console.log interference
24
- mockFetch.mockImplementation((url) => {
25
- callCount++;
26
- if (url.includes("auth/temporary") && callCount === 1) {
27
- return Promise.resolve({
28
- ok: true,
29
- status: 200,
30
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
31
- });
32
- }
33
- if (url.includes("fields") && callCount === 2) {
34
- return Promise.resolve({
35
- ok: false,
36
- status: 401,
37
- json: () => Promise.resolve({ message: "Unauthorized" }),
38
- });
39
- }
40
- if (url.includes("auth/temporary") && callCount === 3) {
41
- return Promise.resolve({
42
- ok: true,
43
- status: 200,
44
- json: () =>
45
- Promise.resolve({ temporaryAuthorization: mockToken + "_retry" }),
46
- });
47
- }
48
- if (url.includes("fields") && callCount === 4) {
49
- return Promise.resolve({
50
- ok: true,
51
- status: 200,
52
- json: () => Promise.resolve(mockFields),
53
- });
54
- }
55
- return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
56
- });
57
-
58
- const consoleSpy = vi.spyOn(console, "log");
59
-
60
- const result = await client.getFields({ tableId: QB_TABLE_ID_1 });
61
-
62
- expect(result).toEqual(mockFields);
63
- expect(mockFetch).toHaveBeenCalledTimes(4);
64
- expect(mockFetch).toHaveBeenNthCalledWith(
65
- 1,
66
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
67
- expect.any(Object)
68
- );
69
- expect(mockFetch).toHaveBeenNthCalledWith(
70
- 2,
71
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
72
- expect.objectContaining({
73
- headers: expect.objectContaining({
74
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
75
- }),
76
- })
77
- );
78
- expect(mockFetch).toHaveBeenNthCalledWith(
79
- 3,
80
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
81
- expect.any(Object)
82
- );
83
- expect(mockFetch).toHaveBeenNthCalledWith(
84
- 4,
85
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`,
86
- expect.objectContaining({
87
- headers: expect.objectContaining({
88
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
89
- }),
90
- })
91
- );
92
-
93
- // Filter console logs to only those we care about
94
- const tokenLogs = consoleSpy.mock.calls.filter((call) =>
95
- call[0].includes("Fetched and cached new token")
96
- );
97
- expect(tokenLogs).toContainEqual([
98
- `Fetched and cached new token for dbid: ${QB_TABLE_ID_1}`,
99
- mockToken,
100
- ]);
101
- expect(tokenLogs).toContainEqual([
102
- `Fetched and cached new token for dbid: ${QB_TABLE_ID_1}`,
103
- mockToken + "_retry",
104
- ]);
105
-
106
- expect(consoleSpy).toHaveBeenCalledWith(
107
- "Authorization error for getFields, refreshing token:",
108
- expect.any(String)
109
- );
110
- expect(consoleSpy).toHaveBeenCalledWith(
111
- "Retrying getFields with new token"
112
- );
113
-
114
- consoleSpy.mockRestore();
115
- });
116
- });
@@ -1,86 +0,0 @@
1
- // tests/vitest/unit/auth/two401s.test.ts
2
- import { describe, it, expect, beforeEach, vi } from "vitest";
3
- import { createClient, mockFetch } from "@tests/setup.ts";
4
-
5
- describe("QuickbaseClient - Two 401s in a Row", () => {
6
- let client: ReturnType<typeof createClient>;
7
-
8
- beforeEach(() => {
9
- mockFetch.mockClear();
10
- client = createClient(mockFetch, { useTempTokens: true, debug: true });
11
- });
12
-
13
- it("logs an error after two 401s in a row", async () => {
14
- const mockDbid = "mockDbid123";
15
- const mockToken = "new_token_456";
16
- let callCount = 0;
17
-
18
- mockFetch.mockImplementation((url) => {
19
- callCount++;
20
- if (url.includes("auth/temporary") && callCount === 1) {
21
- return Promise.resolve({
22
- ok: true,
23
- status: 200,
24
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
25
- });
26
- }
27
- if (url.includes("fields") && callCount === 2) {
28
- return Promise.resolve({
29
- ok: false,
30
- status: 401,
31
- text: () => Promise.resolve("Unauthorized"),
32
- });
33
- }
34
- if (url.includes("auth/temporary") && callCount === 3) {
35
- return Promise.resolve({
36
- ok: true,
37
- status: 200,
38
- json: () =>
39
- Promise.resolve({ temporaryAuthorization: mockToken + "_retry" }),
40
- });
41
- }
42
- if (url.includes("fields") && callCount === 4) {
43
- return Promise.resolve({
44
- ok: false,
45
- status: 401,
46
- json: () => Promise.resolve({ message: "Unauthorized again" }),
47
- });
48
- }
49
- return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
50
- });
51
-
52
- const consoleSpy = vi.spyOn(console, "log");
53
-
54
- await expect(client.getFields({ tableId: mockDbid })).rejects.toThrow(
55
- "API Error: Unauthorized again (Status: 401)"
56
- );
57
-
58
- expect(mockFetch).toHaveBeenCalledTimes(4);
59
-
60
- const tokenLogs = consoleSpy.mock.calls.filter((call) =>
61
- call[0].includes("Fetched and cached new token")
62
- );
63
- expect(tokenLogs).toContainEqual([
64
- "Fetched and cached new token for dbid: mockDbid123",
65
- mockToken,
66
- ]);
67
- expect(tokenLogs).toContainEqual([
68
- "Fetched and cached new token for dbid: mockDbid123",
69
- mockToken + "_retry",
70
- ]);
71
-
72
- expect(consoleSpy).toHaveBeenCalledWith(
73
- "Authorization error for getFields, refreshing token:",
74
- expect.any(String)
75
- );
76
- expect(consoleSpy).toHaveBeenCalledWith(
77
- "Retrying getFields with new token"
78
- );
79
- expect(consoleSpy).toHaveBeenCalledWith(
80
- "Error response body for getFields:",
81
- { message: "Unauthorized again" }
82
- );
83
-
84
- consoleSpy.mockRestore();
85
- });
86
- });
@@ -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
- });
@@ -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();