teamplay 0.4.0-alpha.7 → 0.4.0-alpha.9

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.
@@ -168,7 +168,9 @@ class SignalCompat extends Signal {
168
168
  if (isRootCollectionCall) {
169
169
  if (arguments.length !== 2) throw Error('Signal.add() expects (collection, object)')
170
170
  if (!value || typeof value !== 'object') throw Error('Signal.add() expects an object argument')
171
- return this[collectionOrValue].add(value)
171
+ const $root = getRoot(this) || this
172
+ const $collection = resolveSignal($root, [collectionOrValue])
173
+ return $collection.add(value)
172
174
  }
173
175
 
174
176
  if (arguments.length > 1) throw Error('Signal.add() expects a single argument')
package/orm/getSignal.js CHANGED
@@ -49,6 +49,9 @@ export default function getSignal ($root, segments = [], {
49
49
  // but without it calling the methods of root signal like $.get() doesn't work
50
50
  proxy[ROOT] = $root || getSignal(undefined, [], { rootId: GLOBAL_ROOT_ID })
51
51
  }
52
+ signal[ROOT] = proxy[ROOT]
53
+ } else {
54
+ signal[ROOT] = proxy
52
55
  }
53
56
  PROXY_TO_SIGNAL.set(proxy, signal)
54
57
  const dependencies = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.4.0-alpha.7",
3
+ "version": "0.4.0-alpha.9",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -34,12 +34,12 @@
34
34
  "dependencies": {
35
35
  "@nx-js/observer-util": "^4.1.3",
36
36
  "@startupjs/sharedb-mingo-memory": "^4.0.0-2",
37
- "@teamplay/backend": "^0.4.0-alpha.1",
38
- "@teamplay/cache": "^0.4.0-alpha.1",
39
- "@teamplay/channel": "^0.4.0-alpha.1",
40
- "@teamplay/debug": "^0.4.0-alpha.1",
41
- "@teamplay/schema": "^0.4.0-alpha.1",
42
- "@teamplay/utils": "^0.4.0-alpha.1",
37
+ "@teamplay/backend": "^0.4.0-alpha.9",
38
+ "@teamplay/cache": "^0.4.0-alpha.9",
39
+ "@teamplay/channel": "^0.4.0-alpha.9",
40
+ "@teamplay/debug": "^0.4.0-alpha.9",
41
+ "@teamplay/schema": "^0.4.0-alpha.9",
42
+ "@teamplay/utils": "^0.4.0-alpha.9",
43
43
  "diff-match-patch": "^1.0.5",
44
44
  "events": "^3.3.0",
45
45
  "json0-ot-diff": "^1.1.2",
@@ -80,5 +80,6 @@
80
80
  "<rootDir>/test_client/helpers"
81
81
  ]
82
82
  },
83
- "license": "MIT"
83
+ "license": "MIT",
84
+ "gitHead": "df4107d9fda0cdf2474c0dffd1c4f3c9527c8168"
84
85
  }
