teamplay 0.5.0-alpha.36 → 0.5.0-alpha.38

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/README.md CHANGED
@@ -13,16 +13,16 @@ Features:
13
13
 
14
14
  > __*__ deep signals -- with support for objects and arrays\
15
15
  > __**__ concurrent changes to the same data are auto-merged using [OT](https://en.wikipedia.org/wiki/Operational_transformation)\
16
- > __***__ similar to Firebase but with your own MongoDB-compatible database
16
+ > __***__ similar to Firebase but with your own MongoDB database
17
17
 
18
18
  ## Installation
19
19
 
20
20
  For installation and documentation see [teamplay.dev](https://teamplay.dev)
21
21
 
22
- ## ORM Compat Helpers
22
+ ## ORM Helpers
23
23
 
24
24
  For legacy Racer-style model mixins (for example versioning libraries which call
25
- `getAssociations()`), use ORM compat helpers from the `teamplay/orm` subpath:
25
+ `getAssociations()`), use ORM helpers from the `teamplay/orm` subpath:
26
26
 
27
27
  ```js
28
28
  import BaseModel, { hasMany, hasOne, belongsTo } from 'teamplay/orm'
package/dist/orm/Query.js CHANGED
@@ -2,7 +2,6 @@ import { raw } from '@nx-js/observer-util';
2
2
  import { set as _set, getRaw } from './dataTree.js';
3
3
  import getSignal from "./getSignal.js";
4
4
  import { getConnection } from "./connection.js";
5
- import { isCompatEnv } from './compatEnv.js';
6
5
  import { docSubscriptions } from './Doc.js';
7
6
  import FinalizationRegistry from "../utils/MockFinalizationRegistry.js";
8
7
  import SubscriptionState from './SubscriptionState.js';
@@ -836,8 +835,6 @@ export class QuerySubscriptions {
836
835
  }
837
836
  export const querySubscriptions = new QuerySubscriptions();
838
837
  function maybeMaterializeQueryDocsToCollection(collectionName, shareDocs) {
839
- if (!isCompatEnv())
840
- return;
841
838
  for (const doc of shareDocs) {
842
839
  if (!doc?.id || doc.data == null)
843
840
  continue;
@@ -937,7 +934,6 @@ function getQueryOwnerKey(rootId, transportHash) {
937
934
  return getScopedSignalHash(rootId, transportHash, 'queryOwner');
938
935
  }
939
936
  export function cloneQueryParams(collectionName, params) {
940
- warnIfCompatQueryParamsHaveUndefinedFields(collectionName, params);
941
937
  return JSON.parse(JSON.stringify(params));
942
938
  }
943
939
  function parseQuerySignalOptions(options) {
@@ -951,55 +947,8 @@ function parseQuerySignalOptions(options) {
951
947
  return { root, signalOptions };
952
948
  }
953
949
  function normalizeQueryParamsForHash(collectionName, params) {
954
- warnIfCompatQueryParamsHaveUndefinedFields(collectionName, params);
955
950
  return params;
956
951
  }
957
- const warnedUndefinedQueryParamKeys = new Set();
958
- function warnIfCompatQueryParamsHaveUndefinedFields(collectionName, params) {
959
- if (!isCompatEnv())
960
- return;
961
- const paths = getUndefinedQueryParamFieldPaths(params);
962
- if (paths.length === 0)
963
- return;
964
- const key = `${collectionName || '<unknown>'}:${paths.join(',')}`;
965
- if (warnedUndefinedQueryParamKeys.has(key))
966
- return;
967
- warnedUndefinedQueryParamKeys.add(key);
968
- console.warn('[teamplay] Compat query params contain object fields with undefined values. ' +
969
- 'TeamPlay now clones query params like non-compat mode, so these fields are dropped ' +
970
- 'instead of being converted to null. Normalize query params explicitly.', {
971
- collectionName,
972
- paths
973
- }, new Error().stack);
974
- }
975
- function getUndefinedQueryParamFieldPaths(value) {
976
- const paths = [];
977
- collectUndefinedQueryParamFieldPaths(value, '', paths, new WeakSet());
978
- return paths;
979
- }
980
- function collectUndefinedQueryParamFieldPaths(value, path, paths, seen) {
981
- if (value == null || typeof value !== 'object')
982
- return;
983
- if (seen.has(value))
984
- return;
985
- seen.add(value);
986
- if (Array.isArray(value)) {
987
- for (let i = 0; i < value.length; i++) {
988
- collectUndefinedQueryParamFieldPaths(value[i], `${path}[${i}]`, paths, seen);
989
- }
990
- return;
991
- }
992
- for (const key in value) {
993
- if (!Object.prototype.hasOwnProperty.call(value, key))
994
- continue;
995
- const childPath = path ? `${path}.${key}` : key;
996
- if (value[key] === undefined) {
997
- paths.push(childPath);
998
- continue;
999
- }
1000
- collectUndefinedQueryParamFieldPaths(value[key], childPath, paths, seen);
1001
- }
1002
- }
1003
952
  function createPendingDestroyEntry() {
1004
953
  let resolvePending;
1005
954
  let rejectPending;
@@ -1,4 +1,3 @@
1
- import SignalCompat from './Compat/SignalCompat.js';
2
1
  import type { SignalConstructor } from './types/signal.js';
3
2
  export type { FromJsonSchema, InferZodSchema, JsonSchema, JsonSchemaObject, ZodLikeSchema } from './types/jsonSchema.js';
4
3
  export type { ComputedQueryParamsInput, QueryParams, QueryParamsInput } from './types/query.js';
@@ -7,6 +6,5 @@ export type { AppendPath, JoinPath, PathSegment, SignalPath, WildcardPathSegment
7
6
  export type { AggregationSignal, AnySignal, ArraySignal, CollectionAggregationSignal, CollectionDocument, CollectionDocumentModel, CollectionQuerySignal, CollectionSignal, CollectionSignalFromSpec, CollectionSpec, DocumentSignal, JsonSchemaSpec, MaybePromise, MaybePromiseSubResult, PublicSignal, LocalSignalFactory, RegisteredAggregationInput, RuntimeSignalConstructor, RuntimeSignalInstance, QuerySignal, RootCollections, RootSignal, SignalBaseInstance, SignalChild, SignalChildren, SignalClass, SignalConstructor, SignalForKind, SignalKind, SignalInstance, SignalModelConstructor, PrivateSignalFromSpec, RootPrivateCollections, SubResult, TypedAggregationInput, TypedAggregationSignal, TypedSignal, ZodSchemaSpec } from './types/signal.js';
8
7
  export type { CollectionsFromManifest, ModelEntry, ModelManifest, PathModelsFromManifest, PrivateCollectionsFromManifest } from './types/modelManifest.js';
9
8
  export { Signal, SEGMENTS, ARRAY_METHOD, GET, GETTERS, DEFAULT_GETTERS, regularBindings, extremelyLateBindings, isPublicCollectionSignal, isPublicDocumentSignal, isPublicCollection, isPrivateCollection } from './SignalBase.js';
10
- export { SignalCompat };
11
9
  declare const DefaultSignal: SignalConstructor;
12
10
  export default DefaultSignal;
@@ -1,7 +1,4 @@
1
1
  import { Signal } from "./SignalBase.js";
2
- import SignalCompat from './Compat/SignalCompat.js';
3
- import { isCompatEnv } from './compatEnv.js';
4
2
  export { Signal, SEGMENTS, ARRAY_METHOD, GET, GETTERS, DEFAULT_GETTERS, regularBindings, extremelyLateBindings, isPublicCollectionSignal, isPublicDocumentSignal, isPublicCollection, isPrivateCollection } from "./SignalBase.js";
5
- export { SignalCompat };
6
- const DefaultSignal = (isCompatEnv() ? SignalCompat : Signal);
3
+ const DefaultSignal = Signal;
7
4
  export default DefaultSignal;
@@ -4,7 +4,6 @@ import diffMatchPatch from 'diff-match-patch';
4
4
  import { getConnection } from "./connection.js";
5
5
  import setDiffDeep from '../utils/setDiffDeep.js';
6
6
  import { getIdFieldsForSegments, injectIdFields, stripIdFields, isPlainObject, isIdFieldPath } from "./idFields.js";
7
- import { isCompatEnv } from './compatEnv.js';
8
7
  import { isMissingShareDoc } from './missingDoc.js';
9
8
  import { getLogicalRootSnapshot as getLogicalRootSnapshotFromTree } from "./rootScope.js";
10
9
  import { getRootContext } from "./rootContext.js";
@@ -150,7 +149,7 @@ export async function setPublicDoc(segments, value, deleteValue = false) {
150
149
  const doc = getConnection().get(collection, docId);
151
150
  let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal: true });
152
151
  if (!docState.exists && segments.length > 2) {
153
- docState = await resolvePublicDocStateWithFetchFallback({
152
+ docState = await resolvePublicDocStateWithLocalRecovery({
154
153
  collection,
155
154
  docId,
156
155
  doc,
@@ -159,7 +158,7 @@ export async function setPublicDoc(segments, value, deleteValue = false) {
159
158
  });
160
159
  }
161
160
  if (!docState.exists && deleteValue)
162
- throw Error(ERRORS.deleteNonExistentDoc(segments));
161
+ return;
163
162
  // make sure that the value is not observable to not trigger extra reads. And clone it
164
163
  value = raw(value);
165
164
  if (value == null) {
@@ -258,7 +257,7 @@ export async function setPublicDocReplace(segments, value) {
258
257
  const doc = getConnection().get(collection, docId);
259
258
  let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal: true });
260
259
  if (!docState.exists && segments.length > 2) {
261
- docState = await resolvePublicDocStateWithFetchFallback({
260
+ docState = await resolvePublicDocStateWithLocalRecovery({
262
261
  collection,
263
262
  docId,
264
263
  doc,
@@ -306,7 +305,7 @@ export async function setPublicDocReplace(segments, value) {
306
305
  }
307
306
  const relativePath = segments.slice(2);
308
307
  // json0 direct replace ops require every ancestor container to already exist.
309
- // Racer-like compat set, however, materializes missing/primitive parents while
308
+ // setPublicDoc() materializes missing/primitive parents while
310
309
  // descending into the path. Fall back to the older diff-based path when the
311
310
  // direct op would target a non-existent/non-object ancestor.
312
311
  if (!canApplyDirectReplaceOp(docState.snapshot || {}, relativePath)) {
@@ -381,13 +380,13 @@ function resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDat
381
380
  }
382
381
  return { exists: true, snapshot: localSnapshot, source: 'local' };
383
382
  }
384
- async function resolvePublicDocStateWithFetchFallback({ collection, docId, doc, idFields, hydrateDocDataFromLocal = false }) {
383
+ async function resolvePublicDocStateWithLocalRecovery({ collection, docId, doc, idFields, hydrateDocDataFromLocal = false }) {
385
384
  let docState = resolvePublicDocState({ collection, docId, doc, idFields, hydrateDocDataFromLocal });
386
385
  if (docState.exists)
387
386
  return docState;
388
- const shouldFetch = isCompatEnv() || (getRaw([collection, docId]) != null &&
387
+ const shouldFetch = getRaw([collection, docId]) != null &&
389
388
  isMissingShareDoc(doc) &&
390
- doc.version == null);
389
+ doc.version == null;
391
390
  if (!shouldFetch)
392
391
  return docState;
393
392
  await new Promise((resolve, reject) => {
@@ -529,7 +528,7 @@ export async function incrementPublic(segments, byNumber) {
529
528
  throw Error(ERRORS.publicDoc(segments));
530
529
  const doc = getConnection().get(collection, docId);
531
530
  const idFields = getIdFieldsForSegments([collection, docId]);
532
- const docState = await resolvePublicDocStateWithFetchFallback({
531
+ const docState = await resolvePublicDocStateWithLocalRecovery({
533
532
  collection,
534
533
  docId,
535
534
  doc,
@@ -572,7 +571,7 @@ export async function arrayInsertPublic(segments, index, values) {
572
571
  throw Error(ERRORS.publicDoc(segments));
573
572
  const doc = getConnection().get(collection, docId);
574
573
  const idFields = getIdFieldsForSegments([collection, docId]);
575
- const docState = await resolvePublicDocStateWithFetchFallback({
574
+ const docState = await resolvePublicDocStateWithLocalRecovery({
576
575
  collection,
577
576
  docId,
578
577
  doc,
@@ -631,7 +630,7 @@ export async function arrayRemovePublic(segments, index, howMany = 1) {
631
630
  throw Error(ERRORS.publicDoc(segments));
632
631
  const doc = getConnection().get(collection, docId);
633
632
  const idFields = getIdFieldsForSegments([collection, docId]);
634
- const docState = await resolvePublicDocStateWithFetchFallback({
633
+ const docState = await resolvePublicDocStateWithLocalRecovery({
635
634
  collection,
636
635
  docId,
637
636
  doc,
@@ -659,7 +658,7 @@ export async function arrayMovePublic(segments, from, to, howMany = 1) {
659
658
  throw Error(ERRORS.publicDoc(segments));
660
659
  const doc = getConnection().get(collection, docId);
661
660
  const idFields = getIdFieldsForSegments([collection, docId]);
662
- const docState = await resolvePublicDocStateWithFetchFallback({
661
+ const docState = await resolvePublicDocStateWithLocalRecovery({
663
662
  collection,
664
663
  docId,
665
664
  doc,
@@ -734,7 +733,7 @@ export async function stringInsertPublic(segments, index, text) {
734
733
  throw Error(ERRORS.publicDoc(segments));
735
734
  const doc = getConnection().get(collection, docId);
736
735
  const idFields = getIdFieldsForSegments([collection, docId]);
737
- const docState = await resolvePublicDocStateWithFetchFallback({
736
+ const docState = await resolvePublicDocStateWithLocalRecovery({
738
737
  collection,
739
738
  docId,
740
739
  doc,
@@ -768,7 +767,7 @@ export async function stringRemovePublic(segments, index, howMany) {
768
767
  throw Error(ERRORS.publicDoc(segments));
769
768
  const doc = getConnection().get(collection, docId);
770
769
  const idFields = getIdFieldsForSegments([collection, docId]);
771
- const docState = await resolvePublicDocStateWithFetchFallback({
770
+ const docState = await resolvePublicDocStateWithLocalRecovery({
772
771
  collection,
773
772
  docId,
774
773
  doc,
@@ -807,11 +806,6 @@ const ERRORS = {
807
806
  `,
808
807
  publicDocIdNumber: segments => `
809
808
  Public doc id must be a string. Got a number: ${segments}
810
- `,
811
- deleteNonExistentDoc: segments => `
812
- Trying to delete data from a non-existing doc ${segments}.
813
- Make sure that the document exists and you are subscribed to it
814
- before trying to delete anything from it or the doc itself.
815
809
  `,
816
810
  publicDocIdUndefined: segments => `
817
811
  Trying to modify a public document with the id 'undefined'.
@@ -36,7 +36,7 @@ function warnIfSchemaWasNotDefined(pattern, schema) {
36
36
  warnedUnwrappedSchemaPatterns.add(pattern);
37
37
  console.warn(`[teamplay] Schema for model "${pattern}" was loaded as a plain object. ` +
38
38
  'Wrap it with defineSchema(schema) to enable the conventional schema setup. ' +
39
- 'Plain schemas still work for backward compatibility.');
39
+ 'Plain schemas still work.');
40
40
  }
41
41
  function shouldWarnAboutUnwrappedSchemas() {
42
42
  const env = getProcessEnv();
@@ -157,7 +157,7 @@ function createImperativeQueryReadinessError($query, timeoutMs) {
157
157
  }
158
158
  }
159
159
  return Error(`
160
- Compat query did not fully materialize within ${timeoutMs}ms.
160
+ Query did not fully materialize within ${timeoutMs}ms.
161
161
  Collection: ${collection}
162
162
  Params: ${JSON.stringify(params)}
163
163
  Hash: ${hash}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.5.0-alpha.36",
3
+ "version": "0.5.0-alpha.38",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -57,16 +57,13 @@
57
57
  "scripts": {
58
58
  "build": "rm -rf dist && tsc -p tsconfig.build.json && rsync -a --include='*/' --include='*.d.ts' --include='*.cjs' --exclude='*' src/ dist/ && node ../../scripts/rewrite-dts-extensions.cjs dist",
59
59
  "prepublishOnly": "npm run build",
60
- "test": "npm run test-types && npm run test-types:external && npm run test-server && npm run test-client && npm run test-compat",
60
+ "test": "npm run test-types && npm run test-types:external && npm run test-server && npm run test-client",
61
61
  "test-types": "tsc -p tsconfig.type-tests.json",
62
62
  "test-types:external": "tsc -p tsconfig.external-consumer.json",
63
63
  "test-types:dist": "tsc -p tsconfig.external-consumer.dist.json",
64
64
  "test-server": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc -C teamplay-ts\" mocha 'test/[!_]*.js'",
65
65
  "test-server-only": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc -C teamplay-ts\" mocha --grep '@only' 'test/[!_]*.js'",
66
66
  "test-client": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc --experimental-vm-modules -C teamplay-ts\" jest --runInBand",
67
- "test-compat": "npm run test-server-compat && npm run test-client-compat",
68
- "test-server-compat": "TEAMPLAY_COMPAT=1 NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc -C teamplay-ts\" mocha 'test/[!_]*.js'",
69
- "test-client-compat": "TEAMPLAY_COMPAT=1 NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc --experimental-vm-modules -C teamplay-ts\" jest --runInBand",
70
67
  "coverage-server": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc -C teamplay-ts\" c8 --include 'src/orm/**' --include 'src/react/**' --include 'src/utils/**' mocha 'test/[!_]*.js'",
71
68
  "coverage-client": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --expose-gc --experimental-vm-modules -C teamplay-ts\" jest --coverage --coverageDirectory=coverage-client",
72
69
  "coverage": "npm run coverage-server && npm run coverage-client"
@@ -74,13 +71,13 @@
74
71
  "dependencies": {
75
72
  "@nx-js/observer-util": "^4.1.3",
76
73
  "@startupjs/sharedb-mingo-memory": "^4.0.0-2",
77
- "@teamplay/backend": "^0.5.0-alpha.13",
78
- "@teamplay/cache": "^0.5.0-alpha.7",
79
- "@teamplay/channel": "^0.5.0-alpha.10",
80
- "@teamplay/debug": "^0.5.0-alpha.7",
81
- "@teamplay/schema": "^0.5.0-alpha.13",
82
- "@teamplay/utils": "^0.5.0-alpha.7",
83
- "babel-plugin-teamplay": "^0.5.0-alpha.13",
74
+ "@teamplay/backend": "^0.5.0-alpha.38",
75
+ "@teamplay/cache": "^0.5.0-alpha.38",
76
+ "@teamplay/channel": "^0.5.0-alpha.38",
77
+ "@teamplay/debug": "^0.5.0-alpha.38",
78
+ "@teamplay/schema": "^0.5.0-alpha.38",
79
+ "@teamplay/utils": "^0.5.0-alpha.38",
80
+ "babel-plugin-teamplay": "^0.5.0-alpha.38",
84
81
  "diff-match-patch": "^1.0.5",
85
82
  "events": "^3.3.0",
86
83
  "json0-ot-diff": "^1.1.2",
@@ -139,5 +136,5 @@
139
136
  ]
140
137
  },
141
138
  "license": "MIT",
142
- "gitHead": "c83e516afe64e94e7703529d04aed73ac3b27bfa"
139
+ "gitHead": "bda8f3ddc879c9b9ac491b946e24b7a3e04adb65"
143
140
  }
