@forge/react 11.6.1-next.1-experimental-f85d28a → 11.7.0-next.2

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.
package/CHANGELOG.md CHANGED
@@ -1,15 +1,15 @@
1
1
  # @forge/react
2
2
 
3
- ## 11.6.1-next.1-experimental-f85d28a
3
+ ## 11.7.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - f23a84e: Add useObjectStore hook
4
8
 
5
9
  ### Patch Changes
6
10
 
7
- - Updated dependencies [1d129f0]
8
- - Updated dependencies [fe20eb0]
9
- - Updated dependencies [08fbb0a]
10
- - Updated dependencies [617c3d9]
11
- - Updated dependencies [a4bccb7]
12
- - @forge/bridge@5.8.0-next.12-experimental-f85d28a
11
+ - Updated dependencies [f23a84e]
12
+ - @forge/bridge@5.8.0-next.13
13
13
 
14
14
  ## 11.6.1-next.1
15
15
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=useObjectStore.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useObjectStore.test.d.ts","sourceRoot":"","sources":["../../../src/hooks/__test__/useObjectStore.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,542 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ // Mock data - define before imports
6
+ const MOCK_FUNCTION_KEY = 'test-function-key';
7
+ const MOCK_OBJECT_KEY_1 = 'object-key-1';
8
+ const MOCK_OBJECT_KEY_2 = 'object-key-2';
9
+ const MOCK_BLOB = new Blob(['test content'], { type: 'text/plain' });
10
+ const MOCK_BASE64_OBJECT = { data: 'dGVzdCBjb250ZW50', mimeType: 'text/plain', fileSize: 12 };
11
+ // Mock functions - define before imports
12
+ const mockUpload = jest.fn();
13
+ const mockCreateUploadPromises = jest.fn();
14
+ const mockDelete = jest.fn();
15
+ const mockDownload = jest.fn();
16
+ const mockGetMetadata = jest.fn();
17
+ const reconcilerTestRenderer_1 = tslib_1.__importDefault(require("../../__test__/reconcilerTestRenderer"));
18
+ const testUtils_1 = require("../../__test__/testUtils");
19
+ const useObjectStore_1 = require("../useObjectStore");
20
+ // Mock @forge/bridge
21
+ jest.mock('@forge/bridge', () => ({
22
+ objectStore: {
23
+ upload: mockUpload,
24
+ delete: mockDelete,
25
+ download: mockDownload,
26
+ getMetadata: mockGetMetadata
27
+ },
28
+ createUploadPromises: mockCreateUploadPromises
29
+ }));
30
+ describe('useObjectStore', () => {
31
+ beforeEach(() => {
32
+ // Clear all mocks before each test
33
+ jest.clearAllMocks();
34
+ (0, testUtils_1.setupBridge)(); // Set up global bridge for tests
35
+ });
36
+ describe('Initialization', () => {
37
+ it('should initialize with empty objectStates when no defaultValues provided', async () => {
38
+ let hookResult;
39
+ const TestComponent = () => {
40
+ hookResult = (0, useObjectStore_1.useObjectStore)();
41
+ return null;
42
+ };
43
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
44
+ expect(hookResult).toBeDefined();
45
+ if (!hookResult)
46
+ return;
47
+ expect(hookResult.objectStates).toEqual([]);
48
+ });
49
+ it('should initialize with defaultValues when provided', async () => {
50
+ const defaultValues = [
51
+ { key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 },
52
+ { key: MOCK_OBJECT_KEY_2, success: true, objectSize: 200 }
53
+ ];
54
+ let hookResult;
55
+ const TestComponent = () => {
56
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
57
+ return null;
58
+ };
59
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
60
+ expect(hookResult).toBeDefined();
61
+ if (!hookResult)
62
+ return;
63
+ expect(hookResult.objectStates).toEqual(defaultValues);
64
+ });
65
+ });
66
+ describe('uploadObjects', () => {
67
+ it('should upload Blob objects successfully', async () => {
68
+ const mockResults = [{ key: MOCK_OBJECT_KEY_1, success: true, status: 200 }];
69
+ mockCreateUploadPromises.mockResolvedValueOnce([
70
+ { promise: Promise.resolve(mockResults[0]), index: 0, objectType: 'text/plain', objectSize: 12 }
71
+ ]);
72
+ let hookResult;
73
+ const TestComponent = () => {
74
+ hookResult = (0, useObjectStore_1.useObjectStore)();
75
+ return null;
76
+ };
77
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
78
+ expect(hookResult).toBeDefined();
79
+ if (!hookResult)
80
+ return;
81
+ // Before upload
82
+ expect(hookResult.objectStates).toEqual([]);
83
+ // Trigger upload
84
+ await hookResult.uploadObjects({
85
+ functionKey: MOCK_FUNCTION_KEY,
86
+ objects: [MOCK_BLOB]
87
+ });
88
+ // Should call bridge startUploads
89
+ expect(mockCreateUploadPromises).toHaveBeenCalledWith({
90
+ functionKey: MOCK_FUNCTION_KEY,
91
+ objects: [MOCK_BLOB]
92
+ });
93
+ // Should update state with results
94
+ expect(hookResult.objectStates).toHaveLength(1);
95
+ expect(hookResult.objectStates[0]).toMatchObject({
96
+ key: MOCK_OBJECT_KEY_1,
97
+ success: true,
98
+ status: 200,
99
+ objectType: 'text/plain',
100
+ objectSize: MOCK_BLOB.size
101
+ });
102
+ });
103
+ it('should upload Base64Object successfully with fileSize', async () => {
104
+ const mockResults = [{ key: MOCK_OBJECT_KEY_1, success: true, status: 200 }];
105
+ mockCreateUploadPromises.mockResolvedValueOnce([
106
+ { promise: Promise.resolve(mockResults[0]), index: 0, objectType: 'text/plain', objectSize: 12 }
107
+ ]);
108
+ let hookResult;
109
+ const TestComponent = () => {
110
+ hookResult = (0, useObjectStore_1.useObjectStore)();
111
+ return null;
112
+ };
113
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
114
+ expect(hookResult).toBeDefined();
115
+ if (!hookResult)
116
+ return;
117
+ await hookResult.uploadObjects({
118
+ functionKey: MOCK_FUNCTION_KEY,
119
+ objects: [MOCK_BASE64_OBJECT]
120
+ });
121
+ expect(hookResult.objectStates[0]).toMatchObject({
122
+ key: MOCK_OBJECT_KEY_1,
123
+ success: true,
124
+ objectType: 'text/plain',
125
+ objectSize: 12 // Uses provided fileSize
126
+ });
127
+ });
128
+ it('should calculate objectSize from base64 data when fileSize not provided', async () => {
129
+ const base64WithoutSize = { data: 'dGVzdA==', mimeType: 'text/plain' };
130
+ const mockResults = [{ key: MOCK_OBJECT_KEY_1, success: true, status: 200 }];
131
+ mockCreateUploadPromises.mockResolvedValueOnce([
132
+ { promise: Promise.resolve(mockResults[0]), index: 0, objectType: 'text/plain', objectSize: 4 }
133
+ ]);
134
+ let hookResult;
135
+ const TestComponent = () => {
136
+ hookResult = (0, useObjectStore_1.useObjectStore)();
137
+ return null;
138
+ };
139
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
140
+ expect(hookResult).toBeDefined();
141
+ if (!hookResult)
142
+ return;
143
+ await hookResult.uploadObjects({
144
+ functionKey: MOCK_FUNCTION_KEY,
145
+ objects: [base64WithoutSize]
146
+ });
147
+ // Should calculate size from base64: (8 * 3 / 4) - 2 = 4
148
+ expect(hookResult.objectStates[0].objectSize).toBe(4);
149
+ });
150
+ it('should show isUploading state during upload', async () => {
151
+ let resolveUpload;
152
+ const uploadPromise = new Promise((resolve) => {
153
+ resolveUpload = resolve;
154
+ });
155
+ mockCreateUploadPromises.mockResolvedValueOnce([
156
+ { promise: uploadPromise, index: 0, objectType: 'text/plain', objectSize: 12 }
157
+ ]);
158
+ let hookResult;
159
+ const TestComponent = () => {
160
+ hookResult = (0, useObjectStore_1.useObjectStore)();
161
+ return null;
162
+ };
163
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
164
+ expect(hookResult).toBeDefined();
165
+ if (!hookResult)
166
+ return;
167
+ // Start upload (don't await)
168
+ const uploadPromiseResult = hookResult.uploadObjects({
169
+ functionKey: MOCK_FUNCTION_KEY,
170
+ objects: [MOCK_BLOB]
171
+ });
172
+ // Give a tick for state to update
173
+ await new Promise((resolve) => setTimeout(resolve, 0));
174
+ // Should have temp object with isUploading: true
175
+ expect(hookResult.objectStates).toHaveLength(1);
176
+ expect(hookResult.objectStates[0].isUploading).toBe(true);
177
+ expect(hookResult.objectStates[0].key).toMatch(/^temp-upload-/);
178
+ // Resolve upload
179
+ resolveUpload({ key: MOCK_OBJECT_KEY_1, success: true, status: 200 });
180
+ await uploadPromiseResult;
181
+ // Temp object should be replaced with real object (no isUploading)
182
+ expect(hookResult.objectStates).toHaveLength(1);
183
+ expect(hookResult.objectStates[0].key).toBe(MOCK_OBJECT_KEY_1);
184
+ expect(hookResult.objectStates[0].isUploading).toBeUndefined();
185
+ });
186
+ it('should handle upload errors and remove temp objects', async () => {
187
+ const uploadError = new Error('Upload failed');
188
+ mockCreateUploadPromises.mockRejectedValueOnce(uploadError);
189
+ let hookResult;
190
+ const TestComponent = () => {
191
+ hookResult = (0, useObjectStore_1.useObjectStore)();
192
+ return null;
193
+ };
194
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
195
+ expect(hookResult).toBeDefined();
196
+ if (!hookResult)
197
+ return;
198
+ await expect(hookResult.uploadObjects({
199
+ functionKey: MOCK_FUNCTION_KEY,
200
+ objects: [MOCK_BLOB]
201
+ })).rejects.toThrow('Upload failed');
202
+ // Temp objects should be removed
203
+ expect(hookResult.objectStates).toEqual([]);
204
+ });
205
+ it('should upload multiple objects', async () => {
206
+ const mockResults = [
207
+ { key: MOCK_OBJECT_KEY_1, success: true, status: 200 },
208
+ { key: MOCK_OBJECT_KEY_2, success: true, status: 200 }
209
+ ];
210
+ mockCreateUploadPromises.mockResolvedValueOnce([
211
+ { promise: Promise.resolve(mockResults[0]), index: 0, objectType: 'text/plain', objectSize: 12 },
212
+ { promise: Promise.resolve(mockResults[1]), index: 1, objectType: 'text/plain', objectSize: 12 }
213
+ ]);
214
+ let hookResult;
215
+ const TestComponent = () => {
216
+ hookResult = (0, useObjectStore_1.useObjectStore)();
217
+ return null;
218
+ };
219
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
220
+ expect(hookResult).toBeDefined();
221
+ if (!hookResult)
222
+ return;
223
+ const blob1 = new Blob(['test1'], { type: 'text/plain' });
224
+ const blob2 = new Blob(['test2'], { type: 'text/plain' });
225
+ await hookResult.uploadObjects({
226
+ functionKey: MOCK_FUNCTION_KEY,
227
+ objects: [blob1, blob2]
228
+ });
229
+ expect(hookResult.objectStates).toHaveLength(2);
230
+ expect(hookResult.objectStates[0].key).toBe(MOCK_OBJECT_KEY_1);
231
+ expect(hookResult.objectStates[1].key).toBe(MOCK_OBJECT_KEY_2);
232
+ });
233
+ });
234
+ describe('deleteObject', () => {
235
+ it('should delete object successfully', async () => {
236
+ mockDelete.mockResolvedValueOnce(undefined);
237
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
238
+ let hookResult;
239
+ const TestComponent = () => {
240
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
241
+ return null;
242
+ };
243
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
244
+ expect(hookResult).toBeDefined();
245
+ if (!hookResult)
246
+ return;
247
+ // Before delete
248
+ expect(hookResult.objectStates).toHaveLength(1);
249
+ // Delete object
250
+ await hookResult.deleteObjects({ functionKey: MOCK_FUNCTION_KEY, keys: [MOCK_OBJECT_KEY_1] });
251
+ // Should call bridge delete
252
+ expect(mockDelete).toHaveBeenCalledWith({
253
+ functionKey: MOCK_FUNCTION_KEY,
254
+ keys: [MOCK_OBJECT_KEY_1]
255
+ });
256
+ // Object should be removed from state
257
+ expect(hookResult.objectStates).toEqual([]);
258
+ });
259
+ it('should show isDeleting state during delete', async () => {
260
+ let resolveDelete;
261
+ const deletePromise = new Promise((resolve) => {
262
+ resolveDelete = resolve;
263
+ });
264
+ mockDelete.mockReturnValueOnce(deletePromise);
265
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
266
+ let hookResult;
267
+ const TestComponent = () => {
268
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
269
+ return null;
270
+ };
271
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
272
+ expect(hookResult).toBeDefined();
273
+ if (!hookResult)
274
+ return;
275
+ // Start delete (don't await)
276
+ const deletePromiseResult = hookResult.deleteObjects({
277
+ functionKey: MOCK_FUNCTION_KEY,
278
+ keys: [MOCK_OBJECT_KEY_1]
279
+ });
280
+ // Give a tick for state to update
281
+ await new Promise((resolve) => setTimeout(resolve, 0));
282
+ // Should have isDeleting: true
283
+ expect(hookResult.objectStates[0].isDeleting).toBe(true);
284
+ // Resolve delete
285
+ resolveDelete(undefined);
286
+ await deletePromiseResult;
287
+ // Object should be removed
288
+ expect(hookResult.objectStates).toEqual([]);
289
+ });
290
+ it('should handle delete errors and set error state', async () => {
291
+ const deleteError = new Error('Delete failed');
292
+ mockDelete.mockRejectedValueOnce(deleteError);
293
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
294
+ let hookResult;
295
+ const TestComponent = () => {
296
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
297
+ return null;
298
+ };
299
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
300
+ expect(hookResult).toBeDefined();
301
+ if (!hookResult)
302
+ return;
303
+ await expect(hookResult.deleteObjects({ functionKey: MOCK_FUNCTION_KEY, keys: [MOCK_OBJECT_KEY_1] })).rejects.toThrow('Delete failed');
304
+ // Object should still exist with error state
305
+ expect(hookResult.objectStates).toHaveLength(1);
306
+ expect(hookResult.objectStates[0].isDeleting).toBe(false);
307
+ expect(hookResult.objectStates[0].error).toBe('Delete failed');
308
+ });
309
+ it('should delete multiple objects successfully', async () => {
310
+ mockDelete.mockResolvedValueOnce(undefined);
311
+ const defaultValues = [
312
+ { key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 },
313
+ { key: MOCK_OBJECT_KEY_2, success: true, objectSize: 200 }
314
+ ];
315
+ let hookResult;
316
+ const TestComponent = () => {
317
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
318
+ return null;
319
+ };
320
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
321
+ expect(hookResult).toBeDefined();
322
+ if (!hookResult)
323
+ return;
324
+ // Before delete
325
+ expect(hookResult.objectStates).toHaveLength(2);
326
+ // Delete both objects
327
+ await hookResult.deleteObjects({
328
+ functionKey: MOCK_FUNCTION_KEY,
329
+ keys: [MOCK_OBJECT_KEY_1, MOCK_OBJECT_KEY_2]
330
+ });
331
+ // Should call bridge delete with both keys
332
+ expect(mockDelete).toHaveBeenCalledWith({
333
+ functionKey: MOCK_FUNCTION_KEY,
334
+ keys: [MOCK_OBJECT_KEY_1, MOCK_OBJECT_KEY_2]
335
+ });
336
+ // Both objects should be removed from state
337
+ expect(hookResult.objectStates).toEqual([]);
338
+ });
339
+ it('should show isDeleting state for multiple objects during delete', async () => {
340
+ let resolveDelete;
341
+ const deletePromise = new Promise((resolve) => {
342
+ resolveDelete = resolve;
343
+ });
344
+ mockDelete.mockReturnValueOnce(deletePromise);
345
+ const defaultValues = [
346
+ { key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 },
347
+ { key: MOCK_OBJECT_KEY_2, success: true, objectSize: 200 }
348
+ ];
349
+ let hookResult;
350
+ const TestComponent = () => {
351
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
352
+ return null;
353
+ };
354
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
355
+ expect(hookResult).toBeDefined();
356
+ if (!hookResult)
357
+ return;
358
+ // Start delete (don't await)
359
+ const deletePromiseResult = hookResult.deleteObjects({
360
+ functionKey: MOCK_FUNCTION_KEY,
361
+ keys: [MOCK_OBJECT_KEY_1, MOCK_OBJECT_KEY_2]
362
+ });
363
+ // Give a tick for state to update
364
+ await new Promise((resolve) => setTimeout(resolve, 0));
365
+ // Both should have isDeleting: true
366
+ expect(hookResult.objectStates[0].isDeleting).toBe(true);
367
+ expect(hookResult.objectStates[1].isDeleting).toBe(true);
368
+ // Resolve delete
369
+ resolveDelete(undefined);
370
+ await deletePromiseResult;
371
+ // Both objects should be removed
372
+ expect(hookResult.objectStates).toEqual([]);
373
+ });
374
+ });
375
+ describe('downloadObjects', () => {
376
+ it('should download object successfully', async () => {
377
+ const mockBlob = new Blob(['downloaded content']);
378
+ const mockResults = [{ success: true, key: MOCK_OBJECT_KEY_1, blob: mockBlob }];
379
+ mockDownload.mockResolvedValueOnce(mockResults);
380
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
381
+ let hookResult;
382
+ const TestComponent = () => {
383
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
384
+ return null;
385
+ };
386
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
387
+ expect(hookResult).toBeDefined();
388
+ if (!hookResult)
389
+ return;
390
+ const result = await hookResult.downloadObjects({ functionKey: MOCK_FUNCTION_KEY, keys: [MOCK_OBJECT_KEY_1] });
391
+ // Should call bridge download
392
+ expect(mockDownload).toHaveBeenCalledWith({
393
+ functionKey: MOCK_FUNCTION_KEY,
394
+ keys: [MOCK_OBJECT_KEY_1]
395
+ });
396
+ // Should return results with blob
397
+ expect(result[0].blob).toBe(mockBlob);
398
+ });
399
+ it('should show isDownloading state during download', async () => {
400
+ let resolveDownload;
401
+ const downloadPromise = new Promise((resolve) => {
402
+ resolveDownload = resolve;
403
+ });
404
+ mockDownload.mockReturnValueOnce(downloadPromise);
405
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
406
+ let hookResult;
407
+ const TestComponent = () => {
408
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
409
+ return null;
410
+ };
411
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
412
+ expect(hookResult).toBeDefined();
413
+ if (!hookResult)
414
+ return;
415
+ // Start download (don't await)
416
+ const downloadPromiseResult = hookResult.downloadObjects({
417
+ functionKey: MOCK_FUNCTION_KEY,
418
+ keys: [MOCK_OBJECT_KEY_1]
419
+ });
420
+ // Give a tick for state to update
421
+ await new Promise((resolve) => setTimeout(resolve, 0));
422
+ // Should have isDownloading: true
423
+ expect(hookResult.objectStates[0].isDownloading).toBe(true);
424
+ // Resolve download
425
+ const mockBlob = new Blob(['content']);
426
+ resolveDownload([{ success: true, key: MOCK_OBJECT_KEY_1, blob: mockBlob }]);
427
+ await downloadPromiseResult;
428
+ // isDownloading should be false
429
+ expect(hookResult.objectStates[0].isDownloading).toBe(false);
430
+ });
431
+ it('should handle download errors and set error state', async () => {
432
+ const downloadError = new Error('Download failed');
433
+ mockDownload.mockRejectedValueOnce(downloadError);
434
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
435
+ let hookResult;
436
+ const TestComponent = () => {
437
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
438
+ return null;
439
+ };
440
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
441
+ expect(hookResult).toBeDefined();
442
+ if (!hookResult)
443
+ return;
444
+ await expect(hookResult.downloadObjects({ functionKey: MOCK_FUNCTION_KEY, keys: [MOCK_OBJECT_KEY_1] })).rejects.toThrow('Download failed');
445
+ // Object should have error state
446
+ expect(hookResult.objectStates[0].isDownloading).toBe(false);
447
+ expect(hookResult.objectStates[0].error).toBe('Download failed');
448
+ });
449
+ it('should return null when download result has no blob', async () => {
450
+ const mockResults = [{ success: false, key: MOCK_OBJECT_KEY_1, error: 'Not found' }];
451
+ mockDownload.mockResolvedValueOnce(mockResults);
452
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
453
+ let hookResult;
454
+ const TestComponent = () => {
455
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
456
+ return null;
457
+ };
458
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
459
+ expect(hookResult).toBeDefined();
460
+ if (!hookResult)
461
+ return;
462
+ const result = await hookResult.downloadObjects({
463
+ functionKey: MOCK_FUNCTION_KEY,
464
+ keys: [MOCK_OBJECT_KEY_1]
465
+ });
466
+ expect(result[0].success).toBe(false);
467
+ expect(result[0].blob).toBeUndefined();
468
+ expect(hookResult.objectStates[0].error).toBe('Download failed');
469
+ });
470
+ });
471
+ describe('getObjectMetadata', () => {
472
+ it('should return object from state when it exists', async () => {
473
+ const defaultValues = [{ key: MOCK_OBJECT_KEY_1, success: true, objectSize: 100 }];
474
+ let hookResult;
475
+ const TestComponent = () => {
476
+ hookResult = (0, useObjectStore_1.useObjectStore)({ defaultValues });
477
+ return null;
478
+ };
479
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
480
+ expect(hookResult).toBeDefined();
481
+ if (!hookResult)
482
+ return;
483
+ const result = hookResult.getObjectMetadata(MOCK_OBJECT_KEY_1);
484
+ expect(result).toEqual(defaultValues[0]);
485
+ });
486
+ it('should return null when object not in state', async () => {
487
+ let hookResult;
488
+ const TestComponent = () => {
489
+ hookResult = (0, useObjectStore_1.useObjectStore)();
490
+ return null;
491
+ };
492
+ await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, {}));
493
+ expect(hookResult).toBeDefined();
494
+ if (!hookResult)
495
+ return;
496
+ const result = hookResult.getObjectMetadata(MOCK_OBJECT_KEY_1);
497
+ expect(result).toBeNull();
498
+ });
499
+ });
500
+ describe('Function memoization', () => {
501
+ it('should preserve uploadObjects function reference between renders', async () => {
502
+ let uploadFunction1;
503
+ let uploadFunction2;
504
+ const TestComponent = ({ renderKey }) => {
505
+ const { uploadObjects } = (0, useObjectStore_1.useObjectStore)();
506
+ if (renderKey === 1)
507
+ uploadFunction1 = uploadObjects;
508
+ if (renderKey === 2)
509
+ uploadFunction2 = uploadObjects;
510
+ return null;
511
+ };
512
+ const renderer = await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, { renderKey: 1 }));
513
+ await renderer.update((0, jsx_runtime_1.jsx)(TestComponent, { renderKey: 2 }));
514
+ // Verify both functions exist and are callable
515
+ expect(uploadFunction1).toBeDefined();
516
+ expect(uploadFunction2).toBeDefined();
517
+ expect(typeof uploadFunction1).toBe('function');
518
+ expect(typeof uploadFunction2).toBe('function');
519
+ // Note: Function reference stability depends on test renderer behavior
520
+ });
521
+ it('should preserve deleteObjects function reference between renders', async () => {
522
+ let deleteFunction1;
523
+ let deleteFunction2;
524
+ const TestComponent = ({ renderKey }) => {
525
+ const { deleteObjects } = (0, useObjectStore_1.useObjectStore)();
526
+ if (renderKey === 1)
527
+ deleteFunction1 = deleteObjects;
528
+ if (renderKey === 2)
529
+ deleteFunction2 = deleteObjects;
530
+ return null;
531
+ };
532
+ const renderer = await reconcilerTestRenderer_1.default.create((0, jsx_runtime_1.jsx)(TestComponent, { renderKey: 1 }));
533
+ await renderer.update((0, jsx_runtime_1.jsx)(TestComponent, { renderKey: 2 }));
534
+ // Verify both functions exist and are callable
535
+ expect(deleteFunction1).toBeDefined();
536
+ expect(deleteFunction2).toBeDefined();
537
+ expect(typeof deleteFunction1).toBe('function');
538
+ expect(typeof deleteFunction2).toBe('function');
539
+ // Note: Function reference stability depends on test renderer behavior
540
+ });
541
+ });
542
+ });
@@ -0,0 +1,33 @@
1
+ import type { UploadObject, UploadResult, Base64Object } from '@forge/bridge';
2
+ export type { UploadObject, UploadResult, Base64Object };
3
+ export declare type ObjectState = {
4
+ key: string;
5
+ success?: boolean;
6
+ status?: number;
7
+ error?: string;
8
+ objectType?: string;
9
+ objectSize?: number;
10
+ isUploading?: boolean;
11
+ isDeleting?: boolean;
12
+ isDownloading?: boolean;
13
+ };
14
+ export declare type UseObjectStoreProps = {
15
+ defaultValues?: ObjectState[];
16
+ };
17
+ export declare type UploadParams = {
18
+ functionKey: string;
19
+ objects: UploadObject[];
20
+ };
21
+ export declare type DeleteParams = {
22
+ functionKey: string;
23
+ keys: string[];
24
+ };
25
+ export declare type DownloadParams = {
26
+ functionKey: string;
27
+ keys: string[];
28
+ };
29
+ export declare type ClearAllObjectsOptions = {
30
+ deleteFromStore?: boolean;
31
+ functionKey?: string;
32
+ };
33
+ //# sourceMappingURL=objectStoreProps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"objectStoreProps.d.ts","sourceRoot":"","sources":["../../../src/hooks/types/objectStoreProps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9E,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC;AAEzD,oBAAY,WAAW,GAAG;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,oBAAY,mBAAmB,GAAG;IAChC,aAAa,CAAC,EAAE,WAAW,EAAE,CAAC;CAC/B,CAAC;AAEF,oBAAY,YAAY,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,CAAC;AAEF,oBAAY,YAAY,GAAG;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,oBAAY,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,oBAAY,sBAAsB,GAAG;IACnC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { ObjectState, UseObjectStoreProps, UploadParams, DeleteParams, DownloadParams } from './types/objectStoreProps';
2
+ export declare function useObjectStore(props?: UseObjectStoreProps): {
3
+ objectStates: ObjectState[];
4
+ getObjectMetadata: (key: string) => ObjectState | null;
5
+ uploadObjects: (params: UploadParams) => Promise<void>;
6
+ deleteObjects: (params: DeleteParams) => Promise<void>;
7
+ downloadObjects: (params: DownloadParams) => Promise<import("@forge/bridge").DownloadResult[]>;
8
+ };
9
+ //# sourceMappingURL=useObjectStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useObjectStore.d.ts","sourceRoot":"","sources":["../../src/hooks/useObjectStore.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,YAAY,EACZ,cAAc,EACf,MAAM,0BAA0B,CAAC;AAqFlC,wBAAgB,cAAc,CAAC,KAAK,GAAE,mBAAwB;;6BAQ5B,MAAM,KAAG,WAAW,GAAG,IAAI;4BAeV,YAAY,KAAG,QAAQ,IAAI,CAAC;4BA2C5B,YAAY,KAAG,QAAQ,IAAI,CAAC;8BAkB1B,cAAc;EA+BlE"}