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
@@ -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();