quickbase-js 1.1.0-beta.3 → 1.1.0-beta.5

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 (69) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +115 -5
  3. package/build.js +175 -55
  4. package/dist/{quickbase.d.ts → esm/quickbase.d.ts} +1108 -349
  5. package/dist/{quickbase.js → esm/quickbase.js} +828 -263
  6. package/dist/esm/quickbase.js.map +1 -0
  7. package/dist/esm/quickbase.min.js +2 -0
  8. package/dist/esm/quickbase.min.js.map +1 -0
  9. package/dist/umd/quickbase.umd.d.ts +5341 -0
  10. package/dist/umd/quickbase.umd.js +7696 -0
  11. package/dist/umd/quickbase.umd.js.map +1 -0
  12. package/dist/umd/quickbase.umd.min.js +2 -0
  13. package/dist/umd/quickbase.umd.min.js.map +1 -0
  14. package/examples/react.js +12 -12
  15. package/examples/vanillajs.html +12 -12
  16. package/log-gen.yaml +536 -1915
  17. package/open-api/definitions/apps.ts +274 -0
  18. package/open-api/definitions/auth.ts +10 -0
  19. package/open-api/definitions/fields.ts +751 -0
  20. package/open-api/definitions/index.ts +18 -0
  21. package/open-api/definitions/records.ts +269 -0
  22. package/open-api/definitions/relationships.ts +90 -0
  23. package/open-api/definitions/reports.ts +10 -0
  24. package/open-api/definitions/tables.ts +112 -0
  25. package/{src/code-generation → open-api}/fix-spec-main.ts +3 -3
  26. package/{src/code-generation → open-api}/generate-unified-interface.ts +42 -17
  27. package/{src/code-generation → open-api}/output/quickbase-fixed.json +3244 -4586
  28. package/open-api/paths/apps.ts +100 -0
  29. package/open-api/paths/auth.ts +25 -0
  30. package/open-api/paths/fields.ts +207 -0
  31. package/open-api/paths/index.ts +17 -0
  32. package/open-api/paths/records.ts +68 -0
  33. package/open-api/paths/relationships.ts +32 -0
  34. package/open-api/paths/reports.ts +34 -0
  35. package/open-api/paths/tables.ts +162 -0
  36. package/{src/code-generation → open-api}/regenerate-client.ts +4 -4
  37. package/package.json +34 -25
  38. package/playwright-results.txt/.last-run.json +4 -0
  39. package/playwright.config.ts +1 -1
  40. package/rollup.config.mjs +114 -0
  41. package/src/generated-unified/QuickbaseClient.ts +7 -6
  42. package/test-artifacts/.last-run.json +4 -2
  43. package/test-esm.js +1 -0
  44. package/test-umd.js +1 -0
  45. package/test.ts +1 -0
  46. package/tests/playwright/qb/auth/umd.test.ts +10 -0
  47. package/tests/vitest/qb/fields/createField.test.ts +78 -0
  48. package/tests/vitest/qb/fields/deleteFields.test.ts +89 -0
  49. package/tests/vitest/qb/fields/getField.test.ts +85 -0
  50. package/tests/vitest/qb/fields/getFieldUsage.test.ts +174 -0
  51. package/tests/vitest/qb/fields/getFieldsUsage.test.ts +77 -0
  52. package/tests/vitest/qb/index.ts +25 -1
  53. package/tests/vitest/unit/fields/createField.test.ts +391 -0
  54. package/tests/vitest/unit/fields/deleteFields.test.ts +141 -0
  55. package/tests/vitest/unit/fields/getField.test.ts +189 -0
  56. package/tests/vitest/unit/fields/getFieldUsage.test.ts +147 -0
  57. package/tests/vitest/unit/fields/getFieldsUsage.test.ts +160 -0
  58. package/tests/vitest/unit/index.ts +30 -1
  59. package/tests/vitest/unit/umd.test.ts +8 -0
  60. package/{src → tools}/log-gen.ts +14 -10
  61. package/tools/openapi-jars/openapi-generator-cli-7.12.0.jar +0 -0
  62. package/tsconfig.build.json +17 -0
  63. package/tsconfig.json +5 -5
  64. package/dist/quickbase.js.map +0 -1
  65. package/rollup.config.js +0 -70
  66. package/src/code-generation/fix-spec-definitions.ts +0 -809
  67. package/src/code-generation/fix-spec-paths.ts +0 -439
  68. package/test-artifacts/test-results.json +0 -829
  69. /package/{src/specs → specs}/QuickBase_RESTful_API_2025-03-04T06_22_39.725Z.json +0 -0
package/log-gen.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  project: quickbase-js
2
- date: 3/11/2025
2
+ date: 3/12/2025
3
3
  root: /home/drew/Projects/quickbase-js
4
4
  goals:
5
5
  - A library for interacting with the QuickBase RESTful API.
@@ -40,8 +40,8 @@ pipelineOverview:
40
40
  - getTempTokenDBID() method is wrapped to enhance temporary token generation reuse interacting with a token cache.
41
41
  - '#'
42
42
  - 'npm run fix-spec: Generates quickbase-fixed.json from the latest QuickBase_RESTful*.json file.'
43
- - 'npm run regenerate: Generates raw TypeScript-fetch files in src/generated/.'
44
- - 'npm run generate-unified: Generates a unified QuickbaseClient.ts interface in src/generated-unified/.'
43
+ - npm run regenerate:openapi Generates raw TypeScript-fetch files in src/generated/.
44
+ - 'npm run generate:unified: Generates a unified QuickbaseClient.ts interface in src/generated-unified/.'
45
45
  tree:
46
46
  name: .
47
47
  type: directory
@@ -58,15 +58,148 @@ tree:
58
58
  type: file
59
59
  - name: build.js
60
60
  type: file