@@ -1,3 +0,0 @@
1
- import { Signal } from '../SignalBase.js'
2
-
3
- export default class SignalCompat<TValue = unknown> extends Signal<TValue> {}
@@ -1,640 +0,0 @@
1
- import { raw } from '@nx-js/observer-util';
2
- import arrayDiff from 'arraydiff';
3
- import { Signal, GETTERS, DEFAULT_GETTERS, SEGMENTS, isPublicCollection } from "../SignalBase.js";
4
- import { getRoot, ROOT, ROOT_ID } from "../Root.js";
5
- import { IS_QUERY } from '../Query.js';
6
- import { AGGREGATIONS, IS_AGGREGATION } from '../Aggregation.js';
7
- import { getIdFieldsForSegments, isIdFieldPath, isPublicDocPath, normalizeIdFields, isPlainObject } from "../idFields.js";
8
- import { incrementPublic as _incrementPublic, arrayPushPublic as _arrayPushPublic, arrayUnshiftPublic as _arrayUnshiftPublic, arrayInsertPublic as _arrayInsertPublic, arrayPopPublic as _arrayPopPublic, arrayShiftPublic as _arrayShiftPublic, arrayRemovePublic as _arrayRemovePublic, arrayMovePublic as _arrayMovePublic, setPublicDocReplace as _setPublicDocReplace, stringInsertPublic as _stringInsertPublic, stringRemovePublic as _stringRemovePublic } from '../dataTree.js';
9
- import { on as onCustomEvent, removeListener as removeCustomEventListener } from '../events.js';
10
- import { runInBatch } from '../batchScheduler.js';
11
- import { arrayInsertPrivateData, arrayMovePrivateData, arrayPopPrivateData, arrayPushPrivateData, arrayRemovePrivateData, arrayShiftPrivateData, arrayUnshiftPrivateData, delPrivateData, setReplacePrivateData, stringInsertPrivateData, stringRemovePrivateData } from '../privateData.js';
12
- class SignalCompat extends Signal {
13
- static [GETTERS] = DEFAULT_GETTERS;
14
- path() {
15
- if (arguments.length > 0)
16
- throw Error('Signal.path() does not accept any arguments');
17
- return super.path();
18
- }
19
- getId() {
20
- if (isAggregationValuePath(this[SEGMENTS]))
21
- return super.getId();
22
- return super.getId();
23
- }
24
- getCollection() {
25
- return super.getCollection();
26
- }
27
- getCopy() {
28
- if (arguments.length > 0)
29
- throw Error('Signal.getCopy() does not accept any arguments');
30
- return shallowCopy(this.get());
31
- }
32
- getDeepCopy() {
33
- if (arguments.length > 0)
34
- throw Error('Signal.getDeepCopy() does not accept any arguments');
35
- return deepCopy(this.get());
36
- }
37
- getExtra() {
38
- if (arguments.length > 0)
39
- throw Error('Signal.getExtra() does not accept any arguments');
40
- if (this[IS_AGGREGATION])
41
- return this.get();
42
- if (this[IS_QUERY])
43
- return this.extra.get();
44
- return undefined;
45
- }
46
- get() {
47
- if (arguments.length > 0)
48
- throw Error('Signal.get() does not accept any arguments');
49
- return Signal.prototype.get.apply(this, arguments);
50
- }
51
- peek() {
52
- if (arguments.length > 0)
53
- throw Error('Signal.peek() does not accept any arguments');
54
- return Signal.prototype.peek.apply(this, arguments);
55
- }
56
- async set(value) {
57
- if (arguments.length > 1)
58
- throw Error('Signal.set() expects a single argument');
59
- return Signal.prototype.set.call(this, value);
60
- }
61
- async setReplace(value) {
62
- if (arguments.length > 1)
63
- throw Error('Signal.setReplace() expects a single argument');
64
- if (value === undefined)
65
- return Signal.prototype.set.call(this, value);
66
- return setReplaceOnSignal(this, value);
67
- }
68
- async setNull(value) {
69
- if (arguments.length > 1)
70
- throw Error('Signal.setNull() expects a single argument');
71
- if (this.get() != null)
72
- return;
73
- return setReplaceOnSignal(this, value);
74
- }
75
- async setDiffDeep(value) {
76
- if (arguments.length > 1)
77
- throw Error('Signal.setDiffDeep() expects a single argument');
78
- return runInBatch(() => setDiffDeepOnSignal(this, value));
79
- }
80
- async setDiff(value) {
81
- if (arguments.length > 1)
82
- throw Error('Signal.setDiff() expects a single argument');
83
- const before = this.peek();
84
- if (racerEqualCompat(before, value))
85
- return;
86
- return setReplaceOnSignal(this, value);
87
- }
88
- async setEach(object) {
89
- if (arguments.length > 1)
90
- throw Error('Signal.setEach() expects a single argument');
91
- if (!object)
92
- return;
93
- if (typeof object !== 'object') {
94
- throw Error('Signal.setEach() expects an object argument, got: ' + typeof object);
95
- }
96
- return runInBatch(async () => {
97
- const promises = [];
98
- for (const key of Object.keys(object)) {
99
- promises.push(SignalCompat.prototype.set.call(this[key], object[key]));
100
- }
101
- await Promise.all(promises);
102
- });
103
- }
104
- async del() {
105
- if (arguments.length > 0)
106
- throw Error('Signal.del() does not accept any arguments');
107
- try {
108
- return await Signal.prototype.del.call(this);
109
- }
110
- catch (error) {
111
- if (isMissingPublicDocDeleteError(this, error))
112
- return;
113
- throw error;
114
- }
115
- }
116
- async increment(byNumber) {
117
- if (arguments.length > 1)
118
- throw Error('Signal.increment() expects zero or one argument');
119
- if (byNumber != null && (typeof byNumber !== 'number' || !Number.isFinite(byNumber))) {
120
- throw Error('Signal.increment() expects a numeric argument');
121
- }
122
- return incrementOnSignal(this, byNumber);
123
- }
124
- async push(value) {
125
- if (arguments.length > 1)
126
- throw Error('Signal.push() expects a single argument');
127
- return arrayPushOnSignal(this, value);
128
- }
129
- async unshift(value) {
130
- if (arguments.length > 1)
131
- throw Error('Signal.unshift() expects a single argument');
132
- return arrayUnshiftOnSignal(this, value);
133
- }
134
- async insert(index, values) {
135
- if (arguments.length < 2)
136
- throw Error('Not enough arguments for insert');
137
- if (arguments.length > 2)
138
- throw Error('Signal.insert() expects two arguments');
139
- if (typeof index !== 'number' || !Number.isFinite(index)) {
140
- throw Error('Signal.insert() expects a numeric index');
141
- }
142
- return arrayInsertOnSignal(this, index, values);
143
- }
144
- async pop() {
145
- if (arguments.length > 0)
146
- throw Error('Signal.pop() does not accept any arguments');
147
- return arrayPopOnSignal(this);
148
- }
149
- async shift() {
150
- if (arguments.length > 0)
151
- throw Error('Signal.shift() does not accept any arguments');
152
- return arrayShiftOnSignal(this);
153
- }
154
- async remove(index, howMany) {
155
- if (arguments.length === 0) {
156
- const segments = this[SEGMENTS].slice();
157
- if (!segments.length || typeof segments[segments.length - 1] !== 'number') {
158
- throw Error('Signal.remove() expects an index');
159
- }
160
- index = segments.pop();
161
- const $root = getRoot(this) || this;
162
- const $target = resolveSignal($root, segments);
163
- return arrayRemoveOnSignal($target, +index, howMany);
164
- }
165
- if (arguments.length > 2)
166
- throw Error('Signal.remove() expects zero to two arguments');
167
- if (typeof index !== 'number' || !Number.isFinite(index)) {
168
- throw Error('Signal.remove() expects a numeric index');
169
- }
170
- return arrayRemoveOnSignal(this, index, howMany);
171
- }
172
- async move(from, to, howMany) {
173
- if (arguments.length < 2)
174
- throw Error('Not enough arguments for move');
175
- if (arguments.length > 3)
176
- throw Error('Signal.move() expects two or three arguments');
177
- if (typeof from !== 'number' || !Number.isFinite(from) || typeof to !== 'number' || !Number.isFinite(to)) {
178
- throw Error('Signal.move() expects numeric from/to');
179
- }
180
- return arrayMoveOnSignal(this, from, to, howMany);
181
- }
182
- async stringInsert(index, text) {
183
- if (arguments.length < 2)
184
- throw Error('Not enough arguments for stringInsert');
185
- if (arguments.length > 2)
186
- throw Error('Signal.stringInsert() expects two arguments');
187
- if (typeof index !== 'number' || !Number.isFinite(index)) {
188
- throw Error('Signal.stringInsert() expects a numeric index');
189
- }
190
- return stringInsertOnSignal(this, index, text);
191
- }
192
- async stringRemove(index, howMany) {
193
- if (arguments.length < 2)
194
- throw Error('Not enough arguments for stringRemove');
195
- if (arguments.length > 2)
196
- throw Error('Signal.stringRemove() expects two arguments');
197
- if (typeof index !== 'number' || !Number.isFinite(index)) {
198
- throw Error('Signal.stringRemove() expects a numeric index');
199
- }
200
- if (howMany == null)
201
- howMany = 1;
202
- return stringRemoveOnSignal(this, index, howMany);
203
- }
204
- async assign(value) {
205
- if (arguments.length > 1)
206
- throw Error('Signal.assign() expects a single argument');
207
- return Signal.prototype.assign.call(this, value);
208
- }
209
- on(eventName, pattern, handler) {
210
- if (arguments.length < 2)
211
- throw Error('Signal.on() expects at least two arguments');
212
- if ((eventName === 'change' || eventName === 'all') && typeof pattern !== 'function') {
213
- throw Error('Signal model events are not supported. Use reaction() for signal changes.');
214
- }
215
- if (typeof pattern !== 'function')
216
- throw Error('Signal.on() expects a handler function');
217
- return onCustomEvent(eventName, pattern);
218
- }
219
- once(eventName, pattern, handler) {
220
- if (arguments.length < 2)
221
- throw Error('Signal.once() expects at least two arguments');
222
- if ((eventName === 'change' || eventName === 'all') && typeof pattern !== 'function') {
223
- throw Error('Signal model events are not supported. Use reaction() for signal changes.');
224
- }
225
- if (typeof pattern !== 'function')
226
- throw Error('Signal.once() expects a handler function');
227
- const onceHandler = (...args) => {
228
- this.removeListener(eventName, onceHandler);
229
- pattern(...args);
230
- };
231
- this.on(eventName, onceHandler);
232
- return onceHandler;
233
- }
234
- removeListener(eventName, handler) {
235
- if (arguments.length !== 2)
236
- throw Error('Signal.removeListener() expects two arguments');
237
- return removeCustomEventListener(eventName, handler);
238
- }
239
- }
240
- function isAggregationValuePath(segments) {
241
- return Array.isArray(segments) &&
242
- segments.length >= 3 &&
243
- segments[0] === AGGREGATIONS;
244
- }
245
- function isReactLike(value) {
246
- return !!(value && typeof value === 'object' && typeof value.$$typeof === 'symbol');
247
- }
248
- function resolveSignal($signal, segments) {
249
- let $cursor = $signal;
250
- for (const segment of segments) {
251
- $cursor = $cursor[segment];
252
- }
253
- return $cursor;
254
- }
255
- function isMissingPublicDocDeleteError($signal, error) {
256
- const segments = $signal?.[SEGMENTS];
257
- if (!Array.isArray(segments) || segments.length < 2)
258
- return false;
259
- if (!isPublicCollection(segments[0]))
260
- return false;
261
- if (!(error instanceof Error))
262
- return false;
263
- return error.message.includes('Trying to delete data from a non-existing doc');
264
- }
265
- async function setDiffDeepOnSignal($target, value) {
266
- if ($target[SEGMENTS].length === 0)
267
- throw Error('Can\'t set the root signal data');
268
- // Use peek() here. compat start() writes via setDiffDeep inside an observer and must not
269
- // subscribe to its own target, otherwise later local edits on child signals cause start()
270
- // to rerun and overwrite them from source.
271
- const before = $target.peek();
272
- if (isPublicCollection($target[SEGMENTS][0])) {
273
- await diffDeepCompat($target, before, value);
274
- return;
275
- }
276
- diffDeepCompatSync($target, before, value);
277
- }
278
- async function diffDeepCompat($signal, before, after) {
279
- if (before === after)
280
- return;
281
- if (Array.isArray(before) && Array.isArray(after)) {
282
- const diff = arrayDiff(before, after, deepEqualCompat);
283
- if (!diff.length)
284
- return;
285
- const index = getSingleArrayReplacementIndex(diff);
286
- if (index != null) {
287
- await diffDeepCompat(getChildSignal($signal, index), before[index], after[index]);
288
- return;
289
- }
290
- await applyArrayDiffCompat($signal, diff);
291
- return;
292
- }
293
- if (isDiffableObject(before, after)) {
294
- for (const key of Object.keys(before)) {
295
- if (Object.prototype.hasOwnProperty.call(after, key))
296
- continue;
297
- await SignalCompat.prototype.del.call(getChildSignal($signal, key));
298
- }
299
- for (const key of Object.keys(after)) {
300
- await diffDeepCompat(getChildSignal($signal, key), before[key], after[key]);
301
- }
302
- return;
303
- }
304
- await SignalCompat.prototype.set.call($signal, after);
305
- }
306
- function diffDeepCompatSync($signal, before, after) {
307
- if (before === after)
308
- return;
309
- if (Array.isArray(before) && Array.isArray(after)) {
310
- const diff = arrayDiff(before, after, deepEqualCompat);
311
- if (!diff.length)
312
- return;
313
- const index = getSingleArrayReplacementIndex(diff);
314
- if (index != null) {
315
- diffDeepCompatSync(getChildSignal($signal, index), before[index], after[index]);
316
- return;
317
- }
318
- applyArrayDiffCompatSync($signal, diff);
319
- return;
320
- }
321
- if (isDiffableObject(before, after)) {
322
- const preservePath = $signal[SEGMENTS];
323
- for (const key of Object.keys(before)) {
324
- if (Object.prototype.hasOwnProperty.call(after, key))
325
- continue;
326
- delPrivateCompatSync(getChildSignal($signal, key), { preservePath });
327
- }
328
- for (const key of Object.keys(after)) {
329
- diffDeepCompatSync(getChildSignal($signal, key), before[key], after[key]);
330
- }
331
- return;
332
- }
333
- setReplacePrivateCompatSync($signal, after);
334
- }
335
- function isDiffableObject(before, after) {
336
- if (!isPlainObject(before) || !isPlainObject(after))
337
- return false;
338
- if (isReactLike(before) || isReactLike(after))
339
- return false;
340
- return true;
341
- }
342
- function getSingleArrayReplacementIndex(diff) {
343
- if (!Array.isArray(diff) || diff.length !== 2)
344
- return null;
345
- const first = diff[0];
346
- const second = diff[1];
347
- if (first instanceof arrayDiff.RemoveDiff &&
348
- second instanceof arrayDiff.InsertDiff &&
349
- first.index === second.index &&
350
- first.howMany === 1 &&
351
- second.values.length === 1) {
352
- return first.index;
353
- }
354
- return null;
355
- }
356
- async function applyArrayDiffCompat($signal, diff) {
357
- for (const item of diff) {
358
- if (item instanceof arrayDiff.InsertDiff) {
359
- await arrayInsertOnSignal($signal, item.index, item.values);
360
- continue;
361
- }
362
- if (item instanceof arrayDiff.RemoveDiff) {
363
- await arrayRemoveOnSignal($signal, item.index, item.howMany);
364
- continue;
365
- }
366
- if (item instanceof arrayDiff.MoveDiff) {
367
- await arrayMoveOnSignal($signal, item.from, item.to, item.howMany);
368
- }
369
- }
370
- }
371
- function applyArrayDiffCompatSync($signal, diff) {
372
- const segments = ensureArrayTarget($signal);
373
- const rootId = getOwningRootId($signal);
374
- for (const item of diff) {
375
- if (item instanceof arrayDiff.InsertDiff) {
376
- arrayInsertPrivateData(rootId, segments, item.index, item.values);
377
- continue;
378
- }
379
- if (item instanceof arrayDiff.RemoveDiff) {
380
- arrayRemovePrivateData(rootId, segments, item.index, item.howMany);
381
- continue;
382
- }
383
- if (item instanceof arrayDiff.MoveDiff) {
384
- arrayMovePrivateData(rootId, segments, item.from, item.to, item.howMany);
385
- }
386
- }
387
- }
388
- function getChildSignal($parent, key) {
389
- const $child = new SignalCompat([...$parent[SEGMENTS], key]);
390
- const $root = getRoot($parent);
391
- if ($root)
392
- $child[ROOT] = $root;
393
- return $child;
394
- }
395
- function setReplacePrivateCompatSync($signal, value) {
396
- const segments = $signal[SEGMENTS];
397
- if (segments.length === 0)
398
- throw Error('Can\'t set the root signal data');
399
- const idFields = getIdFieldsForSegments(segments);
400
- if (isIdFieldPath(segments, idFields))
401
- return;
402
- if (isPublicDocPath(segments)) {
403
- value = normalizeIdFields(value, idFields, segments[1]);
404
- }
405
- setReplacePrivateData(getOwningRootId($signal), segments, value);
406
- }
407
- function delPrivateCompatSync($signal, options) {
408
- const segments = $signal[SEGMENTS];
409
- if (segments.length === 0)
410
- throw Error('Can\'t delete the root signal data');
411
- const idFields = getIdFieldsForSegments(segments);
412
- if (isIdFieldPath(segments, idFields))
413
- return;
414
- delPrivateData(getOwningRootId($signal), segments, options);
415
- }
416
- function deepEqualCompat(left, right) {
417
- if (left === right)
418
- return true;
419
- if (left == null || right == null)
420
- return false;
421
- if (typeof left !== 'object' || typeof right !== 'object')
422
- return false;
423
- if (Array.isArray(left) !== Array.isArray(right))
424
- return false;
425
- if (Array.isArray(left)) {
426
- if (left.length !== right.length)
427
- return false;
428
- for (let i = 0; i < left.length; i++) {
429
- if (!deepEqualCompat(left[i], right[i]))
430
- return false;
431
- }
432
- return true;
433
- }
434
- if (!isPlainObject(left) || !isPlainObject(right))
435
- return false;
436
- const leftKeys = Object.keys(left);
437
- const rightKeys = Object.keys(right);
438
- if (leftKeys.length !== rightKeys.length)
439
- return false;
440
- for (const key of leftKeys) {
441
- if (!Object.prototype.hasOwnProperty.call(right, key))
442
- return false;
443
- if (!deepEqualCompat(left[key], right[key]))
444
- return false;
445
- }
446
- return true;
447
- }
448
- function racerEqualCompat(left, right) {
449
- return left === right || (Number.isNaN(left) && Number.isNaN(right));
450
- }
451
- async function setReplaceOnSignal($signal, value) {
452
- const segments = $signal[SEGMENTS];
453
- if (segments.length === 0)
454
- throw Error('Can\'t set the root signal data');
455
- const idFields = getIdFieldsForSegments(segments);
456
- if (isIdFieldPath(segments, idFields))
457
- return;
458
- if (isPublicDocPath(segments)) {
459
- value = normalizeIdFields(value, idFields, segments[1]);
460
- }
461
- if (isPublicCollection(segments[0])) {
462
- return _setPublicDocReplace(segments, value);
463
- }
464
- return setReplacePrivateData(getOwningRootId($signal), segments, value);
465
- }
466
- async function incrementOnSignal($signal, byNumber) {
467
- const segments = $signal[SEGMENTS];
468
- if (segments.length === 0)
469
- throw Error('Can\'t increment the root signal data');
470
- const idFields = getIdFieldsForSegments(segments);
471
- if (isIdFieldPath(segments, idFields))
472
- return $signal.get();
473
- if (byNumber == null)
474
- byNumber = 1;
475
- if (typeof byNumber !== 'number')
476
- throw Error('Signal.increment() expects a number argument');
477
- let currentValue = $signal.get();
478
- if (currentValue == null)
479
- currentValue = 0;
480
- if (typeof currentValue !== 'number')
481
- throw Error('Signal.increment() tried to increment a non-number value');
482
- if (isPublicCollection(segments[0])) {
483
- await _incrementPublic(segments, byNumber);
484
- return currentValue + byNumber;
485
- }
486
- setReplacePrivateData(getOwningRootId($signal), segments, currentValue + byNumber);
487
- return currentValue + byNumber;
488
- }
489
- function ensureArrayTarget($signal) {
490
- const segments = $signal[SEGMENTS];
491
- if (segments.length < 2)
492
- throw Error('Can\'t mutate array on a collection or root signal');
493
- if ($signal[IS_QUERY])
494
- throw Error('Array mutators can\'t be used on a query signal');
495
- return segments;
496
- }
497
- function ensureValueTarget($signal) {
498
- const segments = $signal[SEGMENTS];
499
- if (segments.length < 2)
500
- throw Error('Can\'t mutate on a collection or root signal');
501
- if ($signal[IS_QUERY])
502
- throw Error('Mutators can\'t be used on a query signal');
503
- return segments;
504
- }
505
- async function arrayPushOnSignal($signal, value) {
506
- const segments = ensureArrayTarget($signal);
507
- const idFields = getIdFieldsForSegments(segments);
508
- if (isIdFieldPath(segments, idFields))
509
- return;
510
- if (isPublicCollection(segments[0]))
511
- return _arrayPushPublic(segments, value);
512
- return arrayPushPrivateData(getOwningRootId($signal), segments, value);
513
- }
514
- async function arrayUnshiftOnSignal($signal, value) {
515
- const segments = ensureArrayTarget($signal);
516
- const idFields = getIdFieldsForSegments(segments);
517
- if (isIdFieldPath(segments, idFields))
518
- return;
519
- if (isPublicCollection(segments[0]))
520
- return _arrayUnshiftPublic(segments, value);
521
- return arrayUnshiftPrivateData(getOwningRootId($signal), segments, value);
522
- }
523
- async function arrayInsertOnSignal($signal, index, values) {
524
- const segments = ensureArrayTarget($signal);
525
- const idFields = getIdFieldsForSegments(segments);
526
- if (isIdFieldPath(segments, idFields))
527
- return;
528
- if (isPublicCollection(segments[0]))
529
- return _arrayInsertPublic(segments, index, values);
530
- return arrayInsertPrivateData(getOwningRootId($signal), segments, index, values);
531
- }
532
- async function arrayPopOnSignal($signal) {
533
- const segments = ensureArrayTarget($signal);
534
- const idFields = getIdFieldsForSegments(segments);
535
- if (isIdFieldPath(segments, idFields))
536
- return;
537
- if (isPublicCollection(segments[0]))
538
- return _arrayPopPublic(segments);
539
- return arrayPopPrivateData(getOwningRootId($signal), segments);
540
- }
541
- async function arrayShiftOnSignal($signal) {
542
- const segments = ensureArrayTarget($signal);
543
- const idFields = getIdFieldsForSegments(segments);
544
- if (isIdFieldPath(segments, idFields))
545
- return;
546
- if (isPublicCollection(segments[0]))
547
- return _arrayShiftPublic(segments);
548
- return arrayShiftPrivateData(getOwningRootId($signal), segments);
549
- }
550
- async function arrayRemoveOnSignal($signal, index, howMany) {
551
- const segments = ensureArrayTarget($signal);
552
- const idFields = getIdFieldsForSegments(segments);
553
- if (isIdFieldPath(segments, idFields))
554
- return;
555
- if (isPublicCollection(segments[0]))
556
- return _arrayRemovePublic(segments, index, howMany);
557
- return arrayRemovePrivateData(getOwningRootId($signal), segments, index, howMany);
558
- }
559
- async function arrayMoveOnSignal($signal, from, to, howMany) {
560
- const segments = ensureArrayTarget($signal);
561
- const idFields = getIdFieldsForSegments(segments);
562
- if (isIdFieldPath(segments, idFields))
563
- return;
564
- if (isPublicCollection(segments[0]))
565
- return _arrayMovePublic(segments, from, to, howMany);
566
- return arrayMovePrivateData(getOwningRootId($signal), segments, from, to, howMany);
567
- }
568
- async function stringInsertOnSignal($signal, index, text) {
569
- const segments = ensureValueTarget($signal);
570
- const idFields = getIdFieldsForSegments(segments);
571
- if (isIdFieldPath(segments, idFields))
572
- return;
573
- if (isPublicCollection(segments[0]))
574
- return _stringInsertPublic(segments, index, text);
575
- return stringInsertPrivateData(getOwningRootId($signal), segments, index, text);
576
- }
577
- async function stringRemoveOnSignal($signal, index, howMany) {
578
- const segments = ensureValueTarget($signal);
579
- const idFields = getIdFieldsForSegments(segments);
580
- if (isIdFieldPath(segments, idFields))
581
- return;
582
- if (isPublicCollection(segments[0]))
583
- return _stringRemovePublic(segments, index, howMany);
584
- return stringRemovePrivateData(getOwningRootId($signal), segments, index, howMany);
585
- }
586
- function getOwningRootId($signal) {
587
- const $root = getRoot($signal) || $signal;
588
- return $root?.[ROOT_ID];
589
- }
590
- function shallowCopy(value) {
591
- const rawValue = raw(value);
592
- if (Array.isArray(rawValue))
593
- return rawValue.slice();
594
- if (rawValue && typeof rawValue === 'object')
595
- return { ...rawValue };
596
- return rawValue;
597
- }
598
- function deepCopy(value) {
599
- const rawValue = raw(value);
600
- if (!rawValue || typeof rawValue !== 'object')
601
- return rawValue;
602
- if (typeof globalThis.structuredClone === 'function') {
603
- try {
604
- return globalThis.structuredClone(rawValue);
605
- }
606
- catch { }
607
- }
608
- return racerDeepCopy(rawValue);
609
- }
610
- // Racer-style deep copy:
611
- // - Preserves prototypes by instantiating via `new value.constructor()`
612
- // - Copies own enumerable props recursively
613
- // - Keeps functions as-is (no cloning)
614
- // - Handles Date by creating a new Date
615
- // Limitations: does not handle cyclic refs, Map/Set/RegExp/TypedArray, non-enumerables.
616
- function racerDeepCopy(value) {
617
- if (value instanceof Date)
618
- return new Date(value);
619
- if (typeof value === 'object') {
620
- if (value === null)
621
- return null;
622
- if (Array.isArray(value)) {
623
- const array = [];
624
- for (let i = value.length; i--;) {
625
- array[i] = racerDeepCopy(value[i]);
626
- }
627
- return array;
628
- }
629
- const object = new value.constructor();
630
- for (const key in value) {
631
- if (Object.prototype.hasOwnProperty.call(value, key)) {
632
- object[key] = racerDeepCopy(value[key]);
633
- }
634
- }
635
- return object;
636
- }
637
- return value;
638
- }
639
- export { SignalCompat };
640
- export default SignalCompat;
@@ -1 +0,0 @@
1
- export function isCompatEnv (): boolean
@@ -1,4 +0,0 @@
1
- export function isCompatEnv() {
2
- return globalThis?.teamplayCompatibilityMode ??
3
- (typeof process !== 'undefined' && process?.env?.TEAMPLAY_COMPAT === '1');
4
- }