@contember/bindx-react 0.1.52 → 0.1.53

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.
@@ -54,8 +54,10 @@ export type NotFoundEntityResult = UseEntityResultBase & {
54
54
  /**
55
55
  * Ready state — full EntityAccessor with status metadata.
56
56
  *
57
- * `$isRefetching` is `true` while a background re-fetch is in flight
58
- * (triggered by a `queryKey` change while ready data is already present).
57
+ * `$isRefetching` is `true` while the data answers an earlier query: from the re-fetch
58
+ * that a `queryKey` change starts, while ready data is already present, until its result
59
+ * arrives. Unlike `useEntityList`, the flag turns on when the re-fetch starts, one render
60
+ * after the change, not on the render that changes the query.
59
61
  * The accessor identity and field values remain stable until the new data
60
62
  * arrives, so the subtree does not unmount — useful for stale-while-revalidate
61
63
  * indicators (subtle spinner, "stale" badge, etc.).
@@ -1 +1 @@
1
- {"version":3,"file":"useEntity.d.ts","sourceRoot":"","sources":["../../src/hooks/useEntity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,SAAS,EACT,iBAAiB,EAEjB,cAAc,EACd,aAAa,EACb,UAAU,EAIV,YAAY,EACZ,cAAc,EACd,SAAS,EACT,cAAc,EACd,MAAM,kBAAkB,CAAA;AAgBzB;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,sFAAsF;IACtF,EAAE,EAAE,iBAAiB,CAAA;IACrB,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAMD;;;GAGG;AACH,UAAU,mBAAmB;IAC5B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,MAAM,IAAI,IAAI,CAAA;CACd;AAED,MAAM,MAAM,mBAAmB,GAAG,mBAAmB,GAAG;IACvD,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAA;IACzB,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,mBAAmB,GAAG;IACrD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;IACvB,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAA;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG;IACxD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,KAAK,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAA;IAC1B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACnB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,GAAG,OAAO,IACvF,mBAAmB,GAAG,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC,GAAG;IAC1D,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAA;IAC1B,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAA;IACxB,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAA;IAC3B,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAA;CACrB,CAAA;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,OAAO,SAAS,MAAM,EAAE,SAAS,SAAS,MAAM,GAAG,OAAO,IACnF,mBAAmB,GACnB,iBAAiB,GACjB,oBAAoB,GACpB,iBAAiB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAA;AAMxC;;GAEG;AACH,wBAAgB,SAAS,CACxB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,MAAM,SAAS,SAAS,CAAC,QAAQ,CAAC,EAClC,OAAO,SAAS,MAAM,EAEtB,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC3B,OAAO,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,EACxD,OAAO,EAAE,cAAc,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,GAChE,eAAe,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,OAAO,CAAC,CAAA;AAE7D;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,SAAS,MAAM,EACxF,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC3B,OAAO,EAAE,gBAAgB,EACzB,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,GACtD,eAAe,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAA;AAEnD;;;GAGG;AACH,wBAAgB,SAAS,CACxB,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,gBAAgB,GAAG;IAAE,SAAS,EAAE,aAAa,CAAA;CAAE,GACtD,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA"}
1
+ {"version":3,"file":"useEntity.d.ts","sourceRoot":"","sources":["../../src/hooks/useEntity.ts"],"names":[],"mappings":"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"}
@@ -40,8 +40,8 @@ export type ErrorEntityListResult = EntityListResultBase & {
40
40
  readonly $error: FieldError;
41
41
  };
42
42
  /**
43
- * `$isRefetching` is `true` while a background re-fetch is in flight
44
- * (triggered by a `queryKey` change while ready data is already present).
43
+ * `$isRefetching` is `true` while the items answer an earlier query: from the render
44
+ * that changes the options or the `queryKey` until the re-fetch returns.
45
45
  * The accessor identity stays stable so the subtree does not unmount —
46
46
  * stale-while-revalidate semantics.
47
47
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contember/bindx-react",
3
- "version": "0.1.52",
3
+ "version": "0.1.53",
4
4
  "description": "Type-safe React data binding framework",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -15,8 +15,8 @@
15
15
  "react": ">=18.0.0"
16
16
  },
17
17
  "dependencies": {
18
- "@contember/bindx": "0.1.52",
19
- "@contember/bindx-client": "0.1.52",
18
+ "@contember/bindx": "0.1.53",
19
+ "@contember/bindx-client": "0.1.53",
20
20
  "@contember/graphql-client": "^2.1.0-beta.1",
21
21
  "@radix-ui/primitive": "^1.1.3",
22
22
  "@radix-ui/react-slot": "^1.2.4"
@@ -93,8 +93,10 @@ export type NotFoundEntityResult = UseEntityResultBase & {
93
93
  /**
94
94
  * Ready state — full EntityAccessor with status metadata.
95
95
  *
96
- * `$isRefetching` is `true` while a background re-fetch is in flight
97
- * (triggered by a `queryKey` change while ready data is already present).
96
+ * `$isRefetching` is `true` while the data answers an earlier query: from the re-fetch
97
+ * that a `queryKey` change starts, while ready data is already present, until its result
98
+ * arrives. Unlike `useEntityList`, the flag turns on when the re-fetch starts, one render
99
+ * after the change, not on the render that changes the query.
98
100
  * The accessor identity and field values remain stable until the new data
99
101
  * arrives, so the subtree does not unmount — useful for stale-while-revalidate
100
102
  * indicators (subtle spinner, "stale" badge, etc.).
@@ -290,7 +292,8 @@ export function useEntity(
290
292
  if (result.type === 'get' && result.data === null) {
291
293
  dispatcher.dispatch(setLoadState(entityType, id, 'not_found'))
292
294
  } else if (result.type === 'get' && result.data) {
293
- const data = result.data
295
+ store.indexServerResponse(entityType, result.data, selectionMeta, schemaRegistry)
296
+ const data = store.resolveServerOccurrence(result.data)
294
297
  // Revalidation: advance the server baseline but keep local dirty
295
298
  // edits intact (see EntitySnapshotStore.refreshServerData).
296
299
  store.batchNotifications(() => {
@@ -321,7 +324,7 @@ export function useEntity(
321
324
  fetchingRef.current = null
322
325
  }
323
326
  }
324
- }, [entityType, id, byKey, effectiveQueryKey, options.cache, batcher, store, dispatcher, selectionMeta])
327
+ }, [entityType, id, byKey, effectiveQueryKey, options.cache, batcher, store, dispatcher, selectionMeta, schemaRegistry])
325
328
 
326
329
  // --- EntityHandle ---
327
330
  // The handle keeps a stable identity across data changes — it is a stateless live view over the
@@ -58,8 +58,8 @@ export type ErrorEntityListResult = EntityListResultBase & {
58
58
  }
59
59
 
60
60
  /**
61
- * `$isRefetching` is `true` while a background re-fetch is in flight
62
- * (triggered by a `queryKey` change while ready data is already present).
61
+ * `$isRefetching` is `true` while the items answer an earlier query: from the render
62
+ * that changes the options or the `queryKey` until the re-fetch returns.
63
63
  * The accessor identity stays stable so the subtree does not unmount —
64
64
  * stale-while-revalidate semantics.
65
65
  */
@@ -217,16 +217,24 @@ export function useEntityList(
217
217
  return `${entityType}:${selectionContentKey}`
218
218
  }, [options.queryKey, selectionContentKey, entityType])
