@contember/bindx-react 0.1.4 → 0.1.10
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/hooks/ContemberBindxProvider.d.ts +25 -30
- package/dist/hooks/ContemberBindxProvider.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/useEntity.d.ts +43 -75
- package/dist/hooks/useEntity.d.ts.map +1 -1
- package/dist/hooks/useEntityList.d.ts +28 -47
- package/dist/hooks/useEntityList.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/jsx/collectorProxy.d.ts.map +1 -1
- package/dist/jsx/components/Entity.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/hooks/ContemberBindxProvider.tsx +31 -37
- package/src/hooks/index.ts +9 -9
- package/src/hooks/useEntity.ts +106 -217
- package/src/hooks/useEntityList.ts +61 -100
- package/src/index.ts +9 -9
- package/src/jsx/collectorProxy.ts +2 -0
- package/src/jsx/components/Entity.tsx +15 -9
- package/src/jsx/components/EntityList.tsx +5 -5
package/src/hooks/useEntity.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
CommonEntity,
|
|
13
13
|
EntityForRoles,
|
|
14
14
|
RoleNames,
|
|
15
|
+
EntityAccessor,
|
|
15
16
|
} from '@contember/bindx'
|
|
16
17
|
import {
|
|
17
18
|
EntityHandle,
|
|
@@ -51,165 +52,62 @@ export interface UseEntityOptions {
|
|
|
51
52
|
// ============================================================================
|
|
52
53
|
|
|
53
54
|
/**
|
|
54
|
-
*
|
|
55
|
+
* Common properties on every UseEntityResult state.
|
|
56
|
+
* All prefixed with $ to avoid collision with entity field names.
|
|
55
57
|
*/
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
readonly isError: false
|
|
60
|
-
readonly isNotFound: false
|
|
61
|
-
readonly isPersisting: false
|
|
62
|
-
readonly isDirty: false
|
|
63
|
-
readonly id: string
|
|
64
|
-
readonly fields: never
|
|
65
|
-
readonly data: never
|
|
66
|
-
persist(): Promise<void>
|
|
67
|
-
reset(): void
|
|
58
|
+
interface UseEntityResultBase {
|
|
59
|
+
$persist(): Promise<void>
|
|
60
|
+
$reset(): void
|
|
68
61
|
}
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
readonly
|
|
75
|
-
readonly
|
|
76
|
-
readonly isError: true
|
|
77
|
-
readonly isNotFound: false
|
|
78
|
-
readonly error: FieldError
|
|
79
|
-
readonly isPersisting: false
|
|
80
|
-
readonly isDirty: false
|
|
63
|
+
export type LoadingEntityResult = UseEntityResultBase & {
|
|
64
|
+
readonly $status: 'loading'
|
|
65
|
+
readonly $isLoading: true
|
|
66
|
+
readonly $isError: false
|
|
67
|
+
readonly $isNotFound: false
|
|
68
|
+
readonly $error: null
|
|
81
69
|
readonly id: string
|
|
82
|
-
readonly fields: never
|
|
83
|
-
readonly data: never
|
|
84
|
-
persist(): Promise<void>
|
|
85
|
-
reset(): void
|
|
86
70
|
}
|
|
87
71
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
readonly
|
|
93
|
-
readonly
|
|
94
|
-
readonly isError: false
|
|
95
|
-
readonly isNotFound: true
|
|
96
|
-
readonly isPersisting: false
|
|
97
|
-
readonly isDirty: false
|
|
72
|
+
export type ErrorEntityResult = UseEntityResultBase & {
|
|
73
|
+
readonly $status: 'error'
|
|
74
|
+
readonly $isLoading: false
|
|
75
|
+
readonly $isError: true
|
|
76
|
+
readonly $isNotFound: false
|
|
77
|
+
readonly $error: FieldError
|
|
98
78
|
readonly id: string
|
|
99
|
-
readonly fields: never
|
|
100
|
-
readonly data: never
|
|
101
|
-
persist(): Promise<void>
|
|
102
|
-
reset(): void
|
|
103
79
|
}
|
|
104
80
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
readonly
|
|
110
|
-
readonly
|
|
111
|
-
readonly isError: false
|
|
112
|
-
readonly isNotFound: false
|
|
113
|
-
readonly isPersisting: boolean
|
|
114
|
-
readonly isDirty: boolean
|
|
81
|
+
export type NotFoundEntityResult = UseEntityResultBase & {
|
|
82
|
+
readonly $status: 'not_found'
|
|
83
|
+
readonly $isLoading: false
|
|
84
|
+
readonly $isError: false
|
|
85
|
+
readonly $isNotFound: true
|
|
86
|
+
readonly $error: null
|
|
115
87
|
readonly id: string
|
|
116
|
-
readonly fields: SelectedEntityFields<TEntity, TSelected>
|
|
117
|
-
readonly data: TSelected
|
|
118
|
-
persist(): Promise<void>
|
|
119
|
-
reset(): void
|
|
120
88
|
}
|
|
121
89
|
|
|
122
90
|
/**
|
|
123
|
-
* Ready state
|
|
91
|
+
* Ready state — full EntityAccessor with status metadata.
|
|
124
92
|
*/
|
|
125
|
-
export type
|
|
126
|
-
|
|
93
|
+
export type ReadyEntityResult<TEntity extends object, TSelected extends object = TEntity> =
|
|
94
|
+
UseEntityResultBase & EntityAccessor<TEntity, TSelected> & {
|
|
95
|
+
readonly $status: 'ready'
|
|
96
|
+
readonly $isLoading: false
|
|
97
|
+
readonly $isError: false
|
|
98
|
+
readonly $isNotFound: false
|
|
99
|
+
readonly $error: null
|
|
100
|
+
}
|
|
127
101
|
|
|
128
102
|
/**
|
|
129
|
-
* Union of all
|
|
103
|
+
* Union of all useEntity return states.
|
|
104
|
+
* Discriminated on $status.
|
|
130
105
|
*/
|
|
131
|
-
export type
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
137
|
-
// ============================================================================
|
|
138
|
-
// Internal helpers
|
|
139
|
-
// ============================================================================
|
|
140
|
-
|
|
141
|
-
function createLoadingAccessor(id: string): LoadingEntityAccessor {
|
|
142
|
-
return {
|
|
143
|
-
status: 'loading',
|
|
144
|
-
isLoading: true,
|
|
145
|
-
isError: false,
|
|
146
|
-
isNotFound: false,
|
|
147
|
-
isPersisting: false,
|
|
148
|
-
isDirty: false,
|
|
149
|
-
id,
|
|
150
|
-
get fields(): never {
|
|
151
|
-
throw new Error('Cannot access fields while loading')
|
|
152
|
-
},
|
|
153
|
-
get data(): never {
|
|
154
|
-
throw new Error('Cannot access data while loading')
|
|
155
|
-
},
|
|
156
|
-
async persist() {
|
|
157
|
-
// No-op while loading
|
|
158
|
-
},
|
|
159
|
-
reset() {
|
|
160
|
-
// No-op while loading
|
|
161
|
-
},
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function createErrorAccessor(id: string, error: FieldError): ErrorEntityAccessor {
|
|
166
|
-
return {
|
|
167
|
-
status: 'error',
|
|
168
|
-
isLoading: false,
|
|
169
|
-
isError: true,
|
|
170
|
-
isNotFound: false,
|
|
171
|
-
error,
|
|
172
|
-
isPersisting: false,
|
|
173
|
-
isDirty: false,
|
|
174
|
-
id,
|
|
175
|
-
get fields(): never {
|
|
176
|
-
throw new Error('Cannot access fields after error')
|
|
177
|
-
},
|
|
178
|
-
get data(): never {
|
|
179
|
-
throw new Error('Cannot access data after error')
|
|
180
|
-
},
|
|
181
|
-
async persist() {
|
|
182
|
-
// No-op after error
|
|
183
|
-
},
|
|
184
|
-
reset() {
|
|
185
|
-
// No-op after error
|
|
186
|
-
},
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function createNotFoundAccessor(id: string): NotFoundEntityAccessor {
|
|
191
|
-
return {
|
|
192
|
-
status: 'not_found',
|
|
193
|
-
isLoading: false,
|
|
194
|
-
isError: false,
|
|
195
|
-
isNotFound: true,
|
|
196
|
-
isPersisting: false,
|
|
197
|
-
isDirty: false,
|
|
198
|
-
id,
|
|
199
|
-
get fields(): never {
|
|
200
|
-
throw new Error('Cannot access fields — entity not found')
|
|
201
|
-
},
|
|
202
|
-
get data(): never {
|
|
203
|
-
throw new Error('Cannot access data — entity not found')
|
|
204
|
-
},
|
|
205
|
-
async persist() {
|
|
206
|
-
// No-op for not found
|
|
207
|
-
},
|
|
208
|
-
reset() {
|
|
209
|
-
// No-op for not found
|
|
210
|
-
},
|
|
211
|
-
}
|
|
212
|
-
}
|
|
106
|
+
export type UseEntityResult<TEntity extends object, TSelected extends object = TEntity> =
|
|
107
|
+
| LoadingEntityResult
|
|
108
|
+
| ErrorEntityResult
|
|
109
|
+
| NotFoundEntityResult
|
|
110
|
+
| ReadyEntityResult<TEntity, TSelected>
|
|
213
111
|
|
|
214
112
|
// ============================================================================
|
|
215
113
|
// Hook overloads
|
|
@@ -217,11 +115,6 @@ function createNotFoundAccessor(id: string): NotFoundEntityAccessor {
|
|
|
217
115
|
|
|
218
116
|
/**
|
|
219
117
|
* Hook to fetch and manage a single entity with role-expanded type inference.
|
|
220
|
-
*
|
|
221
|
-
* @example
|
|
222
|
-
* ```tsx
|
|
223
|
-
* const article = useEntity(schema.Article, { by: { id }, roles: ['admin'] }, e => e.title().internalNotes())
|
|
224
|
-
* ```
|
|
225
118
|
*/
|
|
226
119
|
export function useEntity<
|
|
227
120
|
TRoleMap extends Record<string, object>,
|
|
@@ -231,16 +124,15 @@ export function useEntity<
|
|
|
231
124
|
entity: EntityDef<TRoleMap>,
|
|
232
125
|
options: UseEntityOptions & { roles: readonly TRoles[] },
|
|
233
126
|
definer: SelectionInput<EntityForRoles<TRoleMap, TRoles>, TResult>,
|
|
234
|
-
):
|
|
127
|
+
): UseEntityResult<EntityForRoles<TRoleMap, TRoles>, TResult>
|
|
235
128
|
|
|
236
129
|
/**
|
|
237
130
|
* Hook to fetch and manage a single entity with full type inference.
|
|
238
|
-
* Uses the common (narrowest) entity type when no roles are specified.
|
|
239
131
|
*
|
|
240
132
|
* @example
|
|
241
133
|
* ```tsx
|
|
242
134
|
* const article = useEntity(schema.Article, { by: { id } }, e => e.title().content())
|
|
243
|
-
* if (article
|
|
135
|
+
* if (article.$status !== 'ready') return <Loading />
|
|
244
136
|
* return <input value={article.title.value} onChange={...} />
|
|
245
137
|
* ```
|
|
246
138
|
*/
|
|
@@ -248,31 +140,26 @@ export function useEntity<TRoleMap extends Record<string, object>, TResult exten
|
|
|
248
140
|
entity: EntityDef<TRoleMap>,
|
|
249
141
|
options: UseEntityOptions,
|
|
250
142
|
definer: SelectionInput<CommonEntity<TRoleMap>, TResult>,
|
|
251
|
-
):
|
|
143
|
+
): UseEntityResult<CommonEntity<TRoleMap>, TResult>
|
|
252
144
|
|
|
253
145
|
/**
|
|
254
146
|
* Hook to fetch and manage a single entity with pre-resolved selection.
|
|
255
|
-
*
|
|
256
147
|
* Used internally by Entity component that collects selection from JSX.
|
|
257
148
|
*/
|
|
258
149
|
export function useEntity(
|
|
259
150
|
entity: EntityDef,
|
|
260
151
|
options: UseEntityOptions & { selection: SelectionMeta },
|
|
261
|
-
):
|
|
152
|
+
): UseEntityResult<object, object>
|
|
262
153
|
|
|
263
154
|
// ============================================================================
|
|
264
155
|
// Implementation
|
|
265
156
|
// ============================================================================
|
|
266
157
|
|
|
267
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
268
158
|
export function useEntity(
|
|
269
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
270
159
|
entity: EntityDef<any>,
|
|
271
160
|
options: UseEntityOptions,
|
|
272
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
273
161
|
definer?: SelectionInput<any, any>,
|
|
274
|
-
|
|
275
|
-
): EntityAccessorResult<any, any> {
|
|
162
|
+
): UseEntityResult<any, any> {
|
|
276
163
|
const schemaRegistry = useSchemaRegistry()
|
|
277
164
|
const entityType = entity.$name
|
|
278
165
|
const { store, dispatcher, batcher, batchPersister } = useBindxContext()
|
|
@@ -409,81 +296,83 @@ export function useEntity(
|
|
|
409
296
|
}, [entityType, id, byKey, effectiveQueryKey, options.cache, batcher, store, dispatcher, selectionMeta])
|
|
410
297
|
|
|
411
298
|
// --- EntityHandle ---
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const handle = useMemo(
|
|
415
|
-
() => EntityHandle.create(id, entityType, store, dispatcher, schemaRegistry as SchemaRegistry<Record<string, object>>),
|
|
299
|
+
const rawHandle = useMemo(
|
|
300
|
+
() => EntityHandle.createRaw(id, entityType, store, dispatcher, schemaRegistry as SchemaRegistry<Record<string, object>>),
|
|
416
301
|
[id, entityType, store, dispatcher, schemaRegistry, snapshot],
|
|
417
302
|
)
|
|
418
303
|
|
|
304
|
+
const handle = useMemo(
|
|
305
|
+
() => EntityHandle.wrapProxy(rawHandle),
|
|
306
|
+
[rawHandle],
|
|
307
|
+
)
|
|
308
|
+
|
|
419
309
|
useEffect(() => {
|
|
420
310
|
return () => {
|
|
421
|
-
|
|
311
|
+
rawHandle.dispose()
|
|
422
312
|
}
|
|
423
|
-
}, [
|
|
313
|
+
}, [rawHandle])
|
|
314
|
+
|
|
315
|
+
// --- Persist & reset callbacks ---
|
|
316
|
+
const persist = useCallback(async () => {
|
|
317
|
+
await batchPersister.persist(entityType, id)
|
|
318
|
+
}, [batchPersister, entityType, id])
|
|
424
319
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
320
|
+
const reset = useCallback(() => {
|
|
321
|
+
rawHandle.reset()
|
|
322
|
+
}, [rawHandle])
|
|
323
|
+
|
|
324
|
+
// --- Build result ---
|
|
325
|
+
const result = useMemo((): UseEntityResult<any, any> => {
|
|
326
|
+
if (!loadState || loadState.status === 'loading' || (!snapshot && loadState.status === 'success')) {
|
|
327
|
+
return { $status: 'loading', $isLoading: true, $isError: false, $isNotFound: false, $error: null, id, $persist: persist, $reset: reset }
|
|
430
328
|
}
|
|
431
329
|
|
|
432
330
|
if (loadState.status === 'error') {
|
|
433
|
-
return
|
|
331
|
+
return { $status: 'error', $isLoading: false, $isError: true, $isNotFound: false, $error: loadState.error!, id, $persist: persist, $reset: reset }
|
|
434
332
|
}
|
|
435
333
|
|
|
436
334
|
if (loadState.status === 'not_found') {
|
|
437
|
-
return
|
|
335
|
+
return { $status: 'not_found', $isLoading: false, $isError: false, $isNotFound: true, $error: null, id, $persist: persist, $reset: reset }
|
|
438
336
|
}
|
|
439
337
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
338
|
+
// Ready — layer status metadata on top of EntityHandle proxy
|
|
339
|
+
return createReadyResult(handle, persist, reset)
|
|
340
|
+
}, [snapshot, loadState, isPersisting, id, handle, persist, reset])
|
|
443
341
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
448
|
-
const baseAccessor: ReadyEntityAccessorBase<any, any> = {
|
|
449
|
-
status: 'ready',
|
|
450
|
-
isLoading: false,
|
|
451
|
-
isError: false,
|
|
452
|
-
isNotFound: false,
|
|
453
|
-
isPersisting,
|
|
454
|
-
get isDirty() {
|
|
455
|
-
return handle.$isDirty
|
|
456
|
-
},
|
|
457
|
-
id: realId,
|
|
458
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
459
|
-
fields: handle.$fields as any,
|
|
460
|
-
data: snapshot.data,
|
|
461
|
-
async persist() {
|
|
462
|
-
await batchPersister.persist(entityType, realId)
|
|
463
|
-
},
|
|
464
|
-
reset() {
|
|
465
|
-
handle.reset()
|
|
466
|
-
},
|
|
467
|
-
}
|
|
342
|
+
return result
|
|
343
|
+
}
|
|
468
344
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
}
|
|
345
|
+
// ============================================================================
|
|
346
|
+
// Result factories
|
|
347
|
+
// ============================================================================
|
|
348
|
+
|
|
349
|
+
function createReadyResult(
|
|
350
|
+
handle: EntityAccessor<object, object>,
|
|
351
|
+
persist: () => Promise<void>,
|
|
352
|
+
reset: () => void,
|
|
353
|
+
): ReadyEntityResult<any, any> {
|
|
354
|
+
const meta = {
|
|
355
|
+
$status: 'ready' as const,
|
|
356
|
+
$isLoading: false as const,
|
|
357
|
+
$isError: false as const,
|
|
358
|
+
$isNotFound: false as const,
|
|
359
|
+
$error: null,
|
|
360
|
+
$persist: persist,
|
|
361
|
+
$reset: reset,
|
|
362
|
+
}
|
|
487
363
|
|
|
488
|
-
return
|
|
364
|
+
return new Proxy(handle as any, {
|
|
365
|
+
get(target, prop, receiver) {
|
|
366
|
+
if (typeof prop === 'string' && prop in meta) {
|
|
367
|
+
return (meta as any)[prop]
|
|
368
|
+
}
|
|
369
|
+
return Reflect.get(target, prop, receiver)
|
|
370
|
+
},
|
|
371
|
+
has(target, prop) {
|
|
372
|
+
if (typeof prop === 'string' && prop in meta) {
|
|
373
|
+
return true
|
|
374
|
+
}
|
|
375
|
+
return Reflect.has(target, prop)
|
|
376
|
+
},
|
|
377
|
+
}) as any
|
|
489
378
|
}
|
|
@@ -34,106 +34,66 @@ export interface UseEntityListOptions {
|
|
|
34
34
|
// Result types
|
|
35
35
|
// ============================================================================
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
readonly status: 'loading'
|
|
42
|
-
readonly isLoading: true
|
|
43
|
-
readonly isError: false
|
|
44
|
-
readonly isDirty: false
|
|
45
|
-
readonly items: never
|
|
46
|
-
readonly length: 0
|
|
47
|
-
add(data?: unknown): string
|
|
48
|
-
remove(key: string): void
|
|
49
|
-
move(fromIndex: number, toIndex: number): void
|
|
37
|
+
interface EntityListResultBase {
|
|
38
|
+
$add(data?: unknown): string
|
|
39
|
+
$remove(key: string): void
|
|
40
|
+
$move(fromIndex: number, toIndex: number): void
|
|
50
41
|
}
|
|
51
42
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
readonly
|
|
57
|
-
readonly isLoading: false
|
|
58
|
-
readonly isError: true
|
|
59
|
-
readonly error: FieldError
|
|
60
|
-
readonly isDirty: false
|
|
61
|
-
readonly items: never
|
|
62
|
-
readonly length: 0
|
|
63
|
-
add(data?: unknown): string
|
|
64
|
-
remove(key: string): void
|
|
65
|
-
move(fromIndex: number, toIndex: number): void
|
|
43
|
+
export type LoadingEntityListResult = EntityListResultBase & {
|
|
44
|
+
readonly $status: 'loading'
|
|
45
|
+
readonly $isLoading: true
|
|
46
|
+
readonly $isError: false
|
|
47
|
+
readonly $error: null
|
|
66
48
|
}
|
|
67
49
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
readonly
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
50
|
+
export type ErrorEntityListResult = EntityListResultBase & {
|
|
51
|
+
readonly $status: 'error'
|
|
52
|
+
readonly $isLoading: false
|
|
53
|
+
readonly $isError: true
|
|
54
|
+
readonly $error: FieldError
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export type ReadyEntityListResult<T extends object> = EntityListResultBase & {
|
|
58
|
+
readonly $status: 'ready'
|
|
59
|
+
readonly $isLoading: false
|
|
60
|
+
readonly $isError: false
|
|
61
|
+
readonly $error: null
|
|
62
|
+
readonly $isDirty: boolean
|
|
76
63
|
readonly items: Array<EntityAccessor<T>>
|
|
77
64
|
readonly length: number
|
|
78
|
-
add(data?: Partial<T>): string
|
|
79
|
-
remove(key: string): void
|
|
80
|
-
move(fromIndex: number, toIndex: number): void
|
|
81
65
|
}
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
| LoadingEntityListAccessor
|
|
88
|
-
| ErrorEntityListAccessor
|
|
89
|
-
| ReadyEntityListAccessor<T>
|
|
67
|
+
export type UseEntityListResult<T extends object> =
|
|
68
|
+
| LoadingEntityListResult
|
|
69
|
+
| ErrorEntityListResult
|
|
70
|
+
| ReadyEntityListResult<T>
|
|
90
71
|
|
|
91
72
|
// ============================================================================
|
|
92
73
|
// Internal helpers
|
|
93
74
|
// ============================================================================
|
|
94
75
|
|
|
95
|
-
function
|
|
76
|
+
function createLoadingListResult(): LoadingEntityListResult {
|
|
96
77
|
return {
|
|
97
|
-
status: 'loading',
|
|
98
|
-
isLoading: true,
|
|
99
|
-
isError: false,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
length: 0,
|
|
105
|
-
add() {
|
|
106
|
-
throw new Error('Cannot add items while loading')
|
|
107
|
-
},
|
|
108
|
-
remove() {
|
|
109
|
-
throw new Error('Cannot remove items while loading')
|
|
110
|
-
},
|
|
111
|
-
move() {
|
|
112
|
-
throw new Error('Cannot move items while loading')
|
|
113
|
-
},
|
|
78
|
+
$status: 'loading',
|
|
79
|
+
$isLoading: true,
|
|
80
|
+
$isError: false,
|
|
81
|
+
$error: null,
|
|
82
|
+
$add() { throw new Error('Cannot add items while loading') },
|
|
83
|
+
$remove() { throw new Error('Cannot remove items while loading') },
|
|
84
|
+
$move() { throw new Error('Cannot move items while loading') },
|
|
114
85
|
}
|
|
115
86
|
}
|
|
116
87
|
|
|
117
|
-
function
|
|
88
|
+
function createErrorListResult(error: FieldError): ErrorEntityListResult {
|
|
118
89
|
return {
|
|
119
|
-
status: 'error',
|
|
120
|
-
isLoading: false,
|
|
121
|
-
isError: true,
|
|
122
|
-
error,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
},
|
|
127
|
-
length: 0,
|
|
128
|
-
add() {
|
|
129
|
-
throw new Error('Cannot add items after error')
|
|
130
|
-
},
|
|
131
|
-
remove() {
|
|
132
|
-
throw new Error('Cannot remove items after error')
|
|
133
|
-
},
|
|
134
|
-
move() {
|
|
135
|
-
throw new Error('Cannot move items after error')
|
|
136
|
-
},
|
|
90
|
+
$status: 'error',
|
|
91
|
+
$isLoading: false,
|
|
92
|
+
$isError: true,
|
|
93
|
+
$error: error,
|
|
94
|
+
$add() { throw new Error('Cannot add items after error') },
|
|
95
|
+
$remove() { throw new Error('Cannot remove items after error') },
|
|
96
|
+
$move() { throw new Error('Cannot move items after error') },
|
|
137
97
|
}
|
|
138
98
|
}
|
|
139
99
|
|
|
@@ -157,7 +117,7 @@ export function useEntityList<
|
|
|
157
117
|
entity: EntityDef<TRoleMap>,
|
|
158
118
|
options: UseEntityListOptions & { roles: readonly TRoles[] },
|
|
159
119
|
definer: SelectionInput<EntityForRoles<TRoleMap, TRoles>, TResult>,
|
|
160
|
-
):
|
|
120
|
+
): UseEntityListResult<TResult>
|
|
161
121
|
|
|
162
122
|
/**
|
|
163
123
|
* Hook to fetch and manage a list of entities with full type inference.
|
|
@@ -174,7 +134,7 @@ export function useEntityList<TRoleMap extends Record<string, object>, TResult e
|
|
|
174
134
|
entity: EntityDef<TRoleMap>,
|
|
175
135
|
options: UseEntityListOptions,
|
|
176
136
|
definer: SelectionInput<CommonEntity<TRoleMap>, TResult>,
|
|
177
|
-
):
|
|
137
|
+
): UseEntityListResult<TResult>
|
|
178
138
|
|
|
179
139
|
/**
|
|
180
140
|
* Hook to fetch and manage a list of entities with pre-resolved selection.
|
|
@@ -185,7 +145,7 @@ export function useEntityList<TRoleMap extends Record<string, object>, TResult e
|
|
|
185
145
|
export function useEntityList(
|
|
186
146
|
entity: EntityDef,
|
|
187
147
|
options: UseEntityListOptions & { selection: SelectionMeta },
|
|
188
|
-
):
|
|
148
|
+
): UseEntityListResult<object>
|
|
189
149
|
|
|
190
150
|
// ============================================================================
|
|
191
151
|
// Implementation
|
|
@@ -197,7 +157,7 @@ export function useEntityList(
|
|
|
197
157
|
options: UseEntityListOptions,
|
|
198
158
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
199
159
|
definer?: SelectionInput<any, any>,
|
|
200
|
-
):
|
|
160
|
+
): UseEntityListResult<any> {
|
|
201
161
|
const schemaRegistry = useSchemaRegistry()
|
|
202
162
|
const entityType = entity.$name
|
|
203
163
|
const { store, dispatcher, batcher } = useBindxContext()
|
|
@@ -250,7 +210,7 @@ export function useEntityList(
|
|
|
250
210
|
version: number
|
|
251
211
|
storeVersion: number
|
|
252
212
|
status: string
|
|
253
|
-
result:
|
|
213
|
+
result: UseEntityListResult<any>
|
|
254
214
|
} | null>(null)
|
|
255
215
|
|
|
256
216
|
// --- Store subscription ---
|
|
@@ -310,7 +270,7 @@ export function useEntityList(
|
|
|
310
270
|
)
|
|
311
271
|
|
|
312
272
|
// --- Snapshot ---
|
|
313
|
-
const getSnapshot = useCallback(():
|
|
273
|
+
const getSnapshot = useCallback((): UseEntityListResult<any> => {
|
|
314
274
|
const state = listStateRef.current
|
|
315
275
|
const version = versionRef.current
|
|
316
276
|
const storeVersion = store.getVersion()
|
|
@@ -320,12 +280,12 @@ export function useEntityList(
|
|
|
320
280
|
return cache.result
|
|
321
281
|
}
|
|
322
282
|
|
|
323
|
-
let result:
|
|
283
|
+
let result: UseEntityListResult<any>
|
|
324
284
|
|
|
325
285
|
if (state.status === 'loading') {
|
|
326
|
-
result =
|
|
286
|
+
result = createLoadingListResult()
|
|
327
287
|
} else if (state.status === 'error') {
|
|
328
|
-
result =
|
|
288
|
+
result = createErrorListResult(state.error!)
|
|
329
289
|
} else {
|
|
330
290
|
const items = state.items.map((item) => {
|
|
331
291
|
return EntityHandle.create<object>(
|
|
@@ -334,19 +294,20 @@ export function useEntityList(
|
|
|
334
294
|
store,
|
|
335
295
|
dispatcher,
|
|
336
296
|
schemaRegistry as SchemaRegistry<Record<string, object>>,
|
|
337
|
-
) as unknown as EntityAccessor<
|
|
297
|
+
) as unknown as EntityAccessor<any>
|
|
338
298
|
})
|
|
339
299
|
|
|
340
300
|
result = {
|
|
341
|
-
status: 'ready',
|
|
342
|
-
isLoading: false,
|
|
343
|
-
isError: false,
|
|
344
|
-
|
|
301
|
+
$status: 'ready',
|
|
302
|
+
$isLoading: false,
|
|
303
|
+
$isError: false,
|
|
304
|
+
$error: null,
|
|
305
|
+
$isDirty: false,
|
|
345
306
|
items,
|
|
346
307
|
length: items.length,
|
|
347
|
-
add: addItem,
|
|
348
|
-
remove: removeItem,
|
|
349
|
-
move: moveItem,
|
|
308
|
+
$add: addItem,
|
|
309
|
+
$remove: removeItem,
|
|
310
|
+
$move: moveItem,
|
|
350
311
|
}
|
|
351
312
|
}
|
|
352
313
|
|
|
@@ -361,7 +322,7 @@ export function useEntityList(
|
|
|
361
322
|
}, [entityType, store, dispatcher, schemaRegistry, addItem, removeItem, moveItem])
|
|
362
323
|
|
|
363
324
|
const isEqual = useCallback(
|
|
364
|
-
(a:
|
|
325
|
+
(a: UseEntityListResult<any>, b: UseEntityListResult<any>): boolean => {
|
|
365
326
|
return a === b
|
|
366
327
|
},
|
|
367
328
|
[],
|