package/orm/Compat/REF.md DELETED
@@ -1,315 +0,0 @@
1
- # SignalCompat `ref` / `removeRef` — Compatibility Draft
2
-
3
- This document captures a **draft** implementation of StartupJS/Racer-style `ref` behavior for Teamplay’s `SignalCompat`.
4
-
5
- It is **not active in code** right now. The goal is to discuss and decide whether we want to bring it back, and in what form.
6
-
7
- ---
8
-
9
- ## 1) Why we need this
10
-
11
- In LMS there are a few **real usages** of model refs (not React DOM refs):
12
-
13
- - `components/Media/index.js`
14
- ```js
15
- if ($fullscreen) $localFullscreen.ref($fullscreen)
16
- ```
17
- - `main/components/FilterV2/index.js`
18
- ```js
19
- if (!isMultiSelect) $localValue.ref($value)
20
- ```
21
- - `main/Layout/Tutoring/index.js` and `v5/apps/main/Layout/Tutoring/index.js`
22
- ```js
23
- $session.ref('tutoringSession', $tutoringSession)
24
- $session.removeRef('tutoringSession')
25
- ```
26
-
27
- These use the **Racer model ref**, which effectively makes one path behave like another path (alias). Teamplay doesn’t have this concept, so we explored a minimal compat layer.
28
-
29
- ---
30
-
31
- ## 2) Target API (minimal subset)
32
-
33
- We only target what LMS actually uses:
34
-
35
- ### `ref(target)`
36
-
37
- ```js
38
- $local.ref($.users.user1)
39
- ```
40
-
41
- This means `$local` mirrors `$users.user1` and mutating `$local` mutates `$users.user1`.
42
-
43
- ### `ref(subpath, target)`
44
-
45
- ```js
46
- $session.ref('tutoringSession', $tutoringSession)
47
- ```
48
-
49
- This means `$session.tutoringSession` acts as an alias to `$tutoringSession`.
50
-
51
- ### `removeRef(path?)`
52
-
53
- ```js
54
- $local.removeRef()
55
- $session.removeRef('tutoringSession')
56
- ```
57
-
58
- Stops syncing.
59
-
60
- ---
61
-
62
- ## 3) Semantics vs Racer
63
-
64
- Racer refs are deep and complicated (they respond to all model events, including array insert/remove/move, etc).
65
-
66
- This draft **only covers**:
67
- - Signal-level aliasing (one signal proxies another).
68
- - No `refList`, `refExtra`, `refMap`.
69
- - No automatic path-patching for list inserts/moves.
70
-
71
- It should be enough for current LMS usages.
72
-
73
- ---
74
-
75
- ## 4) Draft Implementation Strategy
76
-
77
- ### 4.1 Keep a ref store on root
78
-
79
- We store refs on root signal:
80
-
81
- ```js
82
- const REFS = Symbol('compat refs')
83
- $root[REFS] = new Map()
84
- ```
85
-
86
- Each entry is keyed by `fromPath` and stores `{ stop }` cleanup.
87
-
88
- ### 4.2 One-way reactive sync (target → alias)
89
-
90
- We use `@nx-js/observer-util` `observe()` to track target changes and push them into alias:
91
-
92
- ```js
93
- const toReaction = observe(() => {
94
- const value = $to.get()
95
- trackDeep(value)
96
- setDiffDeepBypassRef($from, deepCopy(value))
97
- }, { lazy: true })
98
-
99
- toReaction()
100
- ```
101
-
102
- Why deep copy?
103
- - Without it, `setDiffDeep` can re-use same object references and skip updates.
104
- - Deep copy ensures the diffing path detects change.
105
-
106
- ### 4.3 Forward all mutations from alias → target
107
-
108
- To avoid two reactions and feedback loops, we forward all mutator calls:
109
-
110
- - `set`, `setNull`, `setDiffDeep`, `setEach`
111
- - `del`
112
- - `increment`
113
- - `push`, `unshift`, `insert`, `pop`, `shift`, `remove`, `move`
114
- - `stringInsert`, `stringRemove`
115
- - `assign`
116
-
117
- Forwarding uses a hidden `REF_TARGET` symbol on the alias signal.
118
-
119
- ### 4.4 Mutator forward mechanism
120
-
121
- On each mutator:
122
-
123
- ```js
124
- const forwarded = forwardRef(this, 'set', arguments)
125
- if (forwarded) return forwarded
126
- ```
127
-
128
- `forwardRef()` resolves to a target signal if present and applies the same method there.
129
-
130
- ---
131
-
132
- ## 5) Draft Code (for later restoration)
133
-
134
- Below is the exact code we removed from `SignalCompat.js`. It can be re-applied as-is.
135
-
136
- ### 5.1 Imports (add back)
137
-
138
- ```js
139
- import { raw, observe, unobserve } from '@nx-js/observer-util'
140
- ```
141
-
142
- ### 5.2 Symbols and helpers (add near other helpers)
143
-
144
- ```js
145
- const REFS = Symbol('compat refs')
146
- const REF_TARGET = Symbol('compat ref target')
147
-
148
- function getRefStore ($signal) {
149
- const $root = getRoot($signal) || $signal
150
- $root[REFS] ??= new Map()
151
- return $root[REFS]
152
- }
153
-
154
- function createRefLink ($from, $to) {
155
- const toReaction = observe(() => {
156
- const value = $to.get()
157
- trackDeep(value)
158
- setDiffDeepBypassRef($from, deepCopy(value))
159
- }, { lazy: true })
160
-
161
- // Prime sync and start tracking.
162
- toReaction()
163
- return () => {
164
- unobserve(toReaction)
165
- }
166
- }
167
-
168
- function trackDeep (value, seen = new Set()) {
169
- if (!value || typeof value !== 'object') return
170
- if (seen.has(value)) return
171
- seen.add(value)
172
- if (Array.isArray(value)) {
173
- for (const item of value) trackDeep(item, seen)
174
- } else {
175
- for (const key in value) {
176
- if (Object.prototype.hasOwnProperty.call(value, key)) {
177
- trackDeep(value[key], seen)
178
- }
179
- }
180
- }
181
- }
182
-
183
- function resolveRefSignal ($signal) {
184
- let current = $signal
185
- const seen = new Set()
186
- while (current && current[REF_TARGET]) {
187
- if (seen.has(current)) break
188
- seen.add(current)
189
- current = current[REF_TARGET]
190
- }
191
- return current
192
- }
193
-
194
- function forwardRef ($signal, methodName, args) {
195
- const $target = resolveRefSignal($signal)
196
- if ($target === $signal) return null
197
- return SignalCompat.prototype[methodName].apply($target, args)
198
- }
199
-
200
- function setDiffDeepBypassRef ($signal, value) {
201
- return Signal.prototype.set.call($signal, value)
202
- }
203
- ```
204
-
205
- ### 5.3 `ref()` / `removeRef()` methods (add to `SignalCompat`)
206
-
207
- ```js
208
- ref (path, target, options) {
209
- if (arguments.length > 3) throw Error('Signal.ref() expects one to three arguments')
210
- let $from = this
211
- let $to
212
- if (arguments.length === 1) {
213
- $to = resolveRefTarget(this, path, 'Signal.ref()')
214
- } else if (arguments.length === 2) {
215
- if (isSignalLike(target) || typeof target === 'string') {
216
- const segments = parseAtSubpath(path, 1, 'Signal.ref()')
217
- $from = resolveSignal(this, segments)
218
- $to = resolveRefTarget(this, target, 'Signal.ref()')
219
- } else {
220
- $to = resolveRefTarget(this, path, 'Signal.ref()')
221
- options = target
222
- }
223
- } else {
224
- const segments = parseAtSubpath(path, 1, 'Signal.ref()')
225
- $from = resolveSignal(this, segments)
226
- $to = resolveRefTarget(this, target, 'Signal.ref()')
227
- }
228
- if (!$to) throw Error('Signal.ref() expects a target path or signal')
229
- if ($from === $to) return $from
230
- const store = getRefStore($from)
231
- const fromPath = $from.path()
232
- const existing = store.get(fromPath)
233
- if (existing) existing.stop()
234
- const stop = createRefLink($from, $to, options)
235
- store.set(fromPath, { stop })
236
- $from[REF_TARGET] = $to
237
- return $from
238
- }
239
-
240
- removeRef (path) {
241
- if (arguments.length > 1) throw Error('Signal.removeRef() expects a single argument')
242
- let $from = this
243
- if (arguments.length === 1) {
244
- const segments = parseAtSubpath(path, 1, 'Signal.removeRef()')
245
- $from = resolveSignal(this, segments)
246
- }
247
- const store = getRefStore($from)
248
- const fromPath = $from.path()
249
- const existing = store.get(fromPath)
250
- if (existing) {
251
- existing.stop()
252
- store.delete(fromPath)
253
- }
254
- if ($from[REF_TARGET]) delete $from[REF_TARGET]
255
- }
256
- ```
257
-
258
- ### 5.4 Forwarding mutations (add to each mutator)
259
-
260
- Example for `set()`:
261
-
262
- ```js
263
- async set (path, value) {
264
- const forwarded = forwardRef(this, 'set', arguments)
265
- if (forwarded) return forwarded
266
- // ...existing body
267
- }
268
- ```
269
-
270
- Same pattern for:
271
- - `setNull`, `setDiffDeep`, `setEach`
272
- - `del`
273
- - `increment`
274
- - `push`, `unshift`, `insert`, `pop`, `shift`, `remove`, `move`
275
- - `stringInsert`, `stringRemove`
276
- - `assign`
277
-
278
- ### 5.5 Supporting helpers (only needed with ref)
279
-
280
- ```js
281
- function isSignalLike (value) {
282
- return value && typeof value.path === 'function' && typeof value.get === 'function'
283
- }
284
-
285
- function resolveRefTarget ($signal, target, methodName) {
286
- if (isSignalLike(target)) return target
287
- if (typeof target === 'string') {
288
- const segments = parseAtSubpath(target, 1, methodName)
289
- const $root = getRoot($signal) || $signal
290
- return resolveSignal($root, segments)
291
- }
292
- return undefined
293
- }
294
- ```
295
-
296
- ---
297
-
298
- ## 6) Draft tests (removed)
299
-
300
- We also had tests in `packages/teamplay/test/signalCompat.js`. They can be restored if needed:
301
-
302
- - `syncs values both ways for direct signals`
303
- - `supports subpath refs from root`
304
- - `removeRef stops syncing`
305
-
306
- ---
307
-
308
- ## 7) Risks and limitations
309
-
310
- - This is **not a full racer ref** implementation.
311
- - No support for `refList`, `refExtra`, `refMap`.
312
- - No array index patching when list changes.
313
- - Might not handle exotic cases with cyclic refs.
314
-
315
- That said, it’s deliberately scoped to known LMS usage patterns and should be “good enough” for those.