teamplay 0.4.0-alpha.97 → 0.4.0-alpha.98

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.
@@ -29,7 +29,7 @@ import {
29
29
  } from '../dataTree.js'
30
30
  import { on as onCustomEvent, removeListener as removeCustomEventListener } from './eventsCompat.js'
31
31
  import { waitForImperativeQueryReady } from './queryReadiness.js'
32
- import { normalizePattern, onModelEvent, removeModelListener } from './modelEvents.js'
32
+ import { isModelEventsEnabled, normalizePattern, onModelEvent, removeModelListener } from './modelEvents.js'
33
33
  import { setRefLink, removeRefLink, getAllRefLinks } from './refRegistry.js'
34
34
  import { REF_TARGET, resolveRefSignalSafe, resolveRefSegmentsSafe } from './refFallback.js'
35
35
  import { runInBatch } from '../batchScheduler.js'
@@ -1061,7 +1061,9 @@ function setReplacePrivateCompatSync ($signal, value) {
1061
1061
  value = normalizeIdFields(value, idFields, segments[1])
1062
1062
  }
1063
1063
  setReplacePrivateData(getOwningRootId($signal), segments, value)
1064
- if (isSilentContextActive()) mirrorRefMutationFromTarget(segments, value)
1064
+ if (shouldMirrorPrivateRefMutationLocally()) {
1065
+ mirrorRefMutationFromTarget(segments, value)
1066
+ }
1065
1067
  }
1066
1068
 
1067
1069
  function delPrivateCompatSync ($signal, options) {
@@ -1123,7 +1125,9 @@ async function setReplaceOnSignal ($signal, value) {
1123
1125
  }
1124
1126
  if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
1125
1127
  const result = setReplacePrivateData(getOwningRootId($signal), segments, value)
1126
- if (isSilentContextActive()) mirrorRefMutationFromTarget(segments, value)
1128
+ if (shouldMirrorPrivateRefMutationLocally()) {
1129
+ mirrorRefMutationFromTarget(segments, value)
1130
+ }
1127
1131
  return result
1128
1132
  }
1129
1133
 
@@ -1271,6 +1275,11 @@ function shouldMirrorPublicRefMutationLocally (segments) {
1271
1275
  return !docSubscriptions.hasRuntime(transportHash)
1272
1276
  }
1273
1277
 
1278
+ function shouldMirrorPrivateRefMutationLocally () {
1279
+ if (isSilentContextActive()) return true
1280
+ return !isModelEventsEnabled()
1281
+ }
1282
+
1274
1283
  function shallowCopy (value) {
1275
1284
  const rawValue = raw(value)
1276
1285
  if (Array.isArray(rawValue)) return rawValue.slice()
package/orm/dataTree.js CHANGED
@@ -326,6 +326,13 @@ export async function setPublicDocReplace (segments, value) {
326
326
  }
327
327
 
328
328
  const relativePath = segments.slice(2)
329
+ // json0 direct replace ops require every ancestor container to already exist.
330
+ // Racer-like compat set, however, materializes missing/primitive parents while
331
+ // descending into the path. Fall back to the older diff-based path when the
332
+ // direct op would target a non-existent/non-object ancestor.
333
+ if (!canApplyDirectReplaceOp(docState.snapshot || {}, relativePath)) {
334
+ return setPublicDoc(segments, value)
335
+ }
329
336
  const previous = getRaw(segments)
330
337
  const normalizedPrevious = normalizeUndefined(
331
338
  relativePath.length === 0 ? stripIdFields(previous, idFields) : previous
@@ -444,6 +451,16 @@ function normalizeUndefined (value) {
444
451
  return value === undefined ? null : value
445
452
  }
446
453
 
454
+ function canApplyDirectReplaceOp (docSnapshot, relativePath) {
455
+ if (relativePath.length === 0) return true
456
+ let node = docSnapshot
457
+ for (let i = 0; i < relativePath.length - 1; i++) {
458
+ if (node == null || typeof node !== 'object') return false
459
+ node = node[relativePath[i]]
460
+ }
461
+ return node != null && typeof node === 'object'
462
+ }
463
+
447
464
  function normalizeValueForOp (value) {
448
465
  let result = raw(value)
449
466
  if (result != null && typeof result === 'object') result = JSON.parse(JSON.stringify(result))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.4.0-alpha.97",
3
+ "version": "0.4.0-alpha.98",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -83,5 +83,5 @@
83
83
  ]
84
84
  },
85
85
  "license": "MIT",
86
- "gitHead": "633e39dc754332bcd3d95ddc4cfb3522f0bfd694"
86
+ "gitHead": "ae4e0e6ee39e395fa9eb48a2099cc1d5b2a4630d"
87
87
  }
package/react/useSub.js CHANGED
@@ -44,8 +44,8 @@ export function useSubDeferred (signal, params, { async = false, defer, batch =
44
44
  // 1. if it's a promise, throw it so that Suspense can catch it and wait for subscription to finish
45
45
  if (promiseOrSignal.then) {
46
46
  const promise = maybeThrottle(promiseOrSignal)
47
+ const hasPreviousSignal = !!$signalRef.current
47
48
  if (batch) {
48
- const hasPreviousSignal = !!$signalRef.current
49
49
  // Batch suspense must block only on initial load.
50
50
  // On resubscribe we keep rendering previous signal and refresh in background.
51
51
  if (!hasPreviousSignal) {
@@ -61,6 +61,11 @@ export function useSubDeferred (signal, params, { async = false, defer, batch =
61
61
  scheduleUpdate(promise)
62
62
  return
63
63
  }
64
+ // Keep previous snapshot during update re-subscribe and refresh in background.
65
+ if (hasPreviousSignal) {
66
+ scheduleUpdate(promise)
67
+ return $signalRef.current
68
+ }
64
69
  if (compatAttemptCleanup) registerCompatAttemptCleanup(signal, params)
65
70
  throw promise
66
71
  // 2. if it's a signal, we save it into ref to make sure it's not garbage collected while component exists