@contember/bindx-react 0.1.52 → 0.1.54
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
|
|
58
|
-
*
|
|
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
|
|
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;;;;;;;;;;GAUG;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"}
|
|
@@ -40,8 +40,8 @@ export type ErrorEntityListResult = EntityListResultBase & {
|
|
|
40
40
|
readonly $error: FieldError;
|
|
41
41
|
};
|
|
42
42
|
/**
|
|
43
|
-
* `$isRefetching` is `true` while
|
|
44
|
-
*
|
|
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.
|
|
3
|
+
"version": "0.1.54",
|
|
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.
|
|
19
|
-
"@contember/bindx-client": "0.1.
|
|
18
|
+
"@contember/bindx": "0.1.54",
|
|
19
|
+
"@contember/bindx-client": "0.1.54",
|
|
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"
|
package/src/hooks/useEntity.ts
CHANGED
|
@@ -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
|
|
97
|
-
*
|
|
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
|
-
|
|
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
|
|
62
|
-
*
|
|
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 ===
|
|
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:
|
|
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
|
|
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
|
-
|
|
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,
|
|
508
|
+
}, [entityType, optionsKey, requestKey, batcher, dispatcher, store, selectionMeta, schemaRegistry])
|
|
496
509
|
|
|
497
510
|
return accessor
|
|
498
511
|
}
|