teamplay 0.5.0-alpha.31 → 0.5.0-alpha.33

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/dist/index.d.ts CHANGED
@@ -58,7 +58,9 @@ export type { SubMode, SubOptions } from './orm/sub.js';
58
58
  export { default as useSub, useAsyncSub, useBatchSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from './react/useSub.js';
59
59
  export { default as useSuspendMemo, useSuspendMemoByKey } from './react/useSuspendMemo.js';
60
60
  export declare const observer: ObserverFunction;
61
- export { emit, useOn, useEmit } from './orm/Compat/eventsCompat.js';
61
+ export { emit, useOn, useEmit } from './orm/events.js';
62
+ export { default as reaction } from './orm/reaction.js';
63
+ export type { ReactionHandle, ReactionOptions } from './orm/reaction.js';
62
64
  export { useDidUpdate, useOnce, useSyncEffect } from './react/helpers.js';
63
65
  export { connection, setConnection, getConnection, getDefaultFetchOnly, setDefaultFetchOnly, publicOnly, setPublicOnly } from './orm/connection.js';
64
66
  export type { TeamplayConnection, TeamplayShareDoc } from './orm/connection.js';
package/dist/index.js CHANGED
@@ -18,7 +18,8 @@ export { default as sub, unsub } from "./orm/sub.js";
18
18
  export { default as useSub, useAsyncSub, useBatchSub, setUseDeferredValue as __setUseDeferredValue, setDefaultDefer as __setDefaultDefer } from "./react/useSub.js";
19
19
  export { default as useSuspendMemo, useSuspendMemoByKey } from "./react/useSuspendMemo.js";
20
20
  export const observer = runtimeObserver;
21
- export { emit, useOn, useEmit } from './orm/Compat/eventsCompat.js';
21
+ export { emit, useOn, useEmit } from './orm/events.js';
22
+ export { default as reaction } from "./orm/reaction.js";
22
23
  export { useDidUpdate, useOnce, useSyncEffect } from "./react/helpers.js";
23
24
  export { connection, setConnection, getConnection, getDefaultFetchOnly, setDefaultFetchOnly, publicOnly, setPublicOnly } from "./orm/connection.js";
24
25
  export { TEAMPLAY_RUNTIME_CONFIG_SYMBOL, configureTeamplay, getTeamplayConfig, getDefaultIdFields, setDefaultIdFields } from "./config.js";
@@ -7,13 +7,13 @@ import { IS_QUERY, querySubscriptions } from '../Query.js';
7
7
  import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../Aggregation.js';
8
8
  import { getIdFieldsForSegments, isIdFieldPath, isPublicDocPath, normalizeIdFields, isPlainObject } from "../idFields.js";
9
9
  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';
10
- import { on as onCustomEvent, removeListener as removeCustomEventListener } from './eventsCompat.js';
11
- import { waitForImperativeQueryReady } from './queryReadiness.js';
12
- import { isModelEventsEnabled, normalizePattern, onModelEvent, removeModelListener } from './modelEvents.js';
10
+ import { on as onCustomEvent, removeListener as removeCustomEventListener } from '../events.js';
11
+ import { waitForImperativeQueryReady } from '../queryReadiness.js';
12
+ import { isModelEventsEnabled } from './modelEvents.js';
13
13
  import { setRefLink, removeRefLink, getAllRefLinks } from './refRegistry.js';
14
14
  import { REF_TARGET, resolveRefSignalSafe, resolveRefSegmentsSafe } from './refFallback.js';
15
15
  import { runInBatch } from '../batchScheduler.js';
16
- import { runInSilentContext, runInModelEventsSilentContext, isSilentContextActive } from './silentContext.js';
16
+ import { runInModelEventsSilentContext, isSilentContextActive } from './silentContext.js';
17
17
  import universal$ from "../../react/universal$.js";
18
18
  import { getRootContext } from "../rootContext.js";
19
19
  import { arrayInsertPrivateData, arrayMovePrivateData, arrayPopPrivateData, arrayPushPrivateData, arrayRemovePrivateData, arrayShiftPrivateData, arrayUnshiftPrivateData, delPrivateData, setReplacePrivateData, stringInsertPrivateData, stringRemovePrivateData } from '../privateData.js';
@@ -67,12 +67,6 @@ class SignalCompat extends Signal {
67
67
  return this.extra.get();
68
68
  return undefined;
69
69
  }
70
- silent(value) {
71
- if (arguments.length > 1)
72
- throw Error('Signal.silent() expects zero or one argument');
73
- const enabled = value == null ? true : !!value;
74
- return createSilentSignalWrapper(this, enabled);
75
- }
76
70
  get() {
77
71
  if (arguments.length > 0)
78
72
  throw Error('Signal.get() does not accept any arguments');
@@ -298,15 +292,8 @@ class SignalCompat extends Signal {
298
292
  on(eventName, pattern, handler) {
299
293
  if (arguments.length < 2)
300
294
  throw Error('Signal.on() expects at least two arguments');
301
- if (eventName === 'change' || eventName === 'all') {
302
- if (typeof pattern === 'function') {
303
- return onCustomEvent(eventName, pattern);
304
- }
305
- if (typeof handler !== 'function')
306
- throw Error('Signal.on() expects a handler function');
307
- const normalized = normalizePattern(pattern, 'Signal.on()');
308
- const rootId = (getRoot(this) || this)?.[ROOT_ID];
309
- return onModelEvent(rootId, eventName, normalized, handler);
295
+ if ((eventName === 'change' || eventName === 'all') && typeof pattern !== 'function') {
296
+ throw Error('Signal model events are not supported. Use reaction() for signal changes.');
310
297
  }
311
298
  if (typeof pattern !== 'function')
312
299
  throw Error('Signal.on() expects a handler function');
@@ -315,16 +302,8 @@ class SignalCompat extends Signal {
315
302
  once(eventName, pattern, handler) {
316
303
  if (arguments.length < 2)
317
304
  throw Error('Signal.once() expects at least two arguments');
318
- const isModelEvent = eventName === 'change' || eventName === 'all';
319
- if (isModelEvent && typeof pattern !== 'function') {
320
- if (typeof handler !== 'function')
321
- throw Error('Signal.once() expects a handler function');
322
- const onceHandler = (...args) => {
323
- this.removeListener(eventName, onceHandler);
324
- handler(...args);
325
- };
326
- this.on(eventName, pattern, onceHandler);
327
- return onceHandler;
305
+ if ((eventName === 'change' || eventName === 'all') && typeof pattern !== 'function') {
306
+ throw Error('Signal model events are not supported. Use reaction() for signal changes.');
328
307
  }
329
308
  if (typeof pattern !== 'function')
330
309
  throw Error('Signal.once() expects a handler function');
@@ -338,10 +317,6 @@ class SignalCompat extends Signal {
338
317
  removeListener(eventName, handler) {
339
318
  if (arguments.length !== 2)
340
319
  throw Error('Signal.removeListener() expects two arguments');
341
- if (eventName === 'change' || eventName === 'all') {
342
- const rootId = (getRoot(this) || this)?.[ROOT_ID];
343
- return removeModelListener(rootId, eventName, handler);
344
- }
345
320
  return removeCustomEventListener(eventName, handler);
346
321
  }
347
322
  ref(path, target, options) {
@@ -433,53 +408,6 @@ class SignalCompat extends Signal {
433
408
  delete this[REF_TARGET];
434
409
  }
435
410
  }
436
- const SILENT_WRAPPER = Symbol('compat silent wrapper');
437
- const SILENT_WRAPPER_TARGET = Symbol('compat silent wrapper target');
438
- const SILENT_WRAPPER_ENABLED = Symbol('compat silent wrapper enabled');
439
- function createSilentSignalWrapper($signal, enabled = true) {
440
- if (!$signal || typeof $signal !== 'function')
441
- return $signal;
442
- if ($signal[SILENT_WRAPPER]) {
443
- const target = $signal[SILENT_WRAPPER_TARGET] || $signal;
444
- return createSilentSignalWrapper(target, enabled);
445
- }
446
- const handler = {
447
- get(target, key, receiver) {
448
- if (key === SILENT_WRAPPER)
449
- return true;
450
- if (key === SILENT_WRAPPER_TARGET)
451
- return target;
452
- if (key === SILENT_WRAPPER_ENABLED)
453
- return enabled;
454
- if (key === 'silent') {
455
- return function silentWrapper(value) {
456
- if (arguments.length > 1)
457
- throw Error('Signal.silent() expects zero or one argument');
458
- const nextEnabled = value == null ? true : !!value;
459
- return createSilentSignalWrapper(target, nextEnabled);
460
- };
461
- }
462
- const value = Reflect.get(target, key, receiver);
463
- if (isSignalLike(value)) {
464
- return createSilentSignalWrapper(value, enabled);
465
- }
466
- if (typeof value === 'function') {
467
- return function wrappedMethod(...args) {
468
- if (!enabled)
469
- return Reflect.apply(value, target, args);
470
- return runInSilentContext(() => Reflect.apply(value, target, args));
471
- };
472
- }
473
- return value;
474
- },
475
- apply(target, thisArg, args) {
476
- if (!enabled)
477
- return Reflect.apply(target, thisArg, args);
478
- return runInSilentContext(() => Reflect.apply(target, thisArg, args));
479
- }
480
- };
481
- return new Proxy($signal, handler);
482
- }
483
411
  function getRefStore($signal) {
484
412
  const $root = getRoot($signal) || $signal;
485
413
  const rootId = $root?.[ROOT_ID];
@@ -1,5 +1,4 @@
1
1
  export function isSilentContextActive(): boolean;
2
2
  export function isModelEventsSilentContextActive(): boolean;
3
- export function runInSilentContext(fn: any): any;
4
3
  export function runInModelEventsSilentContext(fn: any): any;
5
4
  export function __resetSilentContextForTests(): void;
@@ -1,20 +1,10 @@
1
- let silentDepth = 0;
2
1
  let modelEventsSilentDepth = 0;
3
2
  export function isSilentContextActive() {
4
- return silentDepth > 0;
3
+ return false;
5
4
  }
6
5
  export function isModelEventsSilentContextActive() {
7
6
  return modelEventsSilentDepth > 0;
8
7
  }
9
- export function runInSilentContext(fn) {
10
- silentDepth += 1;
11
- try {
12
- return fn();
13
- }
14
- finally {
15
- silentDepth -= 1;
16
- }
17
- }
18
8
  export function runInModelEventsSilentContext(fn) {
19
9
  modelEventsSilentDepth += 1;
20
10
  try {
@@ -25,6 +15,5 @@ export function runInModelEventsSilentContext(fn) {
25
15
  }
26
16
  }
27
17
  export function __resetSilentContextForTests() {
28
- silentDepth = 0;
29
18
  modelEventsSilentDepth = 0;
30
19
  }
@@ -24,7 +24,6 @@ import { ROOT_FUNCTION, ROOT_ID, closeRootSignal, getRoot } from "./Root.js";
24
24
  import { getDefaultIdFields, getIdFieldsForSegments, isIdFieldPath, isPlainObject, isPublicDocPath, normalizeIdFields, prepareAddPayload, resolveAddDocId } from "./idFields.js";
25
25
  import { isCompatEnv } from './compatEnv.js';
26
26
  import { resolveRefSegmentsSafe, resolveRefSignalSafe } from './Compat/refFallback.js';
27
- import { compatStartOnRoot, compatStopOnRoot, joinScopePath } from './Compat/startStopCompat.js';
28
27
  import { runInBatch } from './batchScheduler.js';
29
28
  import { isPublicCollection } from "./signalPathKind.js";
30
29
  import { ARRAY_METHOD, DEFAULT_GETTERS, GET, GETTERS, SEGMENTS } from "./signalSymbols.js";
@@ -853,23 +852,6 @@ export const extremelyLateBindings = {
853
852
  }
854
853
  }
855
854
  }
856
- if (key === 'start') {
857
- const [relativePath, ...depsAndGetter] = argumentsList;
858
- if (typeof relativePath !== 'string')
859
- throw Error('Signal.start() expects targetPath to be a string');
860
- const absolutePath = joinScopePath($parent.path(), relativePath);
861
- return compatStartOnRoot(getRoot($parent) || $parent, absolutePath, ...depsAndGetter);
862
- }
863
- if (key === 'stop') {
864
- if (argumentsList.length > 1)
865
- throw Error('Signal.stop() expects zero or one argument');
866
- const relativePath = argumentsList.length === 0 ? '' : argumentsList[0];
867
- if (relativePath != null && typeof relativePath !== 'string') {
868
- throw Error('Signal.stop() expects targetPath to be a string');
869
- }
870
- const absolutePath = joinScopePath($parent.path(), relativePath || '');
871
- return compatStopOnRoot(getRoot($parent) || $parent, absolutePath);
872
- }
873
855
  }
874
856
  throw Error(ERRORS.noSignalKey($parent, key));
875
857
  },
@@ -1,7 +1,7 @@
1
- export function beginBatch(): void;
2
- export function endBatch(): void;
3
- export function inBatch(): boolean;
4
- export function runInBatch(fn: any): any;
5
- export function scheduleReaction(reactionFn: any): void;
6
- export function flushReactions(): void;
7
- export function __resetBatchSchedulerForTests(): void;
1
+ export function beginBatch (): void
2
+ export function endBatch (): void
3
+ export function inBatch (): boolean
4
+ export function runInBatch<TResult> (fn: () => TResult): TResult
5
+ export function scheduleReaction (reactionFn: () => unknown): void
6
+ export function flushReactions (): void
7
+ export function __resetBatchSchedulerForTests (): void
@@ -0,0 +1,6 @@
1
+ export const emit: any
2
+ export const on: any
3
+ export const removeListener: any
4
+ export const useOn: any
5
+ export const useEmit: any
6
+ export const __resetEventsForTests: any
@@ -0,0 +1,48 @@
1
+ import { useLayoutEffect } from 'react';
2
+ const listeners = new Map();
3
+ export function emit(eventName, ...args) {
4
+ const subs = listeners.get(eventName);
5
+ if (!subs)
6
+ return;
7
+ const snapshot = Array.from(subs);
8
+ for (const handler of snapshot) {
9
+ handler(...args);
10
+ }
11
+ }
12
+ export function on(eventName, handler) {
13
+ if (!listeners.has(eventName))
14
+ listeners.set(eventName, new Set());
15
+ const subs = listeners.get(eventName);
16
+ subs.add(handler);
17
+ return handler;
18
+ }
19
+ export function removeListener(eventName, handler) {
20
+ const subs = listeners.get(eventName);
21
+ if (!subs)
22
+ return;
23
+ subs.delete(handler);
24
+ if (!subs.size)
25
+ listeners.delete(eventName);
26
+ }
27
+ export function useOn(eventName, patternOrHandler, handler, deps) {
28
+ const isModelEvent = eventName === 'change' || eventName === 'all';
29
+ if (!isModelEvent || typeof patternOrHandler === 'function') {
30
+ if (typeof patternOrHandler !== 'function')
31
+ throw Error('useOn() expects a handler function');
32
+ }
33
+ else {
34
+ throw Error('Signal model events are not supported. Use reaction() for signal changes.');
35
+ }
36
+ useLayoutEffect(() => {
37
+ const listener = on(eventName, patternOrHandler);
38
+ return () => {
39
+ removeListener(eventName, listener);
40
+ };
41
+ }, [eventName, patternOrHandler, deps]);
42
+ }
43
+ export function useEmit() {
44
+ return emit;
45
+ }
46
+ export function __resetEventsForTests() {
47
+ listeners.clear();
48
+ }
@@ -4,4 +4,6 @@ export type { RootSignal, TeamplayCollections, TeamplayFeature, TeamplayFeatures
4
4
  export declare const BaseModel: import("./Signal.js").SignalConstructor;
5
5
  export default BaseModel;
6
6
  export { defineModels, default as initModels, getModels, resetModelsForTests } from './initModels.js';
7
+ export { default as reaction } from './reaction.js';
8
+ export type { ReactionHandle, ReactionOptions } from './reaction.js';
7
9
  export { defineSchema } from '@teamplay/schema';
package/dist/orm/index.js CHANGED
@@ -3,4 +3,5 @@ export { belongsTo, hasMany, hasOne } from "./associations.js";
3
3
  export const BaseModel = Signal;
4
4
  export default BaseModel;
5
5
  export { defineModels, default as initModels, getModels, resetModelsForTests } from "./initModels.js";
6
+ export { default as reaction } from "./reaction.js";
6
7
  export { defineSchema } from '@teamplay/schema';
@@ -1,4 +1,4 @@
1
- import type { PathSegment } from '../types/path.js'
1
+ import type { PathSegment } from './types/path.js'
2
2
 
3
3
  export function isDocReady (segments: readonly PathSegment[]): boolean
4
4
 
@@ -1,12 +1,12 @@
1
- import { getRaw } from '../dataTree.js';
2
- import { getConnection } from "../connection.js";
3
- import { isMissingShareDoc } from '../missingDoc.js';
4
- import { QUERIES, HASH, PARAMS, COLLECTION_NAME, querySubscriptions } from '../Query.js';
5
- import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../Aggregation.js';
6
- import { getPrivateData, setPrivateData } from '../privateData.js';
7
- import { getRoot, ROOT_ID } from "../Root.js";
8
- import { isRootContextClosed } from "../rootContext.js";
9
- import { getScopedSignalHash, normalizeRootId } from "../rootScope.js";
1
+ import { getRaw } from './dataTree.js';
2
+ import { getConnection } from "./connection.js";
3
+ import { isMissingShareDoc } from './missingDoc.js';
4
+ import { QUERIES, HASH, PARAMS, COLLECTION_NAME, querySubscriptions } from './Query.js';
5
+ import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from './Aggregation.js';
6
+ import { getPrivateData, setPrivateData } from './privateData.js';
7
+ import { getRoot, ROOT_ID } from "./Root.js";
8
+ import { isRootContextClosed } from "./rootContext.js";
9
+ import { getScopedSignalHash, normalizeRootId } from "./rootScope.js";
10
10
  let imperativeQueryReadyTimeoutMs = 1000;
11
11
  export function isQueryReady(collection, idsSegments, docsSegments, extraSegments, aggregationSegments, isAggregate, hasExtraResult) {
12
12
  if (hasExtraResult) {
@@ -0,0 +1,11 @@
1
+ export interface ReactionOptions {
2
+ lazy?: boolean;
3
+ debugger?: Function;
4
+ scheduler?: (run: () => unknown) => unknown;
5
+ onError?: (error: unknown) => unknown;
6
+ }
7
+ export interface ReactionHandle {
8
+ dispose: () => void;
9
+ }
10
+ export declare function reaction(fn: () => unknown, options?: ReactionOptions): ReactionHandle;
11
+ export default reaction;
@@ -0,0 +1,47 @@
1
+ import { observe, unobserve } from '@nx-js/observer-util';
2
+ import { scheduleReaction } from './batchScheduler.js';
3
+ export function reaction(fn, options = {}) {
4
+ if (typeof fn !== 'function')
5
+ throw Error('reaction() expects a function');
6
+ let disposed = false;
7
+ let pendingReactionFn;
8
+ function runReaction(runner) {
9
+ if (disposed)
10
+ return;
11
+ try {
12
+ return runner();
13
+ }
14
+ catch (error) {
15
+ if (typeof options.onError === 'function')
16
+ return options.onError(error);
17
+ throw error;
18
+ }
19
+ }
20
+ const scheduledRun = () => {
21
+ if (!pendingReactionFn)
22
+ return;
23
+ return runReaction(pendingReactionFn);
24
+ };
25
+ const runner = observe(fn, {
26
+ lazy: true,
27
+ debugger: options.debugger,
28
+ scheduler: (reactionFn) => {
29
+ pendingReactionFn = reactionFn;
30
+ if (typeof options.scheduler === 'function')
31
+ return options.scheduler(scheduledRun);
32
+ scheduleReaction(scheduledRun);
33
+ }
34
+ });
35
+ if (!options.lazy)
36
+ runReaction(runner);
37
+ return {
38
+ dispose() {
39
+ if (disposed)
40
+ return;
41
+ disposed = true;
42
+ pendingReactionFn = undefined;
43
+ unobserve(runner);
44
+ }
45
+ };
46
+ }
47
+ export default reaction;
@@ -4,7 +4,7 @@ import { useScheduleUpdate, useCache, useDefer } from "./helpers.js";
4
4
  import executionContextTracker from "./executionContextTracker.js";
5
5
  import * as promiseBatcher from "./promiseBatcher.js";
6
6
  import { getPrivateData } from '../orm/privateData.js';
7
- import { isDocReady } from '../orm/Compat/queryReadiness.js';
7
+ import { isDocReady } from '../orm/queryReadiness.js';
8
8
  import { getRoot, ROOT_ID } from "../orm/Root.js";
9
9
  import { COLLECTION_NAME, HASH, IS_QUERY, PARAMS, QUERIES, querySubscriptions, materializeQueryDataDocsToCollection } from '../orm/Query.js';
10
10
  import { AGGREGATIONS, IS_AGGREGATION, aggregationSubscriptions } from '../orm/Aggregation.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "teamplay",
3
- "version": "0.5.0-alpha.31",
3
+ "version": "0.5.0-alpha.33",
4
4
  "description": "Full-stack signals ORM with multiplayer",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -139,5 +139,5 @@
139
139
  ]
140
140
  },
141
141
  "license": "MIT",
142
- "gitHead": "efb7d7215b571620de2f7348ba1cd80e0cff7024"
142
+ "gitHead": "5355598efe68ec4d94d0a08b0450a1bd35358caa"
143
143
  }
@@ -1,3 +0,0 @@
1
- export const emit: any
2
- export const useOn: any
3
- export const useEmit: any
@@ -1,73 +0,0 @@
1
- import { useLayoutEffect } from 'react';
2
- import { isModelEventsEnabled, normalizePattern, onModelEvent, removeModelListener, __resetModelEventsForTests } from './modelEvents.js';
3
- const listeners = new Map();
4
- export function emit(eventName, ...args) {
5
- const subs = listeners.get(eventName);
6
- if (!subs)
7
- return;
8
- const snapshot = Array.from(subs);
9
- for (const handler of snapshot) {
10
- handler(...args);
11
- }
12
- }
13
- export function on(eventName, handler) {
14
- if (!listeners.has(eventName))
15
- listeners.set(eventName, new Set());
16
- const subs = listeners.get(eventName);
17
- subs.add(handler);
18
- return handler;
19
- }
20
- export function removeListener(eventName, handler) {
21
- const subs = listeners.get(eventName);
22
- if (!subs)
23
- return;
24
- subs.delete(handler);
25
- if (!subs.size)
26
- listeners.delete(eventName);
27
- }
28
- export function useOn(eventName, patternOrHandler, handler, deps) {
29
- const isModelEvent = eventName === 'change' || eventName === 'all';
30
- const isCustom = !isModelEvent || typeof patternOrHandler === 'function';
31
- if (isCustom) {
32
- if (typeof patternOrHandler !== 'function')
33
- throw Error('useOn() expects a handler function');
34
- }
35
- else {
36
- if (typeof handler !== 'function')
37
- throw Error('useOn() expects a handler function');
38
- }
39
- const normalizedPattern = isCustom ? null : normalizePatternMaybe(patternOrHandler);
40
- useLayoutEffect(() => {
41
- if (isCustom) {
42
- const listener = on(eventName, patternOrHandler);
43
- return () => {
44
- removeListener(eventName, listener);
45
- };
46
- }
47
- if (normalizedPattern == null) {
48
- handler(patternOrHandler);
49
- return;
50
- }
51
- if (!isModelEventsEnabled())
52
- return;
53
- const listener = onModelEvent(undefined, eventName, normalizedPattern, handler);
54
- return () => {
55
- removeModelListener(undefined, eventName, listener);
56
- };
57
- }, [eventName, patternOrHandler, handler, deps, normalizedPattern, isCustom]);
58
- }
59
- export function useEmit() {
60
- return emit;
61
- }
62
- export function __resetEventsForTests() {
63
- listeners.clear();
64
- __resetModelEventsForTests();
65
- }
66
- function normalizePatternMaybe(pattern) {
67
- try {
68
- return normalizePattern(pattern);
69
- }
70
- catch {
71
- return null;
72
- }
73
- }
@@ -1,3 +0,0 @@
1
- export function compatStartOnRoot($root: any, targetPath: any, ...depsAndGetter: any[]): any;
2
- export function compatStopOnRoot($root: any, targetPath: any): void;
3
- export function joinScopePath(scopePath: any, relativePath: any): string;
@@ -1,250 +0,0 @@
1
- import { observe, raw, unobserve } from '@nx-js/observer-util';
2
- import { getActiveDocOpContext } from '../Doc.js';
3
- import { getRoot } from "../Root.js";
4
- import { scheduleReaction } from '../batchScheduler.js';
5
- const START_REACTIONS = Symbol('compat start reactions');
6
- const SKIP_TICK = Symbol('compat start skip tick');
7
- export function compatStartOnRoot($root, targetPath, ...depsAndGetter) {
8
- if (!isRootSignal($root))
9
- throw Error('Signal.start() is only available on root signal');
10
- if (typeof targetPath !== 'string')
11
- throw Error('Signal.start() expects targetPath to be a string');
12
- if (depsAndGetter.length < 1) {
13
- throw Error('Signal.start() expects targetPath, dependencies, and a getter function');
14
- }
15
- const getter = depsAndGetter[depsAndGetter.length - 1];
16
- if (typeof getter !== 'function') {
17
- throw Error('Signal.start() expects the last argument to be a getter function');
18
- }
19
- const deps = depsAndGetter.slice(0, -1);
20
- const targetSegments = parsePathSegments(targetPath);
21
- const $target = resolveSignal($root, targetSegments);
22
- const targetKey = $target.path();
23
- const depPaths = deps
24
- .map(dep => getStartDepPath(dep, $root))
25
- .filter(Boolean);
26
- const store = getStartStore($root);
27
- const existing = store.get(targetKey);
28
- if (existing)
29
- existing.stop();
30
- let lastSourceSnapshot = UNSET;
31
- let lastTargetSnapshot = UNSET;
32
- const reaction = observe(() => {
33
- const resolvedDeps = [];
34
- for (const dep of deps) {
35
- const resolved = resolveStartDep(dep, $root);
36
- if (resolved === SKIP_TICK)
37
- return;
38
- resolvedDeps.push(resolved);
39
- }
40
- let nextValue;
41
- try {
42
- nextValue = getter(...resolvedDeps);
43
- }
44
- catch (err) {
45
- if (isThenable(err))
46
- return;
47
- throw err;
48
- }
49
- const sourceSnapshot = detachStartValue(nextValue);
50
- if (lastSourceSnapshot !== UNSET && deepEqualStartValue(lastSourceSnapshot, sourceSnapshot)) {
51
- return;
52
- }
53
- const currentTargetSnapshot = lastTargetSnapshot === UNSET
54
- ? UNSET
55
- : detachStartValue($target.peek());
56
- if (currentTargetSnapshot !== UNSET && deepEqualStartValue(currentTargetSnapshot, sourceSnapshot)) {
57
- lastSourceSnapshot = sourceSnapshot;
58
- lastTargetSnapshot = sourceSnapshot;
59
- return;
60
- }
61
- if (currentTargetSnapshot !== UNSET &&
62
- !deepEqualStartValue(lastTargetSnapshot, currentTargetSnapshot) &&
63
- isActiveLocalDocOpForDeps(depPaths)) {
64
- lastSourceSnapshot = sourceSnapshot;
65
- return;
66
- }
67
- lastSourceSnapshot = sourceSnapshot;
68
- const detachedValue = detachStartValue(sourceSnapshot);
69
- lastTargetSnapshot = detachStartValue(detachedValue);
70
- // Keep the detached snapshot to avoid aliasing source and target.
71
- // Old racer start() writes through diffDeep by default. In compat mode we must preserve
72
- // that behavior, but also avoid reading the target reactively inside start(), otherwise
73
- // start() subscribes to its own output and local child edits get immediately overwritten.
74
- const maybePromise = $target.setDiffDeep(detachedValue);
75
- if (maybePromise?.then) {
76
- maybePromise
77
- .then(() => { })
78
- .catch(ignorePromiseRejection);
79
- }
80
- }, { scheduler: scheduleReaction });
81
- store.set(targetKey, { stop: () => unobserve(reaction) });
82
- return $target;
83
- }
84
- export function compatStopOnRoot($root, targetPath) {
85
- if (!isRootSignal($root))
86
- throw Error('Signal.stop() is only available on root signal');
87
- if (typeof targetPath !== 'string')
88
- throw Error('Signal.stop() expects targetPath to be a string');
89
- const targetSegments = parsePathSegments(targetPath);
90
- const $target = resolveSignal($root, targetSegments);
91
- const targetKey = $target.path();
92
- const store = getStartStore($root);
93
- const existing = store.get(targetKey);
94
- if (!existing)
95
- return;
96
- existing.stop();
97
- store.delete(targetKey);
98
- }
99
- export function joinScopePath(scopePath, relativePath) {
100
- if (typeof scopePath !== 'string')
101
- scopePath = '';
102
- const segments = [];
103
- if (scopePath)
104
- segments.push(...parsePathSegments(scopePath));
105
- if (relativePath)
106
- segments.push(...parsePathSegments(relativePath));
107
- return segments.join('.');
108
- }
109
- function getStartStore($root) {
110
- $root[START_REACTIONS] ??= new Map();
111
- return $root[START_REACTIONS];
112
- }
113
- function resolveStartDep(dep, $root) {
114
- try {
115
- if (isSignalLike(dep))
116
- return getStartDepValue(dep);
117
- if (typeof dep === 'string')
118
- return getStartDepValue(resolveSignal($root, parsePathSegments(dep)));
119
- return dep;
120
- }
121
- catch (err) {
122
- if (isThenable(err))
123
- return SKIP_TICK;
124
- throw err;
125
- }
126
- }
127
- function getStartDepPath(dep, $root) {
128
- if (isSignalLike(dep))
129
- return dep.path();
130
- if (typeof dep === 'string')
131
- return resolveSignal($root, parsePathSegments(dep)).path();
132
- }
133
- function getStartDepValue($signal) {
134
- return readReactiveSnapshot($signal.get());
135
- }
136
- function isActiveLocalDocOpForDeps(depPaths) {
137
- const context = getActiveDocOpContext();
138
- if (!context?.source)
139
- return false;
140
- const docPath = `${context.collection}.${context.docId}`;
141
- return depPaths.some(depPath => depPath === docPath || depPath.startsWith(docPath + '.'));
142
- }
143
- function readReactiveSnapshot(value) {
144
- if (!value || typeof value !== 'object')
145
- return value;
146
- if (value instanceof Date)
147
- return new Date(value);
148
- if (Array.isArray(value)) {
149
- const array = [];
150
- for (let i = 0; i < value.length; i++) {
151
- array[i] = readReactiveSnapshot(value[i]);
152
- }
153
- return array;
154
- }
155
- const object = new value.constructor();
156
- for (const key in value) {
157
- if (Object.prototype.hasOwnProperty.call(value, key)) {
158
- object[key] = readReactiveSnapshot(value[key]);
159
- }
160
- }
161
- return object;
162
- }
163
- function isSignalLike(value) {
164
- return value && typeof value.path === 'function' && typeof value.get === 'function';
165
- }
166
- function parsePathSegments(path) {
167
- return path.split('.').filter(Boolean);
168
- }
169
- function resolveSignal($base, segments) {
170
- let $cursor = $base;
171
- for (const segment of segments)
172
- $cursor = $cursor[segment];
173
- return $cursor;
174
- }
175
- function isRootSignal($signal) {
176
- return getRoot($signal) === $signal;
177
- }
178
- function ignorePromiseRejection() { }
179
- function isThenable(value) {
180
- return !!value && typeof value.then === 'function';
181
- }
182
- const UNSET = Symbol('compat start unset');
183
- function detachStartValue(value) {
184
- const rawValue = raw(value);
185
- if (!rawValue || typeof rawValue !== 'object')
186
- return rawValue;
187
- if (typeof globalThis.structuredClone === 'function') {
188
- try {
189
- return globalThis.structuredClone(rawValue);
190
- }
191
- catch { }
192
- }
193
- return racerDeepCopy(rawValue);
194
- }
195
- function racerDeepCopy(value) {
196
- if (value instanceof Date)
197
- return new Date(value);
198
- if (typeof value === 'object') {
199
- if (value === null)
200
- return null;
201
- if (Array.isArray(value)) {
202
- const array = [];
203
- for (let i = value.length; i--;) {
204
- array[i] = racerDeepCopy(value[i]);
205
- }
206
- return array;
207
- }
208
- const object = new value.constructor();
209
- for (const key in value) {
210
- if (Object.prototype.hasOwnProperty.call(value, key)) {
211
- object[key] = racerDeepCopy(value[key]);
212
- }
213
- }
214
- return object;
215
- }
216
- return value;
217
- }
218
- function deepEqualStartValue(left, right) {
219
- if (left === right)
220
- return true;
221
- if (Number.isNaN(left) && Number.isNaN(right))
222
- return true;
223
- if (left instanceof Date || right instanceof Date) {
224
- return left instanceof Date && right instanceof Date && left.getTime() === right.getTime();
225
- }
226
- if (!left || !right || typeof left !== 'object' || typeof right !== 'object')
227
- return false;
228
- if (Array.isArray(left) || Array.isArray(right)) {
229
- if (!Array.isArray(left) || !Array.isArray(right))
230
- return false;
231
- if (left.length !== right.length)
232
- return false;
233
- for (let i = 0; i < left.length; i++) {
234
- if (!deepEqualStartValue(left[i], right[i]))
235
- return false;
236
- }
237
- return true;
238
- }
239
- const leftKeys = Object.keys(left);
240
- const rightKeys = Object.keys(right);
241
- if (leftKeys.length !== rightKeys.length)
242
- return false;
243
- for (const key of leftKeys) {
244
- if (!Object.prototype.hasOwnProperty.call(right, key))
245
- return false;
246
- if (!deepEqualStartValue(left[key], right[key]))
247
- return false;
248
- }
249
- return true;
250
- }