teamplay 0.4.0-alpha.8 → 0.4.0-alpha.80
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/README.md +12 -0
- package/index.d.ts +2 -0
- package/index.js +1 -0
- package/orm/Compat/README.md +221 -25
- package/orm/Compat/SignalCompat.js +535 -85
- package/orm/Compat/eventsCompat.js +2 -1
- package/orm/Compat/hooksCompat.js +108 -22
- package/orm/Compat/modelEvents.js +5 -0
- package/orm/Compat/queryReadiness.js +165 -0
- package/orm/Compat/refFallback.js +61 -0
- package/orm/Compat/refRegistry.js +11 -3
- package/orm/Compat/silentContext.js +51 -0
- package/orm/Compat/startStopCompat.js +168 -0
- package/orm/Doc.js +232 -32
- package/orm/Query.js +198 -23
- package/orm/Reaction.js +2 -1
- package/orm/SignalBase.js +82 -16
- package/orm/associations.js +97 -0
- package/orm/batchScheduler.js +62 -0
- package/orm/dataTree.js +262 -34
- package/orm/getSignal.js +19 -2
- package/orm/idFields.js +33 -2
- package/orm/index.d.ts +6 -0
- package/orm/index.js +5 -0
- package/orm/missingDoc.js +3 -0
- package/orm/subscriptionGcDelay.js +32 -0
- package/package.json +10 -8
- package/react/compatComponentRegistry.js +20 -0
- package/react/convertToObserver.js +19 -2
- package/react/helpers.js +4 -2
- package/react/promiseBatcher.js +115 -0
- package/react/renderAttemptDestroyer.js +37 -0
- package/react/trapRender.js +21 -15
- package/react/useSub.js +48 -3
- package/react/wrapIntoSuspense.js +1 -1
- package/utils/setDiffDeep.js +32 -12
package/README.md
CHANGED
|
@@ -19,6 +19,18 @@ Features:
|
|
|
19
19
|
|
|
20
20
|
For installation and documentation see [teamplay.dev](https://teamplay.dev)
|
|
21
21
|
|
|
22
|
+
## ORM Compat Helpers
|
|
23
|
+
|
|
24
|
+
For legacy Racer-style model mixins (for example versioning libraries which call
|
|
25
|
+
`getAssociations()`), use ORM compat helpers from the `teamplay/orm` subpath:
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import BaseModel, { hasMany, hasOne, belongsTo } from 'teamplay/orm'
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
These helpers attach class-level associations and expose them through
|
|
32
|
+
`$doc.getAssociations()` on model signals.
|
|
33
|
+
|
|
22
34
|
## License
|
|
23
35
|
|
|
24
36
|
MIT
|
package/index.d.ts
CHANGED
|
@@ -94,6 +94,8 @@ export function useDidUpdate (fn: () => EffectCleanup, deps?: any[]): void
|
|
|
94
94
|
export function useOnce (condition: any, fn: () => EffectCleanup): void
|
|
95
95
|
export function useSyncEffect (fn: () => EffectCleanup, deps?: any[]): void
|
|
96
96
|
export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js'
|
|
97
|
+
export function getSubscriptionGcDelay (): number
|
|
98
|
+
export function setSubscriptionGcDelay (ms?: number | null): number
|
|
97
99
|
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
|
|
98
100
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
99
101
|
export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation'
|
package/index.js
CHANGED
|
@@ -66,6 +66,7 @@ export {
|
|
|
66
66
|
useSyncEffect
|
|
67
67
|
} from './react/helpers.js'
|
|
68
68
|
export { connection, setConnection, getConnection, fetchOnly, setFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js'
|
|
69
|
+
export { getSubscriptionGcDelay, setSubscriptionGcDelay } from './orm/subscriptionGcDelay.js'
|
|
69
70
|
export { useId, useNow, useScheduleUpdate, useTriggerUpdate } from './react/helpers.js'
|
|
70
71
|
export { GUID_PATTERN, hasMany, hasOne, hasManyFlags, belongsTo, pickFormFields } from '@teamplay/schema'
|
|
71
72
|
export { aggregation, aggregationHeader as __aggregationHeader } from '@teamplay/utils/aggregation'
|
package/orm/Compat/README.md
CHANGED
|
@@ -94,9 +94,11 @@ $.users.user1.name.parent(2) // $.users
|
|
|
94
94
|
Legacy path navigation. Accepts:
|
|
95
95
|
- string with dot path (`'a.b.c'`)
|
|
96
96
|
- integer index for arrays (`0`)
|
|
97
|
+
- multiple path segments (`'a', 'b', 0`)
|
|
97
98
|
|
|
98
99
|
```js
|
|
99
100
|
$.users.user1.at('profile.name')
|
|
101
|
+
$.users.user1.at('profile', 'name')
|
|
100
102
|
$.items.at(0)
|
|
101
103
|
```
|
|
102
104
|
|
|
@@ -106,6 +108,7 @@ Resolve a path from root, ignoring the current signal path.
|
|
|
106
108
|
|
|
107
109
|
```js
|
|
108
110
|
$.users.user1.scope('users.user2')
|
|
111
|
+
$.users.user1.scope('users', 'user2')
|
|
109
112
|
```
|
|
110
113
|
|
|
111
114
|
### ref(target) / ref(subpath, target)
|
|
@@ -113,6 +116,8 @@ $.users.user1.scope('users.user2')
|
|
|
113
116
|
Creates a lightweight alias between signals (minimal Racer-style ref).
|
|
114
117
|
Mutations on the alias are forwarded to the target. The alias mirrors target updates.
|
|
115
118
|
Reads (`get`/`peek`) are forwarded to the target while the ref is active.
|
|
119
|
+
Ref mirroring is scheduled through Teamplay runtime scheduler, so updates remain batch-friendly
|
|
120
|
+
and do not leak intermediate ref states during a single batched cycle.
|
|
116
121
|
|
|
117
122
|
```js
|
|
118
123
|
const $local = $.local.value
|
|
@@ -164,13 +169,85 @@ $alias.get() // { name: 'Bob' }
|
|
|
164
169
|
$alias.get() === $user.get() // false
|
|
165
170
|
```
|
|
166
171
|
|
|
172
|
+
### `ref` on query/aggregation targets (`mirror-only`)
|
|
173
|
+
|
|
174
|
+
Compat supports `refExtra` / `refIds` and query/aggregation-backed refs, but with a
|
|
175
|
+
different contract from plain document refs.
|
|
176
|
+
|
|
177
|
+
When target is a query or aggregation signal, compat creates a **mirror-only** link:
|
|
178
|
+
- Source path is updated from target changes (target -> source).
|
|
179
|
+
- Source path does **not** become an alias to target path (no `REF_TARGET` forwarding).
|
|
180
|
+
- Writes to source path do not forward to query/aggregation internals.
|
|
181
|
+
|
|
182
|
+
Why:
|
|
183
|
+
- Query/aggregation paths are hashed/synthetic and are not safe as generic alias targets.
|
|
184
|
+
- Racer behavior for these cases is effectively "mirror data into page/local path",
|
|
185
|
+
not "make full bidirectional alias".
|
|
186
|
+
|
|
187
|
+
Reactivity:
|
|
188
|
+
- Initial sync runs immediately on `ref(...)`.
|
|
189
|
+
- Further target updates are mirrored through compat model-change events.
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
const $query = $.query('courses', { active: true })
|
|
193
|
+
const $table = $._page.tables._adminCourses
|
|
194
|
+
|
|
195
|
+
// mirror query.extra/docs into page model
|
|
196
|
+
$query.refExtra('_page.tables._adminCourses.dataSource')
|
|
197
|
+
|
|
198
|
+
// reactively mirrors target -> source
|
|
199
|
+
$table.dataSource.get()
|
|
200
|
+
```
|
|
201
|
+
|
|
167
202
|
**Limitations vs Racer**
|
|
168
|
-
- No `refList`, `
|
|
203
|
+
- No `refList`, `refMap`.
|
|
169
204
|
- No automatic list index patching on insert/remove/move.
|
|
170
|
-
- No support for query/aggregation refs.
|
|
171
205
|
- No event emissions specific to refs.
|
|
172
206
|
- No support for racer-style ref meta/options beyond the basic signature.
|
|
173
207
|
|
|
208
|
+
### start(targetPath, ...deps, getter)
|
|
209
|
+
|
|
210
|
+
Legacy computed binding API from Racer/StartupJS.
|
|
211
|
+
Creates a reactive computation and writes its result into `targetPath`.
|
|
212
|
+
Source of truth is root API (`$root.start(...)`), but non-root calls are supported as sugar:
|
|
213
|
+
- `$scope.start('a.b', ...deps, getter)` → `$root.start('<scopePath>.a.b', ...deps, getter)`
|
|
214
|
+
|
|
215
|
+
- `targetPath`: string path where computed value is written.
|
|
216
|
+
- `deps`: dependencies used by `getter`.
|
|
217
|
+
- `getter`: function called as `getter(...resolvedDeps)`.
|
|
218
|
+
|
|
219
|
+
Dependency resolution:
|
|
220
|
+
- Signal-like dep (`$doc`, `$session.user`) → `dep.get()`.
|
|
221
|
+
- String dep (`'settings.theme'`) → `$root.get(dep)`.
|
|
222
|
+
- Any other dep → passed as-is.
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
$root.start('_virtual.lesson', $.lessons[lessonId], '_session.userId', (lesson, userId) => {
|
|
226
|
+
if (!lesson) return undefined
|
|
227
|
+
return { stageIds: lesson.stageIds, userId }
|
|
228
|
+
})
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Behavior:
|
|
232
|
+
- Calling `start()` again for the same `targetPath` replaces previous reaction.
|
|
233
|
+
- `undefined` result applies compat delete semantics at target path.
|
|
234
|
+
- `null` result is stored as `null`.
|
|
235
|
+
- Returns target signal.
|
|
236
|
+
- If any dependency temporarily suspends (throws a Promise/thenable), compat skips the whole tick (getter is not called and target is not written).
|
|
237
|
+
- If `getter` throws a Promise, compat skips that tick and retries on next reactive update.
|
|
238
|
+
|
|
239
|
+
### stop(targetPath)
|
|
240
|
+
|
|
241
|
+
Stops a computation created with `start(targetPath, ...)`.
|
|
242
|
+
No-op if there is no active computation for the path.
|
|
243
|
+
Source of truth is root API (`$root.stop(...)`), but non-root calls are supported as sugar:
|
|
244
|
+
- `$scope.stop('a.b')` → `$root.stop('<scopePath>.a.b')`
|
|
245
|
+
- `$scope.stop()` → `$root.stop('<scopePath>')`
|
|
246
|
+
|
|
247
|
+
```js
|
|
248
|
+
$root.stop('_virtual.lesson')
|
|
249
|
+
```
|
|
250
|
+
|
|
174
251
|
### query(collection, query, options?)
|
|
175
252
|
|
|
176
253
|
Creates a query signal **without** subscribing. Supports shorthand params:
|
|
@@ -202,6 +279,19 @@ await $.subscribe($user, $$active)
|
|
|
202
279
|
$.unsubscribe($user, $$active)
|
|
203
280
|
```
|
|
204
281
|
|
|
282
|
+
### close(callback?)
|
|
283
|
+
|
|
284
|
+
Compatibility shim for legacy `model.close()` calls.
|
|
285
|
+
|
|
286
|
+
- In Teamplay, `$`/`model` is a global root signal (not a per-request Racer model instance).
|
|
287
|
+
- Therefore `close()` is intentionally a no-op.
|
|
288
|
+
- Optional callback is supported and called immediately.
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
model.close()
|
|
292
|
+
model.close(() => console.log('closed'))
|
|
293
|
+
```
|
|
294
|
+
|
|
205
295
|
### fetch(...signals) / unfetch(...signals)
|
|
206
296
|
|
|
207
297
|
Fetch-only variants of `subscribe` / `unsubscribe`. They load data once without a live subscription.
|
|
@@ -236,6 +326,7 @@ Returns the current value and tracks reactivity.
|
|
|
236
326
|
const name = $.users.user1.name.get()
|
|
237
327
|
$root.get('$render.url')
|
|
238
328
|
$user.get('profile.name')
|
|
329
|
+
$user.get('profile', 'name')
|
|
239
330
|
```
|
|
240
331
|
|
|
241
332
|
### peek(subpath?)
|
|
@@ -245,6 +336,7 @@ Returns the current value **without** tracking reactivity.
|
|
|
245
336
|
```js
|
|
246
337
|
const name = $.users.user1.name.peek()
|
|
247
338
|
$user.peek('profile.name')
|
|
339
|
+
$user.peek('profile', 'name')
|
|
248
340
|
```
|
|
249
341
|
|
|
250
342
|
### getCopy(subpath)
|
|
@@ -306,6 +398,44 @@ for (const $doc of $query) {
|
|
|
306
398
|
}
|
|
307
399
|
```
|
|
308
400
|
|
|
401
|
+
### Mutator Semantics (Core vs Compat)
|
|
402
|
+
|
|
403
|
+
Compatibility mode intentionally aligns mutators with Racer. This differs from core `Signal` behavior.
|
|
404
|
+
|
|
405
|
+
| API | Core (`Signal`) | Compat (`SignalCompat`) |
|
|
406
|
+
| --- | --- | --- |
|
|
407
|
+
| `set` | Uses deep-diff path (`dataTree.set` + internal `setDiffDeep`). | Path-targeted replace semantics, Racer-like. `undefined` keeps delete semantics. |
|
|
408
|
+
| `setEach` | Not a special API in core mutators. | Per-key compat `set` (not `assign` merge/delete behavior). |
|
|
409
|
+
| `setDiffDeep` | Deep-diff engine (`utils/setDiffDeep.js`). | Recursive Racer-like diff implemented via compat mutators (`set` / `del`) on nested paths. |
|
|
410
|
+
| `setDiff` | N/A as compat shim. | Alias to compat `set` for both signatures: `setDiff(value)` and `setDiff(path, value)`. |
|
|
411
|
+
|
|
412
|
+
Migration note: compat behavior is intentionally Racer-aligned and may differ from core mutators.
|
|
413
|
+
Composite compat mutators (`setEach`, `setDiffDeep`) apply updates atomically for Teamplay-scheduled observers via the runtime batch scheduler.
|
|
414
|
+
|
|
415
|
+
### Subscription GC Delay (Compat)
|
|
416
|
+
|
|
417
|
+
To reduce UI blink on rapid `unsub -> sub` cycles, compat uses an unload grace period for docs/queries.
|
|
418
|
+
|
|
419
|
+
- Default in compat: `300ms`
|
|
420
|
+
- Default in non-compat: `0ms` (immediate cleanup)
|
|
421
|
+
|
|
422
|
+
You can tune it globally:
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
import { getSubscriptionGcDelay, setSubscriptionGcDelay } from 'teamplay'
|
|
426
|
+
|
|
427
|
+
setSubscriptionGcDelay(500)
|
|
428
|
+
console.log(getSubscriptionGcDelay()) // 500
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
When refCount drops to `0`, unsubscribe/destroy is scheduled after this delay.
|
|
432
|
+
If a new subscribe arrives before timeout, pending destroy is cancelled and the same doc/query instance is reused.
|
|
433
|
+
|
|
434
|
+
Compat queries also retain lifecycle ownership of docs they materialize into DataTree.
|
|
435
|
+
This means a doc that arrived through `useQuery` / `useBatchQuery` will stay available
|
|
436
|
+
for immediate `useLocal` / `useModel` reads while that query remains subscribed, even if
|
|
437
|
+
some unrelated `useDoc` subscriber for the same `collection.id` unmounts.
|
|
438
|
+
|
|
309
439
|
### set(value) and set(path, value)
|
|
310
440
|
|
|
311
441
|
`SignalCompat` accepts both:
|
|
@@ -315,7 +445,14 @@ $.users.user1.name.set('Alice')
|
|
|
315
445
|
$.users.user1.set('profile.name', 'Alice')
|
|
316
446
|
```
|
|
317
447
|
|
|
318
|
-
In compat mode, `set` replaces
|
|
448
|
+
In compat mode, `set` replaces the value at the target path.
|
|
449
|
+
- `set(path, null)` stores `null`.
|
|
450
|
+
- `set(path, undefined)` applies current delete semantics.
|
|
451
|
+
|
|
452
|
+
```js
|
|
453
|
+
await $.users.user1.set('profile', { name: 'Ann', role: 'student' })
|
|
454
|
+
await $.users.user1.set('profile', { name: 'Kate' }) // role is removed
|
|
455
|
+
```
|
|
319
456
|
|
|
320
457
|
### setNull(path?, value)
|
|
321
458
|
|
|
@@ -327,26 +464,47 @@ $.config.setNull('theme', 'light')
|
|
|
327
464
|
|
|
328
465
|
### setDiffDeep(path?, value)
|
|
329
466
|
|
|
330
|
-
Applies a
|
|
467
|
+
Applies a recursive Racer-like diff using compat mutators (`set` / `del`) on subpaths.
|
|
468
|
+
This is intentionally a compat implementation detail and differs from core deep-diff internals.
|
|
331
469
|
|
|
332
470
|
```js
|
|
333
|
-
$.users.user1.
|
|
471
|
+
await $.users.user1.set({ profile: { name: 'Ann', role: 'student' } })
|
|
472
|
+
await $.users.user1.setDiffDeep({ profile: { name: 'Kate' } }) // deep-diff path
|
|
334
473
|
```
|
|
335
474
|
|
|
336
475
|
### setDiff(path?, value)
|
|
337
476
|
|
|
338
|
-
Alias for `set
|
|
477
|
+
Alias for compat `set` in both forms:
|
|
478
|
+
- `setDiff(value)` -> same as `set(value)`
|
|
479
|
+
- `setDiff(path, value)` -> same as `set(path, value)`
|
|
339
480
|
|
|
340
481
|
```js
|
|
341
|
-
$.users.user1.setDiff({ profile: { name: '
|
|
482
|
+
await $.users.user1.setDiff({ profile: { name: 'Kate' } })
|
|
483
|
+
await $.users.user1.setDiff('profile', { name: 'Bob' })
|
|
342
484
|
```
|
|
343
485
|
|
|
344
486
|
### setEach(path?, object)
|
|
345
487
|
|
|
346
|
-
|
|
488
|
+
Racer-like per-key set. `setEach` iterates keys and applies compat `set` for each key.
|
|
489
|
+
- `setEach({ k: null })` stores `null`.
|
|
490
|
+
- `setEach({ k: undefined })` applies current delete semantics.
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
await $.users.user1.setEach({ name: 'Bob', age: null })
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### Null / Undefined Matrix (Compat)
|
|
497
|
+
|
|
498
|
+
| Call | Result |
|
|
499
|
+
| --- | --- |
|
|
500
|
+
| `set(path, null)` | stores `null` at `path` |
|
|
501
|
+
| `set(path, undefined)` | applies delete semantics at `path` |
|
|
502
|
+
| `setEach({ k: null })` | stores `null` for `k` |
|
|
503
|
+
| `setEach({ k: undefined })` | applies delete semantics for `k` |
|
|
347
504
|
|
|
348
505
|
```js
|
|
349
|
-
$.users.user1.
|
|
506
|
+
await $.users.user1.set('status', null) // status === null
|
|
507
|
+
await $.users.user1.setEach({ status: undefined }) // status deleted
|
|
350
508
|
```
|
|
351
509
|
|
|
352
510
|
### assign(object)
|
|
@@ -360,6 +518,8 @@ $.users.user1.assign({ name: 'Bob', age: null })
|
|
|
360
518
|
### del(path?)
|
|
361
519
|
|
|
362
520
|
Deletes a value. Can be used with a subpath.
|
|
521
|
+
In compat mode, deleting a non-existing **public** document (or its subpath) is a no-op
|
|
522
|
+
to match legacy racer behavior.
|
|
363
523
|
|
|
364
524
|
```js
|
|
365
525
|
$.users.user1.del('profile.name')
|
|
@@ -478,8 +638,12 @@ They are designed to behave close to StartupJS hooks, but adapted to Teamplay’
|
|
|
478
638
|
General notes:
|
|
479
639
|
- Hooks should be used inside `observer()` components to get reactive updates.
|
|
480
640
|
- Sync hooks (`useDoc`, `useQuery`) use Suspense by default (via `useSub`).
|
|
641
|
+
- In compatibility mode, sync hooks are strict (`defer: false`) to match racer-like
|
|
642
|
+
semantics and avoid transient `undefined` / empty snapshots during fast navigation.
|
|
643
|
+
This is enforced by compat hooks (user `defer` option is ignored for sync hooks).
|
|
481
644
|
- Async hooks (`useAsyncDoc`, `useAsyncQuery`) never throw; they return `undefined` until ready.
|
|
482
|
-
- Batch hooks
|
|
645
|
+
- Batch hooks use a Suspense batch barrier (`useBatch`) and wait for both
|
|
646
|
+
subscribe promises and DataTree materialization readiness.
|
|
483
647
|
|
|
484
648
|
### Events
|
|
485
649
|
|
|
@@ -652,10 +816,12 @@ if (!user) return 'Loading...'
|
|
|
652
816
|
|
|
653
817
|
Returns `undefined` until subscription resolves.
|
|
654
818
|
|
|
655
|
-
#### Batch
|
|
819
|
+
#### Batch variants
|
|
656
820
|
|
|
657
|
-
`useBatchDoc` / `useBatchDoc$`
|
|
658
|
-
|
|
821
|
+
`useBatchDoc` / `useBatchDoc$` participate in batch Suspense flow:
|
|
822
|
+
- they register subscribe promises for `useBatch()`;
|
|
823
|
+
- they also register a **materialization readiness check**:
|
|
824
|
+
doc is considered ready only when it is visible in DataTree (or explicitly missing).
|
|
659
825
|
|
|
660
826
|
### Query Hooks
|
|
661
827
|
|
|
@@ -672,10 +838,12 @@ This matches StartupJS and makes updates easy:
|
|
|
672
838
|
$users[userId].name.set('New Name')
|
|
673
839
|
```
|
|
674
840
|
|
|
675
|
-
`useQuery$` returns the
|
|
841
|
+
`useQuery$` returns the **query signal**:
|
|
676
842
|
|
|
677
843
|
```js
|
|
678
|
-
const $
|
|
844
|
+
const $query = useQuery$('users', { active: true })
|
|
845
|
+
const ids = $query.getIds()
|
|
846
|
+
const docs = $query.get()
|
|
679
847
|
```
|
|
680
848
|
|
|
681
849
|
If `query == null`, a warning is logged and `{ _id: '__NON_EXISTENT__' }` is used.
|
|
@@ -690,9 +858,18 @@ if (!users) return 'Loading...'
|
|
|
690
858
|
|
|
691
859
|
Async variant: no Suspense, returns `undefined` until ready.
|
|
692
860
|
|
|
693
|
-
#### Batch
|
|
861
|
+
#### Batch variants
|
|
694
862
|
|
|
695
|
-
`useBatchQuery` / `useBatchQuery$`
|
|
863
|
+
`useBatchQuery` / `useBatchQuery$` participate in batch Suspense flow:
|
|
864
|
+
- they register subscribe promises for `useBatch()`;
|
|
865
|
+
- they register a **query readiness check**:
|
|
866
|
+
query ids must be materialized in DataTree, and each `collection.id` from ids must
|
|
867
|
+
be visible in DataTree (or explicitly missing).
|
|
868
|
+
- for `$aggregate` queries, readiness is query-level:
|
|
869
|
+
DataTree must have `$queries.<hash>.docs` (array, including empty), or `extra`.
|
|
870
|
+
Aggregate rows are not required to exist as `collection.<id>` docs.
|
|
871
|
+
Presence of `$queries.<hash>.ids` alone does not mark aggregate readiness.
|
|
872
|
+
For Teamplay aggregation subscriptions, `$aggregations.<hash>` also marks readiness.
|
|
696
873
|
|
|
697
874
|
### Query Helpers
|
|
698
875
|
|
|
@@ -706,7 +883,7 @@ const [users] = useQueryIds('users', ['b', 'a'])
|
|
|
706
883
|
Options:
|
|
707
884
|
- `reverse: true` — reverse order of IDs before mapping.
|
|
708
885
|
|
|
709
|
-
`useBatchQueryIds` and `useAsyncQueryIds` are
|
|
886
|
+
`useBatchQueryIds` and `useAsyncQueryIds` are batch/async variants.
|
|
710
887
|
|
|
711
888
|
#### `useQueryDoc`
|
|
712
889
|
|
|
@@ -721,16 +898,33 @@ Implementation details:
|
|
|
721
898
|
- Adds default `$sort: { createdAt: -1 }` if `$sort` is missing
|
|
722
899
|
|
|
723
900
|
`useQueryDoc$` returns only the doc signal (or `undefined`).
|
|
724
|
-
`useBatchQueryDoc` / `useAsyncQueryDoc` are
|
|
901
|
+
`useBatchQueryDoc` / `useAsyncQueryDoc` are batch/async variants.
|
|
725
902
|
|
|
726
|
-
###
|
|
903
|
+
### Batch Barrier
|
|
727
904
|
|
|
728
|
-
`useBatch()` is a
|
|
729
|
-
All batch hooks are **aliases** to their non-batch versions.
|
|
905
|
+
`useBatch()` is a Suspense barrier for batch hooks.
|
|
730
906
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
907
|
+
It throws while:
|
|
908
|
+
- batch subscribe promises are pending;
|
|
909
|
+
- or subscribe promises are resolved but requested docs/queries are not yet
|
|
910
|
+
materialized in DataTree.
|
|
911
|
+
|
|
912
|
+
After `useBatch()` stops throwing in compat mode, immediate reads via
|
|
913
|
+
`useLocal(...).get(...)` for already requested batch entities should not produce
|
|
914
|
+
transient `undefined` caused by materialization races.
|
|
915
|
+
|
|
916
|
+
### Missing ShareDB Docs
|
|
917
|
+
|
|
918
|
+
Compat now mirrors Racer behavior for **missing public docs** (`type === null`,
|
|
919
|
+
`version === 0`) after subscribe/fetch:
|
|
920
|
+
|
|
921
|
+
- `connection.get(collection, id).data` becomes a truthy empty observable object;
|
|
922
|
+
- but the compat/model path still stays unresolved, so `$.collection[id].get()`
|
|
923
|
+
continues to return `undefined` until the document is actually created.
|
|
924
|
+
|
|
925
|
+
This matters for legacy consumers which read `shareDoc.data` directly (for
|
|
926
|
+
example readonly rich-text paths) while still expecting normal public-doc
|
|
927
|
+
creation semantics from model mutators.
|
|
734
928
|
|
|
735
929
|
## Examples
|
|
736
930
|
|
|
@@ -758,6 +952,8 @@ const Component = observer(() => {
|
|
|
758
952
|
```js
|
|
759
953
|
const Component = observer(() => {
|
|
760
954
|
const [users, $users] = useQuery('users', { active: true })
|
|
955
|
+
const $query = useQuery$('users', { active: true })
|
|
956
|
+
const ids = $query.getIds()
|
|
761
957
|
return (
|
|
762
958
|
<>
|
|
763
959
|
{users.map(u => <div key={u._id}>{u.name}</div>)}
|