quickbase-js 1.0.1-beta.0

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 (52) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +102 -0
  4. package/build.js +66 -0
  5. package/dist/quickbaseClient.d.ts +3888 -0
  6. package/dist/quickbaseClient.js +5812 -0
  7. package/dist/quickbaseClient.js.map +1 -0
  8. package/examples/react.js +22 -0
  9. package/examples/vanillajs.html +23 -0
  10. package/logGen.yaml +1820 -0
  11. package/package.json +72 -0
  12. package/playwright.config.ts +37 -0
  13. package/rollup.config.js +70 -0
  14. package/src/code-generation/fix-spec-definitions.ts +136 -0
  15. package/src/code-generation/fix-spec-main.ts +176 -0
  16. package/src/code-generation/fix-spec-paths.ts +163 -0
  17. package/src/code-generation/generate-unified-interface.ts +152 -0
  18. package/src/code-generation/output/quickbase-fixed.json +8435 -0
  19. package/src/code-generation/regenerate-client.ts +110 -0
  20. package/src/generated-unified/QuickbaseClient.ts +59 -0
  21. package/src/index.ts +1 -0
  22. package/src/log-gen.ts +122 -0
  23. package/src/quickbaseClient.ts +320 -0
  24. package/src/specs/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +46878 -0
  25. package/src/tokenCache.ts +51 -0
  26. package/src/utils.ts +5 -0
  27. package/test-artifacts/.last-run.json +4 -0
  28. package/test-artifacts/test-results.json +1801 -0
  29. package/test-results.json +2050 -0
  30. package/tests/playwright/qb/auth/cacheSwitching.test.ts +189 -0
  31. package/tests/playwright/qb/auth/fetchTempToken401.test.ts +217 -0
  32. package/tests/playwright/qb/auth/getAppWithUserToken.test.ts +151 -0
  33. package/tests/playwright/qb/auth/getTempToken.test.ts +180 -0
  34. package/tests/playwright/qb/auth/tokenPrefetch.test.ts +192 -0
  35. package/tests/playwright/qb/auth/tokenRenewal.test.ts +110 -0
  36. package/tests/setup.ts +19 -0
  37. package/tests/vitest/qb/apps/getApp.test.ts +46 -0
  38. package/tests/vitest/qb/fields/getFields.test.ts +234 -0
  39. package/tests/vitest/qb/index.ts +4 -0
  40. package/tests/vitest/qb/tables/getAppTables.test.ts +67 -0
  41. package/tests/vitest/unit/apps/getApp.test.ts +81 -0
  42. package/tests/vitest/unit/auth/fetchTempToken401.test.ts +59 -0
  43. package/tests/vitest/unit/auth/getTempToken.test.ts +83 -0
  44. package/tests/vitest/unit/auth/retryOn401.test.ts +72 -0
  45. package/tests/vitest/unit/auth/two401s.test.ts +77 -0
  46. package/tests/vitest/unit/fields/getFields.test.ts +115 -0
  47. package/tests/vitest/unit/index.ts +4 -0
  48. package/tests/vitest/unit/tables/getAppTables.test.ts +72 -0
  49. package/text.txt +104 -0
  50. package/tsconfig.json +28 -0
  51. package/versionrc.json +9 -0
  52. package/vitest.config.ts +21 -0
