teamplay 0.4.0-alpha.34 → 0.4.0-alpha.36

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.
@@ -589,6 +589,9 @@ They are designed to behave close to StartupJS hooks, but adapted to Teamplay’
589
589
  General notes:
590
590
  - Hooks should be used inside `observer()` components to get reactive updates.
591
591
  - Sync hooks (`useDoc`, `useQuery`) use Suspense by default (via `useSub`).
592
+ - In compatibility mode, sync hooks are strict (`defer: false`) to match racer-like
593
+ semantics and avoid transient `undefined` / empty snapshots during fast navigation.
594
+ This is enforced by compat hooks (user `defer` option is ignored for sync hooks).
592
595
  - Async hooks (`useAsyncDoc`, `useAsyncQuery`) never throw; they return `undefined` until ready.
593
596
  - Batch hooks use a Suspense batch barrier (`useBatch`) and wait for both
594
597
  subscribe promises and DataTree materialization readiness.
@@ -813,6 +816,11 @@ Async variant: no Suspense, returns `undefined` until ready.
813
816
  - they register a **query readiness check**:
814
817
  query ids must be materialized in DataTree, and each `collection.id` from ids must
815
818
  be visible in DataTree (or explicitly missing).
819
+ - for `$aggregate` queries, readiness is query-level:
820
+ DataTree must have `$queries.<hash>.docs` (array, including empty), or `extra`.
821
+ Aggregate rows are not required to exist as `collection.<id>` docs.
822
+ Presence of `$queries.<hash>.ids` alone does not mark aggregate readiness.
823
+ For Teamplay aggregation subscriptions, `$aggregations.<hash>` also marks readiness.
816
824
 
817
825
  ### Query Helpers
818
826
 
@@ -6,6 +6,7 @@ import { getRaw } from '../dataTree.js'
6
6
  import { getConnection } from '../connection.js'
7
7
  import { isCompatEnv } from '../compatEnv.js'
8
8
  import { hashQuery, QUERIES } from '../Query.js'
9
+ import { AGGREGATIONS } from '../Aggregation.js'
9
10
 
10
11
  const $root = getRootSignal({ rootId: GLOBAL_ROOT_ID, rootFunction: universal$ })
11
12
 
@@ -84,7 +85,7 @@ export function useBatch () {
84
85
 
85
86
  export function useDoc$ (collection, id, options) {
86
87
  const $doc = getDocSignal(collection, id, 'useDoc')
87
- const normalizedOptions = options ? { ...options, async: false } : options
88
+ const normalizedOptions = normalizeSyncSubOptions(options)
88
89
  return useSub($doc, undefined, normalizedOptions)
89
90
  }
90
91
 
@@ -119,14 +120,14 @@ export function useAsyncDoc (collection, id, options) {
119
120
 
120
121
  export function useQuery$ (collection, query, options) {
121
122
  const $collection = getCollectionSignal(collection, query, 'useQuery')
122
- const normalizedOptions = options ? { ...options, async: false } : options
123
+ const normalizedOptions = normalizeSyncSubOptions(options)
123
124
  const $query = useSub($collection, normalizeQuery(query, 'useQuery'), normalizedOptions)
124
125
  return $query
125
126
  }
126
127
 
127
128
  export function useQuery (collection, query, options) {
128
129
  const $collection = getCollectionSignal(collection, query, 'useQuery')
129
- const normalizedOptions = options ? { ...options, async: false } : options
130
+ const normalizedOptions = normalizeSyncSubOptions(options)
130
131
  const $query = useSub($collection, normalizeQuery(query, 'useQuery'), normalizedOptions)
131
132
  return [$query.get(), $collection]
132
133
  }
@@ -324,6 +325,18 @@ const BATCH_SUB_OPTIONS = Object.freeze({
324
325
  defer: false
325
326
  })
326
327
 
328
+ function normalizeSyncSubOptions (options) {
329
+ if (!isCompatEnv()) {
330
+ return options ? { ...options, async: false } : options
331
+ }
332
+ return {
333
+ ...(options || {}),
334
+ async: false,
335
+ // Compat sync hooks are strict by design: no deferred snapshots between route/tab switches.
336
+ defer: false
337
+ }
338
+ }
339
+
327
340
  function getDocIdFromSignal ($doc) {
328
341
  const path = typeof $doc?.path === 'function' ? $doc.path() : ''
329
342
  const segments = path ? path.split('.').filter(Boolean) : []
@@ -359,16 +372,26 @@ function registerBatchQueryReadinessCheck (collection, query) {
359
372
  if (!collection || !query || typeof query !== 'object') return
360
373
  const hash = hashQuery(collection, query)
361
374
  const idsSegments = [QUERIES, hash, 'ids']
375
+ const docsSegments = [QUERIES, hash, 'docs']
376
+ const extraSegments = [QUERIES, hash, 'extra']
377
+ const aggregationSegments = [AGGREGATIONS, hash]
378
+ const isAggregate = Array.isArray(query.$aggregate)
362
379
  promiseBatcher.addCheck({
363
380
  key: `query:${hash}`,
364
381
  type: 'query',
365
- details: { collection, hash, query },
366
- isReady: () => isQueryReady(collection, idsSegments),
382
+ details: { collection, hash, query, isAggregate },
383
+ isReady: () => isQueryReady(collection, idsSegments, docsSegments, extraSegments, aggregationSegments, isAggregate),
367
384
  getState: () => {
368
385
  const ids = getRaw(idsSegments)
386
+ const docs = getRaw(docsSegments)
387
+ const extra = getRaw(extraSegments)
388
+ const aggregation = getRaw(aggregationSegments)
369
389
  return {
370
390
  ids,
371
- docs: Array.isArray(ids)
391
+ queryDocs: docs,
392
+ extra,
393
+ aggregation,
394
+ idMaterialization: Array.isArray(ids)
372
395
  ? ids.map(id => ({
373
396
  id,
374
397
  raw: getRaw([collection, id])
@@ -379,10 +402,17 @@ function registerBatchQueryReadinessCheck (collection, query) {
379
402
  })
380
403
  }
381
404
 
382
- function isQueryReady (collection, idsSegments) {
405
+ function isQueryReady (collection, idsSegments, docsSegments, extraSegments, aggregationSegments, isAggregate) {
406
+ if (isAggregate) {
407
+ const docs = getRaw(docsSegments)
408
+ if (Array.isArray(docs)) return true
409
+ if (getRaw(extraSegments) !== undefined) return true
410
+ return getRaw(aggregationSegments) !== undefined
411
+ }
383
412
  const ids = getRaw(idsSegments)
384
413
  if (!Array.isArray(ids)) return false
385
414
  for (const id of ids) {
415
+ if (id == null) continue
386
416
  if (!isDocReady([collection, id])) return false
387
417
  }
388
418
  return true
@@ -404,3 +434,7 @@ function getShareDoc (collection, id) {
404
434
  return undefined
405
435
  }
406
436
  }
437
+
438
+ export const __COMPAT_BATCH_READY__ = {
439
+ isQueryReady
440
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.4.0-alpha.34",
3
+ "version": "0.4.0-alpha.36",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -81,5 +81,5 @@
81
81
  ]
82
82
  },
83
83
  "license": "MIT",
84
- "gitHead": "619024ea6ea3699ab461c279791a59e5a58334ab"
84
+ "gitHead": "0f90958b39502634ec8fefc99d7e688f0ac4c4bc"
85
85
  }