219
219
 
220
+ const requestKey = useMemo(
221
+ () => JSON.stringify([effectiveQueryKey, optionsKey]),
222
+ [effectiveQueryKey, optionsKey],
223
+ )
224
+
220
225
  // --- List state tracking ---
221
226
  const listStateRef = useRef<{
222
227
  status: 'loading' | 'error' | 'ready'
223
228
  items: Array<{ id: string; data: object }>
224
229
  error?: FieldError
225
230
  isRefetching: boolean
231
+ /** The request the items were loaded for. */
232
+ requestKey: string | null
226
233
  }>({
227
234
  status: 'loading',
228
235
  items: [],
229
236
  isRefetching: false,
237
+ requestKey: null,
230
238
  })
231
239
 
232
240
  const versionRef = useRef(0)
@@ -350,6 +358,9 @@ export function useEntityList(
350
358
  const state = listStateRef.current
351
359
  const version = versionRef.current
352
360
  const storeVersion = store.getVersion()
361
+ // Until the data-loading effect marks the refetch, the items of the previous request are
362
+ // already stale on the render that changed it.
363
+ const isRefetching = state.isRefetching || state.requestKey !== requestKey
353
364
 
354
365
  const cache = listCacheRef.current
355
366
  if (
@@ -357,7 +368,7 @@ export function useEntityList(
357
368
  cache.version === version &&
358
369
  cache.storeVersion === storeVersion &&
359
370
  cache.status === state.status &&
360
- cache.isRefetching === state.isRefetching &&
371
+ cache.isRefetching === isRefetching &&
361
372
  cache.accessorCache === itemAccessorCache
362
373
  ) {
363
374
  return cache.result
@@ -382,7 +393,7 @@ export function useEntityList(
382
393
  result = {
383
394
  $status: 'ready',
384
395
  $isLoading: false,
385
- $isRefetching: state.isRefetching,
396
+ $isRefetching: isRefetching,
386
397
  $isError: false,
387
398
  $error: null,
388
399
  $isDirty: false,
@@ -398,13 +409,13 @@ export function useEntityList(
398
409
  version,
399
410
  storeVersion,
400
411
  status: state.status,
401
- isRefetching: state.isRefetching,
412
+ isRefetching,
402
413
  accessorCache: itemAccessorCache,
403
414
  result,
404
415
  }
405
416
 
406
417
  return result
407
- }, [store, itemAccessorCache, addItem, removeItem, moveItem, resolveItemId])
418
+ }, [store, requestKey, itemAccessorCache, addItem, removeItem, moveItem, resolveItemId])
408
419
 
409
420
  const isEqual = useCallback(
410
421
  (a: UseEntityListResult<any>, b: UseEntityListResult<any>): boolean => {
@@ -429,7 +440,7 @@ export function useEntityList(
429
440
  if (prev.status === 'ready') {
430
441
  listStateRef.current = { ...prev, isRefetching: true }
431
442
  } else {
432
- listStateRef.current = { status: 'loading', items: [], isRefetching: false }
443
+ listStateRef.current = { status: 'loading', items: [], isRefetching: false, requestKey: null }
433
444
  }
434
445
  versionRef.current++
435
446
  store.notify()
@@ -462,13 +473,14 @@ export function useEntityList(
462
473
  throw new Error('Unexpected query result type')
463
474
  }
464
475
 
465
- const items = result.data.map(data => ({ id: getRowId(entityType, data), data }))
476
+ store.indexServerResponse(entityType, result.data, selectionMeta, schemaRegistry)
477
+ const items = result.data.map(data => ({ id: getRowId(entityType, data), data: store.resolveServerOccurrence(data) }))
466
478
  store.batchNotifications(() => {
467
479
  for (const item of items) {
468
480
  // Revalidation preserves local edits while advancing the server baseline.
469
481
  dispatcher.dispatch(refreshServerData(entityType, item.id, item.data))
470
482
  }
471
- listStateRef.current = { status: 'ready', items, isRefetching: false }
483
+ listStateRef.current = { status: 'ready', items, isRefetching: false, requestKey }
472
484
  versionRef.current++
473
485
  store.notify()
474
486
  })
@@ -481,6 +493,7 @@ export function useEntityList(
481
493
  items: [],
482
494
  error: createLoadError(normalizedError),
483
495
  isRefetching: false,
496
+ requestKey,
484
497
  }
485
498
  versionRef.current++
486
499
  store.notify()
@@ -492,7 +505,7 @@ export function useEntityList(
492
505
  return () => {
493
506
  abortController.abort()
494
507
  }
495
- }, [entityType, optionsKey, effectiveQueryKey, batcher, dispatcher, store, selectionMeta])
508
+ }, [entityType, optionsKey, requestKey, batcher, dispatcher, store, selectionMeta, schemaRegistry])
496
509
 
497
510
  return accessor
498
511
  }