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,5 +1,3 @@
1
- // tests/vitest/unit/apps/copyApp.test.ts
2
-
3
1
  import { describe, it, expect, beforeEach } from "vitest";
4
2
  import {
5
3
  createClient,
@@ -45,8 +43,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
45
43
  id: "bpqe82s1",
46
44
  name: "Copied App",
47
45
  description: "A copy of the original app",
48
- created: new Date("2025-03-11T10:00:00Z"), // Expect Date object
49
- updated: new Date("2025-03-11T10:00:00Z"), // Expect Date object
46
+ created: new Date("2025-03-11T10:00:00Z"),
47
+ updated: new Date("2025-03-11T10:00:00Z"),
50
48
  dateFormat: "MM-DD-YYYY",
51
49
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
52
50
  hasEveryoneOnTheInternet: false,
@@ -97,8 +95,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
97
95
  id: "bpqe82s1",
98
96
  name: "Copied App",
99
97
  description: "A copy of the original app",
100
- created: "2025-03-11T10:00:00Z", // Expect string
101
- updated: "2025-03-11T10:00:00Z", // Expect string
98
+ created: "2025-03-11T10:00:00Z",
99
+ updated: "2025-03-11T10:00:00Z",
102
100
  dateFormat: "MM-DD-YYYY",
103
101
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
104
102
  hasEveryoneOnTheInternet: false,
@@ -149,8 +147,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
149
147
  id: "bpqe82s2",
150
148
  name: "Copied App with Temp Token",
151
149
  description: "A copy using temp token",
152
- created: new Date("2025-03-11T11:00:00Z"), // Expect Date object
153
- updated: new Date("2025-03-11T11:00:00Z"), // Expect Date object
150
+ created: new Date("2025-03-11T11:00:00Z"),
151
+ updated: new Date("2025-03-11T11:00:00Z"),
154
152
  dateFormat: "MM-DD-YYYY",
155
153
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
156
154
  hasEveryoneOnTheInternet: true,
@@ -178,27 +176,15 @@ describe("QuickbaseClient Unit - copyApp", () => {
178
176
  expect(mockFetch).toHaveBeenNthCalledWith(
179
177
  1,
180
178
  `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
181
- expect.objectContaining({
182
- method: "GET",
183
- headers: expect.objectContaining({
184
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
185
- "Content-Type": "application/json",
186
- }),
187
- credentials: "include",
188
- })
179
+ expect.any(Object)
189
180
  );
190
181
  expect(mockFetch).toHaveBeenNthCalledWith(
191
182
  2,
192
183
  `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
193
184
  expect.objectContaining({
194
- method: "POST",
195
185
  headers: expect.objectContaining({
196
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
197
186
  Authorization: "QB-TEMP-TOKEN temp_token",
198
- "Content-Type": "application/json",
199
187
  }),
200
- credentials: "omit",
201
- body: JSON.stringify(request),
202
188
  })
203
189
  );
204
190
  });
@@ -225,8 +211,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
225
211
  id: "bpqe82s2",
226
212
  name: "Copied App with Temp Token",
227
213
  description: "A copy using temp token",
228
- created: "2025-03-11T11:00:00Z", // Expect string
229
- updated: "2025-03-11T11:00:00Z", // Expect string
214
+ created: "2025-03-11T11:00:00Z",
215
+ updated: "2025-03-11T11:00:00Z",
230
216
  dateFormat: "MM-DD-YYYY",
231
217
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
232
218
  hasEveryoneOnTheInternet: true,
@@ -254,27 +240,15 @@ describe("QuickbaseClient Unit - copyApp", () => {
254
240
  expect(mockFetch).toHaveBeenNthCalledWith(
255
241
  1,
256
242
  `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
257
- expect.objectContaining({
258
- method: "GET",
259
- headers: expect.objectContaining({
260
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
261
- "Content-Type": "application/json",
262
- }),
263
- credentials: "include",
264
- })
243
+ expect.any(Object)
265
244
  );
266
245
  expect(mockFetch).toHaveBeenNthCalledWith(
267
246
  2,
268
247
  `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
269
248
  expect.objectContaining({
270
- method: "POST",
271
249
  headers: expect.objectContaining({
272
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
273
250
  Authorization: "QB-TEMP-TOKEN temp_token",
274
- "Content-Type": "application/json",
275
251
  }),
276
- credentials: "omit",
277
- body: JSON.stringify(request),
278
252
  })
279
253
  );
280
254
  });
@@ -297,8 +271,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
297
271
  id: "bpqe82s3",
298
272
  name: "Retry Copied App",
299
273
  description: "Retry after 401",
300
- created: new Date("2025-03-11T12:00:00Z"), // Expect Date object
301
- updated: new Date("2025-03-11T12:00:00Z"), // Expect Date object
274
+ created: new Date("2025-03-11T12:00:00Z"),
275
+ updated: new Date("2025-03-11T12:00:00Z"),
302
276
  dateFormat: "MM-DD-YYYY",
303
277
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
304
278
  hasEveryoneOnTheInternet: false,
@@ -386,8 +360,8 @@ describe("QuickbaseClient Unit - copyApp", () => {
386
360
  id: "bpqe82s3",
387
361
  name: "Retry Copied App",
388
362
  description: "Retry after 401",
389
- created: "2025-03-11T12:00:00Z", // Expect string
390
- updated: "2025-03-11T12:00:00Z", // Expect string
363
+ created: "2025-03-11T12:00:00Z",
364
+ updated: "2025-03-11T12:00:00Z",
391
365
  dateFormat: "MM-DD-YYYY",
392
366
  timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
393
367
  hasEveryoneOnTheInternet: false,
@@ -485,4 +459,123 @@ describe("QuickbaseClient Unit - copyApp", () => {
485
459
  })
486
460
  );
487
461
  });
