teamplay 0.4.0-alpha.97 → 0.4.0-alpha.99
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/orm/Compat/SignalCompat.js +70 -20
- package/orm/Compat/startStopCompat.js +41 -2
- package/orm/Doc.js +34 -0
- package/orm/dataTree.js +66 -29
- package/package.json +2 -2
- package/react/useSub.js +6 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { raw, observe, unobserve } from '@nx-js/observer-util'
|
|
2
|
+
import arrayDiff from 'arraydiff'
|
|
2
3
|
import {
|
|
3
4
|
Signal,
|
|
4
5
|
GETTERS,
|
|
@@ -29,7 +30,7 @@ import {
|
|
|
29
30
|
} from '../dataTree.js'
|
|
30
31
|
import { on as onCustomEvent, removeListener as removeCustomEventListener } from './eventsCompat.js'
|
|
31
32
|
import { waitForImperativeQueryReady } from './queryReadiness.js'
|
|
32
|
-
import { normalizePattern, onModelEvent, removeModelListener } from './modelEvents.js'
|
|
33
|
+
import { isModelEventsEnabled, normalizePattern, onModelEvent, removeModelListener } from './modelEvents.js'
|
|
33
34
|
import { setRefLink, removeRefLink, getAllRefLinks } from './refRegistry.js'
|
|
34
35
|
import { REF_TARGET, resolveRefSignalSafe, resolveRefSegmentsSafe } from './refFallback.js'
|
|
35
36
|
import { runInBatch } from '../batchScheduler.js'
|
|
@@ -974,14 +975,14 @@ async function diffDeepCompat ($signal, before, after) {
|
|
|
974
975
|
if (before === after) return
|
|
975
976
|
|
|
976
977
|
if (Array.isArray(before) && Array.isArray(after)) {
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
978
|
+
const diff = arrayDiff(before, after, deepEqualCompat)
|
|
979
|
+
if (!diff.length) return
|
|
980
|
+
const index = getSingleArrayReplacementIndex(diff)
|
|
981
|
+
if (index != null) {
|
|
981
982
|
await diffDeepCompat(getChildSignal($signal, index), before[index], after[index])
|
|
982
983
|
return
|
|
983
984
|
}
|
|
984
|
-
await
|
|
985
|
+
await applyArrayDiffCompat($signal, diff)
|
|
985
986
|
return
|
|
986
987
|
}
|
|
987
988
|
|
|
@@ -1003,14 +1004,14 @@ function diffDeepCompatSync ($signal, before, after) {
|
|
|
1003
1004
|
if (before === after) return
|
|
1004
1005
|
|
|
1005
1006
|
if (Array.isArray(before) && Array.isArray(after)) {
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1007
|
+
const diff = arrayDiff(before, after, deepEqualCompat)
|
|
1008
|
+
if (!diff.length) return
|
|
1009
|
+
const index = getSingleArrayReplacementIndex(diff)
|
|
1010
|
+
if (index != null) {
|
|
1010
1011
|
diffDeepCompatSync(getChildSignal($signal, index), before[index], after[index])
|
|
1011
1012
|
return
|
|
1012
1013
|
}
|
|
1013
|
-
|
|
1014
|
+
applyArrayDiffCompatSync($signal, diff)
|
|
1014
1015
|
return
|
|
1015
1016
|
}
|
|
1016
1017
|
|
|
@@ -1035,14 +1036,54 @@ function isDiffableObject (before, after) {
|
|
|
1035
1036
|
return true
|
|
1036
1037
|
}
|
|
1037
1038
|
|
|
1038
|
-
function
|
|
1039
|
-
if (!Array.isArray(
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1039
|
+
function getSingleArrayReplacementIndex (diff) {
|
|
1040
|
+
if (!Array.isArray(diff) || diff.length !== 2) return null
|
|
1041
|
+
const first = diff[0]
|
|
1042
|
+
const second = diff[1]
|
|
1043
|
+
if (
|
|
1044
|
+
first instanceof arrayDiff.RemoveDiff &&
|
|
1045
|
+
second instanceof arrayDiff.InsertDiff &&
|
|
1046
|
+
first.index === second.index &&
|
|
1047
|
+
first.howMany === 1 &&
|
|
1048
|
+
second.values.length === 1
|
|
1049
|
+
) {
|
|
1050
|
+
return first.index
|
|
1051
|
+
}
|
|
1052
|
+
return null
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
async function applyArrayDiffCompat ($signal, diff) {
|
|
1056
|
+
for (const item of diff) {
|
|
1057
|
+
if (item instanceof arrayDiff.InsertDiff) {
|
|
1058
|
+
await arrayInsertOnSignal($signal, item.index, item.values)
|
|
1059
|
+
continue
|
|
1060
|
+
}
|
|
1061
|
+
if (item instanceof arrayDiff.RemoveDiff) {
|
|
1062
|
+
await arrayRemoveOnSignal($signal, item.index, item.howMany)
|
|
1063
|
+
continue
|
|
1064
|
+
}
|
|
1065
|
+
if (item instanceof arrayDiff.MoveDiff) {
|
|
1066
|
+
await arrayMoveOnSignal($signal, item.from, item.to, item.howMany)
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function applyArrayDiffCompatSync ($signal, diff) {
|
|
1072
|
+
const segments = ensureArrayTarget($signal)
|
|
1073
|
+
const rootId = getOwningRootId($signal)
|
|
1074
|
+
for (const item of diff) {
|
|
1075
|
+
if (item instanceof arrayDiff.InsertDiff) {
|
|
1076
|
+
arrayInsertPrivateData(rootId, segments, item.index, item.values)
|
|
1077
|
+
continue
|
|
1078
|
+
}
|
|
1079
|
+
if (item instanceof arrayDiff.RemoveDiff) {
|
|
1080
|
+
arrayRemovePrivateData(rootId, segments, item.index, item.howMany)
|
|
1081
|
+
continue
|
|
1082
|
+
}
|
|
1083
|
+
if (item instanceof arrayDiff.MoveDiff) {
|
|
1084
|
+
arrayMovePrivateData(rootId, segments, item.from, item.to, item.howMany)
|
|
1085
|
+
}
|
|
1044
1086
|
}
|
|
1045
|
-
return changed
|
|
1046
1087
|
}
|
|
1047
1088
|
|
|
1048
1089
|
function getChildSignal ($parent, key) {
|
|
@@ -1061,7 +1102,9 @@ function setReplacePrivateCompatSync ($signal, value) {
|
|
|
1061
1102
|
value = normalizeIdFields(value, idFields, segments[1])
|
|
1062
1103
|
}
|
|
1063
1104
|
setReplacePrivateData(getOwningRootId($signal), segments, value)
|
|
1064
|
-
if (
|
|
1105
|
+
if (shouldMirrorPrivateRefMutationLocally()) {
|
|
1106
|
+
mirrorRefMutationFromTarget(segments, value)
|
|
1107
|
+
}
|
|
1065
1108
|
}
|
|
1066
1109
|
|
|
1067
1110
|
function delPrivateCompatSync ($signal, options) {
|
|
@@ -1123,7 +1166,9 @@ async function setReplaceOnSignal ($signal, value) {
|
|
|
1123
1166
|
}
|
|
1124
1167
|
if (isPrivateMutationForbidden()) throw Error(ERRORS.publicOnly)
|
|
1125
1168
|
const result = setReplacePrivateData(getOwningRootId($signal), segments, value)
|
|
1126
|
-
if (
|
|
1169
|
+
if (shouldMirrorPrivateRefMutationLocally()) {
|
|
1170
|
+
mirrorRefMutationFromTarget(segments, value)
|
|
1171
|
+
}
|
|
1127
1172
|
return result
|
|
1128
1173
|
}
|
|
1129
1174
|
|
|
@@ -1271,6 +1316,11 @@ function shouldMirrorPublicRefMutationLocally (segments) {
|
|
|
1271
1316
|
return !docSubscriptions.hasRuntime(transportHash)
|
|
1272
1317
|
}
|
|
1273
1318
|
|
|
1319
|
+
function shouldMirrorPrivateRefMutationLocally () {
|
|
1320
|
+
if (isSilentContextActive()) return true
|
|
1321
|
+
return !isModelEventsEnabled()
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1274
1324
|
function shallowCopy (value) {
|
|
1275
1325
|
const rawValue = raw(value)
|
|
1276
1326
|
if (Array.isArray(rawValue)) return rawValue.slice()
|
|
@@ -24,6 +24,7 @@ export function compatStartOnRoot ($root, targetPath, ...depsAndGetter) {
|
|
|
24
24
|
const existing = store.get(targetKey)
|
|
25
25
|
if (existing) existing.stop()
|
|
26
26
|
|
|
27
|
+
let lastSourceSnapshot = UNSET
|
|
27
28
|
const reaction = observe(() => {
|
|
28
29
|
const resolvedDeps = []
|
|
29
30
|
for (const dep of deps) {
|
|
@@ -38,13 +39,22 @@ export function compatStartOnRoot ($root, targetPath, ...depsAndGetter) {
|
|
|
38
39
|
if (isThenable(err)) return
|
|
39
40
|
throw err
|
|
40
41
|
}
|
|
41
|
-
const
|
|
42
|
+
const sourceSnapshot = detachStartValue(nextValue)
|
|
43
|
+
if (lastSourceSnapshot !== UNSET && deepEqualStartValue(lastSourceSnapshot, sourceSnapshot)) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
lastSourceSnapshot = sourceSnapshot
|
|
47
|
+
const detachedValue = detachStartValue(sourceSnapshot)
|
|
42
48
|
// Keep the detached snapshot to avoid aliasing source and target.
|
|
43
49
|
// Old racer start() writes through diffDeep by default. In compat mode we must preserve
|
|
44
50
|
// that behavior, but also avoid reading the target reactively inside start(), otherwise
|
|
45
51
|
// start() subscribes to its own output and local child edits get immediately overwritten.
|
|
46
52
|
const maybePromise = $target.setDiffDeep(detachedValue)
|
|
47
|
-
if (maybePromise?.
|
|
53
|
+
if (maybePromise?.then) {
|
|
54
|
+
maybePromise
|
|
55
|
+
.then(() => {})
|
|
56
|
+
.catch(ignorePromiseRejection)
|
|
57
|
+
}
|
|
48
58
|
}, { scheduler: scheduleReaction })
|
|
49
59
|
store.set(targetKey, { stop: () => unobserve(reaction) })
|
|
50
60
|
return $target
|
|
@@ -134,6 +144,8 @@ function isThenable (value) {
|
|
|
134
144
|
return !!value && typeof value.then === 'function'
|
|
135
145
|
}
|
|
136
146
|
|
|
147
|
+
const UNSET = Symbol('compat start unset')
|
|
148
|
+
|
|
137
149
|
function detachStartValue (value) {
|
|
138
150
|
const rawValue = raw(value)
|
|
139
151
|
if (!rawValue || typeof rawValue !== 'object') return rawValue
|
|
@@ -166,3 +178,30 @@ function racerDeepCopy (value) {
|
|
|
166
178
|
}
|
|
167
179
|
return value
|
|
168
180
|
}
|
|
181
|
+
|
|
182
|
+
function deepEqualStartValue (left, right) {
|
|
183
|
+
if (left === right) return true
|
|
184
|
+
if (Number.isNaN(left) && Number.isNaN(right)) return true
|
|
185
|
+
if (left instanceof Date || right instanceof Date) {
|
|
186
|
+
return left instanceof Date && right instanceof Date && left.getTime() === right.getTime()
|
|
187
|
+
}
|
|
188
|
+
if (!left || !right || typeof left !== 'object' || typeof right !== 'object') return false
|
|
189
|
+
|
|
190
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
191
|
+
if (!Array.isArray(left) || !Array.isArray(right)) return false
|
|
192
|
+
if (left.length !== right.length) return false
|
|
193
|
+
for (let i = 0; i < left.length; i++) {
|
|
194
|
+
if (!deepEqualStartValue(left[i], right[i])) return false
|
|
195
|
+
}
|
|
196
|
+
return true
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const leftKeys = Object.keys(left)
|
|
200
|
+
const rightKeys = Object.keys(right)
|
|
201
|
+
if (leftKeys.length !== rightKeys.length) return false
|
|
202
|
+
for (const key of leftKeys) {
|
|
203
|
+
if (!Object.prototype.hasOwnProperty.call(right, key)) return false
|
|
204
|
+
if (!deepEqualStartValue(left[key], right[key])) return false
|
|
205
|
+
}
|
|
206
|
+
return true
|
|
207
|
+
}
|
package/orm/Doc.js
CHANGED
|
@@ -35,6 +35,34 @@ function getOwningRootId ($doc) {
|
|
|
35
35
|
return rootId
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function deepEqualDocData (left, right) {
|
|
39
|
+
if (left === right) return true
|
|
40
|
+
if (left == null || right == null) return left === right
|
|
41
|
+
|
|
42
|
+
const leftIsArray = Array.isArray(left)
|
|
43
|
+
if (leftIsArray || Array.isArray(right)) {
|
|
44
|
+
if (!leftIsArray || !Array.isArray(right)) return false
|
|
45
|
+
if (left.length !== right.length) return false
|
|
46
|
+
for (let i = 0; i < left.length; i++) {
|
|
47
|
+
if (!deepEqualDocData(left[i], right[i])) return false
|
|
48
|
+
}
|
|
49
|
+
return true
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (typeof left !== 'object' || typeof right !== 'object') return false
|
|
53
|
+
|
|
54
|
+
const leftKeys = Object.keys(left)
|
|
55
|
+
const rightKeys = Object.keys(right)
|
|
56
|
+
if (leftKeys.length !== rightKeys.length) return false
|
|
57
|
+
|
|
58
|
+
for (const key of leftKeys) {
|
|
59
|
+
if (!Object.prototype.hasOwnProperty.call(right, key)) return false
|
|
60
|
+
if (!deepEqualDocData(left[key], right[key])) return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true
|
|
64
|
+
}
|
|
65
|
+
|
|
38
66
|
class Doc {
|
|
39
67
|
initialized
|
|
40
68
|
|
|
@@ -162,6 +190,12 @@ class Doc {
|
|
|
162
190
|
if (isPlainObject(doc.data)) injectIdFields(doc.data, idFields, this.docId)
|
|
163
191
|
const path = [this.collection, this.docId]
|
|
164
192
|
const data = isObservable(doc.data) ? raw(doc.data) : doc.data
|
|
193
|
+
const current = _getRaw(path)
|
|
194
|
+
if (deepEqualDocData(current, data)) {
|
|
195
|
+
if (current != null && current !== raw(doc.data)) doc.data = current
|
|
196
|
+
if (!isObservable(doc.data)) doc.data = observable(doc.data)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
165
199
|
_set(path, data)
|
|
166
200
|
const synced = _getRaw(path)
|
|
167
201
|
if (synced != null && synced !== raw(doc.data)) doc.data = synced
|
package/orm/dataTree.js
CHANGED
|
@@ -73,7 +73,7 @@ export function set (segments, value, tree = dataTree, eventContext) {
|
|
|
73
73
|
const shouldEmit = shouldEmitModelEvents(tree, eventContext)
|
|
74
74
|
const prevValue = shouldEmit ? get(segments, getTreeRaw(tree)) : undefined
|
|
75
75
|
let dataNode = writableTree
|
|
76
|
-
let dataNodeRaw =
|
|
76
|
+
let dataNodeRaw = getTreeRaw(writableTree)
|
|
77
77
|
for (let i = 0; i < segments.length - 1; i++) {
|
|
78
78
|
const segment = segments[i]
|
|
79
79
|
const nextSegment = segments[i + 1]
|
|
@@ -84,34 +84,15 @@ export function set (segments, value, tree = dataTree, eventContext) {
|
|
|
84
84
|
else dataNode[segment] = {}
|
|
85
85
|
}
|
|
86
86
|
dataNode = dataNode[segment]
|
|
87
|
-
dataNodeRaw =
|
|
87
|
+
dataNodeRaw = getTreeRaw(dataNode)
|
|
88
88
|
}
|
|
89
89
|
const key = segments[segments.length - 1]
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
// handle when the value didn't change
|
|
98
|
-
if (value === dataNodeRaw[key]) return
|
|
99
|
-
// handle setting undefined value
|
|
100
|
-
if (value == null) {
|
|
101
|
-
if (Array.isArray(dataNodeRaw)) {
|
|
102
|
-
// if parent is an array -- we set array element to undefined
|
|
103
|
-
// IMPORTANT: JSON serialization will replace `undefined` with `null`
|
|
104
|
-
// so if the data will go to the server, it will be serialized as `null`.
|
|
105
|
-
// And when it comes back from the server it will be still `null`.
|
|
106
|
-
// This can lead to confusion since when you set `undefined` the value
|
|
107
|
-
// might end up becoming `null` for seemingly no reason (like in this case).
|
|
108
|
-
dataNode[key] = undefined
|
|
109
|
-
} else {
|
|
110
|
-
// if parent is an object -- we completely delete the property.
|
|
111
|
-
// Deleting the property is better for the JSON serialization
|
|
112
|
-
// since JSON does not have `undefined` values and replaces them with `null`.
|
|
113
|
-
delete dataNode[key]
|
|
114
|
-
}
|
|
90
|
+
const keyExists = hasOwnDataKey(dataNodeRaw, key)
|
|
91
|
+
// Preserve racer local semantics: assigning undefined creates/keeps the slot/key
|
|
92
|
+
// instead of deleting it, and sparse array writes keep holes intact.
|
|
93
|
+
if (keyExists && value === dataNodeRaw[key]) return
|
|
94
|
+
if (value == null || typeof value !== 'object') {
|
|
95
|
+
dataNode[key] = value
|
|
115
96
|
emitModelEvent(segments, prevValue, { op: 'set' }, tree, eventContext)
|
|
116
97
|
return
|
|
117
98
|
}
|
|
@@ -124,6 +105,12 @@ export function set (segments, value, tree = dataTree, eventContext) {
|
|
|
124
105
|
emitModelEvent(segments, prevValue, { op: 'set' }, tree, eventContext)
|
|
125
106
|
}
|
|
126
107
|
|
|
108
|
+
function hasOwnDataKey (node, key) {
|
|
109
|
+
if (node == null) return false
|
|
110
|
+
if (Array.isArray(node)) return key in node
|
|
111
|
+
return Object.prototype.hasOwnProperty.call(node, key)
|
|
112
|
+
}
|
|
113
|
+
|
|
127
114
|
// Like set(), but always assigns the value without equality checks or delete-on-null behavior
|
|
128
115
|
export function setReplace (segments, value, tree = dataTree, eventContext) {
|
|
129
116
|
const writableTree = getWritableTree(tree)
|
|
@@ -253,7 +240,7 @@ export async function setPublicDoc (segments, value, deleteValue = false) {
|
|
|
253
240
|
if (deleteValue) {
|
|
254
241
|
del(segments.slice(2), newDoc)
|
|
255
242
|
} else {
|
|
256
|
-
set(segments.slice(2), value, newDoc)
|
|
243
|
+
set(segments.slice(2), normalizeUndefined(value), newDoc)
|
|
257
244
|
}
|
|
258
245
|
const diff = jsonDiff(oldDoc, newDoc, diffMatchPatch)
|
|
259
246
|
return new Promise((resolve, reject) => {
|
|
@@ -326,6 +313,13 @@ export async function setPublicDocReplace (segments, value) {
|
|
|
326
313
|
}
|
|
327
314
|
|
|
328
315
|
const relativePath = segments.slice(2)
|
|
316
|
+
// json0 direct replace ops require every ancestor container to already exist.
|
|
317
|
+
// Racer-like compat set, however, materializes missing/primitive parents while
|
|
318
|
+
// descending into the path. Fall back to the older diff-based path when the
|
|
319
|
+
// direct op would target a non-existent/non-object ancestor.
|
|
320
|
+
if (!canApplyDirectReplaceOp(docState.snapshot || {}, relativePath)) {
|
|
321
|
+
return setPublicDoc(segments, value)
|
|
322
|
+
}
|
|
329
323
|
const previous = getRaw(segments)
|
|
330
324
|
const normalizedPrevious = normalizeUndefined(
|
|
331
325
|
relativePath.length === 0 ? stripIdFields(previous, idFields) : previous
|
|
@@ -342,7 +336,13 @@ export async function setPublicDocReplace (segments, value) {
|
|
|
342
336
|
return new Promise((resolve, reject) => {
|
|
343
337
|
doc.submitOp(op, err => {
|
|
344
338
|
if (err) return reject(err)
|
|
345
|
-
|
|
339
|
+
syncLocalDocAfterPublicWrite({
|
|
340
|
+
collection,
|
|
341
|
+
docId,
|
|
342
|
+
doc,
|
|
343
|
+
idFields,
|
|
344
|
+
relativePath
|
|
345
|
+
})
|
|
346
346
|
resolve()
|
|
347
347
|
})
|
|
348
348
|
})
|
|
@@ -440,10 +440,47 @@ function ensureLocalDocSyncedWithShareDoc ({
|
|
|
440
440
|
setReplace([collection, docId], shared)
|
|
441
441
|
}
|
|
442
442
|
|
|
443
|
+
function syncLocalDocAfterPublicWrite ({
|
|
444
|
+
collection,
|
|
445
|
+
docId,
|
|
446
|
+
doc,
|
|
447
|
+
idFields,
|
|
448
|
+
relativePath = []
|
|
449
|
+
}) {
|
|
450
|
+
if (!Array.isArray(relativePath) || relativePath.length === 0) {
|
|
451
|
+
ensureLocalDocSyncedWithShareDoc({ collection, docId, doc, idFields })
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
if (isMissingShareDoc(doc)) return
|
|
455
|
+
if (doc?.data == null) return
|
|
456
|
+
const shared = raw(doc.data)
|
|
457
|
+
const nextValue = get(relativePath, shared)
|
|
458
|
+
setReplace([collection, docId, ...relativePath], clonePublicLocalSyncValue(nextValue))
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function clonePublicLocalSyncValue (value) {
|
|
462
|
+
const rawValue = raw(value)
|
|
463
|
+
if (rawValue == null || typeof rawValue !== 'object') return rawValue
|
|
464
|
+
if (typeof globalThis.structuredClone === 'function') {
|
|
465
|
+
return globalThis.structuredClone(rawValue)
|
|
466
|
+
}
|
|
467
|
+
return JSON.parse(JSON.stringify(rawValue))
|
|
468
|
+
}
|
|
469
|
+
|
|
443
470
|
function normalizeUndefined (value) {
|
|
444
471
|
return value === undefined ? null : value
|
|
445
472
|
}
|
|
446
473
|
|
|
474
|
+
function canApplyDirectReplaceOp (docSnapshot, relativePath) {
|
|
475
|
+
if (relativePath.length === 0) return true
|
|
476
|
+
let node = docSnapshot
|
|
477
|
+
for (let i = 0; i < relativePath.length - 1; i++) {
|
|
478
|
+
if (node == null || typeof node !== 'object') return false
|
|
479
|
+
node = node[relativePath[i]]
|
|
480
|
+
}
|
|
481
|
+
return node != null && typeof node === 'object'
|
|
482
|
+
}
|
|
483
|
+
|
|
447
484
|
function normalizeValueForOp (value) {
|
|
448
485
|
let result = raw(value)
|
|
449
486
|
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.
|
|
3
|
+
"version": "0.4.0-alpha.99",
|
|
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": "
|
|
86
|
+
"gitHead": "22d0358b80fa29754147b4318975b26fff3eae5a"
|
|
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
|