teamplay 0.4.0-alpha.26 → 0.4.0-alpha.27

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.
@@ -353,6 +353,19 @@ for (const $doc of $query) {
353
353
  }
354
354
  ```
355
355
 
356
+ ### Mutator Semantics (Core vs Compat)
357
+
358
+ Compatibility mode intentionally aligns mutators with Racer. This differs from core `Signal` behavior.
359
+
360
+ | API | Core (`Signal`) | Compat (`SignalCompat`) |
361
+ | --- | --- | --- |
362
+ | `set` | Uses deep-diff path (`dataTree.set` + internal `setDiffDeep`). | Path-targeted replace semantics, Racer-like. `undefined` keeps delete semantics. |
363
+ | `setEach` | Not a special API in core mutators. | Per-key compat `set` (not `assign` merge/delete behavior). |
364
+ | `setDiffDeep` | Deep-diff engine (`utils/setDiffDeep.js`). | Explicit deep-diff mutator (`SignalCompat.setDiffDeep`) using base deep-diff path. |
365
+ | `setDiff` | N/A as compat shim. | `setDiff(value)` -> base `Signal.set(value)` on current signal. `setDiff(path, value)` -> compat `set(path, value)`. |
366
+
367
+ Migration note: compat behavior is intentionally Racer-aligned and may differ from core mutators.
368
+
356
369
  ### set(value) and set(path, value)
357
370
 
358
371
  `SignalCompat` accepts both:
@@ -362,7 +375,14 @@ $.users.user1.name.set('Alice')
362
375
  $.users.user1.set('profile.name', 'Alice')
363
376
  ```
364
377
 
365
- In compat mode, `set` replaces values at the target path.
378
+ In compat mode, `set` replaces the value at the target path.
379
+ - `set(path, null)` stores `null`.
380
+ - `set(path, undefined)` applies current delete semantics.
381
+
382
+ ```js
383
+ await $.users.user1.set('profile', { name: 'Ann', role: 'student' })
384
+ await $.users.user1.set('profile', { name: 'Kate' }) // role is removed
385
+ ```
366
386
 
367
387
  ### setNull(path?, value)
368
388
 
@@ -377,23 +397,43 @@ $.config.setNull('theme', 'light')
377
397
  Applies a diff-deep update (uses base `Signal.set` internally).
378
398
 
379
399
  ```js
380
- $.users.user1.setDiffDeep({ profile: { name: 'Alice' } })
400
+ await $.users.user1.set({ profile: { name: 'Ann', role: 'student' } })
401
+ await $.users.user1.setDiffDeep({ profile: { name: 'Kate' } }) // deep-diff path
381
402
  ```
382
403
 
383
404
  ### setDiff(path?, value)
384
405
 
385
- Alias for `set()` in compat. Accepts the same arguments and semantics.
406
+ `setDiff` has two branches in compat:
407
+ - `setDiff(value)` calls base `Signal.set(value)` on current signal (deep-diff semantics).
408
+ - `setDiff(path, value)` delegates to compat `set(path, value)`.
386
409
 
387
410
  ```js
388
- $.users.user1.setDiff({ profile: { name: 'Alice' } })
411
+ await $.users.user1.setDiff({ profile: { name: 'Kate' } })
412
+ await $.users.user1.setDiff('profile', { name: 'Bob' }) // compat set semantics
389
413
  ```
390
414
 
391
415
  ### setEach(path?, object)
392
416
 
393
- Shorthand for assign. Sets or deletes fields from an object.
417
+ Racer-like per-key set. `setEach` iterates keys and applies compat `set` for each key.
418
+ - `setEach({ k: null })` stores `null`.
419
+ - `setEach({ k: undefined })` applies current delete semantics.
420
+
421
+ ```js
422
+ await $.users.user1.setEach({ name: 'Bob', age: null })
423
+ ```
424
+
425
+ ### Null / Undefined Matrix (Compat)
426
+
427
+ | Call | Result |
428
+ | --- | --- |
429
+ | `set(path, null)` | stores `null` at `path` |
430
+ | `set(path, undefined)` | applies delete semantics at `path` |
431
+ | `setEach({ k: null })` | stores `null` for `k` |
432
+ | `setEach({ k: undefined })` | applies delete semantics for `k` |
394
433
 
395
434
  ```js
396
- $.users.user1.setEach({ name: 'Bob', age: 30 })
435
+ await $.users.user1.set('status', null) // status === null
436
+ await $.users.user1.setEach({ status: undefined }) // status deleted
397
437
  ```
398
438
 
399
439
  ### assign(object)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.4.0-alpha.26",
3
+ "version": "0.4.0-alpha.27",
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": "659791230881f0419bf28dc983f2624f6a05d076"
84
+ "gitHead": "f7a6412190f8b2ec9322331ad2dab7b8ac452f53"
85
85
  }
@@ -4,6 +4,12 @@ function isReactLike (value) {
4
4
  return !!(value && typeof value === 'object' && typeof value.$$typeof === 'symbol')
5
5
  }
6
6
 
7
+ function canDeepMutateObject (existing, updated) {
8
+ if (!isPlainObject(existing) || !isPlainObject(updated)) return false
9
+ if (isReactLike(existing) || isReactLike(updated)) return false
10
+ return true
11
+ }
12
+
7
13
  export default function setDiffDeep (existing, updated) {
8
14
  // Handle primitive types, null, and type mismatches
9
15
  if (existing === null || updated === null ||
@@ -16,30 +22,37 @@ export default function setDiffDeep (existing, updated) {
16
22
  // so we just return the original reference
17
23
  if (existing === updated) return existing
18
24
 
25
+ if (isReactLike(existing) || isReactLike(updated)) return updated
26
+
19
27
  // Handle arrays
20
28
  if (Array.isArray(updated)) {
21
- existing.length = updated.length
22
- for (let i = 0; i < updated.length; i++) {
23
- existing[i] = setDiffDeep(existing[i], updated[i])
29
+ try {
30
+ if (!Reflect.set(existing, 'length', updated.length)) return updated
31
+ for (let i = 0; i < updated.length; i++) {
32
+ const nextValue = setDiffDeep(existing[i], updated[i])
33
+ if (!Reflect.set(existing, i, nextValue)) return updated
34
+ }
35
+ return existing
36
+ } catch {
37
+ return updated
24
38
  }
25
- return existing
26
39
  }
27
40
 
28
- // React elements are plain objects but must be treated as non-plain
29
- if (isReactLike(updated)) return updated
30
-
31
41
  // Handle non-plain objects - just return them as-is to fully overwrite
32
42
  // and don't try to update an old object in-place
33
- if (!isPlainObject(updated)) return updated
43
+ if (!canDeepMutateObject(existing, updated)) return updated
34
44
 
35
45
  // Handle objects
36
- for (const key in existing) {
37
- if (!(key in updated)) {
38
- delete existing[key]
46
+ try {
47
+ for (const key of Object.keys(existing)) {
48
+ if (!(key in updated) && !Reflect.deleteProperty(existing, key)) return updated
39
49
  }
50
+ for (const key of Object.keys(updated)) {
51
+ const nextValue = setDiffDeep(existing[key], updated[key])
52
+ if (!Reflect.set(existing, key, nextValue)) return updated
53
+ }
54
+ return existing
55
+ } catch {
56
+ return updated
40
57
  }
41
- for (const key in updated) {
42
- existing[key] = setDiffDeep(existing[key], updated[key])
43
- }
44
- return existing
45
58
  }