@@ -0,0 +1,180 @@
1
+ // tests/integration/auth/getTempToken.test.ts
2
+ import { test, expect } from "@playwright/test";
3
+ import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
4
+ import fetch from "node-fetch";
5
+ import dotenv from "dotenv";
6
+
7
+ dotenv.config();
8
+
9
+ const loginToQuickbase = async (
10
+ page: import("@playwright/test").Page,
11
+ quickbaseUrl: string,
12
+ username: string,
13
+ password: string
14
+ ): Promise<boolean> => {
15
+ console.log("Logging in to QuickBase");
16
+ let loginSuccess = false;
17
+ const maxLoginAttempts = 3;
18
+ let loginAttempt = 0;
19
+
20
+ while (!loginSuccess && loginAttempt < maxLoginAttempts) {
21
+ try {
22
+ loginAttempt++;
23
+ if (loginAttempt > 1) console.log(`Login attempt ${loginAttempt}`);
24
+
25
+ await page.goto(quickbaseUrl, { timeout: 60000 });
26
+ await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
27
+ await page.fill("input[name='loginid']", username);
28
+ await page.fill("input[name='password']", password);
29
+ await page.click("#signin");
30
+ await page.waitForURL(`https://*.quickbase.com/**`, {
31
+ waitUntil: "networkidle",
32
+ timeout: 60000,
33
+ });
34
+
35
+ const loginError = await page.$(".login-error");
36
+ if (loginError) {
37
+ throw new Error("Login failed. Please check your credentials.");
38
+ }
39
+
40
+ console.log("Signed In to QuickBase.");
41
+ loginSuccess = true;
42
+ } catch (error) {
43
+ console.error(
44
+ `Login attempt ${loginAttempt} failed: ${(error as Error).message}`
45
+ );
46
+ if (loginAttempt >= maxLoginAttempts) {
47
+ console.error("Max login attempts reached. Exiting.");
48
+ await page.context().browser()!.close();
49
+ return false;
50
+ }
51
+ await new Promise((resolve) => setTimeout(resolve, 5000));
52
+ }
53
+ }
54
+ return loginSuccess;
55
+ };
56
+
57
+ test.describe("QuickbaseClient Integration - getApp with Temp Tokens", () => {
58
+ test("uses temp tokens automatically for getApp in browser", async ({
59
+ page,
60
+ }) => {
61
+ const realm = process.env.QB_REALM;
62
+ const appId = process.env.QB_APP_ID;
63
+ const username = process.env.QB_USERNAME;
64
+ const password = process.env.QB_PASSWORD;
65
+
66
+ if (!realm) throw new Error("QB_REALM is not defined in .env");
67
+ if (!appId) throw new Error("QB_APP_ID is not defined in .env");
68
+ if (!username) throw new Error("QB_USERNAME is not defined in .env");
69
+ if (!password) throw new Error("QB_PASSWORD is not defined in .env");
70
+
71
+ // Login to QuickBase
72
+ const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
73
+ const loginSuccess = await loginToQuickbase(
74
+ page,
75
+ quickbaseUrl,
76
+ username,
77
+ password
78
+ );
79
+ if (!loginSuccess) {
80
+ throw new Error("Failed to log in to QuickBase after max attempts.");
81
+ }
82
+
83
+ // Navigate to the app page to ensure session context
84
+ await page.goto(`https://${realm}.quickbase.com/db/${appId}`, {
85
+ waitUntil: "networkidle",
86
+ timeout: 60000,
87
+ });
88
+ console.log("Post-login URL after app navigation:", page.url());
89
+
90
+ // Define browser fetch function
91
+ const browserFetch = async (url: string, init?: RequestInit) => {
92
+ const response = await page.evaluate(
93
+ async ([fetchUrl, fetchInit]) => {
94
+ const res = await fetch(fetchUrl, {
95
+ ...fetchInit,
96
+ credentials: "include",
97
+ });
98
+ const body = await res.text();
99
+ return {
100
+ ok: res.ok,
101
+ status: res.status,
102
+ statusText: res.statusText,
103
+ headers: Object.fromEntries(res.headers.entries()),
104
+ body,
105
+ };
106
+ },
107
+ [url, init] as [string, RequestInit]
108
+ );
109
+
110
+ console.log(`Raw response from ${url}:`, {
111
+ status: response.status,
112
+ statusText: response.statusText,
113
+ body: response.body,
114
+ });
115
+
116
+ const fetchResponse = new Response(response.body || null, {
117
+ status: response.status,
118
+ statusText: response.statusText,
119
+ headers: response.headers,
120
+ });
121
+
122
+ fetchResponse.json = async () => {
123
+ if (!response.body) throw new Error("Empty response body from API");
124
+ try {
125
+ return JSON.parse(response.body);
126
+ } catch (e) {
127
+ throw new SyntaxError(`Invalid JSON response: ${response.body}`);
128
+ }
129
+ };
130
+
131
+ return fetchResponse;
132
+ };
133
+
134
+ // Single client with conditional fetch
135
+ const client = quickbaseClient({
136
+ realm,
137
+ useTempTokens: true,
138
+ debug: true,
139
+ fetchApi: async (url, init) => {
140
+ if (url.includes("/auth/temporary/")) {
141
+ return browserFetch(url, init); // Use browser context for temp token
142
+ }
143
+ console.log(`Fetching ${url} with init:`, init);
144
+ const response = await fetch(url, init as RequestInit); // Node-fetch for API calls
145
+ const body = await response.text();
146
+
147
+ console.log(`Raw response from ${url}:`, {
148
+ status: response.status,
149
+ statusText: response.statusText,
150
+ body,
151
+ });
152
+
153
+ const fetchResponse = new Response(body || null, {
154
+ status: response.status,
155
+ statusText: response.statusText,
156
+ headers: Object.fromEntries(response.headers.entries()),
157
+ });
158
+
159
+ fetchResponse.json = async () => {
160
+ if (!body) throw new Error("Empty response body from API");
161
+ try {
162
+ return JSON.parse(body);
163
+ } catch (e) {
164
+ throw new SyntaxError(`Invalid JSON response: ${body}`);
165
+ }
166
+ };
167
+
168
+ return fetchResponse;
169
+ },
170
+ });
171
+
172
+ // Call getApp directly, letting the library handle temp token logic
173
+ const appResult = await client.getApp({ appId });
174
+ console.log("App response using auto-fetched temp token:", appResult);
175
+
176
+ // Validate the app response
177
+ expect(appResult).toHaveProperty("id", appId);
178
+ expect(appResult).toHaveProperty("name");
179
+ });
180
+ });
@@ -0,0 +1,192 @@
1
+ // tests/playwright/qb/auth/tokenPrefetch.test.ts
2
+ import { test, expect } from "@playwright/test";
3
+ import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
4
+ import fetch from "node-fetch";
5
+ import dotenv from "dotenv";
6
+
7
+ dotenv.config();
8
+
9
+ const loginToQuickbase = async (
10
+ page: import("@playwright/test").Page,
11
+ quickbaseUrl: string,
12
+ username: string,
13
+ password: string
14
+ ): Promise<boolean> => {
15
+ console.log("Logging in to QuickBase");
16
+ let loginSuccess = false;
17
+ const maxLoginAttempts = 3;
18
+ let loginAttempt = 0;
19
+
20
+ while (!loginSuccess && loginAttempt < maxLoginAttempts) {
21
+ try {
22
+ loginAttempt++;
23
+ if (loginAttempt > 1) console.log(`Login attempt ${loginAttempt}`);
24
+
25
+ await page.goto(quickbaseUrl, { timeout: 60000 });
26
+ await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
27
+ await page.fill("input[name='loginid']", username);
28
+ await page.fill("input[name='password']", password);
29
+ await page.click("#signin");
30
+ await page.waitForURL(`https://*.quickbase.com/**`, {
31
+ waitUntil: "networkidle",
32
+ timeout: 60000,
33
+ });
34
+
35
+ const loginError = await page.$(".login-error");
36
+ if (loginError) {
37
+ throw new Error("Login failed. Please check your credentials.");
38
+ }
39
+
40
+ console.log("Signed In to QuickBase.");
41
+ loginSuccess = true;
42
+ } catch (error) {
43
+ console.error(
44
+ `Login attempt ${loginAttempt} failed: ${(error as Error).message}`
45
+ );
46
+ if (loginAttempt >= maxLoginAttempts) {
47
+ console.error("Max login attempts reached. Exiting.");
48
+ await page.context().browser()!.close();
49
+ return false;
50
+ }
51
+ await new Promise((resolve) => setTimeout(resolve, 5000));
52
+ }
53
+ }
54
+ return loginSuccess;
55
+ };
56
+
57
+ test.describe("QuickbaseClient Integration - Token Pre-Fetch and Cache Reuse", () => {
58
+ test("pre-fetches token and reuses it for subsequent API calls", async ({
59
+ page,
60
+ }) => {
61
+ const realm = process.env.QB_REALM;
62
+ const appId = process.env.QB_APP_ID;
63
+ const username = process.env.QB_USERNAME;
64
+ const password = process.env.QB_PASSWORD;
65
+
66
+ if (!realm) throw new Error("QB_REALM is not defined in .env");
67
+ if (!appId) throw new Error("QB_APP_ID is not defined in .env");
68
+ if (!username) throw new Error("QB_USERNAME is not defined in .env");
69
+ if (!password) throw new Error("QB_PASSWORD is not defined in .env");
70
+
71
+ // Login to QuickBase
72
+ const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
73
+ const loginSuccess = await loginToQuickbase(
74
+ page,
75
+ quickbaseUrl,
76
+ username,
77
+ password
78
+ );
79
+ if (!loginSuccess) {
80
+ throw new Error("Failed to log in to QuickBase after max attempts.");
81
+ }
82
+
83
+ // Navigate to the app page to ensure session context
84
+ await page.goto(`https://${realm}.quickbase.com/db/${appId}`, {
85
+ waitUntil: "networkidle",
86
+ timeout: 60000,
87
+ });
88
+ console.log("Post-login URL after app navigation:", page.url());
89
+
90
+ // Track tokens and call counts
91
+ let callCount = 0;
92
+ let cachedToken: string | null = null;
93
+
94
+ const browserFetch = async (url: string, init?: RequestInit) => {
95
+ console.log(`browserFetch called for ${url}, callCount: ${callCount}`);
96
+ const response = await page.evaluate(
97
+ async ([fetchUrl, fetchInit]) => {
98
+ const res = await fetch(fetchUrl, {
99
+ ...fetchInit,
100
+ credentials: "include",
101
+ });
102
+ const body = await res.text();
103
+ return {
104
+ ok: res.ok,
105
+ status: res.status,
106
+ statusText: res.statusText,
107
+ headers: Object.fromEntries(res.headers.entries()),
108
+ body,
109
+ };
110
+ },
111
+ [url, init] as [string, RequestInit]
112
+ );
113
+
114
+ console.log(`Raw response from ${url}:`, {
115
+ status: response.status,
116
+ statusText: response.statusText,
117
+ body: response.body,
118
+ });
119
+
120
+ const fetchResponse = new Response(response.body || null, {
121
+ status: response.status,
122
+ statusText: response.statusText,
123
+ headers: response.headers,
124
+ });
125
+
126
+ // Capture token from response
127
+ if (url.includes("/auth/temporary/")) {
128
+ const json = await fetchResponse.clone().json();
129
+ const token = json.temporaryAuthorization;
130
+ cachedToken = token; // Store the pre-fetched token
131
+ console.log(`Captured pre-fetched token: ${cachedToken}`);
132
+ }
133
+
134
+ return fetchResponse;
135
+ };
136
+
137
+ const client = quickbaseClient({
138
+ realm,
139
+ useTempTokens: true,
140
+ debug: true,
141
+ fetchApi: async (url, init) => {
142
+ callCount++;
143
+ console.log(`fetchApi called for ${url}, callCount: ${callCount}`);
144
+
145
+ if (url.includes("/auth/temporary/")) {
146
+ return browserFetch(url, init); // Use browser fetch for temp tokens
147
+ }
148
+
149
+ // Use node-fetch for app calls, ensuring we track reuse
150
+ console.log(`Fetching ${url} with init:`, init);
151
+ const response = await fetch(url, init as RequestInit);
152
+ const body = await response.text();
153
+
154
+ console.log(`Raw response from ${url}:`, {
155
+ status: response.status,
156
+ statusText: response.statusText,
157
+ body,
158
+ });
159
+
160
+ return new Response(body || null, {
161
+ status: response.status,
162
+ statusText: response.statusText,
163
+ headers: Object.fromEntries(response.headers.entries()),
164
+ });
165
+ },
166
+ });
167
+
168
+ // Step 1: Pre-fetch token
169
+ console.log("Pre-fetching token...");
170
+ const tokenResult = await client.getTempTokenDBID({ dbid: appId });
171
+ console.log("Pre-fetched token result:", tokenResult);
172
+ expect(tokenResult).toHaveProperty("temporaryAuthorization");
173
+ expect(cachedToken).toBe(tokenResult.temporaryAuthorization);
174
+
175
+ // Step 2: Call getApp, expecting it to reuse the cached token
176
+ console.log("Calling getApp with pre-fetched token...");
177
+ const appResult = await client.getApp({ appId });
178
+ console.log("App response:", appResult);
179
+
180
+ // Validate the app response
181
+ expect(appResult).toHaveProperty("id", appId);
182
+ expect(appResult).toHaveProperty("name");
183
+
184
+ // Verify call count and token reuse
185
+ console.log(`Final callCount: ${callCount}`);
186
+ expect(callCount).toBe(2); // One for pre-fetch, one for getApp
187
+ expect(cachedToken).toBeDefined();
188
+
189
+ // Check that getApp reused the token by inspecting logs manually or adding a header check if needed
190
+ console.log(`Pre-fetched token: ${cachedToken}`);
191
+ });
192
+ });
@@ -0,0 +1,110 @@
1
+ // tests/playwright/qb/auth/tokenRenewal.test.ts
2
+ import { test, expect } from "@playwright/test";
3
+ import { quickbaseClient } from "../../../../src/quickbaseClient.ts";
4
+ import dotenv from "dotenv";
5
+
6
+ dotenv.config();
7
+
8
+ const loginToQuickbase = async (
9
+ page,
10
+ quickbaseUrl,
11
+ username,
12
+ password,
13
+ appUrl
14
+ ) => {
15
+ console.log("Logging in to QuickBase");
16
+ await page.goto(quickbaseUrl, { timeout: 60000 });
17
+ await page.waitForSelector("input[name='loginid']", { timeout: 60000 });
18
+ await page.fill("input[name='loginid']", username);
19
+ await page.fill("input[name='password']", password);
20
+ await page.click("#signin");
21
+ await page.waitForURL(`https://*.quickbase.com/**`, {
22
+ waitUntil: "networkidle",
23
+ timeout: 60000,
24
+ });
25
+ console.log("Signed In to QuickBase.");
26
+ await page.goto(appUrl, { waitUntil: "networkidle", timeout: 60000 });
27
+ console.log("Navigated to app URL:", page.url());
28
+ };
29
+
30
+ test.describe("QuickbaseClient Integration - Temp Token Renewal", () => {
31
+ test("renews temp token after 5 minutes with getApp → wait → getApp sequence", async ({
32
+ page,
33
+ }) => {
34
+ const realm = process.env.QB_REALM!;
35
+ const appId = process.env.QB_APP_ID!;
36
+ const username = process.env.QB_USERNAME!;
37
+ const password = process.env.QB_PASSWORD!;
38
+ const quickbaseUrl = `https://${realm}.quickbase.com/db/main?a=SignIn`;
39
+ const appUrl = `https://${realm}.quickbase.com/db/${appId}`;
40
+
41
+ await loginToQuickbase(page, quickbaseUrl, username, password, appUrl);
42
+
43
+ const browserFetch = async (
44
+ url: string,
45
+ init: RequestInit
46
+ ): Promise<Response> => {
47
+ const response = await page.request.fetch(url, {
48
+ method: init.method || "GET",
49
+ headers: {
50
+ ...init.headers,
51
+ Referer: `https://${realm}.quickbase.com/db/${appId}`,
52
+ Origin: `https://${realm}.quickbase.com`,
53
+ },
54
+ data: init.body,
55
+ credentials: "include",
56
+ });
57
+ const body = await response.text();
58
+ console.log(
59
+ "Response from:",
60
+ url,
61
+ "status:",
62
+ response.status(),
63
+ "body:",
64
+ body
65
+ );
66
+ return new Response(body, {
67
+ status: response.status(),
68
+ statusText: response.statusText(),
69
+ headers: response.headers(),
70
+ });
71
+ };
72
+
73
+ const client = quickbaseClient({
74
+ realm,
75
+ useTempTokens: true,
76
+ debug: true,
77
+ fetchApi: browserFetch,
78
+ });
79
+
80
+ console.log("Checking session state before getApp...");
81
+ const cookies = await page.context().cookies();
82
+ const sessionCookie = cookies.find(
83
+ (c) => c.name === "TICKET" || c.name.startsWith("TICKET_")
84
+ );
85
+ console.log(
86
+ "Session cookie present:",
87
+ !!sessionCookie,
88
+ "Cookies:",
89
+ cookies
90
+ );
91
+ if (!sessionCookie) throw new Error("TICKET session cookie not found");
92
+
93
+ await page.waitForTimeout(5000); // Debug delay
94
+ console.log("Cookies after delay:", await page.context().cookies());
95
+
96
+ const appResult1 = await client.getApp({ appId });
97
+ console.log("First getApp response:", appResult1);
98
+ expect(appResult1.id).toBe(appId);
99
+
100
+ console.log("Waiting 5 minutes to test token expiration...");
101
+ await new Promise((resolve) => setTimeout(resolve, 5 * 60 * 1000));
102
+
103
+ await page.reload({ waitUntil: "load", timeout: 60000 });
104
+ console.log("Refreshed page after 5 minutes, URL:", page.url());
105
+
106
+ const appResult2 = await client.getApp({ appId });
107
+ console.log("Second getApp response:", appResult2);
108
+ expect(appResult2.id).toBe(appId);
109
+ });
110
+ });
package/tests/setup.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { quickbaseClient, QuickbaseConfig } from "../src/quickbaseClient.ts";
2
+ import dotenv from "dotenv";
3
+ import { vi } from "vitest";
4
+
5
+ dotenv.config();
6
+
7
+ export const createClient = (
8
+ fetchApi?: any,
9
+ config: Partial<QuickbaseConfig> = {}
10
+ ) =>
11
+ quickbaseClient({
12
+ realm: process.env.QB_REALM || "default-realm",
13
+ userToken: process.env.QB_USER_TOKEN || "default-token",
14
+ debug: true,
15
+ fetchApi,
16
+ ...config,
17
+ });
18
+
19
+ export const mockFetch = vi.fn();
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { createClient } from "../../../setup.ts";
3
+
4
+ describe("QuickbaseClient Integration - getApp", () => {
5
+ const client = createClient();
6
+
7
+ it(
8
+ "fetches real app data from QuickBase",
9
+ async () => {
10
+ const getAppId = process.env.QB_APP_ID;
11
+ if (!getAppId) throw new Error("QB_APP_ID is not defined in .env");
12
+ if (!process.env.QB_REALM)
13
+ throw new Error("QB_REALM is not defined in .env");
14
+ if (!process.env.QB_USER_TOKEN)
15
+ throw new Error("QB_USER_TOKEN is not defined in .env");
16
+
17
+ console.log("Config used:", {
18
+ realm: process.env.QB_REALM,
19
+ userToken: process.env.QB_USER_TOKEN,
20
+ appId: getAppId,
21
+ });
22
+ const result = await client.getApp({ appId: getAppId });
23
+ console.log("Real API response:", result);
24
+ expect(result).toEqual({
25
+ id: getAppId,
26
+ name: "qb-copy",
27
+ created: new Date("2025-02-13T18:22:33Z"),
28
+ updated: new Date("2025-03-04T04:25:51Z"),
29
+ description: "",
30
+ timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
31
+ dateFormat: "MM-DD-YYYY",
32
+ hasEveryoneOnTheInternet: false,
33
+ memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
34
+ securityProperties: {
35
+ allowClone: false,
36
+ allowExport: true,
37
+ enableAppTokens: true,
38
+ hideFromPublic: false,
39
+ mustBeRealmApproved: false,
40
+ useIPFilter: false,
41
+ },
42
+ });
43
+ },
44
+ { timeout: 10000 }
45
+ );
46
+ });