teamplay 0.4.0-alpha.71 → 0.4.0-alpha.72
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/README.md
CHANGED
|
@@ -169,10 +169,39 @@ $alias.get() // { name: 'Bob' }
|
|
|
169
169
|
$alias.get() === $user.get() // false
|
|
170
170
|
```
|
|
171
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
|
+
|
|
172
202
|
**Limitations vs Racer**
|
|
173
|
-
- No `refList`, `
|
|
203
|
+
- No `refList`, `refMap`.
|
|
174
204
|
- No automatic list index patching on insert/remove/move.
|
|
175
|
-
- No support for query/aggregation refs.
|
|
176
205
|
- No event emissions specific to refs.
|
|
177
206
|
- No support for racer-style ref meta/options beyond the basic signature.
|
|
178
207
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { raw } from '@nx-js/observer-util'
|
|
1
|
+
import { raw, observe, unobserve } from '@nx-js/observer-util'
|
|
2
2
|
import {
|
|
3
3
|
Signal,
|
|
4
4
|
GETTERS,
|
|
@@ -601,10 +601,19 @@ class SignalCompat extends Signal {
|
|
|
601
601
|
const fromPath = $from.path()
|
|
602
602
|
const existing = store.get(fromPath)
|
|
603
603
|
if (existing) existing.stop()
|
|
604
|
-
const
|
|
604
|
+
const mirrorOnly = !!($to?.[IS_QUERY] || $to?.[IS_AGGREGATION])
|
|
605
|
+
const { stop, onChange } = createRefLink($from, $to, { mirrorOnly, options })
|
|
605
606
|
store.set(fromPath, { stop })
|
|
606
|
-
|
|
607
|
-
|
|
607
|
+
if (!mirrorOnly) {
|
|
608
|
+
$from[REF_TARGET] = $to
|
|
609
|
+
setRefLink(fromPath, $to.path(), $from[SEGMENTS], $to[SEGMENTS], { mirrorOnly: false })
|
|
610
|
+
} else {
|
|
611
|
+
setRefLink(fromPath, $to.path(), $from[SEGMENTS], $to[SEGMENTS], {
|
|
612
|
+
mirrorOnly: true,
|
|
613
|
+
onChange
|
|
614
|
+
})
|
|
615
|
+
if ($from[REF_TARGET]) delete $from[REF_TARGET]
|
|
616
|
+
}
|
|
608
617
|
return $from
|
|
609
618
|
}
|
|
610
619
|
|
|
@@ -715,23 +724,55 @@ function createSilentSignalWrapper ($signal, enabled = true) {
|
|
|
715
724
|
}
|
|
716
725
|
|
|
717
726
|
const REFS = Symbol('compat refs')
|
|
718
|
-
const SKIP_REF_TICK = Symbol('compat ref skip tick')
|
|
719
|
-
|
|
720
727
|
function getRefStore ($signal) {
|
|
721
728
|
const $root = getRoot($signal) || $signal
|
|
722
729
|
$root[REFS] ??= new Map()
|
|
723
730
|
return $root[REFS]
|
|
724
731
|
}
|
|
725
732
|
|
|
726
|
-
function createRefLink ($from, $to) {
|
|
733
|
+
function createRefLink ($from, $to, { mirrorOnly = false } = {}) {
|
|
734
|
+
let disposed = false
|
|
735
|
+
let pendingRead = null
|
|
736
|
+
let mirrorObserver
|
|
737
|
+
|
|
727
738
|
const syncFromTarget = () => {
|
|
728
739
|
const value = readRefValue($to)
|
|
729
|
-
if (value
|
|
740
|
+
if (isThenable(value)) {
|
|
741
|
+
pendingRead = value
|
|
742
|
+
value.then(() => {
|
|
743
|
+
if (disposed || pendingRead !== value) return
|
|
744
|
+
pendingRead = null
|
|
745
|
+
syncFromTarget()
|
|
746
|
+
}, () => {
|
|
747
|
+
if (pendingRead === value) pendingRead = null
|
|
748
|
+
})
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
if (value === undefined) return
|
|
730
752
|
setDiffDeepBypassRef($from, deepCopy(value))
|
|
731
753
|
}
|
|
754
|
+
|
|
732
755
|
syncFromTarget()
|
|
733
|
-
|
|
734
|
-
|
|
756
|
+
if (mirrorOnly) {
|
|
757
|
+
mirrorObserver = observe(
|
|
758
|
+
() => {
|
|
759
|
+
syncFromTarget()
|
|
760
|
+
return readRefValue($to)
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
scheduler: job => job()
|
|
764
|
+
}
|
|
765
|
+
)
|
|
766
|
+
// initialize dependency graph
|
|
767
|
+
mirrorObserver()
|
|
768
|
+
}
|
|
769
|
+
return {
|
|
770
|
+
onChange: syncFromTarget,
|
|
771
|
+
stop: () => {
|
|
772
|
+
disposed = true
|
|
773
|
+
if (mirrorObserver) unobserve(mirrorObserver)
|
|
774
|
+
// Subsequent sync happens directly at mutation time via mirrorRefMutationFromTarget().
|
|
775
|
+
}
|
|
735
776
|
}
|
|
736
777
|
}
|
|
737
778
|
|
|
@@ -739,7 +780,7 @@ function readRefValue ($signal) {
|
|
|
739
780
|
try {
|
|
740
781
|
return $signal.get()
|
|
741
782
|
} catch (err) {
|
|
742
|
-
if (isThenable(err)) return
|
|
783
|
+
if (isThenable(err)) return err
|
|
743
784
|
throw err
|
|
744
785
|
}
|
|
745
786
|
}
|
|
@@ -66,6 +66,9 @@ export function emitModelChange (path, value, prevValue, meta) {
|
|
|
66
66
|
|
|
67
67
|
for (const link of getRefLinks().values()) {
|
|
68
68
|
if (!isPathPrefix(link.toSegments, segments)) continue
|
|
69
|
+
if (link.mirrorOnly && typeof link.onChange === 'function') {
|
|
70
|
+
link.onChange()
|
|
71
|
+
}
|
|
69
72
|
const suffix = segments.slice(link.toSegments.length)
|
|
70
73
|
const nextSegments = link.fromSegments.concat(suffix)
|
|
71
74
|
const nextKey = nextSegments.join('.')
|
|
@@ -39,6 +39,7 @@ export function resolveRefSegmentsSafe (segments, maxDepth = 32) {
|
|
|
39
39
|
function findBestMatchingLink (segments) {
|
|
40
40
|
let best
|
|
41
41
|
for (const link of getRefLinks().values()) {
|
|
42
|
+
if (link.mirrorOnly) continue
|
|
42
43
|
if (!isPathPrefix(link.fromSegments, segments)) continue
|
|
43
44
|
if (!best || link.fromSegments.length > best.fromSegments.length) {
|
|
44
45
|
best = link
|
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
const refLinks = new Map()
|
|
2
2
|
|
|
3
|
-
export function setRefLink (fromPath, toPath) {
|
|
3
|
+
export function setRefLink (fromPath, toPath, fromSegments, toSegments, options = {}) {
|
|
4
4
|
if (typeof fromPath !== 'string' || typeof toPath !== 'string') return
|
|
5
|
+
const normalizedFromSegments = Array.isArray(fromSegments)
|
|
6
|
+
? fromSegments.map(segment => String(segment))
|
|
7
|
+
: splitPath(fromPath)
|
|
8
|
+
const normalizedToSegments = Array.isArray(toSegments)
|
|
9
|
+
? toSegments.map(segment => String(segment))
|
|
10
|
+
: splitPath(toPath)
|
|
5
11
|
refLinks.set(fromPath, {
|
|
6
12
|
fromPath,
|
|
7
13
|
toPath,
|
|
8
|
-
fromSegments:
|
|
9
|
-
toSegments:
|
|
14
|
+
fromSegments: normalizedFromSegments,
|
|
15
|
+
toSegments: normalizedToSegments,
|
|
16
|
+
mirrorOnly: !!options.mirrorOnly,
|
|
17
|
+
onChange: typeof options.onChange === 'function' ? options.onChange : undefined
|
|
10
18
|
})
|
|
11
19
|
}
|
|
12
20
|
|
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.72",
|
|
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": "204b5dd49e950162c6930bb9fcfbceeea51382ec"
|
|
87
87
|
}
|