61
+ contents: |-
62
+ import { execSync } from 'child_process';
63
+ import { rollup } from 'rollup';
64
+ import terser from '@rollup/plugin-terser';
65
+ import nodeResolve from '@rollup/plugin-node-resolve';
66
+ import commonjs from '@rollup/plugin-commonjs';
67
+ import dts from 'rollup-plugin-dts';
68
+ import { rimrafSync } from 'rimraf';
69
+ import { copyFileSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
70
+ import { join } from 'path';
71
+
72
+ // Clean the dist folder
73
+ rimrafSync('dist');
74
+ rimrafSync('dist/temp-src');
75
+
76
+ // Step 1: Copy runtime-relevant files from src/ to dist/temp-src and remove .ts extensions
77
+ const srcDir = join(process.cwd(), 'src');
78
+ const tempSrcDir = join(process.cwd(), 'dist/temp-src');
79
+ const runtimeDirs = ['generated', 'generated-unified'];
80
+ const runtimeFiles = ['index.ts', 'quickbaseClient.ts', 'tokenCache.ts'];
81
+
82
+ function copyAndRewriteTsExtensions(src, dest) {
83
+ console.log(`Copying from ${src} to ${dest}`);
84
+ mkdirSync(dest, { recursive: true });
85
+ const files = readdirSync(src, { withFileTypes: true });
86
+ for (const file of files) {
87
+ const srcPath = join(src, file.name);
88
+ const destPath = join(dest, file.name);
89
+ if (file.isDirectory()) {
90
+ if (runtimeDirs.includes(file.name) || runtimeDirs.some(dir => src.includes(dir))) {
91
+ copyAndRewriteTsExtensions(srcPath, destPath);
92
+ } else {
93
+ console.log(`Skipping non-runtime directory ${srcPath}`);
94
+ }
95
+ } else if (file.name.endsWith('.ts')) {
96
+ if (runtimeFiles.includes(file.name) || src.includes('generated') || src.includes('generated-unified')) {
97
+ console.log(`Processing ${srcPath} -> ${destPath}`);
98
+ let content = readFileSync(srcPath, 'utf8');
99
+ content = content.replace(/(from\s+["']\.[\w\/-]+)\.ts(["'])/g, '$1$2');
100
+ content = content.replace(/(export\s+\*\s+from\s+["']\.[\w\/-]+)\.ts(["'])/g, '$1$2');
101
+ writeFileSync(destPath, content, 'utf8');
102
+ } else {
103
+ console.log(`Skipping non-runtime file ${srcPath}`);
104
+ }
105
+ } else {
106
+ console.log(`Skipping non-.ts file ${srcPath}`);
107
+ }
108
+ }
109
+ }
110
+ copyAndRewriteTsExtensions(srcDir, tempSrcDir);
111
+
112
+ // Step 2: Compile TypeScript to JavaScript with tsc
113
+ console.log('Running tsc...');
114
+ const tsFiles = readdirSync(tempSrcDir, { recursive: true })
115
+ .filter(file => file.endsWith('.ts'))
116
+ .map(file => join(tempSrcDir, file));
117
+ const tscCommand = `npx tsc ${tsFiles.join(' ')} --outDir ${join(process.cwd(), 'dist/temp')} --module ESNext --target ESNext --sourceMap --moduleResolution bundler --baseUrl ${process.cwd()}`;
118
+ console.log(`Command: ${tscCommand}`);
119
+ execSync(tscCommand, { stdio: 'inherit' });
120
+
121
+ // Step 3: Rollup configuration
122
+ const isProd = process.env.NODE_ENV === 'production';
123
+ const external = [
124
+ 'node:http', 'node:https', 'node:zlib', 'node:stream', 'node:buffer',
125
+ 'node:util', 'node:url', 'node:net', 'node:fs', 'node:path', 'node-fetch',
126
+ ];
127
+ const globals = { 'node-fetch': 'fetch' };
128
+
129
+ // ESM Build
130
+ console.log(`Building ESM bundle (${isProd ? 'minified' : 'unminified'})...`);
131
+ const esmBundle = await rollup({
132
+ input: 'dist/temp/quickbaseClient.js',
133
+ external,
134
+ plugins: [
135
+ nodeResolve({ preferBuiltins: true }),
136
+ commonjs(),
137
+ isProd && terser(), // Only minify in prod
138
+ ],
139
+ });
140
+ await esmBundle.write({
141
+ dir: 'dist/esm',
142
+ format: 'esm',
143
+ sourcemap: true,
144
+ entryFileNames: isProd ? 'quickbase.min.js' : 'quickbase.js',
145
+ });
146
+ await esmBundle.close();
147
+
148
+ // UMD Build
149
+ console.log(`Building UMD bundle (${isProd ? 'minified' : 'unminified'})...`);
150
+ const umdBundle = await rollup({
151
+ input: 'dist/temp/quickbaseClient.js',
152
+ external,
153
+ plugins: [
154
+ nodeResolve({ preferBuiltins: true, browser: true }),
155
+ commonjs(),
156
+ isProd && terser(), // Only minify in prod
157
+ ],
158
+ });
159
+ await umdBundle.write({
160
+ file: join(process.cwd(), 'dist/umd', isProd ? 'quickbase.umd.min.js' : 'quickbase.umd.js'),
161
+ format: 'umd',
162
+ name: 'QuickbaseJS',
163
+ sourcemap: true,
164
+ globals,
165
+ });
166
+ await umdBundle.close();
167
+
168
+ // Declarations
169
+ console.log('Generating declarations...');
170
+ const dtsBundle = await rollup({
171
+ input: 'src/quickbaseClient.ts',
172
+ plugins: [dts({ tsconfig: './tsconfig.json', respectExternal: true })],
173
+ });
174
+ await dtsBundle.write({ file: 'dist/esm/quickbase.d.ts', format: 'esm' });
175
+ await dtsBundle.write({ file: 'dist/umd/quickbase.umd.d.ts', format: 'umd' });
176
+ await dtsBundle.close();
177
+
178
+ // Clean up
179
+ rimrafSync('dist/temp');
180
+ rimrafSync('dist/temp-src');
181
+ console.log('Build completed successfully');
61
182
  - name: dist
62
183
  type: directory
63
184
  children:
64
- - name: quickbase.d.ts
65
- type: file
66
- - name: quickbase.js
67
- type: file
68
- - name: quickbase.js.map
69
- type: file
185
+ - name: esm
186
+ type: directory
187
+ children:
188
+ - name: quickbase.d.ts
189
+ type: file
190
+ - name: quickbase.min.js
191
+ type: file
192
+ - name: quickbase.min.js.map
193
+ type: file
194
+ - name: umd
195
+ type: directory
196
+ children:
197
+ - name: quickbase.umd.d.ts
198
+ type: file
199
+ - name: quickbase.umd.min.js
200
+ type: file
201
+ - name: quickbase.umd.min.js.map
202
+ type: file
70
203
  - name: examples
71
204
  type: directory
72
205
  children:
@@ -76,37 +209,167 @@ tree:
76
209
  type: file
77
210
  - name: log-gen.yaml
78
211
  type: file
212
+ - name: open-api
213
+ type: directory
214
+ children:
215
+ - name: definitions
216
+ type: directory
217
+ children:
218
+ - name: apps.ts
219
+ type: file
220
+ - name: auth.ts
221
+ type: file
222
+ - name: fields.ts
223
+ type: file
224
+ - name: index.ts
225
+ type: file
226
+ - name: records.ts
227
+ type: file
228
+ - name: relationships.ts
229
+ type: file
230
+ - name: reports.ts
231
+ type: file
232
+ - name: tables.ts
233
+ type: file
234
+ - name: fix-spec-main.ts
235
+ type: file
236
+ - name: generate-unified-interface.ts
237
+ type: file
238
+ - name: output
239
+ type: directory
240
+ children:
241
+ - name: quickbase-fixed.json
242
+ type: file
243
+ - name: paths
244
+ type: directory
245
+ children:
246
+ - name: apps.ts
247
+ type: file
248
+ - name: auth.ts
249
+ type: file
250
+ - name: fields.ts
251
+ type: file
252
+ - name: index.ts
253
+ type: file
254
+ - name: records.ts
255
+ type: file
256
+ - name: relationships.ts
257
+ type: file
258
+ - name: reports.ts
259
+ type: file
260
+ - name: tables.ts
261
+ type: file
262
+ - name: regenerate-client.ts
263
+ type: file
79
264
  - name: openapitools.json
80
265
  type: file
81
266
  - name: package-lock.json
82
267
  type: file
83
268
  - name: package.json
84
269
  type: file
270
+ contents: |
271
+ {
272
+ "name": "quickbase-js",
273
+ "version": "1.1.0-beta.4",
274
+ "description": "QuickBase JavaScript client for frameworks and vanilla JS",
275
+ "type": "module",
276
+ "main": "dist/esm/quickbase.js",
277
+ "module": "dist/esm/quickbase.js",
278
+ "types": "dist/esm/quickbase.d.ts",
279
+ "publishConfig": {
280
+ "access": "public"
281
+ },
282
+ "exports": {
283
+ ".": {
284
+ "import": {
285
+ "minified": "./dist/esm/quickbase.min.js",
286
+ "default": "./dist/esm/quickbase.js"
287
+ },
288
+ "require": {
289
+ "minified": "./dist/umd/quickbase.umd.min.js",
290
+ "default": "./dist/umd/quickbase.umd.js"
291
+ },
292
+ "types": "./dist/esm/quickbase.d.ts",
293
+ "umd:types": "./dist/umd/quickbase.umd.d.ts"
294
+ }
295
+ },
296
+ "scripts": {
297
+ "release": "standard-version",
298
+ "release:beta": "standard-version --prerelease beta",
299
+ "release:alpha": "standard-version --prerelease alpha",
300
+ "release:push": "npm run release:beta && git push --follow-tags origin main",
301
+ "release:publish": "npm run release:push && npm publish --tag beta",
302
+
303
+
304
+ "fix-spec": "node --loader ts-node/esm open-api/fix-spec-main.ts",
305
+ "regenerate:openapi": "rimraf src/generated && node --loader ts-node/esm open-api/regenerate-client.ts",
306
+ "regenerate:unified": "node --loader ts-node/esm open-api/generate-unified-interface.ts",
307
+ "regenerate:all": "npm run fix-spec && npm run regenerate:openapi && npm run regenerate:unified",
308
+ "log-gen": "node --loader ts-node/esm tools/log-gen.ts",
309
+ "build": "rimraf dist && node --loader ts-node/esm build.js",
310
+ "build:prod": "rimraf dist && NODE_ENV=production node --loader ts-node/esm build.js",
311
+
312
+
313
+ "build-old": "rimraf dist && rollup -c",
314
+ "build:prod-old": "rimraf dist && NODE_ENV=production rollup -c",
315
+ "test:all": "vitest run tests/vitest/**/**/*.test.ts --reporter=basic && playwright test tests/playwright/qb --config playwright.config.ts --reporter=dot --quiet",
316
+ "test:pw:qb:all": "playwright test tests/playwright/qb --config playwright.config.ts",
317
+ "test:vitest:all": "vitest run tests/vitest/**/**/*.test.ts",
318
+ "test:vitest:qb": "vitest run tests/vitest/qb/**/*.test.ts",
319
+ "test:vitest:unit": "vitest run tests/vitest/unit/**/*.test.ts",
320
+ "test:vitest:qb:apps": "vitest run tests/vitest/qb/apps/**/*.test.ts",
321
+ "test:vitest:qb:auth": "vitest run tests/vitest/qb/auth/**/*.test.ts",
322
+ "test:vitest:qb:fields": "vitest run tests/vitest/qb/fields/**/*.test.ts",
323
+ "test:vitest:qb:records": "vitest run tests/vitest/qb/records/**/*.test.ts",
324
+ "test:vitest:qb:tables": "vitest run tests/vitest/qb/tables/**/*.test.ts",
325
+ "test:vitest:unit:apps": "vitest run tests/vitest/unit/apps/**/*.test.ts",
326
+ "test:vitest:unit:auth": "vitest run tests/vitest/unit/auth/**/*.test.ts",
327
+ "test:vitest:unit:fields": "vitest run tests/vitest/unit/fields/**/*.test.ts",
328
+ "test:vitest:unit:records": "vitest run tests/vitest/unit/records/**/*.test.ts",
329
+ "test:vitest:unit:tables": "vitest run tests/vitest/unit/tables/**/*.test.ts",
330
+ "test:pw:qb:auth": "playwright test tests/playwright/qb/auth"
331
+ },
332
+ "dependencies": {
333
+ "typescript": "^5.8.2"
334
+ },
335
+ "devDependencies": {
336
+ "@openapitools/openapi-generator-cli": "^2.17.0",
337
+ "@playwright/test": "^1.51.0",
338
+ "@rollup/plugin-commonjs": "^28.0.3",
339
+ "@rollup/plugin-node-resolve": "^16.0.0",
340
+ "@rollup/plugin-terser": "^0.4.4",
341
+ "@rollup/plugin-typescript": "^12.1.2",
342
+ "@types/js-yaml": "^4.0.9",
343
+ "@types/node": "^22.13.10",
344
+ "dotenv": "^16.4.7",
345
+ "glob": "^11.0.1",
346
+ "js-yaml": "^4.1.0",
347
+ "jsdom": "^26.0.0",
348
+ "node-fetch": "^3.3.2",
349
+ "openapi-types": "^12.1.3",
350
+ "rimraf": "^5.0.10",
351
+ "rollup": "^4.35.0",
352
+ "rollup-plugin-dts": "^6.1.1",
353
+ "standard-version": "^9.5.0",
354
+ "ts-morph": "^25.0.1",
355
+ "ts-node": "^10.9.2",
356
+ "tslib": "^2.8.1",
357
+ "undici-types": "^7.5.0",
358
+ "vitest": "^3.0.8"
359
+ }
360
+ }
361
+ - name: playwright-results.txt
362
+ type: directory
363
+ children:
364
+ - name: .last-run.json
365
+ type: file
85
366
  - name: playwright.config.ts
86
367
  type: file
87
- - name: rollup.config.js
368
+ - name: rollup.config.mjs
88
369
  type: file
89
370
  - name: src
90
371
  type: directory
91
372
  children:
92
- - name: code-generation
93
- type: directory
94
- children:
95
- - name: fix-spec-definitions.ts
96
- type: file
97
- - name: fix-spec-main.ts
98
- type: file
99
- - name: fix-spec-paths.ts
100
- type: file
101
- - name: generate-unified-interface.ts
102
- type: file
103
- - name: output
104
- type: directory
105
- children:
106
- - name: quickbase-fixed.json
107
- type: file
108
- - name: regenerate-client.ts
109
- type: file
110
373
  - name: generated
111
374
  type: directory
112
375
  children:
@@ -191,6 +454,10 @@ tree:
191
454
  type: file
192
455
  - name: CreateAppRequestVariablesInner.ts
193
456
  type: file
457
+ - name: CreateField200Response.ts
458
+ type: file
459
+ - name: CreateFieldRequest.ts
460
+ type: file
194
461
  - name: CreateRelationship200Response.ts
195
462
  type: file
196
463
  - name: CreateRelationshipRequest.ts
@@ -199,6 +466,14 @@ tree:
199
466
  type: file
200
467
  - name: DeactivateUserToken200Response.ts
201
468
  type: file
469
+ - name: DeleteApp200Response.ts
470
+ type: file
471
+ - name: DeleteAppRequest.ts
472
+ type: file
473
+ - name: DeleteFields200Response.ts
474
+ type: file
475
+ - name: DeleteFieldsRequest.ts
476
+ type: file
202
477
  - name: DeleteFile200Response.ts
203
478
  type: file
204
479
  - name: DeleteRecords200Response.ts
@@ -221,9 +496,13 @@ tree:
221
496
  type: file
222
497
  - name: FieldProperties.ts
223
498
  type: file
224
- - name: GenerateDocument200Response.ts
499
+ - name: FieldUsage.ts
225
500
  type: file
226
- - name: GetField200Response.ts
501
+ - name: FieldUsageField.ts
502
+ type: file
503
+ - name: FieldUsageUsage.ts
504
+ type: file
505
+ - name: GenerateDocument200Response.ts
227
506
  type: file
228
507
  - name: GetRelationships200Response.ts
229
508
  type: file
@@ -297,8 +576,6 @@ tree:
297
576
  type: file
298
577
  - name: UndenyUsers200Response.ts
299
578
  type: file
300
- - name: UpdateFieldRequest.ts
301
- type: file
302
579
  - name: UpdateRelationshipRequest.ts
303
580
  type: file
304
581
  - name: UpdateTableRequest.ts
@@ -315,6 +592,8 @@ tree:
315
592
  type: file
316
593
  - name: UpsertRequest.ts
317
594
  type: file
595
+ - name: UsageCount.ts
596
+ type: file
318
597
  - name: index.ts
319
598
  type: file
320
599
  - name: runtime.ts
@@ -325,12 +604,13 @@ tree:
325
604
  - name: QuickbaseClient.ts
326
605
  type: file
327
606
  contents: |
328
- // Generated on 2025-03-11T21:24:23.349Z
329
- import { App, CopyApp200Response, CopyAppRequest, CreateApp200Response, CreateAppRequest, DeleteRecords200Response, DeleteRecordsRequest, DeleteTableResponse, Field, GetRelationships200Response, GetTempTokenDBID200Response, ReportRunResponse, RunQueryRequest, RunQueryResponse, Table, UpdateTableRequest, Upsert200Response, Upsert207Response, UpsertRequest } from "../generated/models";
607
+ // Generated on 2025-03-12T19:20:50.952Z
608
+ import { App, CopyApp200Response, CopyAppRequest, CreateApp200Response, CreateAppRequest, CreateField200Response, CreateFieldRequest, DeleteApp200Response, DeleteAppRequest, DeleteFields200Response, DeleteFieldsRequest, DeleteRecords200Response, DeleteRecordsRequest, DeleteTableResponse, Field, FieldUsage, GetRelationships200Response, GetTempTokenDBID200Response, ReportRunResponse, RunQueryRequest, RunQueryResponse, Table, UpdateTableRequest, Upsert200Response, Upsert207Response, UpsertRequest } from "../generated/models";
330
609
 
331
610
  export interface QuickbaseClient {
332
611
  createApp: (params: { body: CreateAppRequest }) => Promise<CreateApp200Response>;
333
612
  getApp: (params: { appId: string }) => Promise<App>;
613
+ deleteApp: (params: { appId: string; body: DeleteAppRequest }) => Promise<DeleteApp200Response>;
334
614
  getAppEvents: (params: { appId: string }) => Promise<any[]>;
335
615
  copyApp: (params: { appId: string; body: CopyAppRequest }) => Promise<CopyApp200Response>;
336
616
  getAppTables: (params: { appId: string }) => Promise<Table[]>;
@@ -346,10 +626,11 @@ tree:
346
626
  getReport: (params: { tableId: string; reportId: string }) => Promise<{ [key: string]: any }>;
347
627
  runReport: (params: { reportId: string; generated: any }) => Promise<ReportRunResponse[]>;
348
628
  getFields: (params: { tableId: string; includeFieldPerms?: boolean }) => Promise<Field[]>;
349
- getField: (params: { tableId: string; includeFieldPerms?: boolean; fieldId: number }) => Promise<{ [key: string]: any }>;
350
- updateField: (params: { tableId: string; fieldId: number; generated?: any }) => Promise<{ [key: string]: any }>;
351
- getFieldsUsage: (params: { tableId: string; skip?: number; top?: number }) => Promise<any[]>;
352
- getFieldUsage: (params: { tableId: string; fieldId: number }) => Promise<any[]>;
629
+ createField: (params: { tableId: string; body: CreateFieldRequest }) => Promise<CreateField200Response>;
630
+ deleteFields: (params: { tableId: string; body: DeleteFieldsRequest }) => Promise<DeleteFields200Response>;
631
+ getField: (params: { fieldId: number; tableId: string; includeFieldPerms?: boolean }) => Promise<Field>;
632
+ getFieldsUsage: (params: { tableId: string; skip?: number; top?: number }) => Promise<FieldUsage[]>;
633
+ getFieldUsage: (params: { fieldId: number; tableId: string }) => Promise<FieldUsage[]>;
353
634
  runFormula: (params: { generated?: any }) => Promise<{ [key: string]: any }>;
354
635
  upsert: (params: { body: UpsertRequest }) => Promise<Upsert200Response | Upsert207Response>;
355
636
  deleteRecords: (params: { body: DeleteRecordsRequest }) => Promise<DeleteRecords200Response>;
@@ -387,13 +668,6 @@ tree:
387
668
  }
388
669
  - name: index.ts
389
670
  type: file
390
- - name: log-gen.ts
391
- type: file
392
- - name: openapi-jars
393
- type: directory
394
- children:
395
- - name: openapi-generator-cli-7.12.0.jar
396
- type: file
397
671
  - name: quickbaseClient.ts
398
672
  type: file
399
673
  contents: |
@@ -478,15 +752,15 @@ tree:
478
752
 
479
753
  const tokenCache = new TokenCache();
480
754
 
481
- const headers: HTTPHeaders = {
755
+ const baseHeaders: HTTPHeaders = {
482
756
  "QB-Realm-Hostname": `${realm}.quickbase.com`,
483
757
  "Content-Type": "application/json",
484
758
  };
485
759
 
486
760
  if (initialTempToken) {
487
- headers["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
761
+ baseHeaders["Authorization"] = `QB-TEMP-TOKEN ${initialTempToken}`;
488
762
  } else if (userToken && !useTempTokens) {
489
- headers["Authorization"] = `QB-USER-TOKEN ${userToken}`;
763
+ baseHeaders["Authorization"] = `QB-USER-TOKEN ${userToken}`;
490
764
  }
491
765
 
492
766
  const defaultFetch: typeof fetch | undefined =
@@ -495,7 +769,7 @@ tree:
495
769
  : undefined;
496
770
  const configuration = new Configuration({
497
771
  basePath: baseUrl,
498
- headers: { ...headers },
772
+ headers: { ...baseHeaders },
499
773
  fetchApi: fetchApi || defaultFetch,
500
774
  credentials: useTempTokens ? "include" : "omit",
501
775
  });
@@ -561,7 +835,7 @@ tree:
561
835
  `https://api.quickbase.com/v1/auth/temporary/${dbid}`,
562
836
  {
563
837
  method: "GET",
564
- headers: { ...headers },
838
+ headers: { ...baseHeaders },
565
839
  credentials: "include",
566
840
  }
567
841
  );
@@ -593,23 +867,24 @@ tree:
593
867
  return token;
594
868
  };
595
869
 
596
- const invokeMethod = async <K extends keyof QuickbaseClient>(
870
+ async function invokeMethod<K extends keyof QuickbaseClient>(
597
871
  methodName: K,
598
872
  params: Parameters<QuickbaseClient[K]>[0] & Partial<TempTokenParams>,
599
873
  retryCount: number = 0
600
- ): Promise<ReturnType<QuickbaseClient[K]>> => {
874
+ ): Promise<ReturnType<QuickbaseClient[K]>> {
601
875
  const methodInfo = methodMap[methodName];
602
876
  if (!methodInfo) {
603
877
  throw new Error(`Method ${methodName} not found`);
604
878
  }
605
879
 
606
- const args: [any, RequestInit | undefined] =
880
+ const requestParameters =
607
881
  methodInfo.paramMap.length === 1 &&
608
882
  methodInfo.paramMap[0] === "requestParameters"
609
- ? [{ requestParameters: params }, undefined]
610
- : [params, undefined];
883
+ ? { requestParameters: params }
884
+ : params;
885
+ let requestOptions: RequestInit = {};
611
886
 
612
- let token =
887
+ const selectedToken =
613
888
  initialTempToken || (userToken && !useTempTokens ? userToken : undefined);
614
889
 
615
890
  if (methodName === "getTempTokenDBID" && useTempTokens) {
@@ -622,43 +897,38 @@ tree:
622
897
  }
623
898
  }
624
899
 
625
- if (useTempTokens && !token) {
900
+ let authorizationToken = selectedToken;
901
+ if (useTempTokens && !authorizationToken) {
626
902
  const dbid = extractDbid(
627
903
  params,
628
904
  `No dbid found in params for ${methodName} to fetch temp token`
629
905
  );
630
906
  const cachedToken = tokenCache.get(dbid);
631
- if (cachedToken) {
632
- token = cachedToken;
633
- } else {
634
- if (typeof globalThis.window === "undefined" && !fetchApi) {
635
- throw new Error(
636
- "Temporary tokens require a browser environment or a custom fetchApi with browser-like session support"
637
- );
638
- }
639
- token = await fetchTempToken(dbid);
640
- if (methodName === "getTempTokenDBID") {
641
- return { temporaryAuthorization: token } as ReturnType<
642
- QuickbaseClient[K]
643
- >;
644
- }
907
+ authorizationToken = cachedToken || (await fetchTempToken(dbid));
908
+ if (methodName === "getTempTokenDBID") {
909
+ return { temporaryAuthorization: authorizationToken } as ReturnType<
910
+ QuickbaseClient[K]
911
+ >;
645
912
  }
646
- args[1] = {
647
- headers: {
648
- ...headers,
649
- Authorization: `QB-TEMP-TOKEN ${token}`,
650
- },
913
+ requestOptions.headers = {
914
+ ...baseHeaders,
915
+ Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
916
+ };
917
+ } else if (authorizationToken) {
918
+ requestOptions.headers = {
919
+ ...baseHeaders,
920
+ Authorization: `QB-USER-TOKEN ${authorizationToken}`,
651
921
  };
652
922
  }
653
923
 
654
924
  try {
655
- const response = await methodInfo.method(...args);
656
- return response;
925
+ return await methodInfo.method(requestParameters, requestOptions);
657
926
  } catch (error) {
658
927
  if (
659
928
  error instanceof ResponseError &&
660
929
  error.response.status === 401 &&
661
- retryCount < 1
930
+ retryCount < 1 &&
931
+ useTempTokens
662
932
  ) {
663
933
  if (debug) {
664
934
  console.log(
@@ -670,12 +940,10 @@ tree:
670
940
  params,
671
941
  `No dbid to refresh token after authorization error`
672
942
  );
673
- token = await fetchTempToken(dbid);
674
- args[1] = {
675
- headers: {
676
- ...headers,
677
- Authorization: `QB-TEMP-TOKEN ${token}`,
678
- },
943
+ authorizationToken = await fetchTempToken(dbid);
944
+ requestOptions.headers = {
945
+ ...baseHeaders,
946
+ Authorization: `QB-TEMP-TOKEN ${authorizationToken}`,
679
947
  };
680
948
  if (debug) {
681
949
  console.log(`Retrying ${methodName} with new token`);
@@ -699,7 +967,7 @@ tree:
699
967
  }
700
968
  throw error;
701
969
  }
702
- };
970
+ }
703
971
 
704
972
  const proxy = new Proxy<QuickbaseClient>({} as QuickbaseClient, {
705
973
  get: (_, prop: string): ((params: any) => Promise<any>) | undefined => {
@@ -716,15 +984,71 @@ tree:
716
984
  }
717
985
  - name: tokenCache.ts
718
986
  type: file
987
+ contents: |
988
+ interface CachedToken {
989
+ token: string;
990
+ expiresAt: number; // Timestamp in milliseconds
991
+ }
992
+
993
+ export class TokenCache {
994
+ private cache: Map<string, CachedToken>;
995
+ private readonly lifespan: number; // Token lifespan in milliseconds
996
+
997
+ constructor(lifespan: number = 4 * 60 * 1000 + 50 * 1000) {
998
+ // Default 4:50
999
+ this.cache = new Map<string, CachedToken>();
1000
+ this.lifespan = lifespan;
1001
+ }
1002
+
1003
+ get(dbid: string): string | undefined {
1004
+ const entry = this.cache.get(dbid);
1005
+ const now = Date.now();
1006
+ if (entry && entry.expiresAt > now) {
1007
+ return entry.token;
1008
+ }
1009
+ return undefined; // Expired or not found
1010
+ }
1011
+
1012
+ // New method to get full entry
1013
+ getEntry(dbid: string): CachedToken | undefined {
1014
+ const entry = this.cache.get(dbid);
1015
+ const now = Date.now();
1016
+ if (entry && entry.expiresAt > now) {
1017
+ return entry;
1018
+ }
1019
+ return undefined; // Expired or not found
1020
+ }
1021
+
1022
+ set(dbid: string, token: string): void {
1023
+ const now = Date.now();
1024
+ this.cache.set(dbid, {
1025
+ token,
1026
+ expiresAt: now + this.lifespan,
1027
+ });
1028
+ }
1029
+
1030
+ dump(): [string, CachedToken][] {
1031
+ return Array.from(this.cache.entries());
1032
+ }
1033
+
1034
+ // New method to clear the cache
1035
+ clear(): void {
1036
+ this.cache.clear();
1037
+ }
1038
+ }
719
1039
  - name: test-artifacts
720
1040
  type: directory
721
1041
  children:
722
1042
  - name: .last-run.json
723
1043
  type: file
724
- - name: test-results.json
725
- type: file
1044
+ - name: test-esm.js
1045
+ type: file
726
1046
  - name: test-results.json
727
1047
  type: file
1048
+ - name: test-umd.js
1049
+ type: file
1050
+ - name: test.ts
1051
+ type: file
728
1052
  - name: tests
729
1053
  type: directory
730
1054
  children:
@@ -749,6 +1073,8 @@ tree:
749
1073
  type: file
750
1074
  - name: tokenRenewal.test.ts
751
1075
  type: file
1076
+ - name: umd.test.ts
1077
+ type: file
752
1078
  - name: setup.ts
753
1079
  type: file
754
1080
  - name: vitest
@@ -760,13 +1086,29 @@ tree:
760
1086
  - name: apps
761
1087
  type: directory
762
1088
  children:
1089
+ - name: copyApp.test.ts
1090
+ type: file
1091
+ - name: createApp.test.ts
1092
+ type: file
1093
+ - name: deleteApp.test.ts
1094
+ type: file
763
1095
  - name: getApp.test.ts
764
1096
  type: file
765
1097
  - name: fields
766
1098
  type: directory
767
1099
  children:
1100
+ - name: createField.test.ts
1101
+ type: file
1102
+ - name: deleteFields.test.ts
1103
+ type: file
1104
+ - name: getField.test.ts
1105
+ type: file
1106
+ - name: getFieldUsage.test.ts
1107
+ type: file
768
1108
  - name: getFields.test.ts
769
1109
  type: file
1110
+ - name: getFieldsUsage.test.ts
1111
+ type: file
770
1112
  - name: index.ts
771
1113
  type: file
772
1114
  - name: records
@@ -799,1882 +1141,161 @@ tree:
799
1141
  - name: apps
800
1142
  type: directory
801
1143
  children:
1144
+ - name: copyApp.test.ts
1145
+ type: file
1146
+ - name: createApp.test.ts
1147
+ type: file
1148
+ - name: deleteApp.test.ts
1149
+ type: file
802
1150
  - name: getApp.test.ts
803
1151
  type: file
804
- contents: |
805
- // tests/vitest/unit/apps/getApp.test.ts
806
- import { describe, it, expect, beforeEach } from "vitest";
807
- import {
808
- createClient,
809
- mockFetch,
810
- QB_APP_ID,
811
- QB_REALM,
812
- QB_USER_TOKEN,
813
- } from "@tests/setup.ts";
814
-
815
- describe("QuickbaseClient Unit - getApp", () => {
816
- let client: ReturnType<typeof createClient>;
817
-
818
- beforeEach(() => {
819
- mockFetch.mockClear();
820
- client = createClient(mockFetch, { debug: true }); // Add debug: true for consistency
821
- });
822
-
823
- it("initializes without errors", () => {
824
- expect(client).toBeDefined();
825
- });
826
-
827
- it("has getApp method", () => {
828
- expect(typeof client.getApp).toBe("function");
829
- });
830
-
831
- it("calls getApp successfully", async () => {
832
- mockFetch.mockImplementation((url: string, options: any) => {
833
- console.log("Mock fetch:", url, options);
834
- return Promise.resolve({
835
- ok: true,
836
- status: 200,
837
- json: () =>
838
- Promise.resolve({
839
- id: QB_APP_ID,
840
- name: "qb-copy",
841
- created: "2025-02-13T18:22:33Z",
842
- updated: "2025-03-04T04:25:51Z",
843
- description: "",
844
- timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
845
- dateFormat: "MM-DD-YYYY",
846
- hasEveryoneOnTheInternet: false,
847
- memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
848
- securityProperties: {
849
- allowClone: false,
850
- allowExport: true,
851
- enableAppTokens: true,
852
- hideFromPublic: false,
853
- mustBeRealmApproved: false,
854
- useIPFilter: false,
855
- },
856
- }),
857
- } as Response);
858
- });
859
-
860
- const result = await client.getApp({ appId: QB_APP_ID });
861
- expect(result).toEqual({
862
- id: QB_APP_ID,
863
- name: "qb-copy",
864
- created: new Date("2025-02-13T18:22:33Z"),
865
- updated: new Date("2025-03-04T04:25:51Z"),
866
- description: "",
867
- timeZone: "(UTC-08:00) Pacific Time (US & Canada)",
868
- dateFormat: "MM-DD-YYYY",
869
- hasEveryoneOnTheInternet: false,
870
- memoryInfo: { estMemory: 0, estMemoryInclDependentApps: 0 },
871
- securityProperties: {
872
- allowClone: false,
873
- allowExport: true,
874
- enableAppTokens: true,
875
- hideFromPublic: false,
876
- mustBeRealmApproved: false,
877
- useIPFilter: false,
878
- },
879
- });
880
- expect(mockFetch).toHaveBeenCalledWith(
881
- `https://api.quickbase.com/v1/apps/${QB_APP_ID}`,
882
- expect.objectContaining({
883
- headers: expect.objectContaining({
884
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
885
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
886
- }),
887
- })
888
- );
889
- });
890
- });
891
1152
  - name: auth
892
1153
  type: directory
893
1154
  children:
894
1155
  - name: fetchTempToken401.test.ts
895
1156
  type: file
896
- contents: |
897
- // tests/vitest/unit/auth/fetchTempToken401.test.ts
898
- import { describe, it, expect, beforeEach, vi } from "vitest";
899
- import {
900
- createClient,
901
- mockFetch,
902
- QB_REALM,
903
- QB_TABLE_ID_1,
904
- } from "@tests/setup.ts";
905
-
906
- describe("QuickbaseClient Unit - 401 with fetchTempToken 401", () => {
907
- let client: ReturnType<typeof createClient>;
908
-
909
- beforeEach(() => {
910
- mockFetch.mockClear();
911
- client = createClient(mockFetch, { useTempTokens: true, debug: true });
912
- });
913
-
914
- it("fails without infinite loop when fetchTempToken returns 401 after initial 401", async () => {
915
- let callCount = 0;
916
-
917
- mockFetch.mockImplementation((url) => {
918
- callCount++;
919
- console.log(`Mock fetch call ${callCount}: ${url}`);
920
- if (url.includes("auth/temporary") && callCount === 1) {
921
- return Promise.resolve({
922
- ok: true,
923
- status: 200,
924
- json: () =>
925
- Promise.resolve({ temporaryAuthorization: "initial_token" }),
926
- });
927
- }
928
- if (url.includes("fields") && callCount === 2) {
929
- return Promise.resolve({
930
- ok: false,
931
- status: 401,
932
- json: () => Promise.resolve({ message: "Unauthorized" }),
933
- });
934
- }
935
- if (url.includes("auth/temporary") && callCount === 3) {
936
- return Promise.resolve({
937
- ok: false,
938
- status: 401,
939
- json: () =>
940
- Promise.resolve({ message: "Unauthorized in fetchTempToken" }),
941
- });
942
- }
943
- return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
944
- });
945
-
946
- const consoleSpy = vi.spyOn(console, "log");
947
- await expect(client.getFields({ tableId: QB_TABLE_ID_1 })).rejects.toThrow(
948
- "API Error: Unauthorized in fetchTempToken (Status: 401)"
949
- );
950
-
951
- expect(mockFetch).toHaveBeenCalledTimes(3);
952
- expect(mockFetch).toHaveBeenNthCalledWith(
953
- 1,
954
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
955
- expect.any(Object)
956
- );
957
- expect(mockFetch).toHaveBeenNthCalledWith(
958
- 2,
959
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`, // Removed &includeFieldPerms=false
960
- expect.objectContaining({
961
- headers: expect.objectContaining({
962
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
963
- }),
964
- })
965
- );
966
- expect(mockFetch).toHaveBeenNthCalledWith(
967
- 3,
968
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
969
- expect.any(Object)
970
- );
971
-
972
- expect(consoleSpy).toHaveBeenCalledWith(
973
- "Authorization error for getFields, refreshing token:",
974
- expect.any(String)
975
- );
976
- expect(consoleSpy).not.toHaveBeenCalledWith(
977
- "Retrying getFields with new token"
978
- );
979
- consoleSpy.mockRestore();
980
- });
981
- });
982
1157
  - name: getTempToken.test.ts
983
1158
  type: file
984
- contents: |
985
- // tests/vitest/unit/auth/getTempTokenDBID.test.ts
986
- import { describe, it, expect, beforeEach } from "vitest";
987
- import {
988
- createClient,
989
- mockFetch,
990
- QB_REALM,
991
- QB_TABLE_ID_1,
992
- } from "@tests/setup.ts";
993
-
994
- describe("QuickbaseClient Unit - getTempTokenDBID", () => {
995
- let client: ReturnType<typeof createClient>;
996
-
997
- beforeEach(() => {
998
- mockFetch.mockClear();
999
- client = createClient(mockFetch, { useTempTokens: true, debug: false });
1000
- });
1001
-
1002
- it("initializes without errors", () => {
1003
- expect(client).toBeDefined();
1004
- });
1005
-
1006
- it("has getTempTokenDBID method", () => {
1007
- expect(typeof client.getTempTokenDBID).toBe("function");
1008
- });
1009
-
1010
- it("fetches and caches temp token on first call", async () => {
1011
- const mockToken = "b123xyz_temp_token";
1012
-
1013
- mockFetch.mockResolvedValueOnce({
1014
- ok: true,
1015
- status: 200,
1016
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
1017
- });
1018
-
1019
- const result = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
1020
- expect(result).toEqual({ temporaryAuthorization: mockToken });
1021
- expect(mockFetch).toHaveBeenCalledWith(
1022
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
1023
- expect.objectContaining({
1024
- method: "GET",
1025
- headers: expect.objectContaining({
1026
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1027
- "Content-Type": "application/json",
1028
- }),
1029
- credentials: "include",
1030
- })
1031
- );
1032
- });
1033
-
1034
- it("reuses cached temp token on second call", async () => {
1035
- const mockToken = "b123xyz_temp_token";
1036
-
1037
- mockFetch.mockResolvedValueOnce({
1038
- ok: true,
1039
- status: 200,
1040
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
1041
- });
1042
-
1043
- const firstResult = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
1044
- expect(firstResult).toEqual({ temporaryAuthorization: mockToken });
1045
- expect(mockFetch).toHaveBeenCalledTimes(1);
1046
-
1047
- mockFetch.mockClear();
1048
- const secondResult = await client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 });
1049
- expect(secondResult).toEqual({ temporaryAuthorization: mockToken });
1050
- expect(mockFetch).not.toHaveBeenCalled();
1051
- });
1052
-
1053
- it("handles API error", async () => {
1054
- mockFetch.mockResolvedValueOnce({
1055
- ok: false,
1056
- status: 401,
1057
- json: () => Promise.resolve({ message: "Unauthorized" }),
1058
- });
1059
-
1060
- await expect(
1061
- client.getTempTokenDBID({ dbid: QB_TABLE_ID_1 })
1062
- ).rejects.toSatisfy((error: Error) => {
1063
- expect(error).toBeInstanceOf(Error);
1064
- expect(error.message).toBe("API Error: Unauthorized (Status: 401)");
1065
- return true;
1066
- });
1067
- expect(mockFetch).toHaveBeenCalledWith(
1068
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
1069
- expect.objectContaining({
1070
- method: "GET",
1071
- headers: expect.objectContaining({
1072
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1073
- "Content-Type": "application/json",
1074
- }),
1075
- credentials: "include",
1076
- })
1077
- );
1078
- });
1079
- });
1080
1159
  - name: retryOn401.test.ts
1081
1160
  type: file
1082
- contents: |
1083
- // tests/vitest/unit/auth/retryOn401.test.ts
1084
- import { describe, it, expect, beforeEach, vi } from "vitest";
1085
- import {
1086
- createClient,
1087
- mockFetch,
1088
- QB_REALM,
1089
- QB_TABLE_ID_1,
1090
- } from "@tests/setup.ts";
1091
-
1092
- describe("QuickbaseClient Unit - 401 Retry Creates New Token", () => {
1093
- let client: ReturnType<typeof createClient>;
1094
-
1095
- beforeEach(() => {
1096
- mockFetch.mockClear();
1097
- client = createClient(mockFetch, { useTempTokens: true, debug: true });
1098
- });
1099
-
1100
- it("creates a new token on 401 and retries successfully", async () => {
1101
- const mockToken = "new_token_456";
1102
- const mockFields = [{ id: 1, label: "Field1" }];
1103
- let callCount = 0;
1104
-
1105
- mockFetch.mockImplementation((url) => {
1106
- callCount++;
1107
- console.log(`Mock fetch call ${callCount}: ${url}`);
1108
- if (url.includes("auth/temporary") && callCount === 1) {
1109
- return Promise.resolve({
1110
- ok: true,
1111
- status: 200,
1112
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
1113
- });
1114
- }
1115
- if (url.includes("fields") && callCount === 2) {
1116
- return Promise.resolve({
1117
- ok: false,
1118
- status: 401,
1119
- text: () => Promise.resolve("Unauthorized"),
1120
- });
1121
- }
1122
- if (url.includes("auth/temporary") && callCount === 3) {
1123
- return Promise.resolve({
1124
- ok: true,
1125
- status: 200,
1126
- json: () =>
1127
- Promise.resolve({ temporaryAuthorization: mockToken + "_retry" }),
1128
- });
1129
- }
1130
- if (url.includes("fields") && callCount === 4) {
1131
- return Promise.resolve({
1132
- ok: true,
1133
- status: 200,
1134
- json: () => Promise.resolve(mockFields),
1135
- });
1136
- }
1137
- return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
1138
- });
1139
-
1140
- const consoleSpy = vi.spyOn(console, "log");
1141
- const result = await client.getFields({ tableId: QB_TABLE_ID_1 });
1142
-
1143
- expect(result).toEqual(mockFields);
1144
- expect(mockFetch).toHaveBeenCalledTimes(4);
1145
- expect(mockFetch).toHaveBeenNthCalledWith(
1146
- 1,
1147
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
1148
- expect.any(Object)
1149
- );
1150
- expect(mockFetch).toHaveBeenNthCalledWith(
1151
- 2,
1152
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`, // Removed &includeFieldPerms=false
1153
- expect.objectContaining({
1154
- headers: expect.objectContaining({
1155
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1156
- }),
1157
- })
1158
- );
1159
- expect(mockFetch).toHaveBeenNthCalledWith(
1160
- 3,
1161
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
1162
- expect.any(Object)
1163
- );
1164
- expect(mockFetch).toHaveBeenNthCalledWith(
1165
- 4,
1166
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}`, // Removed &includeFieldPerms=false
1167
- expect.objectContaining({
1168
- headers: expect.objectContaining({
1169
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1170
- }),
1171
- })
1172
- );
1173
-
1174
- expect(consoleSpy).toHaveBeenCalledWith(
1175
- "Authorization error for getFields, refreshing token:",
1176
- expect.any(String)
1177
- );
1178
- expect(consoleSpy).toHaveBeenCalledWith(
1179
- `Fetched and cached new token for dbid: ${QB_TABLE_ID_1}`,
1180
- mockToken + "_retry",
1181
- expect.any(String)
1182
- );
1183
- expect(consoleSpy).toHaveBeenCalledWith(
1184
- "Retrying getFields with new token"
1185
- );
1186
- consoleSpy.mockRestore();
1187
- });
1188
- });
1189
1161
  - name: two401s.test.ts
1190
1162
  type: file
1191
- contents: |
1192
- import { describe, it, expect, beforeEach, vi } from "vitest";
1193
- import { createClient, mockFetch } from "@tests/setup.ts";
1194
-
1195
- describe("QuickbaseClient - Two 401s in a Row", () => {
1196
- let client: ReturnType<typeof createClient>;
1197
-
1198
- beforeEach(() => {
1199
- mockFetch.mockClear();
1200
- client = createClient(mockFetch, { useTempTokens: true, debug: true });
1201
- });
1202
-
1203
- it("logs an error after two 401s in a row", async () => {
1204
- const mockDbid = "mockDbid123";
1205
- const mockToken = "new_token_456";
1206
- let callCount = 0;
1207
-
1208
- mockFetch.mockImplementation((url) => {
1209
- callCount++;
1210
- console.log(`Mock fetch call ${callCount}: ${url}`);
1211
- if (url.includes("auth/temporary") && callCount === 1) {
1212
- return Promise.resolve({
1213
- ok: true,
1214
- status: 200,
1215
- json: () => Promise.resolve({ temporaryAuthorization: mockToken }),
1216
- });
1217
- }
1218
- if (url.includes("fields") && callCount === 2) {
1219
- return Promise.resolve({
1220
- ok: false,
1221
- status: 401,
1222
- text: () => Promise.resolve("Unauthorized"),
1223
- });
1224
- }
1225
- if (url.includes("auth/temporary") && callCount === 3) {
1226
- return Promise.resolve({
1227
- ok: true,
1228
- status: 200,
1229
- json: () =>
1230
- Promise.resolve({ temporaryAuthorization: mockToken + "_retry" }),
1231
- });
1232
- }
1233
- if (url.includes("fields") && callCount === 4) {
1234
- return Promise.resolve({
1235
- ok: false,
1236
- status: 401,
1237
- json: () => Promise.resolve({ message: "Unauthorized again" }), // Changed to json()
1238
- text: () => Promise.resolve("Unauthorized again"), // Keep text() for fallback
1239
- });
1240
- }
1241
- return Promise.reject(new Error(`Unexpected fetch call: ${url}`));
1242
- });
1243
-
1244
- const consoleSpy = vi.spyOn(console, "log");
1245
- await expect(client.getFields({ tableId: mockDbid })).rejects.toThrow(
1246
- "API Error: Unauthorized again (Status: 401)"
1247
- );
1248
-
1249
- expect(mockFetch).toHaveBeenCalledTimes(4);
1250
- expect(consoleSpy).toHaveBeenCalledWith(
1251
- "Authorization error for getFields, refreshing token:",
1252
- expect.any(String)
1253
- );
1254
- expect(consoleSpy).toHaveBeenCalledWith(
1255
- "Fetched and cached new token for dbid: mockDbid123",
1256
- mockToken + "_retry",
1257
- expect.any(String)
1258
- );
1259
- expect(consoleSpy).toHaveBeenCalledWith(
1260
- "Retrying getFields with new token"
1261
- );
1262
- expect(consoleSpy).toHaveBeenCalledWith(
1263
- "Error response body for getFields:",
1264
- { message: "Unauthorized again" }
1265
- );
1266
- consoleSpy.mockRestore();
1267
- });
1268
- });
1269
1163
  - name: fields
1270
1164
  type: directory
1271
1165
  children:
1166
+ - name: createField.test.ts
1167
+ type: file
1168
+ - name: deleteFields.test.ts
1169
+ type: file
1170
+ - name: getField.test.ts
1171
+ type: file
1172
+ - name: getFieldUsage.test.ts
1173
+ type: file
1272
1174
  - name: getFields.test.ts
1273
1175
  type: file
1274
- contents: |
1275
- // tests/vitest/unit/fields/getFields.test.ts
1276
- import { describe, it, expect } from "vitest";
1277
- import {
1278
- createClient,
1279
- mockFetch,
1280
- QB_REALM,
1281
- QB_USER_TOKEN,
1282
- QB_TABLE_ID_1,
1283
- } from "@tests/setup.ts";
1284
-
1285
- describe("QuickbaseClient Unit - getFields", () => {
1286
- it("calls getFields successfully", async () => {
1287
- mockFetch.mockImplementation((url: string, options: any) => {
1288
- console.log("Mock fetch for getFields:", url, options);
1289
- return Promise.resolve({
1290
- ok: true,
1291
- status: 200,
1292
- json: () =>
1293
- Promise.resolve([
1294
- {
1295
- id: 1,
1296
- label: "Field1",
1297
- fieldType: "text",
1298
- noWrap: false,
1299
- bold: false,
1300
- required: false,
1301
- appearsByDefault: false,
1302
- findEnabled: false,
1303
- unique: false,
1304
- doesDataCopy: false,
1305
- fieldHelp: "field help",
1306
- audited: false,
1307
- properties: {
1308
- primaryKey: false,
1309
- foreignKey: false,
1310
- numLines: 1,
1311
- maxLength: 0,
1312
- appendOnly: false,
1313
- allowHTML: false,
1314
- allowMentions: false,
1315
- sortAsGiven: false,
1316
- carryChoices: true,
1317
- allowNewChoices: false,
1318
- formula: "",
1319
- defaultValue: "",
1320
- },
1321
- permissions: [
1322
- { permissionType: "View", role: "Viewer", roleId: 10 },
1323
- { permissionType: "None", role: "Participant", roleId: 11 },
1324
- { permissionType: "Modify", role: "Administrator", roleId: 12 },
1325
- ],
1326
- },
1327
- ]),
1328
- } as Response);
1329
- });
1330
-
1331
- const client = createClient(mockFetch, { debug: true }); // Add debug: true for consistency
1332
- const result = await client.getFields({
1333
- tableId: QB_TABLE_ID_1,
1334
- includeFieldPerms: true,
1335
- });
1336
- console.log("getFields response:", result);
1337
- expect(result).toEqual(
1338
- expect.arrayContaining([
1339
- expect.objectContaining({
1340
- id: 1,
1341
- label: "Field1",
1342
- fieldType: "text",
1343
- noWrap: false,
1344
- bold: false,
1345
- required: false,
1346
- appearsByDefault: false,
1347
- findEnabled: false,
1348
- unique: false,
1349
- doesDataCopy: false,
1350
- fieldHelp: "field help",
1351
- audited: false,
1352
- properties: expect.objectContaining({
1353
- primaryKey: false,
1354
- foreignKey: false,
1355
- numLines: 1,
1356
- maxLength: 0,
1357
- appendOnly: false,
1358
- allowHTML: false,
1359
- allowMentions: false,
1360
- sortAsGiven: false,
1361
- carryChoices: true,
1362
- allowNewChoices: false,
1363
- formula: "",
1364
- defaultValue: "",
1365
- }),
1366
- permissions: expect.arrayContaining([
1367
- expect.objectContaining({
1368
- permissionType: "View",
1369
- role: "Viewer",
1370
- roleId: 10,
1371
- }),
1372
- expect.objectContaining({
1373
- permissionType: "None",
1374
- role: "Participant",
1375
- roleId: 11,
1376
- }),
1377
- expect.objectContaining({
1378
- permissionType: "Modify",
1379
- role: "Administrator",
1380
- roleId: 12,
1381
- }),
1382
- ]),
1383
- }),
1384
- ])
1385
- );
1386
- expect(mockFetch).toHaveBeenCalledWith(
1387
- `https://api.quickbase.com/v1/fields?tableId=${QB_TABLE_ID_1}&includeFieldPerms=true`,
1388
- expect.objectContaining({
1389
- headers: expect.objectContaining({
1390
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1391
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1392
- }),
1393
- })
1394
- );
1395
- });
1396
- });
1176
+ - name: getFieldsUsage.test.ts
1177
+ type: file
1397
1178
  - name: index.ts
1398
1179
  type: file
1399
- contents: |
1400
- // tests/unit/index.ts
1401
- export * from "./apps/getApp.test.ts";
1402
- export * from "./fields/getFields.test.ts";
1403
- export * from "./tables/getAppTables.test.ts";
1404
1180
  - name: records
1405
1181
  type: directory
1406
1182
  children:
1407
1183
  - name: deleteRecords.test.ts
1408
1184
  type: file
1409
- contents: |
1410
- // tests/vitest/unit/records/deleteRecords.test.ts
1411
- import { describe, expect, test, beforeEach } from "vitest";
1412
- import {
1413
- createClient,
1414
- mockFetch,
1415
- QB_REALM,
1416
- QB_USER_TOKEN,
1417
- QB_TABLE_ID_1,
1418
- } from "@tests/setup.ts";
1419
-
1420
- describe("QuickbaseClient Unit - deleteRecords", () => {
1421
- let qb: ReturnType<typeof createClient>;
1422
-
1423
- beforeEach(() => {
1424
- mockFetch.mockClear();
1425
- qb = createClient(mockFetch, { debug: true }); // Use debug: true for consistency with other tests
1426
- });
1427
-
1428
- test("sends correct DELETE request and handles success", async () => {
1429
- mockFetch.mockResolvedValueOnce({
1430
- ok: true,
1431
- status: 200,
1432
- json: () => Promise.resolve({ numberDeleted: 1 }),
1433
- });
1434
-
1435
- const result = await qb.deleteRecords({
1436
- body: {
1437
- from: QB_TABLE_ID_1,
1438
- where: "{3.EX.'5'}", // Simulate deleting Record ID 5
1439
- },
1440
- });
1441
-
1442
- expect(mockFetch).toHaveBeenCalledWith(
1443
- "https://api.quickbase.com/v1/records",
1444
- expect.objectContaining({
1445
- method: "DELETE",
1446
- headers: expect.objectContaining({
1447
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1448
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1449
- "Content-Type": "application/json",
1450
- }),
1451
- body: JSON.stringify({ from: QB_TABLE_ID_1, where: "{3.EX.'5'}" }),
1452
- })
1453
- );
1454
- expect(result).toEqual({ numberDeleted: 1 });
1455
- });
1456
-
1457
- test("handles no records deleted", async () => {
1458
- mockFetch.mockResolvedValueOnce({
1459
- ok: true,
1460
- status: 200,
1461
- json: () => Promise.resolve({ numberDeleted: 0 }),
1462
- });
1463
-
1464
- const result = await qb.deleteRecords({
1465
- body: {
1466
- from: QB_TABLE_ID_1,
1467
- where: "{3.EX.'999999'}", // Non-existent Record ID
1468
- },
1469
- });
1470
-
1471
- expect(mockFetch).toHaveBeenCalledWith(
1472
- "https://api.quickbase.com/v1/records",
1473
- expect.objectContaining({
1474
- method: "DELETE",
1475
- headers: expect.objectContaining({
1476
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1477
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1478
- "Content-Type": "application/json",
1479
- }),
1480
- body: JSON.stringify({
1481
- from: QB_TABLE_ID_1,
1482
- where: "{3.EX.'999999'}",
1483
- }),
1484
- })
1485
- );
1486
- expect(result).toEqual({ numberDeleted: 0 });
1487
- });
1488
-
1489
- test("throws error on API failure", async () => {
1490
- mockFetch.mockResolvedValueOnce({
1491
- ok: false,
1492
- status: 400,
1493
- json: () => Promise.resolve({ message: "Invalid query" }),
1494
- });
1495
-
1496
- await expect(() =>
1497
- qb.deleteRecords({
1498
- body: {
1499
- from: QB_TABLE_ID_1,
1500
- where: "{3.EX.'invalid'}",
1501
- },
1502
- })
1503
- ).rejects.toThrow("API Error: Invalid query (Status: 400)");
1504
-
1505
- expect(mockFetch).toHaveBeenCalledWith(
1506
- "https://api.quickbase.com/v1/records",
1507
- expect.objectContaining({
1508
- method: "DELETE",
1509
- headers: expect.objectContaining({
1510
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1511
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1512
- "Content-Type": "application/json",
1513
- }),
1514
- body: JSON.stringify({
1515
- from: QB_TABLE_ID_1,
1516
- where: "{3.EX.'invalid'}",
1517
- }),
1518
- })
1519
- );
1520
- });
1521
- });
1522
1185
  - name: runQuery.test.ts
1523
1186
  type: file
1524
- contents: |
1525
- // tests/vitest/unit/records/runQuery.test.ts
1526
- import { describe, test, expect, beforeEach } from "vitest";
1527
- import {
1528
- createClient,
1529
- mockFetch,
1530
- QB_REALM,
1531
- QB_USER_TOKEN,
1532
- QB_TABLE_ID_1,
1533
- } from "@tests/setup.ts";
1534
- import { RunQueryRequest, RunQueryResponse } from "@/generated";
1535
-
1536
- describe("QuickbaseClient Unit - runQuery", () => {
1537
- let client: ReturnType<typeof createClient>;
1538
-
1539
- beforeEach(() => {
1540
- mockFetch.mockClear();
1541
- client = createClient(mockFetch, { debug: true });
1542
- });
1543
-
1544
- test("sends correct POST request and handles successful query response", async () => {
1545
- const queryRequest: RunQueryRequest = {
1546
- from: QB_TABLE_ID_1,
1547
- select: [3, 6, 7],
1548
- where: "{6.EX.'Task 1'}",
1549
- sortBy: [{ fieldId: 6, order: "ASC" }],
1550
- groupBy: [{ fieldId: 7, grouping: "equal-values" }],
1551
- options: { skip: 0, top: 100, compareWithAppLocalTime: false },
1552
- };
1553
-
1554
- const mockResponse: RunQueryResponse = {
1555
- data: [
1556
- { "3": { value: 1 }, "6": { value: "Task 1" }, "7": { value: "High" } },
1557
- ],
1558
- fields: [
1559
- { id: 3, label: "Record ID#", type: "recordid" },
1560
- { id: 6, label: "Task Name", type: "text" },
1561
- { id: 7, label: "Priority", type: "text" },
1562
- ],
1563
- metadata: {
1564
- numFields: 3,
1565
- numRecords: 1,
1566
- skip: 0,
1567
- top: 100,
1568
- totalRecords: 1,
1569
- },
1570
- };
1571
-
1572
- mockFetch.mockResolvedValueOnce({
1573
- ok: true,
1574
- status: 200,
1575
- json: () => Promise.resolve(mockResponse),
1576
- });
1577
-
1578
- const result = await client.runQuery({ body: queryRequest });
1579
-
1580
- expect(mockFetch).toHaveBeenCalledWith(
1581
- "https://api.quickbase.com/v1/records/query",
1582
- expect.objectContaining({
1583
- method: "POST",
1584
- headers: expect.objectContaining({
1585
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1586
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1587
- "Content-Type": "application/json",
1588
- }),
1589
- body: JSON.stringify(queryRequest),
1590
- })
1591
- );
1592
-
1593
- expect(result).toEqual(mockResponse);
1594
- });
1595
-
1596
- test("handles API error with invalid query", async () => {
1597
- const invalidQueryRequest = {
1598
- from: QB_TABLE_ID_1,
1599
- select: [3],
1600
- where: "{999.EX.'Invalid'}", // Invalid field ID
1601
- };
1602
-
1603
- mockFetch.mockResolvedValueOnce({
1604
- ok: false,
1605
- status: 400,
1606
- json: () => Promise.resolve({ message: "Invalid query" }),
1607
- });
1608
-
1609
- await expect(
1610
- client.runQuery({ body: invalidQueryRequest })
1611
- ).rejects.toThrow("API Error: Invalid query (Status: 400)");
1612
-
1613
- expect(mockFetch).toHaveBeenCalledWith(
1614
- "https://api.quickbase.com/v1/records/query",
1615
- expect.objectContaining({
1616
- method: "POST",
1617
- headers: expect.objectContaining({
1618
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1619
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1620
- "Content-Type": "application/json",
1621
- }),
1622
- body: JSON.stringify(invalidQueryRequest),
1623
- })
1624
- );
1625
- });
1626
- });
1627
1187
  - name: upsert.test.ts
1628
1188
  type: file
1629
- contents: |
1630
- // tests/vitest/unit/records/upsert.test.ts
1631
- import { describe, expect, test, beforeEach } from "vitest";
1632
- import {
1633
- createClient,
1634
- mockFetch,
1635
- QB_REALM,
1636
- QB_USER_TOKEN,
1637
- QB_TABLE_ID_1,
1638
- } from "@tests/setup.ts";
1639
- import { vi } from "vitest";
1640
-
1641
- // Note: Type augmentation moved to src/types.d.ts
1642
- describe("QuickbaseClient Unit - upsert", () => {
1643
- beforeEach(() => {
1644
- vi.resetModules(); // Reset module cache
1645
- mockFetch.mockClear();
1646
- });
1647
-
1648
- test("sends correct POST request and handles successful upsert", async () => {
1649
- const qb = createClient(mockFetch, { debug: true });
1650
-
1651
- mockFetch.mockResolvedValueOnce({
1652
- ok: true,
1653
- status: 200,
1654
- json: () =>
1655
- Promise.resolve({
1656
- data: [{ id: 1, "6": { value: "value1" } }],
1657
- metadata: {
1658
- createdRecordIds: [1],
1659
- updatedRecordIds: [],
1660
- unchangedRecordIds: [],
1661
- totalNumberOfRecordsProcessed: 1,
1662
- },
1663
- }),
1664
- });
1665
-
1666
- console.log("[Test] Calling qb.upsert with:", {
1667
- body: { to: QB_TABLE_ID_1, data: [{ "6": { value: "value1" } }] },
1668
- });
1669
- const result = await qb.upsert({
1670
- body: { to: QB_TABLE_ID_1, data: [{ "6": { value: "value1" } }] },
1671
- });
1672
-
1673
- console.log("[Test] mockFetch calls:", mockFetch.mock.calls);
1674
- expect(mockFetch).toHaveBeenCalledWith(
1675
- "https://api.quickbase.com/v1/records",
1676
- expect.objectContaining({
1677
- method: "POST",
1678
- headers: expect.objectContaining({
1679
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1680
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1681
- "Content-Type": "application/json",
1682
- }),
1683
- body: JSON.stringify({
1684
- to: QB_TABLE_ID_1,
1685
- data: [{ "6": { value: "value1" } }],
1686
- }),
1687
- })
1688
- );
1689
-
1690
- expect(result).toEqual({
1691
- data: [{ id: 1, "6": { value: "value1" } }],
1692
- metadata: {
1693
- createdRecordIds: [1],
1694
- updatedRecordIds: [],
1695
- unchangedRecordIds: [],
1696
- totalNumberOfRecordsProcessed: 1,
1697
- },
1698
- });
1699
- });
1700
-
1701
- test("handles API error with invalid data", async () => {
1702
- const qb = createClient(mockFetch, { debug: true });
1703
-
1704
- mockFetch.mockResolvedValueOnce({
1705
- ok: false,
1706
- status: 400,
1707
- json: () => Promise.resolve({ message: "Invalid data format" }),
1708
- });
1709
-
1710
- await expect(
1711
- qb.upsert({
1712
- body: { to: QB_TABLE_ID_1, data: [{ "999": { value: "invalid" } }] },
1713
- })
1714
- ).rejects.toThrow("API Error: Invalid data format (Status: 400)");
1715
-
1716
- expect(mockFetch).toHaveBeenCalledWith(
1717
- "https://api.quickbase.com/v1/records",
1718
- expect.objectContaining({
1719
- method: "POST",
1720
- headers: expect.objectContaining({
1721
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1722
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1723
- "Content-Type": "application/json",
1724
- }),
1725
- body: JSON.stringify({
1726
- to: QB_TABLE_ID_1,
1727
- data: [{ "999": { value: "invalid" } }],
1728
- }),
1729
- })
1730
- );
1731
- });
1732
-
1733
- test("handles upsert with temp token", async () => {
1734
- const qb = createClient(mockFetch, { useTempTokens: true, debug: true });
1735
-
1736
- mockFetch
1737
- .mockResolvedValueOnce({
1738
- ok: true,
1739
- status: 200,
1740
- json: () => Promise.resolve({ temporaryAuthorization: "temp-token" }),
1741
- })
1742
- .mockResolvedValueOnce({
1743
- ok: true,
1744
- status: 200,
1745
- json: () =>
1746
- Promise.resolve({
1747
- data: [{ id: 2, "7": { value: "value2" } }],
1748
- metadata: {
1749
- createdRecordIds: [2],
1750
- updatedRecordIds: [],
1751
- unchangedRecordIds: [],
1752
- totalNumberOfRecordsProcessed: 1,
1753
- },
1754
- }),
1755
- });
1756
-
1757
- const result = await qb.upsert({
1758
- body: { to: QB_TABLE_ID_1, data: [{ "7": { value: "value2" } }] },
1759
- dbid: QB_TABLE_ID_1, // Use QB_TABLE_ID_1 as dbid for temp token
1760
- } as any); // Temporary type assertion until types.d.ts is applied
1761
-
1762
- expect(mockFetch).toHaveBeenCalledTimes(2);
1763
- expect(mockFetch).toHaveBeenNthCalledWith(
1764
- 1,
1765
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
1766
- expect.objectContaining({
1767
- method: "GET",
1768
- headers: expect.objectContaining({
1769
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1770
- "Content-Type": "application/json",
1771
- }),
1772
- credentials: "include",
1773
- })
1774
- );
1775
- expect(mockFetch).toHaveBeenNthCalledWith(
1776
- 2,
1777
- "https://api.quickbase.com/v1/records",
1778
- expect.objectContaining({
1779
- method: "POST",
1780
- headers: expect.objectContaining({
1781
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1782
- Authorization: "QB-TEMP-TOKEN temp-token",
1783
- "Content-Type": "application/json",
1784
- }),
1785
- body: JSON.stringify({
1786
- to: QB_TABLE_ID_1,
1787
- data: [{ "7": { value: "value2" } }],
1788
- }),
1789
- })
1790
- );
1791
-
1792
- expect(result).toEqual({
1793
- data: [{ id: 2, "7": { value: "value2" } }],
1794
- metadata: {
1795
- createdRecordIds: [2],
1796
- updatedRecordIds: [],
1797
- unchangedRecordIds: [],
1798
- totalNumberOfRecordsProcessed: 1,
1799
- },
1800
- });
1801
- });
1802
-
1803
- test("sends correct POST request with data fields", async () => {
1804
- const qb = createClient(mockFetch, { debug: true });
1805
-
1806
- mockFetch.mockResolvedValueOnce({
1807
- ok: true,
1808
- status: 200,
1809
- json: () =>
1810
- Promise.resolve({
1811
- data: [{ id: 1, "6": { value: "value1" } }],
1812
- metadata: {
1813
- createdRecordIds: [1],
1814
- updatedRecordIds: [],
1815
- unchangedRecordIds: [],
1816
- totalNumberOfRecordsProcessed: 1,
1817
- },
1818
- }),
1819
- });
1820
-
1821
- const result = await qb.upsert({
1822
- body: { to: QB_TABLE_ID_1, data: [{ "6": { value: "value1" } }] },
1823
- });
1824
-
1825
- expect(mockFetch).toHaveBeenCalledWith(
1826
- "https://api.quickbase.com/v1/records",
1827
- expect.objectContaining({
1828
- method: "POST",
1829
- headers: expect.objectContaining({
1830
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1831
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1832
- "Content-Type": "application/json",
1833
- }),
1834
- body: JSON.stringify({
1835
- to: QB_TABLE_ID_1,
1836
- data: [{ "6": { value: "value1" } }],
1837
- }),
1838
- })
1839
- );
1840
-
1841
- if (result.data && result.data.length > 0) {
1842
- expect((result.data[0]["6"] as { value: any }).value).toBe("value1");
1843
- } else {
1844
- throw new Error("Expected data array to have at least one element");
1845
- }
1846
- });
1847
- });
1848
1189
  - name: tables
1849
1190
  type: directory
1850
1191
  children:
1851
1192
  - name: createTable.test.ts
1852
1193
  type: file
1853
- contents: |
1854
- // tests/vitest/unit/records/createTable.test.ts
1855
- import { describe, it, expect, beforeEach } from "vitest";
1856
- import {
1857
- createClient,
1858
- mockFetch,
1859
- QB_APP_ID,
1860
- QB_REALM,
1861
- QB_USER_TOKEN,
1862
- } from "@tests/setup.ts";
1863
-
1864
- describe("QuickbaseClient Unit - createTable", () => {
1865
- let client: ReturnType<typeof createClient>;
1866
-
1867
- beforeEach(() => {
1868
- mockFetch.mockClear();
1869
- client = createClient(mockFetch, { debug: true });
1870
- });
1871
-
1872
- it("calls createTable successfully with user token", async () => {
1873
- const mockBody = {
1874
- name: "TestTable",
1875
- description: "Unit test table",
1876
- singleRecordName: "Test",
1877
- pluralRecordName: "Tests",
1878
- };
1879
- const mockResponse = {
1880
- id: "newly-created-table-id-1234567890", // Changed from "buya8h9iz"
1881
- name: "TestTable",
1882
- alias: "_DBID_TESTTABLE",
1883
- description: "Unit test table",
1884
- created: new Date("2025-03-10T21:00:00.000Z"),
1885
- updated: new Date("2025-03-10T21:00:00.000Z"),
1886
- nextRecordId: 1,
1887
- nextFieldId: 6,
1888
- defaultSortFieldId: 2,
1889
- defaultSortOrder: "DESC",
1890
- keyFieldId: 3,
1891
- singleRecordName: "Test",
1892
- pluralRecordName: "Tests",
1893
- sizeLimit: "500 MB",
1894
- spaceUsed: "0 KB",
1895
- spaceRemaining: "500 MB",
1896
- };
1897
-
1898
- mockFetch.mockResolvedValueOnce({
1899
- ok: true,
1900
- status: 200,
1901
- json: () => Promise.resolve(mockResponse),
1902
- });
1903
-
1904
- const response = await client.createTable({
1905
- appId: QB_APP_ID,
1906
- body: mockBody,
1907
- });
1908
- expect(response).toEqual(mockResponse);
1909
- expect(mockFetch).toHaveBeenCalledTimes(1);
1910
- expect(mockFetch).toHaveBeenCalledWith(
1911
- `https://api.quickbase.com/v1/tables?appId=${QB_APP_ID}`,
1912
- expect.objectContaining({
1913
- method: "POST",
1914
- headers: expect.objectContaining({
1915
- "Content-Type": "application/json",
1916
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1917
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1918
- }),
1919
- body: JSON.stringify(mockBody),
1920
- })
1921
- );
1922
- });
1923
- });
1924
1194
  - name: deleteTable.test.ts
1925
1195
  type: file
1926
- contents: |
1927
- // tests/vitest/unit/records/deleteTable.test.ts
1928
- import { describe, it, expect, beforeEach } from "vitest";
1929
- import {
1930
- createClient,
1931
- mockFetch,
1932
- QB_APP_ID,
1933
- QB_TABLE_ID_1,
1934
- QB_REALM,
1935
- QB_USER_TOKEN,
1936
- } from "@tests/setup.ts";
1937
-
1938
- describe("QuickbaseClient Unit - deleteTable", () => {
1939
- let client: ReturnType<typeof createClient>;
1940
-
1941
- beforeEach(() => {
1942
- mockFetch.mockClear();
1943
- client = createClient(mockFetch, { debug: true });
1944
- });
1945
-
1946
- it("calls deleteTable successfully with user token", async () => {
1947
- const mockResponse = {
1948
- deletedTableId: QB_TABLE_ID_1,
1949
- };
1950
-
1951
- mockFetch.mockResolvedValueOnce({
1952
- ok: true,
1953
- status: 200,
1954
- json: () => Promise.resolve(mockResponse),
1955
- });
1956
-
1957
- const response = await client.deleteTable({
1958
- tableId: QB_TABLE_ID_1,
1959
- appId: QB_APP_ID,
1960
- });
1961
- expect(response).toEqual(mockResponse);
1962
- expect(mockFetch).toHaveBeenCalledTimes(1);
1963
- expect(mockFetch).toHaveBeenCalledWith(
1964
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
1965
- expect.objectContaining({
1966
- method: "DELETE",
1967
- headers: expect.objectContaining({
1968
- "Content-Type": "application/json",
1969
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1970
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1971
- }),
1972
- })
1973
- );
1974
- });
1975
-
1976
- it("handles 404 error for non-existent table", async () => {
1977
- mockFetch.mockResolvedValueOnce({
1978
- ok: false,
1979
- status: 404,
1980
- json: () =>
1981
- Promise.resolve({
1982
- message: "Invalid DBID",
1983
- description: "Table not found in app.",
1984
- }),
1985
- });
1986
-
1987
- await expect(
1988
- client.deleteTable({ tableId: QB_TABLE_ID_1, appId: QB_APP_ID })
1989
- ).rejects.toThrow("API Error: Invalid DBID (Status: 404)");
1990
- expect(mockFetch).toHaveBeenCalledTimes(1);
1991
- expect(mockFetch).toHaveBeenCalledWith(
1992
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
1993
- expect.objectContaining({
1994
- method: "DELETE",
1995
- headers: expect.objectContaining({
1996
- "Content-Type": "application/json",
1997
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
1998
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
1999
- }),
2000
- })
2001
- );
2002
- });
2003
- });
2004
1196
  - name: getAppTables.test.ts
2005
1197
  type: file
2006
- contents: |
2007
- // tests/vitest/unit/tables/getAppTables.test.ts
2008
- import { describe, it, expect, beforeEach } from "vitest";
2009
- import {
2010
- createClient,
2011
- mockFetch,
2012
- QB_APP_ID,
2013
- QB_REALM,
2014
- QB_USER_TOKEN,
2015
- } from "@tests/setup.ts";
2016
-
2017
- describe("QuickbaseClient Unit - getAppTables", () => {
2018
- let client: ReturnType<typeof createClient>;
2019
-
2020
- beforeEach(() => {
2021
- mockFetch.mockClear();
2022
- client = createClient(mockFetch, { debug: true }); // Add debug: true for consistency
2023
- });
2024
-
2025
- it("calls getAppTables successfully", async () => {
2026
- mockFetch.mockImplementation((url: string, options: any) => {
2027
- console.log("Mock fetch for getAppTables:", url, options);
2028
- return Promise.resolve({
2029
- ok: true,
2030
- status: 200,
2031
- json: () =>
2032
- Promise.resolve([
2033
- {
2034
- id: "buwai2zr4",
2035
- name: "Root",
2036
- alias: "_DBID_ROOT",
2037
- description: "",
2038
- created: new Date("2025-02-13T18:22:33.000Z"),
2039
- updated: new Date("2025-02-13T18:22:34.000Z"),
2040
- nextRecordId: 1,
2041
- nextFieldId: 6,
2042
- defaultSortFieldId: 2,
2043
- defaultSortOrder: "DESC",
2044
- keyFieldId: 3,
2045
- singleRecordName: "Root",
2046
- pluralRecordName: "Roots",
2047
- sizeLimit: "500 MB",
2048
- spaceUsed: "0 KB",
2049
- spaceRemaining: "500 MB",
2050
- },
2051
- ]),
2052
- } as Response);
2053
- });
2054
-
2055
- const result = await client.getAppTables({ appId: QB_APP_ID });
2056
- console.log("getAppTables response:", result);
2057
- expect(result).toEqual(
2058
- expect.arrayContaining([
2059
- expect.objectContaining({
2060
- id: "buwai2zr4",
2061
- name: "Root",
2062
- alias: "_DBID_ROOT",
2063
- description: "",
2064
- created: new Date("2025-02-13T18:22:33.000Z"),
2065
- updated: new Date("2025-02-13T18:22:34.000Z"),
2066
- nextRecordId: 1,
2067
- nextFieldId: 6,
2068
- defaultSortFieldId: 2,
2069
- defaultSortOrder: "DESC",
2070
- keyFieldId: 3,
2071
- singleRecordName: "Root",
2072
- pluralRecordName: "Roots",
2073
- sizeLimit: "500 MB",
2074
- spaceUsed: "0 KB",
2075
- spaceRemaining: "500 MB",
2076
- }),
2077
- ])
2078
- );
2079
- expect(mockFetch).toHaveBeenCalledWith(
2080
- `https://api.quickbase.com/v1/tables?appId=${QB_APP_ID}`,
2081
- expect.objectContaining({
2082
- method: "GET",
2083
- headers: expect.objectContaining({
2084
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2085
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2086
- "Content-Type": "application/json",
2087
- }),
2088
- })
2089
- );
2090
- });
2091
- });
2092
1198
  - name: getRelationships.test.ts
2093
1199
  type: file
2094
- contents: |
2095
- // tests/vitest/unit/relationships/getRelationships.test.ts
2096
- import { describe, it, expect, beforeEach } from "vitest";
2097
- import {
2098
- createClient,
2099
- mockFetch,
2100
- QB_REALM,
2101
- QB_USER_TOKEN,
2102
- QB_TABLE_ID_1,
2103
- QB_TABLE_ID_2,
2104
- } from "@tests/setup.ts";
2105
- import { GetRelationships200Response } from "/home/drew/Projects/quickbase-js/src/generated/models";
2106
-
2107
- describe("QuickbaseClient Unit - getRelationships", () => {
2108
- let client: ReturnType<typeof createClient>;
2109
-
2110
- beforeEach(() => {
2111
- mockFetch.mockClear();
2112
- client = createClient(mockFetch, { debug: true }); // Consistent with output
2113
- });
2114
-
2115
- it("initializes without errors", () => {
2116
- expect(client).toBeDefined();
2117
- });
2118
-
2119
- it("has getRelationships method", () => {
2120
- expect(typeof client.getRelationships).toBe("function");
2121
- });
2122
-
2123
- it("sends correct GET request and handles successful response", async () => {
2124
- const mockResponse: GetRelationships200Response = {
2125
- metadata: {
2126
- numRelationships: 1,
2127
- skip: 0,
2128
- totalRelationships: 1,
2129
- },
2130
- relationships: [
2131
- {
2132
- id: 6,
2133
- parentTableId: QB_TABLE_ID_1, // Parent table
2134
- childTableId: QB_TABLE_ID_2, // Child table
2135
- foreignKeyField: {
2136
- id: 6,
2137
- label: "Related record",
2138
- type: "numeric",
2139
- },
2140
- isCrossApp: false,
2141
- lookupFields: [
2142
- {
2143
- id: 3,
2144
- label: "Name",
2145
- type: "text",
2146
- },
2147
- ],
2148
- summaryFields: [
2149
- {
2150
- id: 8,
2151
- label: "my summary field",
2152
- type: "numeric",
2153
- },
2154
- ],
2155
- },
2156
- ],
2157
- };
2158
-
2159
- mockFetch.mockResolvedValueOnce({
2160
- ok: true,
2161
- status: 200,
2162
- json: () => Promise.resolve(mockResponse),
2163
- });
2164
-
2165
- const result = await client.getRelationships({
2166
- tableId: QB_TABLE_ID_2,
2167
- skip: 0,
2168
- });
2169
-
2170
- expect(mockFetch).toHaveBeenCalledWith(
2171
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
2172
- expect.objectContaining({
2173
- method: "GET",
2174
- headers: expect.objectContaining({
2175
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2176
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2177
- "Content-Type": "application/json",
2178
- }),
2179
- })
2180
- );
2181
-
2182
- expect(result).toEqual(mockResponse);
2183
- expect(result.metadata.numRelationships).toBe(1);
2184
- expect(result.relationships).toHaveLength(1);
2185
- expect(result.relationships[0].id).toBe(6);
2186
- expect(result.relationships[0].foreignKeyField.label).toBe(
2187
- "Related record"
2188
- );
2189
- });
2190
-
2191
- it("handles request without skip parameter", async () => {
2192
- const mockResponse: GetRelationships200Response = {
2193
- metadata: {
2194
- numRelationships: 1,
2195
- skip: 0,
2196
- totalRelationships: 1,
2197
- },
2198
- relationships: [
2199
- {
2200
- id: 6,
2201
- parentTableId: QB_TABLE_ID_1,
2202
- childTableId: QB_TABLE_ID_2,
2203
- foreignKeyField: {
2204
- id: 6,
2205
- label: "Related record",
2206
- type: "numeric",
2207
- },
2208
- isCrossApp: false,
2209
- lookupFields: [],
2210
- summaryFields: [],
2211
- },
2212
- ],
2213
- };
2214
-
2215
- mockFetch.mockResolvedValueOnce({
2216
- ok: true,
2217
- status: 200,
2218
- json: () => Promise.resolve(mockResponse),
2219
- });
2220
-
2221
- const result = await client.getRelationships({
2222
- tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
2223
- });
2224
-
2225
- expect(mockFetch).toHaveBeenCalledWith(
2226
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships`,
2227
- expect.objectContaining({
2228
- method: "GET",
2229
- headers: expect.objectContaining({
2230
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2231
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2232
- "Content-Type": "application/json",
2233
- }),
2234
- })
2235
- );
2236
-
2237
- expect(result).toEqual(mockResponse);
2238
- });
2239
-
2240
- it("handles API error response", async () => {
2241
- mockFetch.mockResolvedValueOnce({
2242
- ok: false,
2243
- status: 404,
2244
- json: () => Promise.resolve({ message: "Table not found" }),
2245
- });
2246
-
2247
- await expect(
2248
- client.getRelationships({
2249
- tableId: QB_TABLE_ID_2, // Corrected from QB_TABLE_ID_1
2250
- skip: 0,
2251
- })
2252
- ).rejects.toThrow("API Error: Table not found (Status: 404)");
2253
-
2254
- expect(mockFetch).toHaveBeenCalledWith(
2255
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
2256
- expect.objectContaining({
2257
- method: "GET",
2258
- headers: expect.objectContaining({
2259
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2260
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2261
- "Content-Type": "application/json",
2262
- }),
2263
- })
2264
- );
2265
- });
2266
-
2267
- it("retries with temp token after 401", async () => {
2268
- const mockResponse: GetRelationships200Response = {
2269
- metadata: {
2270
- numRelationships: 1,
2271
- skip: 0,
2272
- totalRelationships: 1,
2273
- },
2274
- relationships: [
2275
- {
2276
- id: 6,
2277
- parentTableId: QB_TABLE_ID_1,
2278
- childTableId: QB_TABLE_ID_2,
2279
- foreignKeyField: {
2280
- id: 6,
2281
- label: "Related record",
2282
- type: "numeric",
2283
- },
2284
- isCrossApp: false,
2285
- },
2286
- ],
2287
- };
2288
-
2289
- mockFetch
2290
- .mockResolvedValueOnce({
2291
- ok: true,
2292
- status: 200,
2293
- json: () =>
2294
- Promise.resolve({ temporaryAuthorization: "initial_token" }),
2295
- })
2296
- .mockResolvedValueOnce({
2297
- ok: false,
2298
- status: 401,
2299
- json: () => Promise.resolve({ message: "Unauthorized" }),
2300
- })
2301
- .mockResolvedValueOnce({
2302
- ok: true,
2303
- status: 200,
2304
- json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
2305
- })
2306
- .mockResolvedValueOnce({
2307
- ok: true,
2308
- status: 200,
2309
- json: () => Promise.resolve(mockResponse),
2310
- });
2311
-
2312
- const tempClient = createClient(mockFetch, {
2313
- useTempTokens: true,
2314
- debug: true,
2315
- });
2316
- const result = await tempClient.getRelationships({
2317
- tableId: QB_TABLE_ID_2,
2318
- skip: 0,
2319
- });
2320
-
2321
- expect(mockFetch).toHaveBeenCalledTimes(4);
2322
- expect(mockFetch).toHaveBeenNthCalledWith(
2323
- 1,
2324
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
2325
- expect.any(Object)
2326
- );
2327
- expect(mockFetch).toHaveBeenNthCalledWith(
2328
- 2,
2329
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
2330
- expect.objectContaining({
2331
- headers: expect.objectContaining({
2332
- Authorization: "QB-TEMP-TOKEN initial_token",
2333
- }),
2334
- })
2335
- );
2336
- expect(mockFetch).toHaveBeenNthCalledWith(
2337
- 3,
2338
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_2}`,
2339
- expect.any(Object)
2340
- );
2341
- expect(mockFetch).toHaveBeenNthCalledWith(
2342
- 4,
2343
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_2}/relationships?skip=0`,
2344
- expect.objectContaining({
2345
- headers: expect.objectContaining({
2346
- Authorization: "QB-TEMP-TOKEN new_token",
2347
- }),
2348
- })
2349
- );
2350
-
2351
- expect(result).toEqual(mockResponse);
2352
- });
2353
- });
2354
1200
  - name: getTable.test.ts
2355
1201
  type: file
2356
- contents: |
2357
- // tests/vitest/unit/tables/getTable.test.ts
2358
- import { describe, it, expect, beforeEach } from "vitest";
2359
- import {
2360
- createClient,
2361
- mockFetch,
2362
- QB_APP_ID,
2363
- QB_TABLE_ID_1,
2364
- QB_REALM,
2365
- QB_USER_TOKEN,
2366
- } from "@tests/setup.ts";
2367
-
2368
- describe("QuickbaseClient Unit - getTable", () => {
2369
- let client: ReturnType<typeof createClient>;
2370
-
2371
- beforeEach(() => {
2372
- mockFetch.mockClear();
2373
- });
2374
-
2375
- it("calls getTable successfully with user token", async () => {
2376
- client = createClient(mockFetch, { debug: true });
2377
-
2378
- const mockResponse = {
2379
- id: QB_TABLE_ID_1,
2380
- name: "Root",
2381
- alias: "_DBID_ROOT",
2382
- description: "",
2383
- created: new Date("2025-02-13T18:22:33.000Z"),
2384
- updated: new Date("2025-02-13T18:22:34.000Z"),
2385
- nextRecordId: 1,
2386
- nextFieldId: 6,
2387
- defaultSortFieldId: 2,
2388
- defaultSortOrder: "DESC",
2389
- keyFieldId: 3,
2390
- singleRecordName: "Root",
2391
- pluralRecordName: "Roots",
2392
- sizeLimit: "500 MB",
2393
- spaceUsed: "0 KB",
2394
- spaceRemaining: "500 MB",
2395
- };
2396
-
2397
- mockFetch.mockResolvedValueOnce({
2398
- ok: true,
2399
- status: 200,
2400
- json: () => Promise.resolve(mockResponse),
2401
- });
2402
-
2403
- const response = await client.getTable({
2404
- tableId: QB_TABLE_ID_1,
2405
- appId: QB_APP_ID,
2406
- });
2407
- expect(response).toEqual(mockResponse);
2408
- expect(mockFetch).toHaveBeenCalledWith(
2409
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
2410
- expect.objectContaining({
2411
- method: "GET",
2412
- headers: expect.objectContaining({
2413
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2414
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2415
- }),
2416
- })
2417
- );
2418
- });
2419
-
2420
- it("calls getTable successfully with temp token", async () => {
2421
- client = createClient(mockFetch, { debug: true, useTempTokens: true });
2422
-
2423
- const mockResponse = {
2424
- id: QB_TABLE_ID_1,
2425
- name: "Root",
2426
- alias: "_DBID_ROOT",
2427
- description: "",
2428
- created: new Date("2025-02-13T18:22:33.000Z"),
2429
- updated: new Date("2025-02-13T18:22:34.000Z"),
2430
- nextRecordId: 1,
2431
- nextFieldId: 6,
2432
- defaultSortFieldId: 2,
2433
- defaultSortOrder: "DESC",
2434
- keyFieldId: 3,
2435
- singleRecordName: "Root",
2436
- pluralRecordName: "Roots",
2437
- sizeLimit: "500 MB",
2438
- spaceUsed: "0 KB",
2439
- spaceRemaining: "500 MB",
2440
- };
2441
-
2442
- mockFetch
2443
- .mockResolvedValueOnce({
2444
- ok: true,
2445
- status: 200,
2446
- json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
2447
- })
2448
- .mockResolvedValueOnce({
2449
- ok: true,
2450
- status: 200,
2451
- json: () => Promise.resolve(mockResponse),
2452
- });
2453
-
2454
- const response = await client.getTable({
2455
- tableId: QB_TABLE_ID_1,
2456
- appId: QB_APP_ID,
2457
- });
2458
- expect(response).toEqual(mockResponse);
2459
- expect(mockFetch).toHaveBeenCalledTimes(2);
2460
- expect(mockFetch).toHaveBeenCalledWith(
2461
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
2462
- expect.any(Object)
2463
- );
2464
- expect(mockFetch).toHaveBeenCalledWith(
2465
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
2466
- expect.objectContaining({
2467
- headers: expect.objectContaining({
2468
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2469
- Authorization: "QB-TEMP-TOKEN temp_token",
2470
- }),
2471
- })
2472
- );
2473
- });
2474
-
2475
- it("retries successfully after 401 with temp token", async () => {
2476
- client = createClient(mockFetch, { debug: true, useTempTokens: true });
2477
-
2478
- const mockResponse = {
2479
- id: QB_TABLE_ID_1,
2480
- name: "Root",
2481
- alias: "_DBID_ROOT",
2482
- description: "",
2483
- created: new Date("2025-02-13T18:22:33.000Z"),
2484
- updated: new Date("2025-02-13T18:22:34.000Z"),
2485
- nextRecordId: 1,
2486
- nextFieldId: 6,
2487
- defaultSortFieldId: 2,
2488
- defaultSortOrder: "DESC",
2489
- keyFieldId: 3,
2490
- singleRecordName: "Root",
2491
- pluralRecordName: "Roots",
2492
- sizeLimit: "500 MB",
2493
- spaceUsed: "0 KB",
2494
- spaceRemaining: "500 MB",
2495
- };
2496
-
2497
- mockFetch
2498
- .mockResolvedValueOnce({
2499
- ok: true,
2500
- status: 200,
2501
- json: () =>
2502
- Promise.resolve({ temporaryAuthorization: "initial_token" }),
2503
- })
2504
- .mockResolvedValueOnce({
2505
- ok: false,
2506
- status: 401,
2507
- json: () => Promise.resolve({ message: "Unauthorized" }),
2508
- })
2509
- .mockResolvedValueOnce({
2510
- ok: true,
2511
- status: 200,
2512
- json: () => Promise.resolve({ temporaryAuthorization: "new_token" }),
2513
- })
2514
- .mockResolvedValueOnce({
2515
- ok: true,
2516
- status: 200,
2517
- json: () => Promise.resolve(mockResponse),
2518
- });
2519
-
2520
- const response = await client.getTable({
2521
- tableId: QB_TABLE_ID_1,
2522
- appId: QB_APP_ID,
2523
- });
2524
- expect(response).toEqual(mockResponse);
2525
- expect(mockFetch).toHaveBeenCalledTimes(4);
2526
- expect(mockFetch).toHaveBeenCalledWith(
2527
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
2528
- expect.objectContaining({
2529
- headers: expect.objectContaining({
2530
- Authorization: "QB-TEMP-TOKEN new_token",
2531
- }),
2532
- })
2533
- );
2534
- });
2535
-
2536
- it("handles 401 with failed temp token retry", async () => {
2537
- client = createClient(mockFetch, { debug: true, useTempTokens: true });
2538
-
2539
- mockFetch
2540
- .mockResolvedValueOnce({
2541
- ok: true,
2542
- status: 200,
2543
- json: () =>
2544
- Promise.resolve({ temporaryAuthorization: "initial_token" }),
2545
- })
2546
- .mockResolvedValueOnce({
2547
- ok: false,
2548
- status: 401,
2549
- json: () => Promise.resolve({ message: "Unauthorized" }),
2550
- })
2551
- .mockResolvedValueOnce({
2552
- ok: false,
2553
- status: 401,
2554
- json: () =>
2555
- Promise.resolve({ message: "Unauthorized in fetchTempToken" }),
2556
- });
2557
-
2558
- await expect(
2559
- client.getTable({ tableId: QB_TABLE_ID_1, appId: QB_APP_ID })
2560
- ).rejects.toThrow(
2561
- "API Error: Unauthorized in fetchTempToken (Status: 401)"
2562
- );
2563
- expect(mockFetch).toHaveBeenCalledTimes(3);
2564
- });
2565
-
2566
- it("handles 404 Not Found", async () => {
2567
- client = createClient(mockFetch, { debug: true });
2568
-
2569
- mockFetch.mockResolvedValueOnce({
2570
- ok: false,
2571
- status: 404,
2572
- json: () => Promise.resolve({ message: "Table not found" }),
2573
- });
2574
-
2575
- await expect(
2576
- client.getTable({ tableId: QB_TABLE_ID_1, appId: QB_APP_ID })
2577
- ).rejects.toThrow("API Error: Table not found (Status: 404)");
2578
- expect(mockFetch).toHaveBeenCalledWith(
2579
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
2580
- expect.objectContaining({
2581
- method: "GET",
2582
- headers: expect.objectContaining({
2583
- "QB-Realm-Hostname": `${QB_REALM}.quickbase.com`,
2584
- Authorization: `QB-USER-TOKEN ${QB_USER_TOKEN}`,
2585
- }),
2586
- })
2587
- );
2588
- });
2589
- });
2590
1202
  - name: updateTable.test.ts
2591
1203
  type: file
2592
- contents: |
2593
- // tests/vitest/unit/records/updateTable.test.ts
2594
- import { describe, it, expect, beforeEach } from "vitest";
2595
- import {
2596
- createClient,
2597
- mockFetch,
2598
- QB_APP_ID,
2599
- QB_TABLE_ID_1,
2600
- } from "@tests/setup.ts";
2601
-
2602
- describe("QuickbaseClient Unit - updateTable", () => {
2603
- let client: ReturnType<typeof createClient>;
2604
-
2605
- beforeEach(() => {
2606
- mockFetch.mockClear();
2607
- client = createClient(mockFetch, { debug: true });
2608
- });
2609
-
2610
- it("calls updateTable successfully with temp token", async () => {
2611
- client = createClient(mockFetch, { debug: true, useTempTokens: true });
2612
-
2613
- const mockBody = {
2614
- name: "Updated Root",
2615
- description: "Updated description",
2616
- };
2617
- const mockResponse = {
2618
- id: QB_TABLE_ID_1,
2619
- name: "Updated Root",
2620
- alias: "_DBID_ROOT",
2621
- description: "Updated description",
2622
- created: new Date("2025-02-13T18:22:33.000Z"),
2623
- updated: new Date("2025-03-10T12:00:00.000Z"),
2624
- nextRecordId: 1,
2625
- nextFieldId: 6,
2626
- defaultSortFieldId: 2,
2627
- defaultSortOrder: "DESC",
2628
- keyFieldId: 3,
2629
- singleRecordName: "Root",
2630
- pluralRecordName: "Roots",
2631
- sizeLimit: "500 MB",
2632
- spaceUsed: "0 KB",
2633
- spaceRemaining: "500 MB",
2634
- };
2635
-
2636
- mockFetch
2637
- .mockResolvedValueOnce({
2638
- ok: true,
2639
- status: 200,
2640
- json: () => Promise.resolve({ temporaryAuthorization: "temp_token" }),
2641
- })
2642
- .mockResolvedValueOnce({
2643
- ok: true,
2644
- status: 200,
2645
- json: () => Promise.resolve(mockResponse),
2646
- });
2647
-
2648
- const response = await client.updateTable({
2649
- tableId: QB_TABLE_ID_1,
2650
- appId: QB_APP_ID,
2651
- body: mockBody,
2652
- });
2653
- expect(response).toEqual(mockResponse);
2654
- expect(mockFetch).toHaveBeenCalledTimes(2);
2655
- expect(mockFetch).toHaveBeenCalledWith(
2656
- `https://api.quickbase.com/v1/auth/temporary/${QB_TABLE_ID_1}`,
2657
- expect.any(Object)
2658
- );
2659
- expect(mockFetch).toHaveBeenCalledWith(
2660
- `https://api.quickbase.com/v1/tables/${QB_TABLE_ID_1}?appId=${QB_APP_ID}`,
2661
- expect.objectContaining({
2662
- method: "POST",
2663
- headers: expect.objectContaining({
2664
- "QB-Realm-Hostname": `${process.env.QB_REALM}.quickbase.com`,
2665
- Authorization: "QB-TEMP-TOKEN temp_token",
2666
- "Content-Type": "application/json",
2667
- }),
2668
- body: JSON.stringify(mockBody),
2669
- })
2670
- );
2671
- });
2672
- });
1204
+ - name: umd.test.ts
1205
+ type: file
2673
1206
  - name: text.txt
2674
1207
  type: file
1208
+ - name: tools
1209
+ type: directory
1210
+ children:
1211
+ - name: log-gen.ts
1212
+ type: file
1213
+ - name: openapi-jars
1214
+ type: directory
1215
+ children:
1216
+ - name: openapi-generator-cli-7.12.0.jar
1217
+ type: file
1218
+ - name: tsconfig.build.json
1219
+ type: file
1220
+ contents: |-
1221
+ {
1222
+ "extends": "./tsconfig.json",
1223
+ "compilerOptions": {
1224
+ "outDir": "dist/temp",
1225
+ "noEmit": false,
1226
+ "allowImportingTsExtensions": false,
1227
+ "declaration": false,
1228
+ "sourceMap": true
1229
+ },
1230
+ "include": ["dist/temp-src/**/*"],
1231
+ "exclude": [
1232
+ "node_modules",
1233
+ "dist",
1234
+ "tests/**/*",
1235
+ "src/**/*" // Exclude original src/ to avoid confusion
1236
+ ]
1237
+ }
2675
1238
  - name: tsconfig.json
2676
1239
  type: file
1240
+ contents: |-
1241
+ {
1242
+ "compilerOptions": {
1243
+ "target": "ESNext",
1244
+ "module": "ESNext",
1245
+ "moduleResolution": "bundler",
1246
+ "strict": true,
1247
+ "lib": ["ES2015", "ESNext", "DOM"],
1248
+ "types": ["node", "playwright/test", "undici-types"],
1249
+ "esModuleInterop": true,
1250
+ "resolveJsonModule": true,
1251
+ "allowImportingTsExtensions": true,
1252
+ "noEmit": true,
1253
+ "outDir": "dist",
1254
+ "rootDir": ".",
1255
+ "declaration": true,
1256
+ "sourceMap": true,
1257
+ "baseUrl": ".",
1258
+ "paths": {
1259
+ "@/*": ["src/*"],
1260
+ "@tests/*": ["tests/*"]
1261
+ }
1262
+ },
1263
+ "include": ["src/**/*", "tests/**/*", "open-api/**/*", "tools/**/*"],
1264
+ "exclude": ["node_modules", "dist", "specs"],
1265
+ "ts-node": {
1266
+ "esm": true,
1267
+ "transpileOnly": true
1268
+ }
1269
+ }
2677
1270
  - name: versionrc.json
2678
1271
  type: file
2679
1272
  - name: vitest.config.ts
2680
1273
  type: file
1274
+ contents: |
1275
+ // vitest.config.ts
1276
+ import { defineConfig } from "vitest/config";
1277
+ import path from "path";
1278
+ import dotenv from "dotenv"; // Add this import
1279
+
1280
+ // Load .env variables before tests run
1281
+ dotenv.config({ path: path.resolve(__dirname, ".env") });
1282
+
1283
+ export default defineConfig({
1284
+ test: {
1285
+ environment: "jsdom",
1286
+ include: ["tests/vitest/**/**/*.test.ts"],
1287
+ setupFiles: ["tests/setup.ts"], // Relative path from root
1288
+ pool: "forks",
1289
+ poolOptions: {
1290
+ forks: {
1291
+ singleFork: true, // Run tests sequentially
1292
+ },
1293
+ },
1294
+ },
1295
+ resolve: {
1296
+ alias: {
1297
+ "@": path.resolve(__dirname, "./src"), // For src/ imports
1298
+ "@tests": path.resolve(__dirname, "./tests"), // For tests/ imports
1299
+ },
1300
+ },
1301
+ });