462
+
463
+ it("exhausts default maxRetries (3) with temp token and throws error", async () => {
464
+ client = createClient(mockFetch, { useTempTokens: true, debug: true }); // No explicit maxRetries, uses default 3
465
+
466
+ const request: CopyAppRequest = {
467
+ name: "Fail App",
468
+ description: "Test retry limit",
469
+ properties: {
470
+ keepData: false,
471
+ excludeFiles: true,
472
+ usersAndRoles: false,
473
+ assignUserToken: true,
474
+ },
475
+ };
476
+
477
+ mockFetch
478
+ .mockResolvedValueOnce({
479
+ ok: true,
480
+ status: 200,
481
+ json: () => Promise.resolve({ temporaryAuthorization: "token_1" }),
482
+ })
483
+ .mockResolvedValueOnce({
484
+ ok: false,
485
+ status: 401,
486
+ json: () => Promise.resolve({ message: "Unauthorized 1" }),
487
+ })
488
+ .mockResolvedValueOnce({
489
+ ok: true,
490
+ status: 200,
491
+ json: () => Promise.resolve({ temporaryAuthorization: "token_2" }),
492
+ })
493
+ .mockResolvedValueOnce({
494
+ ok: false,
495
+ status: 401,
496
+ json: () => Promise.resolve({ message: "Unauthorized 2" }),
497
+ })
498
+ .mockResolvedValueOnce({
499
+ ok: true,
500
+ status: 200,
501
+ json: () => Promise.resolve({ temporaryAuthorization: "token_3" }),
502
+ })
503
+ .mockResolvedValueOnce({
504
+ ok: false,
505
+ status: 401,
506
+ json: () => Promise.resolve({ message: "Unauthorized 3" }),
507
+ })
508
+ .mockResolvedValueOnce({
509
+ ok: true,
510
+ status: 200,
511
+ json: () => Promise.resolve({ temporaryAuthorization: "token_4" }),
512
+ })
513
+ .mockResolvedValueOnce({
514
+ ok: false,
515
+ status: 401,
516
+ json: () => Promise.resolve({ message: "Unauthorized 4" }),
517
+ });
518
+
519
+ await expect(
520
+ client.copyApp({ appId: QB_APP_ID, body: request })
521
+ ).rejects.toThrow("API Error: Unauthorized 4 (Status: 401)");
522
+
523
+ expect(mockFetch).toHaveBeenCalledTimes(8); // 4 token fetches + 4 API attempts
524
+ expect(mockFetch).toHaveBeenNthCalledWith(
525
+ 1,
526
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
527
+ expect.any(Object)
528
+ );
529
+ expect(mockFetch).toHaveBeenNthCalledWith(
530
+ 2,
531
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
532
+ expect.objectContaining({
533
+ headers: expect.objectContaining({
534
+ Authorization: "QB-TEMP-TOKEN token_1",
535
+ }),
536
+ })
537
+ );
538
+ expect(mockFetch).toHaveBeenNthCalledWith(
539
+ 3,
540
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
541
+ expect.any(Object)
542
+ );
543
+ expect(mockFetch).toHaveBeenNthCalledWith(
544
+ 4,
545
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
546
+ expect.objectContaining({
547
+ headers: expect.objectContaining({
548
+ Authorization: "QB-TEMP-TOKEN token_2",
549
+ }),
550
+ })
551
+ );
552
+ expect(mockFetch).toHaveBeenNthCalledWith(
553
+ 5,
554
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
555
+ expect.any(Object)
556
+ );
557
+ expect(mockFetch).toHaveBeenNthCalledWith(
558
+ 6,
559
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
560
+ expect.objectContaining({
561
+ headers: expect.objectContaining({
562
+ Authorization: "QB-TEMP-TOKEN token_3",
563
+ }),
564
+ })
565
+ );
566
+ expect(mockFetch).toHaveBeenNthCalledWith(
567
+ 7,
568
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
569
+ expect.any(Object)
570
+ );
571
+ expect(mockFetch).toHaveBeenNthCalledWith(
572
+ 8,
573
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/copy`,
574
+ expect.objectContaining({
575
+ headers: expect.objectContaining({
576
+ Authorization: "QB-TEMP-TOKEN token_4",
577
+ }),
578
+ })
579
+ );
580
+ });
488
581
  });
@@ -0,0 +1,193 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import {
3
+ createClient,
4
+ mockFetch,
5
+ QB_APP_ID,
6
+ QB_REALM,
7
+ QB_USER_TOKEN,
8
+ } from "@tests/setup.ts";
9
+ import { GetAppEvents200Response } from "@/generated/models";
10
+
11
+ describe("QuickbaseClient Unit - getAppEvents", () => {
12
+ let client: ReturnType<typeof createClient>;
13
+
14
+ beforeEach(() => {
15
+ mockFetch.mockClear();
16
+ });
17
+
18
+ it("initializes and has getAppEvents method", () => {
19
+ client = createClient(mockFetch, { debug: true });
20
+ expect(client).toBeDefined();
21
+ expect(typeof client.getAppEvents).toBe("function");
22
+ });
23
+
24
+ it("fetches app events successfully with user token", async () => {
25
+ client = createClient(mockFetch, {
26
+ realm: QB_REALM,
27
+ userToken: QB_USER_TOKEN,
28
+ debug: true,
29
+ });
30
+
31
+ const mockResponse: GetAppEvents200Response = [
32
+ {
33
+ type: "qb-action",
34
+ owner: {
35
+ email: "jsmith@quickbase.com",
36
+ id: "123456.ab1s",
37
+ name: "Juliet Smith",
38
+ userName: "jsmith",
39
+ },
40
+ isActive: true,
41
+ tableId: "bqwriv8bw",
42
+ name: "Quick Base Action",
43
+ },
44
+ {
45
+ type: "webhook",
46
+ owner: {
47
+ email: "tanderson@quickbase.com",
48
+ id: "654321.ab1s",
49
+ name: "Thomas A. Anderson",
50
+ userName: "tanderson",
51
+ },
52
+ isActive: false,
53
+ tableId: "bqwriv8bw",
54
+ name: "Webhook",
55
+ },
56
+ {
57
+ type: "email-notification",
58
+ owner: {
59
+ email: "jsmith@quickbase.com",
60
+ id: "123456.ab1s",
61
+ name: "Juliet Smith",
62
+ userName: "jsmith",
63
+ },
64
+ isActive: false,
65
+ tableId: "bqwriv8bw",
66
+ name: "Notification",
67
+ },
68
+ {
69
+ type: "subscription",
70
+ owner: {
71
+ email: "tanderson@quickbase.com",
72
+ id: "654321.ab1s",
73
+ name: "Thomas A. Anderson",
74
+ userName: "tanderson",
75
+ },
76
+ isActive: true,
77
+ tableId: "bqwriv8bw",
78
+ name: "Subscription",
79
+ },
80
+ {
81
+ type: "reminder",
82
+ owner: {
83
+ email: "jsmith@quickbase.com",
84
+ id: "123456.ab1s",
85
+ name: "Juliet Smith",
86
+ userName: "jsmith",
87
+ },
88
+ isActive: true,
89
+ tableId: "bqwriv8bw",
90
+ name: "Reminder",
91
+ },
92
+ {
93
+ type: "automation",
94
+ owner: {
95
+ email: "tanderson@quickbase.com",
96
+ id: "654321.ab1s",
97
+ name: "Thomas A. Anderson",
98
+ userName: "tanderson",
99
+ },
100
+ isActive: true,
101
+ tableId: "bqwriv8bw",
102
+ url: "https://realm.quickbase.com/ui/automation/qb/db/bqwria893/automations/70eecab9-634f-42d9-9037-9340a1a9e8ce",
103
+ },
104
+ ];
105
+
106
+ mockFetch.mockResolvedValueOnce({
107
+ ok: true,
108
+ status: 200,
109
+ json: () => Promise.resolve(mockResponse),
110
+ });
111
+
112
+ const result = await client.getAppEvents({ appId: QB_APP_ID });
113
+
114
+ expect(result).toEqual(mockResponse);
115
+ expect(mockFetch).toHaveBeenCalledWith(
116
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/events`,
117
+ expect.objectContaining({
118
+ method: "GET",
119
+ headers: expect.objectContaining({
120
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
121
+ Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
122
+ "Content-Type": "application/json",
123
+ }),
124
+ credentials: "omit",
125
+ })
126
+ );
127
+ });
128
+
129
+ it("fetches app events successfully with temp token", async () => {
130
+ client = createClient(mockFetch, {
131
+ realm: QB_REALM,
132
+ useTempTokens: true,
133
+ debug: true,
134
+ });
135
+
136
+ const mockResponse: GetAppEvents200Response = [
137
+ {
138
+ type: "webhook",
139
+ owner: {
140
+ email: "tanderson@quickbase.com",
141
+ id: "654321.ab1s",
142
+ name: "Thomas A. Anderson",
143
+ userName: "tanderson",
144
+ },
145
+ isActive: false,
146
+ tableId: "bqwriv8bw",
147
+ name: "Webhook",
148
+ },
149
+ ];
150
+
151
+ mockFetch
152
+ .mockResolvedValueOnce({
153
+ ok: true,
154
+ status: 200,
155
+ json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
156
+ })
157
+ .mockResolvedValueOnce({
158
+ ok: true,
159
+ status: 200,
160
+ json: () => Promise.resolve(mockResponse),
161
+ });
162
+
163
+ const result = await client.getAppEvents({ appId: QB_APP_ID });
164
+
165
+ expect(result).toEqual(mockResponse);
166
+ expect(mockFetch).toHaveBeenCalledTimes(2);
167
+ expect(mockFetch).toHaveBeenNthCalledWith(
168
+ 1,
169
+ `https://api.quickbase.com/v1/auth/temporary/${QB_APP_ID}`,
170
+ expect.objectContaining({
171
+ method: "GET",
172
+ headers: expect.objectContaining({
173
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
174
+ "Content-Type": "application/json",
175
+ }),
176
+ credentials: "include",
177
+ })
178
+ );
179
+ expect(mockFetch).toHaveBeenNthCalledWith(
180
+ 2,
181
+ `https://api.quickbase.com/v1/apps/${QB_APP_ID}/events`,
182
+ expect.objectContaining({
183
+ method: "GET",
184
+ headers: expect.objectContaining({
185
+ "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
186
+ Authorization: "QB-TEMP-TOKEN temp_token",
187
+ "Content-Type": "application/json",
188
+ }),
189
+ credentials: "omit",
190
+ })
191
+ );
192
+ });
193
+ });