@objectstack/client-react 17.0.0-rc.1 → 17.0.0-rc.3

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/dist/index.js CHANGED
@@ -35,6 +35,8 @@ __export(index_exports, {
35
35
  ObjectStackLocaleContext: () => ObjectStackLocaleContext,
36
36
  ObjectStackProvider: () => ObjectStackProvider,
37
37
  useAutoRefresh: () => useAutoRefresh,
38
+ useBulkDataSubscription: () => useBulkDataSubscription,
39
+ useBulkDataSubscriptionCallback: () => useBulkDataSubscriptionCallback,
38
40
  useClient: () => useClient,
39
41
  useDataSubscription: () => useDataSubscription,
40
42
  useDataSubscriptionCallback: () => useDataSubscriptionCallback,
@@ -80,14 +82,33 @@ function useClient() {
80
82
  }
81
83
 
82
84
  // src/data-hooks.tsx
85
+ var import_react3 = require("react");
86
+
87
+ // src/internal-deps.ts
83
88
  var import_react2 = require("react");
89
+ function stableKey(input) {
90
+ if (input === null || typeof input !== "object") return JSON.stringify(input) ?? "undefined";
91
+ if (Array.isArray(input)) return "[" + input.map(stableKey).join(",") + "]";
92
+ const obj = input;
93
+ const keys = Object.keys(obj).sort();
94
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableKey(obj[k])).join(",") + "}";
95
+ }
96
+ function useEventCallback(fn) {
97
+ const ref = (0, import_react2.useRef)(fn);
98
+ (0, import_react2.useEffect)(() => {
99
+ ref.current = fn;
100
+ }, [fn]);
101
+ return (0, import_react2.useCallback)((...args) => ref.current?.(...args), []);
102
+ }
103
+
104
+ // src/data-hooks.tsx
84
105
  function useQuery(object, options = {}) {
85
106
  const client = useClient();
86
- const [data, setData] = (0, import_react2.useState)(null);
87
- const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
88
- const [isRefetching, setIsRefetching] = (0, import_react2.useState)(false);
89
- const [error, setError] = (0, import_react2.useState)(null);
90
- const intervalRef = (0, import_react2.useRef)(void 0);
107
+ const [data, setData] = (0, import_react3.useState)(null);
108
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
109
+ const [isRefetching, setIsRefetching] = (0, import_react3.useState)(false);
110
+ const [error, setError] = (0, import_react3.useState)(null);
111
+ const intervalRef = (0, import_react3.useRef)(void 0);
91
112
  const {
92
113
  query,
93
114
  // Canonical names take precedence over legacy names
@@ -106,7 +127,17 @@ function useQuery(object, options = {}) {
106
127
  const resolvedSort = orderBy;
107
128
  const resolvedLimit = limit;
108
129
  const resolvedOffset = offset;
109
- const fetchData = (0, import_react2.useCallback)(async (isRefetch = false) => {
130
+ const queryKey = stableKey({
131
+ query,
132
+ where: resolvedWhere,
133
+ fields: resolvedFields,
134
+ orderBy: resolvedSort,
135
+ limit: resolvedLimit,
136
+ offset: resolvedOffset
137
+ });
138
+ const handleSuccess = useEventCallback(onSuccess);
139
+ const handleError = useEventCallback(onError);
140
+ const fetchData = (0, import_react3.useCallback)(async (isRefetch = false) => {
110
141
  if (!enabled) return;
111
142
  try {
112
143
  if (isRefetch) {
@@ -128,20 +159,20 @@ function useQuery(object, options = {}) {
128
159
  });
129
160
  }
130
161
  setData(result);
131
- onSuccess?.(result);
162
+ handleSuccess(result);
132
163
  } catch (err) {
133
164
  const error2 = err instanceof Error ? err : new Error("Query failed");
134
165
  setError(error2);
135
- onError?.(error2);
166
+ handleError(error2);
136
167
  } finally {
137
168
  setIsLoading(false);
138
169
  setIsRefetching(false);
139
170
  }
140
- }, [client, object, query, resolvedFields, resolvedWhere, resolvedSort, resolvedLimit, resolvedOffset, enabled, onSuccess, onError]);
141
- (0, import_react2.useEffect)(() => {
171
+ }, [client, object, queryKey, enabled, handleSuccess, handleError]);
172
+ (0, import_react3.useEffect)(() => {
142
173
  fetchData();
143
174
  }, [fetchData]);
144
- (0, import_react2.useEffect)(() => {
175
+ (0, import_react3.useEffect)(() => {
145
176
  if (refetchInterval && enabled) {
146
177
  intervalRef.current = setInterval(() => {
147
178
  fetchData(true);
@@ -154,7 +185,7 @@ function useQuery(object, options = {}) {
154
185
  }
155
186
  return void 0;
156
187
  }, [refetchInterval, enabled, fetchData]);
157
- const refetch = (0, import_react2.useCallback)(async () => {
188
+ const refetch = (0, import_react3.useCallback)(async () => {
158
189
  await fetchData(true);
159
190
  }, [fetchData]);
160
191
  return {
@@ -167,11 +198,11 @@ function useQuery(object, options = {}) {
167
198
  }
168
199
  function useMutation(object, operation, options = {}) {
169
200
  const client = useClient();
170
- const [data, setData] = (0, import_react2.useState)(null);
171
- const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
172
- const [error, setError] = (0, import_react2.useState)(null);
201
+ const [data, setData] = (0, import_react3.useState)(null);
202
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(false);
203
+ const [error, setError] = (0, import_react3.useState)(null);
173
204
  const { onSuccess, onError, onSettled } = options;
174
- const mutateAsync = (0, import_react2.useCallback)(async (variables) => {
205
+ const mutateAsync = (0, import_react3.useCallback)(async (variables) => {
175
206
  setIsLoading(true);
176
207
  setError(null);
177
208
  try {
@@ -216,12 +247,12 @@ function useMutation(object, operation, options = {}) {
216
247
  setIsLoading(false);
217
248
  }
218
249
  }, [client, object, operation, onSuccess, onError, onSettled]);
219
- const mutate = (0, import_react2.useCallback)((variables) => {
250
+ const mutate = (0, import_react3.useCallback)((variables) => {
220
251
  return mutateAsync(variables).catch(() => {
221
252
  return null;
222
253
  });
223
254
  }, [mutateAsync]);
224
- const reset = (0, import_react2.useCallback)(() => {
255
+ const reset = (0, import_react3.useCallback)(() => {
225
256
  setData(null);
226
257
  setError(null);
227
258
  setIsLoading(false);
@@ -237,7 +268,7 @@ function useMutation(object, operation, options = {}) {
237
268
  }
238
269
  function usePagination(object, options = {}) {
239
270
  const { pageSize = 20, initialPage = 1, ...queryOptions } = options;
240
- const [page, setPage] = (0, import_react2.useState)(initialPage);
271
+ const [page, setPage] = (0, import_react3.useState)(initialPage);
241
272
  const queryResult = useQuery(object, {
242
273
  ...queryOptions,
243
274
  limit: pageSize,
@@ -247,17 +278,17 @@ function usePagination(object, options = {}) {
247
278
  const totalPages = Math.ceil(totalCount / pageSize);
248
279
  const hasNextPage = page < totalPages;
249
280
  const hasPreviousPage = page > 1;
250
- const nextPage = (0, import_react2.useCallback)(() => {
281
+ const nextPage = (0, import_react3.useCallback)(() => {
251
282
  if (hasNextPage) {
252
283
  setPage((p) => p + 1);
253
284
  }
254
285
  }, [hasNextPage]);
255
- const previousPage = (0, import_react2.useCallback)(() => {
286
+ const previousPage = (0, import_react3.useCallback)(() => {
256
287
  if (hasPreviousPage) {
257
288
  setPage((p) => p - 1);
258
289
  }
259
290
  }, [hasPreviousPage]);
260
- const goToPage = (0, import_react2.useCallback)((newPage) => {
291
+ const goToPage = (0, import_react3.useCallback)((newPage) => {
261
292
  const clampedPage = Math.max(1, Math.min(newPage, totalPages));
262
293
  setPage(clampedPage);
263
294
  }, [totalPages]);
@@ -290,12 +321,21 @@ function useInfiniteQuery(object, options = {}) {
290
321
  const resolvedFields = fields;
291
322
  const resolvedWhere = where;
292
323
  const resolvedSort = orderBy;
293
- const [pages, setPages] = (0, import_react2.useState)([]);
294
- const [isLoading, setIsLoading] = (0, import_react2.useState)(true);
295
- const [isFetchingNextPage, setIsFetchingNextPage] = (0, import_react2.useState)(false);
296
- const [error, setError] = (0, import_react2.useState)(null);
297
- const [hasNextPage, setHasNextPage] = (0, import_react2.useState)(true);
298
- const fetchPage = (0, import_react2.useCallback)(async (skip, isNextPage = false) => {
324
+ const queryKey = stableKey({
325
+ query,
326
+ where: resolvedWhere,
327
+ fields: resolvedFields,
328
+ orderBy: resolvedSort,
329
+ pageSize
330
+ });
331
+ const handleSuccess = useEventCallback(onSuccess);
332
+ const handleError = useEventCallback(onError);
333
+ const [pages, setPages] = (0, import_react3.useState)([]);
334
+ const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
335
+ const [isFetchingNextPage, setIsFetchingNextPage] = (0, import_react3.useState)(false);
336
+ const [error, setError] = (0, import_react3.useState)(null);
337
+ const [hasNextPage, setHasNextPage] = (0, import_react3.useState)(true);
338
+ const fetchPage = (0, import_react3.useCallback)(async (skip, isNextPage = false) => {
299
339
  try {
300
340
  if (isNextPage) {
301
341
  setIsFetchingNextPage(true);
@@ -327,27 +367,27 @@ function useInfiniteQuery(object, options = {}) {
327
367
  const fetchedCount = result.records?.length ?? 0;
328
368
  const hasMore = fetchedCount === pageSize;
329
369
  setHasNextPage(hasMore);
330
- onSuccess?.(result);
370
+ handleSuccess(result);
331
371
  } catch (err) {
332
372
  const error2 = err instanceof Error ? err : new Error("Query failed");
333
373
  setError(error2);
334
- onError?.(error2);
374
+ handleError(error2);
335
375
  } finally {
336
376
  setIsLoading(false);
337
377
  setIsFetchingNextPage(false);
338
378
  }
339
- }, [client, object, query, resolvedFields, resolvedWhere, resolvedSort, pageSize, onSuccess, onError]);
340
- (0, import_react2.useEffect)(() => {
379
+ }, [client, object, queryKey, handleSuccess, handleError]);
380
+ (0, import_react3.useEffect)(() => {
341
381
  if (enabled) {
342
382
  fetchPage(0);
343
383
  }
344
384
  }, [enabled, fetchPage]);
345
- const fetchNextPage = (0, import_react2.useCallback)(async () => {
385
+ const fetchNextPage = (0, import_react3.useCallback)(async () => {
346
386
  if (!hasNextPage || isFetchingNextPage) return;
347
387
  const nextSkip = pages.length * pageSize;
348
388
  await fetchPage(nextSkip, true);
349
389
  }, [hasNextPage, isFetchingNextPage, pages.length, pageSize, fetchPage]);
350
- const refetch = (0, import_react2.useCallback)(async () => {
390
+ const refetch = (0, import_react3.useCallback)(async () => {
351
391
  setPages([]);
352
392
  await fetchPage(0);
353
393
  }, [fetchPage]);
@@ -365,15 +405,15 @@ function useInfiniteQuery(object, options = {}) {
365
405
  }
366
406
 
367
407
  // src/metadata-hooks.tsx
368
- var import_react3 = require("react");
408
+ var import_react4 = require("react");
369
409
  function useObject(objectName, options = {}) {
370
410
  const client = useClient();
371
411
  const locale = useObjectStackLocale();
372
- const [data, setData] = (0, import_react3.useState)(null);
373
- const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
374
- const [error, setError] = (0, import_react3.useState)(null);
375
- const [etag, setEtag] = (0, import_react3.useState)();
376
- const [fromCache, setFromCache] = (0, import_react3.useState)(false);
412
+ const [data, setData] = (0, import_react4.useState)(null);
413
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
414
+ const [error, setError] = (0, import_react4.useState)(null);
415
+ const [etag, setEtag] = (0, import_react4.useState)();
416
+ const [fromCache, setFromCache] = (0, import_react4.useState)(false);
377
417
  const {
378
418
  enabled = true,
379
419
  useCache = true,
@@ -382,7 +422,15 @@ function useObject(objectName, options = {}) {
382
422
  onSuccess,
383
423
  onError
384
424
  } = options;
385
- const fetchMetadata = (0, import_react3.useCallback)(async () => {
425
+ const dataRef = (0, import_react4.useRef)(data);
426
+ const etagRef = (0, import_react4.useRef)(etag);
427
+ (0, import_react4.useEffect)(() => {
428
+ dataRef.current = data;
429
+ etagRef.current = etag;
430
+ }, [data, etag]);
431
+ const handleSuccess = useEventCallback(onSuccess);
432
+ const handleError = useEventCallback(onError);
433
+ const fetchMetadata = (0, import_react4.useCallback)(async () => {
386
434
  if (!enabled) return;
387
435
  try {
388
436
  setIsLoading(true);
@@ -390,7 +438,7 @@ function useObject(objectName, options = {}) {
390
438
  setFromCache(false);
391
439
  if (useCache) {
392
440
  const result = await client.meta.getCached(objectName, {
393
- ifNoneMatch: ifNoneMatch || etag,
441
+ ifNoneMatch: ifNoneMatch || etagRef.current,
394
442
  ifModifiedSince
395
443
  });
396
444
  if (result.notModified) {
@@ -401,24 +449,24 @@ function useObject(objectName, options = {}) {
401
449
  setEtag(result.etag.value);
402
450
  }
403
451
  }
404
- onSuccess?.(result.data || data);
452
+ handleSuccess(result.data || dataRef.current);
405
453
  } else {
406
454
  const result = await client.meta.getItem("object", objectName);
407
455
  setData(result);
408
- onSuccess?.(result);
456
+ handleSuccess(result);
409
457
  }
410
458
  } catch (err) {
411
459
  const error2 = err instanceof Error ? err : new Error("Failed to fetch object metadata");
412
460
  setError(error2);
413
- onError?.(error2);
461
+ handleError(error2);
414
462
  } finally {
415
463
  setIsLoading(false);
416
464
  }
417
- }, [client, objectName, locale, enabled, useCache, ifNoneMatch, ifModifiedSince, etag, data, onSuccess, onError]);
418
- (0, import_react3.useEffect)(() => {
465
+ }, [client, objectName, locale, enabled, useCache, ifNoneMatch, ifModifiedSince, handleSuccess, handleError]);
466
+ (0, import_react4.useEffect)(() => {
419
467
  fetchMetadata();
420
468
  }, [fetchMetadata]);
421
- const refetch = (0, import_react3.useCallback)(async () => {
469
+ const refetch = (0, import_react4.useCallback)(async () => {
422
470
  await fetchMetadata();
423
471
  }, [fetchMetadata]);
424
472
  return {
@@ -433,30 +481,32 @@ function useObject(objectName, options = {}) {
433
481
  function useView(objectName, viewType = "list", options = {}) {
434
482
  const client = useClient();
435
483
  const locale = useObjectStackLocale();
436
- const [data, setData] = (0, import_react3.useState)(null);
437
- const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
438
- const [error, setError] = (0, import_react3.useState)(null);
484
+ const [data, setData] = (0, import_react4.useState)(null);
485
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
486
+ const [error, setError] = (0, import_react4.useState)(null);
439
487
  const { enabled = true, onSuccess, onError } = options;
440
- const fetchView = (0, import_react3.useCallback)(async () => {
488
+ const handleSuccess = useEventCallback(onSuccess);
489
+ const handleError = useEventCallback(onError);
490
+ const fetchView = (0, import_react4.useCallback)(async () => {
441
491
  if (!enabled) return;
442
492
  try {
443
493
  setIsLoading(true);
444
494
  setError(null);
445
495
  const result = await client.meta.getView(objectName, viewType);
446
496
  setData(result);
447
- onSuccess?.(result);
497
+ handleSuccess(result);
448
498
  } catch (err) {
449
499
  const error2 = err instanceof Error ? err : new Error("Failed to fetch view configuration");
450
500
  setError(error2);
451
- onError?.(error2);
501
+ handleError(error2);
452
502
  } finally {
453
503
  setIsLoading(false);
454
504
  }
455
- }, [client, objectName, viewType, locale, enabled, onSuccess, onError]);
456
- (0, import_react3.useEffect)(() => {
505
+ }, [client, objectName, viewType, locale, enabled, handleSuccess, handleError]);
506
+ (0, import_react4.useEffect)(() => {
457
507
  fetchView();
458
508
  }, [fetchView]);
459
- const refetch = (0, import_react3.useCallback)(async () => {
509
+ const refetch = (0, import_react4.useCallback)(async () => {
460
510
  await fetchView();
461
511
  }, [fetchView]);
462
512
  return {
@@ -481,30 +531,33 @@ function useFields(objectName, options = {}) {
481
531
  function useMetadata(fetcher, options = {}) {
482
532
  const client = useClient();
483
533
  const locale = useObjectStackLocale();
484
- const [data, setData] = (0, import_react3.useState)(null);
485
- const [isLoading, setIsLoading] = (0, import_react3.useState)(true);
486
- const [error, setError] = (0, import_react3.useState)(null);
534
+ const [data, setData] = (0, import_react4.useState)(null);
535
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
536
+ const [error, setError] = (0, import_react4.useState)(null);
487
537
  const { enabled = true, onSuccess, onError } = options;
488
- const fetchMetadata = (0, import_react3.useCallback)(async () => {
538
+ const runFetcher = useEventCallback(fetcher);
539
+ const handleSuccess = useEventCallback(onSuccess);
540
+ const handleError = useEventCallback(onError);
541
+ const fetchMetadata = (0, import_react4.useCallback)(async () => {
489
542
  if (!enabled) return;
490
543
  try {
491
544
  setIsLoading(true);
492
545
  setError(null);
493
- const result = await fetcher(client);
546
+ const result = await runFetcher(client);
494
547
  setData(result);
495
- onSuccess?.(result);
548
+ handleSuccess(result);
496
549
  } catch (err) {
497
550
  const error2 = err instanceof Error ? err : new Error("Failed to fetch metadata");
498
551
  setError(error2);
499
- onError?.(error2);
552
+ handleError(error2);
500
553
  } finally {
501
554
  setIsLoading(false);
502
555
  }
503
- }, [client, fetcher, locale, enabled, onSuccess, onError]);
504
- (0, import_react3.useEffect)(() => {
556
+ }, [client, runFetcher, locale, enabled, handleSuccess, handleError]);
557
+ (0, import_react4.useEffect)(() => {
505
558
  fetchMetadata();
506
559
  }, [fetchMetadata]);
507
- const refetch = (0, import_react3.useCallback)(async () => {
560
+ const refetch = (0, import_react4.useCallback)(async () => {
508
561
  await fetchMetadata();
509
562
  }, [fetchMetadata]);
510
563
  return {
@@ -517,11 +570,11 @@ function useMetadata(fetcher, options = {}) {
517
570
  }
518
571
 
519
572
  // src/realtime-hooks.tsx
520
- var import_react4 = require("react");
573
+ var import_react5 = require("react");
521
574
  function useMetadataSubscription(type, options) {
522
575
  const client = useClient();
523
- const [event, setEvent] = (0, import_react4.useState)(null);
524
- (0, import_react4.useEffect)(() => {
576
+ const [event, setEvent] = (0, import_react5.useState)(null);
577
+ (0, import_react5.useEffect)(() => {
525
578
  if (!client) return;
526
579
  const unsubscribe = client.events.subscribeMetadata(
527
580
  type,
@@ -536,8 +589,8 @@ function useMetadataSubscription(type, options) {
536
589
  }
537
590
  function useDataSubscription(object, options) {
538
591
  const client = useClient();
539
- const [event, setEvent] = (0, import_react4.useState)(null);
540
- (0, import_react4.useEffect)(() => {
592
+ const [event, setEvent] = (0, import_react5.useState)(null);
593
+ (0, import_react5.useEffect)(() => {
541
594
  if (!client) return;
542
595
  const unsubscribe = client.events.subscribeData(
543
596
  object,
@@ -552,36 +605,61 @@ function useDataSubscription(object, options) {
552
605
  }
553
606
  function useMetadataSubscriptionCallback(type, callback, options) {
554
607
  const client = useClient();
555
- (0, import_react4.useEffect)(() => {
608
+ const handleEvent = useEventCallback(callback);
609
+ (0, import_react5.useEffect)(() => {
556
610
  if (!client) return;
557
611
  const unsubscribe = client.events.subscribeMetadata(
558
612
  type,
559
- callback,
613
+ handleEvent,
560
614
  options
561
615
  );
562
616
  return () => {
563
617
  unsubscribe();
564
618
  };
565
- }, [client, type, callback, options?.packageId]);
619
+ }, [client, type, handleEvent, options?.packageId]);
566
620
  }
567
621
  function useDataSubscriptionCallback(object, callback, options) {
568
622
  const client = useClient();
569
- (0, import_react4.useEffect)(() => {
623
+ const handleEvent = useEventCallback(callback);
624
+ (0, import_react5.useEffect)(() => {
570
625
  if (!client) return;
571
626
  const unsubscribe = client.events.subscribeData(
572
627
  object,
573
- callback,
628
+ handleEvent,
574
629
  options
575
630
  );
576
631
  return () => {
577
632
  unsubscribe();
578
633
  };
579
- }, [client, object, callback, options?.recordId]);
634
+ }, [client, object, handleEvent, options?.recordId]);
635
+ }
636
+ function useBulkDataSubscription(object) {
637
+ const client = useClient();
638
+ const [event, setEvent] = (0, import_react5.useState)(null);
639
+ (0, import_react5.useEffect)(() => {
640
+ if (!client) return;
641
+ const unsubscribe = client.events.subscribeBulkData(object, (e) => setEvent(e));
642
+ return () => {
643
+ unsubscribe();
644
+ };
645
+ }, [client, object]);
646
+ return event;
647
+ }
648
+ function useBulkDataSubscriptionCallback(object, callback) {
649
+ const client = useClient();
650
+ const handleEvent = useEventCallback(callback);
651
+ (0, import_react5.useEffect)(() => {
652
+ if (!client) return;
653
+ const unsubscribe = client.events.subscribeBulkData(object, handleEvent);
654
+ return () => {
655
+ unsubscribe();
656
+ };
657
+ }, [client, object, handleEvent]);
580
658
  }
581
659
  function useRealtimeConnection() {
582
660
  const client = useClient();
583
- const [connected, setConnected] = (0, import_react4.useState)(true);
584
- (0, import_react4.useEffect)(() => {
661
+ const [connected, setConnected] = (0, import_react5.useState)(true);
662
+ (0, import_react5.useEffect)(() => {
585
663
  if (!client) {
586
664
  setConnected(false);
587
665
  return;
@@ -591,10 +669,8 @@ function useRealtimeConnection() {
591
669
  return connected;
592
670
  }
593
671
  function useAutoRefresh(object, refetch, options) {
594
- const handleEvent = (0, import_react4.useCallback)((_event) => {
595
- refetch();
596
- }, [refetch]);
597
- useDataSubscriptionCallback(object, handleEvent, options);
672
+ useDataSubscriptionCallback(object, (_event) => refetch(), options);
673
+ useBulkDataSubscriptionCallback(object, (_event) => refetch());
598
674
  }
599
675
 
600
676
  // src/index.tsx
@@ -606,6 +682,8 @@ var import_client = require("@objectstack/client");
606
682
  ObjectStackLocaleContext,
607
683
  ObjectStackProvider,
608
684
  useAutoRefresh,
685
+ useBulkDataSubscription,
686
+ useBulkDataSubscriptionCallback,
609
687
  useClient,
610
688
  useDataSubscription,
611
689
  useDataSubscriptionCallback,