teamplay 0.5.0-alpha.18 → 0.5.0-alpha.19
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.
|
@@ -21,7 +21,7 @@ import disposeRootContext from "../disposeRootContext.js";
|
|
|
21
21
|
import { arrayInsertPrivateData, arrayMovePrivateData, arrayPopPrivateData, arrayPushPrivateData, arrayRemovePrivateData, arrayShiftPrivateData, arrayUnshiftPrivateData, delPrivateData, setReplacePrivateData, stringInsertPrivateData, stringRemovePrivateData } from '../privateData.js';
|
|
22
22
|
class SignalCompat extends Signal {
|
|
23
23
|
static ID_FIELDS = ['_id', 'id'];
|
|
24
|
-
static [GETTERS] =
|
|
24
|
+
static [GETTERS] = DEFAULT_GETTERS;
|
|
25
25
|
path() {
|
|
26
26
|
if (arguments.length > 0)
|
|
27
27
|
throw Error('Signal.path() does not accept any arguments');
|
package/dist/orm/SignalBase.d.ts
CHANGED
|
@@ -54,6 +54,12 @@ export declare class Signal<TValue = unknown> extends Function {
|
|
|
54
54
|
get(): TValue;
|
|
55
55
|
/** Return document ids for a query or aggregation signal. */
|
|
56
56
|
getIds(): string[];
|
|
57
|
+
/** Return query extra data, aggregation data, or undefined for ordinary signals. */
|
|
58
|
+
getExtra(): unknown;
|
|
59
|
+
/** Return a shallow copy of the current value. */
|
|
60
|
+
getCopy(): TValue;
|
|
61
|
+
/** Return a deep copy of the current value. */
|
|
62
|
+
getDeepCopy(): TValue;
|
|
57
63
|
/** Read the current value without tracking it for reactive rendering. */
|
|
58
64
|
peek(): TValue;
|
|
59
65
|
/** Return the document id represented by this document signal. */
|
|
@@ -105,6 +111,18 @@ export declare class Signal<TValue = unknown> extends Function {
|
|
|
105
111
|
* @param value New value to store at this signal path.
|
|
106
112
|
*/
|
|
107
113
|
setReplace(value: TValue): Promise<void>;
|
|
114
|
+
/** Set the current value only when it is null or undefined. */
|
|
115
|
+
setNull(value: TValue): Promise<void>;
|
|
116
|
+
/** Replace the current value unless it is exactly equal to the new value. */
|
|
117
|
+
setDiff(value: TValue): Promise<void>;
|
|
118
|
+
/** Recursively diff objects and arrays at the current signal path. */
|
|
119
|
+
setDiffDeep(value: TValue): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
* Set multiple object fields with per-key replace semantics.
|
|
122
|
+
* Unlike assign(), null is stored as null and undefined follows setReplace() semantics.
|
|
123
|
+
* @param object Object containing fields to set.
|
|
124
|
+
*/
|
|
125
|
+
setEach(object: NonNullable<TValue> extends object ? Partial<NonNullable<TValue>> : never): Promise<void>;
|
|
108
126
|
/**
|
|
109
127
|
* Set multiple object fields at once. Fields set to `null` or `undefined` are deleted.
|
|
110
128
|
* @param value Object containing fields to set or delete.
|
package/dist/orm/SignalBase.js
CHANGED
|
@@ -12,15 +12,17 @@
|
|
|
12
12
|
* 3. If extremely late bindings are enabled, to prevent name collisions when accessing fields
|
|
13
13
|
* in the raw data tree which have the same name as signal's methods
|
|
14
14
|
*/
|
|
15
|
+
import { raw } from '@nx-js/observer-util';
|
|
16
|
+
import arrayDiff from 'arraydiff';
|
|
15
17
|
import uuid from '@teamplay/utils/uuid';
|
|
16
18
|
import { get as _get, setPublicDoc as _setPublicDoc, setPublicDocReplace as _setPublicDocReplace, del as _del, dataTreeRaw, getRaw, getLogicalRootSnapshot, incrementPublic as _incrementPublic, arrayPushPublic as _arrayPushPublic, arrayUnshiftPublic as _arrayUnshiftPublic, arrayInsertPublic as _arrayInsertPublic, arrayPopPublic as _arrayPopPublic, arrayShiftPublic as _arrayShiftPublic, arrayRemovePublic as _arrayRemovePublic, arrayMovePublic as _arrayMovePublic, stringInsertPublic as _stringInsertPublic, stringRemovePublic as _stringRemovePublic } from './dataTree.js';
|
|
17
19
|
import getSignal, { rawSignal } from "./getSignal.js";
|
|
18
20
|
import { docSubscriptions } from './Doc.js';
|
|
19
21
|
import { IS_QUERY, HASH, QUERIES } from './Query.js';
|
|
20
|
-
import { AGGREGATIONS, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js';
|
|
22
|
+
import { AGGREGATIONS, IS_AGGREGATION, getAggregationCollectionName, getAggregationDocId } from './Aggregation.js';
|
|
21
23
|
import { ROOT_FUNCTION, ROOT_ID, getRoot } from "./Root.js";
|
|
22
24
|
import { isPrivateMutationForbidden } from "./connection.js";
|
|
23
|
-
import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, isIdFieldPath, isPublicDocPath, normalizeIdFields, prepareAddPayload, resolveAddDocId } from "./idFields.js";
|
|
25
|
+
import { DEFAULT_ID_FIELDS, getIdFieldsForSegments, isIdFieldPath, isPlainObject, isPublicDocPath, normalizeIdFields, prepareAddPayload, resolveAddDocId } from "./idFields.js";
|
|
24
26
|
import { isCompatEnv } from './compatEnv.js';
|
|
25
27
|
import { resolveRefSegmentsSafe, resolveRefSignalSafe } from './Compat/refFallback.js';
|
|
26
28
|
import { compatStartOnRoot, compatStopOnRoot, joinScopePath } from './Compat/startStopCompat.js';
|
|
@@ -202,6 +204,28 @@ export class Signal extends Function {
|
|
|
202
204
|
throw Error('Signal.getIds() does not accept any arguments');
|
|
203
205
|
return getSignalIds(this, SIGNAL_READ_CONTEXT);
|
|
204
206
|
}
|
|
207
|
+
/** Return query extra data, aggregation data, or undefined for ordinary signals. */
|
|
208
|
+
getExtra() {
|
|
209
|
+
if (arguments.length > 0)
|
|
210
|
+
throw Error('Signal.getExtra() does not accept any arguments');
|
|
211
|
+
if (this[IS_AGGREGATION])
|
|
212
|
+
return this.get();
|
|
213
|
+
if (this[IS_QUERY])
|
|
214
|
+
return this.extra.get();
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
/** Return a shallow copy of the current value. */
|
|
218
|
+
getCopy() {
|
|
219
|
+
if (arguments.length > 0)
|
|
220
|
+
throw Error('Signal.getCopy() does not accept any arguments');
|
|
221
|
+
return shallowCopy(this.get());
|
|
222
|
+
}
|
|
223
|
+
/** Return a deep copy of the current value. */
|
|
224
|
+
getDeepCopy() {
|
|
225
|
+
if (arguments.length > 0)
|
|
226
|
+
throw Error('Signal.getDeepCopy() does not accept any arguments');
|
|
227
|
+
return deepCopy(this.get());
|
|
228
|
+
}
|
|
205
229
|
/** Read the current value without tracking it for reactive rendering. */
|
|
206
230
|
peek() {
|
|
207
231
|
if (arguments.length > 0)
|
|
@@ -292,6 +316,50 @@ export class Signal extends Function {
|
|
|
292
316
|
}
|
|
293
317
|
setReplacePrivateData(getSignalOwningRootId(this), segments, nextValue);
|
|
294
318
|
}
|
|
319
|
+
/** Set the current value only when it is null or undefined. */
|
|
320
|
+
async setNull(value) {
|
|
321
|
+
if (arguments.length > 1)
|
|
322
|
+
throw Error('Signal.setNull() expects a single argument');
|
|
323
|
+
if (this.get() != null)
|
|
324
|
+
return;
|
|
325
|
+
await this.setReplace(value);
|
|
326
|
+
}
|
|
327
|
+
/** Replace the current value unless it is exactly equal to the new value. */
|
|
328
|
+
async setDiff(value) {
|
|
329
|
+
if (arguments.length > 1)
|
|
330
|
+
throw Error('Signal.setDiff() expects a single argument');
|
|
331
|
+
const before = this.peek();
|
|
332
|
+
if (racerEqual(before, value))
|
|
333
|
+
return;
|
|
334
|
+
await this.setReplace(value);
|
|
335
|
+
}
|
|
336
|
+
/** Recursively diff objects and arrays at the current signal path. */
|
|
337
|
+
async setDiffDeep(value) {
|
|
338
|
+
if (arguments.length > 1)
|
|
339
|
+
throw Error('Signal.setDiffDeep() expects a single argument');
|
|
340
|
+
await runInBatch(() => setDiffDeepOnSignal(this, value));
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Set multiple object fields with per-key replace semantics.
|
|
344
|
+
* Unlike assign(), null is stored as null and undefined follows setReplace() semantics.
|
|
345
|
+
* @param object Object containing fields to set.
|
|
346
|
+
*/
|
|
347
|
+
async setEach(object) {
|
|
348
|
+
if (arguments.length > 1)
|
|
349
|
+
throw Error('Signal.setEach() expects a single argument');
|
|
350
|
+
if (!object)
|
|
351
|
+
return;
|
|
352
|
+
if (typeof object !== 'object') {
|
|
353
|
+
throw Error('Signal.setEach() expects an object argument, got: ' + typeof object);
|
|
354
|
+
}
|
|
355
|
+
await runInBatch(async () => {
|
|
356
|
+
const promises = [];
|
|
357
|
+
for (const key of Object.keys(object)) {
|
|
358
|
+
promises.push(this[key].setReplace(object[key]));
|
|
359
|
+
}
|
|
360
|
+
await Promise.all(promises);
|
|
361
|
+
});
|
|
362
|
+
}
|
|
295
363
|
/**
|
|
296
364
|
* Set multiple object fields at once. Fields set to `null` or `undefined` are deleted.
|
|
297
365
|
* @param value Object containing fields to set or delete.
|
|
@@ -522,6 +590,183 @@ export class Signal extends Function {
|
|
|
522
590
|
await deleteSignalValue(this, SIGNAL_VALUE_MUTATION_CONTEXT);
|
|
523
591
|
}
|
|
524
592
|
}
|
|
593
|
+
async function setDiffDeepOnSignal($target, value) {
|
|
594
|
+
if ($target[SEGMENTS].length === 0)
|
|
595
|
+
throw Error('Can\'t set the root signal data');
|
|
596
|
+
await diffDeepOnSignal($target, $target.peek(), value);
|
|
597
|
+
}
|
|
598
|
+
async function diffDeepOnSignal($signal, before, after) {
|
|
599
|
+
if (before === after)
|
|
600
|
+
return;
|
|
601
|
+
if (Array.isArray(before) && Array.isArray(after)) {
|
|
602
|
+
const diff = arrayDiff(before, after, deepEqual);
|
|
603
|
+
if (!diff.length)
|
|
604
|
+
return;
|
|
605
|
+
const index = getSingleArrayReplacementIndex(diff);
|
|
606
|
+
if (index != null) {
|
|
607
|
+
await diffDeepOnSignal(getChildSignal($signal, index), before[index], after[index]);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
await applyArrayDiff($signal, diff);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (isDiffableObject(before, after)) {
|
|
614
|
+
const preservePath = $signal[SEGMENTS];
|
|
615
|
+
for (const key of Object.keys(before)) {
|
|
616
|
+
if (Object.prototype.hasOwnProperty.call(after, key))
|
|
617
|
+
continue;
|
|
618
|
+
await deleteForDiffDeep(getChildSignal($signal, key), preservePath);
|
|
619
|
+
}
|
|
620
|
+
for (const key of Object.keys(after)) {
|
|
621
|
+
await diffDeepOnSignal(getChildSignal($signal, key), before[key], after[key]);
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
await $signal.setReplace(after);
|
|
626
|
+
}
|
|
627
|
+
function isDiffableObject(before, after) {
|
|
628
|
+
if (!isPlainObject(before) || !isPlainObject(after))
|
|
629
|
+
return false;
|
|
630
|
+
if (isReactLike(before) || isReactLike(after))
|
|
631
|
+
return false;
|
|
632
|
+
return true;
|
|
633
|
+
}
|
|
634
|
+
function isReactLike(value) {
|
|
635
|
+
return !!(value && typeof value === 'object' && typeof value.$$typeof === 'symbol');
|
|
636
|
+
}
|
|
637
|
+
function getSingleArrayReplacementIndex(diff) {
|
|
638
|
+
if (!Array.isArray(diff) || diff.length !== 2)
|
|
639
|
+
return null;
|
|
640
|
+
const first = diff[0];
|
|
641
|
+
const second = diff[1];
|
|
642
|
+
if (first instanceof arrayDiff.RemoveDiff &&
|
|
643
|
+
second instanceof arrayDiff.InsertDiff &&
|
|
644
|
+
first.index === second.index &&
|
|
645
|
+
first.howMany === 1 &&
|
|
646
|
+
second.values.length === 1) {
|
|
647
|
+
return first.index;
|
|
648
|
+
}
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
async function applyArrayDiff($signal, diff) {
|
|
652
|
+
for (const item of diff) {
|
|
653
|
+
if (item instanceof arrayDiff.InsertDiff) {
|
|
654
|
+
await $signal.insert(item.index, item.values);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (item instanceof arrayDiff.RemoveDiff) {
|
|
658
|
+
await $signal.remove(item.index, item.howMany);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (item instanceof arrayDiff.MoveDiff) {
|
|
662
|
+
await $signal.move(item.from, item.to, item.howMany);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async function deleteForDiffDeep($signal, preservePath) {
|
|
667
|
+
const segments = $signal[SEGMENTS];
|
|
668
|
+
const idFields = getIdFieldsForSegments(segments);
|
|
669
|
+
if (isIdFieldPath(segments, idFields))
|
|
670
|
+
return;
|
|
671
|
+
if (isPublicCollection(segments[0])) {
|
|
672
|
+
await $signal.del();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (isPrivateMutationForbidden()) {
|
|
676
|
+
throw Error(`
|
|
677
|
+
Can't modify private collections data when 'publicOnly' is enabled.
|
|
678
|
+
On the server you can only work with public collections.
|
|
679
|
+
`);
|
|
680
|
+
}
|
|
681
|
+
delPrivateData(getSignalOwningRootId($signal), segments, { preservePath });
|
|
682
|
+
}
|
|
683
|
+
function getChildSignal($parent, key) {
|
|
684
|
+
return getSignal(getRoot($parent) || $parent, [...$parent[SEGMENTS], key]);
|
|
685
|
+
}
|
|
686
|
+
function deepEqual(left, right) {
|
|
687
|
+
if (left === right)
|
|
688
|
+
return true;
|
|
689
|
+
if (left == null || right == null)
|
|
690
|
+
return false;
|
|
691
|
+
if (typeof left !== 'object' || typeof right !== 'object')
|
|
692
|
+
return false;
|
|
693
|
+
if (Array.isArray(left) !== Array.isArray(right))
|
|
694
|
+
return false;
|
|
695
|
+
if (Array.isArray(left)) {
|
|
696
|
+
if (left.length !== right.length)
|
|
697
|
+
return false;
|
|
698
|
+
for (let i = 0; i < left.length; i++) {
|
|
699
|
+
if (!deepEqual(left[i], right[i]))
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
if (!isPlainObject(left) || !isPlainObject(right))
|
|
705
|
+
return false;
|
|
706
|
+
const leftKeys = Object.keys(left);
|
|
707
|
+
const rightKeys = Object.keys(right);
|
|
708
|
+
if (leftKeys.length !== rightKeys.length)
|
|
709
|
+
return false;
|
|
710
|
+
for (const key of leftKeys) {
|
|
711
|
+
if (!Object.prototype.hasOwnProperty.call(right, key))
|
|
712
|
+
return false;
|
|
713
|
+
if (!deepEqual(left[key], right[key]))
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
function racerEqual(left, right) {
|
|
719
|
+
return left === right || (Number.isNaN(left) && Number.isNaN(right));
|
|
720
|
+
}
|
|
721
|
+
function shallowCopy(value) {
|
|
722
|
+
const rawValue = raw(value);
|
|
723
|
+
if (Array.isArray(rawValue))
|
|
724
|
+
return rawValue.slice();
|
|
725
|
+
if (rawValue && typeof rawValue === 'object')
|
|
726
|
+
return { ...rawValue };
|
|
727
|
+
return rawValue;
|
|
728
|
+
}
|
|
729
|
+
function deepCopy(value) {
|
|
730
|
+
const rawValue = raw(value);
|
|
731
|
+
if (!rawValue || typeof rawValue !== 'object')
|
|
732
|
+
return rawValue;
|
|
733
|
+
if (typeof globalThis.structuredClone === 'function') {
|
|
734
|
+
try {
|
|
735
|
+
return globalThis.structuredClone(rawValue);
|
|
736
|
+
}
|
|
737
|
+
catch { }
|
|
738
|
+
}
|
|
739
|
+
return racerDeepCopy(rawValue);
|
|
740
|
+
}
|
|
741
|
+
// Racer-style deep copy:
|
|
742
|
+
// - Preserves prototypes by instantiating via `new value.constructor()`
|
|
743
|
+
// - Copies own enumerable props recursively
|
|
744
|
+
// - Keeps functions as-is (no cloning)
|
|
745
|
+
// - Handles Date by creating a new Date
|
|
746
|
+
// Limitations: does not handle cyclic refs, Map/Set/RegExp/TypedArray, non-enumerables.
|
|
747
|
+
function racerDeepCopy(value) {
|
|
748
|
+
if (value instanceof Date)
|
|
749
|
+
return new Date(value);
|
|
750
|
+
if (typeof value === 'object') {
|
|
751
|
+
if (value === null)
|
|
752
|
+
return null;
|
|
753
|
+
if (Array.isArray(value)) {
|
|
754
|
+
const array = [];
|
|
755
|
+
for (let i = value.length; i--;) {
|
|
756
|
+
array[i] = racerDeepCopy(value[i]);
|
|
757
|
+
}
|
|
758
|
+
return array;
|
|
759
|
+
}
|
|
760
|
+
const object = new value.constructor();
|
|
761
|
+
for (const key in value) {
|
|
762
|
+
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
763
|
+
object[key] = racerDeepCopy(value[key]);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
return object;
|
|
767
|
+
}
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
525
770
|
// dot syntax returns a child signal only if no such method or property exists
|
|
526
771
|
export const regularBindings = {
|
|
527
772
|
apply(signal, thisArg, argumentsList) {
|
|
@@ -2,4 +2,4 @@ export const SEGMENTS = Symbol('path segments targeting the particular node in t
|
|
|
2
2
|
export const ARRAY_METHOD = Symbol('run array method on the signal');
|
|
3
3
|
export const GET = Symbol('get the value of the signal - either observed or raw');
|
|
4
4
|
export const GETTERS = Symbol('get the list of this signal\'s getters');
|
|
5
|
-
export const DEFAULT_GETTERS = ['path', 'root', 'id', 'get', 'peek', 'getId', 'map', 'reduce', 'find', 'getIds', 'getExtra', 'getCollection'];
|
|
5
|
+
export const DEFAULT_GETTERS = ['path', 'root', 'id', 'get', 'peek', 'getId', 'map', 'reduce', 'find', 'getIds', 'getExtra', 'getCopy', 'getDeepCopy', 'getCollection'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "teamplay",
|
|
3
|
-
"version": "0.5.0-alpha.
|
|
3
|
+
"version": "0.5.0-alpha.19",
|
|
4
4
|
"description": "Full-stack signals ORM with multiplayer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -134,5 +134,5 @@
|
|
|
134
134
|
]
|
|
135
135
|
},
|
|
136
136
|
"license": "MIT",
|
|
137
|
-
"gitHead": "
|
|
137
|
+
"gitHead": "4bafa99265d8aed36e952d89d8d0ed0082748ad5"
|
|
138
138
|
}
|