@xstate/effect 0.1.0-alpha.7 → 1.0.0-alpha.11

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.
@@ -1,13 +1,79 @@
1
- import { Queue, Effect, Fiber, Scope, Exit, Cause, Context, Clock, Duration, Data } from 'effect';
2
- import { stopActor, terminateActor, deliverEvent, isMachineSnapshot } from 'xstate';
1
+ import { Cause, Data, Queue, Effect, Exit, Fiber, Scope, Context, Clock, Duration } from 'effect';
2
+ import { StateMachine, stopActor, terminateActor, deliverEvent, isMachineSnapshot } from 'xstate';
3
3
  import { createDurable } from 'xstate/durable';
4
4
 
5
- /** A mailbox item that reports a failed fire-and-forget action. */
5
+ function _checkPrivateRedeclaration(e, t) {
6
+ if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
7
+ }
8
+
9
+ function _classPrivateFieldInitSpec(e, t, a) {
10
+ _checkPrivateRedeclaration(e, t), t.set(e, a);
11
+ }
12
+
13
+ function _checkInRHS(e) {
14
+ if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null"));
15
+ return e;
16
+ }
17
+
18
+ function _assertClassBrand(e, t, n) {
19
+ if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
20
+ throw new TypeError("Private element is not present on this object");
21
+ }
22
+
23
+ function _classPrivateFieldGet2(s, a) {
24
+ return s.get(_assertClassBrand(s, a));
25
+ }
26
+
27
+ var _effectFailure = /*#__PURE__*/new WeakMap();
28
+ /** Preserves an Effect failure, defect or interruption through XState error routing. */
29
+ class EffectFailure extends Error {
30
+ static is(value) {
31
+ return typeof value === 'object' && value !== null && _effectFailure.has(_checkInRHS(value)) && _classPrivateFieldGet2(_effectFailure, value);
32
+ }
33
+ constructor(cause) {
34
+ super(Cause.pretty(cause));
35
+ this.cause = cause;
36
+ this._tag = 'EffectFailure';
37
+ _classPrivateFieldInitSpec(this, _effectFailure, true);
38
+ this.name = 'EffectFailure';
39
+ }
40
+ }
6
41
 
7
- const actionFailure = Symbol.for('@xstate/effect/actionFailure');
8
- function isActionFailure(value) {
9
- return typeof value === 'object' && value !== null && actionFailure in value;
42
+ /** @deprecated Inspect `EffectFailure.cause` for interruption instead. */
43
+ class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
44
+ get message() {
45
+ return 'Effect was interrupted before the actor completed';
46
+ }
10
47
  }
48
+
49
+ /**
50
+ * Reported by `waitFor` when the actor stops or errors before a snapshot
51
+ * matches, and by `join` when the actor stops without output. `join` reports
52
+ * an errored actor's outcome instead, preserving an Effect failure's Cause.
53
+ */
54
+ class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
55
+ get message() {
56
+ return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Reported by the `send` atom of `createActorAtoms` when an event is sent
62
+ * before the actor's runtime has finished building.
63
+ */
64
+ class NotReadyError extends Data.TaggedError('NotReadyError') {
65
+ get message() {
66
+ return 'The actor is not ready yet';
67
+ }
68
+ }
69
+
70
+ /** @internal */
71
+ function causeFromError(error) {
72
+ return error instanceof EffectFailure ? error.cause : Cause.fail(error);
73
+ }
74
+ /** An event rejected at the actor's delivery boundary. */
75
+ class EventRejectedError extends Data.TaggedError('EventRejectedError') {}
76
+
11
77
  const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
12
78
 
13
79
  /**
@@ -57,6 +123,7 @@ class EffectActor {
57
123
  this._observers = new Set();
58
124
  this._listeners = new Map();
59
125
  this._settled = false;
126
+ this._pending = new Set();
60
127
  this._root = root;
61
128
  this.id = root.id;
62
129
  this.address = root.address;
@@ -73,11 +140,69 @@ class EffectActor {
73
140
 
74
141
  /** Sends an event; the actor processes it on its own fiber. */
75
142
  send(event) {
143
+ if (this._deliveryError(event)) return;
144
+ Queue.offerUnsafe(this._mailbox, {
145
+ kind: 'event',
146
+ event
147
+ });
148
+ }
149
+
150
+ /** @internal */
151
+ _sendAndAwait(event) {
152
+ return Effect.callback(resume => {
153
+ const error = this._deliveryError(event);
154
+ if (error) {
155
+ resume(Effect.fail(error));
156
+ return;
157
+ }
158
+ const receipt = {
159
+ resume: exit => resume(exit)
160
+ };
161
+ this._pending.add(receipt);
162
+ Queue.offerUnsafe(this._mailbox, {
163
+ kind: 'event',
164
+ event,
165
+ receipt
166
+ });
167
+ return Effect.sync(() => {
168
+ this._pending.delete(receipt);
169
+ receipt.resume = undefined;
170
+ });
171
+ });
172
+ }
173
+ _deliveryError(event) {
76
174
  if (this._settled) {
77
175
  this.system.deadLetter(undefined, this._root, event, 'stopped');
78
- return;
176
+ return this._stoppedError();
177
+ }
178
+ if (this.logic instanceof StateMachine && this.logic.isInternalEventType(event.type)) {
179
+ this.system.deadLetter(undefined, this._root, event, 'internalEvent');
180
+ return new EventRejectedError({
181
+ actorId: this.id,
182
+ event,
183
+ reason: 'internalEvent'
184
+ });
79
185
  }
80
- Queue.offerUnsafe(this._mailbox, event);
186
+ return undefined;
187
+ }
188
+
189
+ /** @internal */
190
+ _rejectReceipts() {
191
+ for (const receipt of this._pending) this._completeReceipt(receipt, Exit.fail(this._stoppedError()));
192
+ }
193
+ _stoppedError() {
194
+ return new ActorStoppedError({
195
+ actorId: this.id,
196
+ snapshot: this._snapshot
197
+ });
198
+ }
199
+
200
+ /** @internal */
201
+ _completeReceipt(receipt, exit) {
202
+ if (!receipt || !this._pending.delete(receipt)) return;
203
+ const resume = receipt.resume;
204
+ receipt.resume = undefined;
205
+ resume?.(exit);
81
206
  }
82
207
 
83
208
  /** Stops the actor, its children and every Effect it hosts. */
@@ -146,13 +271,13 @@ class EffectActor {
146
271
  }
147
272
  toJSON() {
148
273
  return {
149
- xstate$$type: 1,
150
- id: this.id
274
+ $actor: this.address
151
275
  };
152
276
  }
153
277
 
154
278
  /** @internal Publishes a snapshot produced by the execution loop. */
155
- _publish(snapshot) {
279
+ _publish(snapshot, receipt) {
280
+ if (this._settled) return;
156
281
  this._snapshot = snapshot;
157
282
  const status = snapshot.status;
158
283
  if (status === 'active') {
@@ -161,16 +286,19 @@ class EffectActor {
161
286
  }
162
287
  return;
163
288
  }
164
- this._settle(snapshot);
289
+ this._settle(snapshot, receipt);
165
290
  }
166
291
 
167
292
  /** @internal Marks the actor stopped and notifies observers. */
168
- _settle(snapshot) {
293
+ _settle(snapshot, processing) {
169
294
  if (this._settled) {
170
295
  return;
171
296
  }
172
297
  this._snapshot = snapshot;
173
298
  this._settled = true;
299
+ for (const receipt of this._pending) {
300
+ if (receipt !== processing) this._completeReceipt(receipt, Exit.fail(this._stoppedError()));
301
+ }
174
302
  const observers = [...this._observers];
175
303
  this._observers.clear();
176
304
  const status = snapshot.status;
@@ -315,7 +443,7 @@ function trackEffect(actor, host, interrupt) {
315
443
  * The fiber belongs to the host scope, so closing the owning actor also
316
444
  * waits for asynchronous invocation finalizers.
317
445
  */
318
- function startHostedEffect(actor, effect, spanName, onExit) {
446
+ function startHostedEffect(actor, effect, spanName, onExit, onCancel) {
319
447
  const host = requireEffectHost(actor);
320
448
  let active = true;
321
449
  const traced = Effect.withSpan(spanName, {
@@ -332,11 +460,18 @@ function startHostedEffect(actor, effect, spanName, onExit) {
332
460
  }
333
461
  active = false;
334
462
  untrackEffect(actor, host, cancel);
463
+ onCancel?.();
335
464
  interrupt();
336
465
  };
337
- const fiber = Fiber.runIn(Effect.runForkWith(host.context)(traced), host.scope);
466
+ // Register the fiber before its body runs: the body can synchronously
467
+ // trigger the host's close, which only awaits registered fibers.
468
+ const registered = Effect.withFiber(fiber => {
469
+ Fiber.runIn(fiber, host.scope);
470
+ host.fibers.add(fiber);
471
+ return traced;
472
+ });
473
+ const fiber = Effect.runForkWith(host.context)(registered);
338
474
  const interrupt = () => fiber.interruptUnsafe();
339
- host.fibers.add(fiber);
340
475
  fiber.addObserver(exit => {
341
476
  host.fibers.delete(fiber);
342
477
  if (!active) {
@@ -352,22 +487,16 @@ function startHostedEffect(actor, effect, spanName, onExit) {
352
487
  return cancel;
353
488
  }
354
489
 
355
- /**
356
- * Runs an Effect in the actor's host context and settles when it exits.
357
- * Interruption settles without error; failures and defects reject with the
358
- * squashed cause so the actor's error handling can observe them.
359
- */
490
+ /** @internal */
360
491
  function runHostedEffect(actor, effect, spanName) {
361
492
  return new Promise((resolve, reject) => {
362
493
  startHostedEffect(actor, effect, spanName, exit => {
363
- if (Exit.isSuccess(exit) || Cause.hasInterruptsOnly(exit.cause)) {
494
+ if (Exit.isSuccess(exit)) {
364
495
  resolve();
365
496
  } else {
366
- // Effect failures are arbitrary values, not necessarily Errors.
367
- // oxlint-disable-next-line typescript/prefer-promise-reject-errors
368
- reject(Cause.squash(exit.cause));
497
+ reject(new EffectFailure(exit.cause));
369
498
  }
370
- });
499
+ }, resolve);
371
500
  });
372
501
  }
373
502
 
@@ -416,7 +545,7 @@ function withActorScope(effect) {
416
545
  }
417
546
 
418
547
  const XSTATE_TIMER = 'xstate.timer';
419
- const XSTATE_INIT = '@xstate.init';
548
+ const XSTATE_INIT = 'xstate.init';
420
549
 
421
550
  /** Options for {@link createEffectActor}. */
422
551
 
@@ -444,6 +573,8 @@ function createEffectActor(logic, ...[options]) {
444
573
  const runFork = Effect.runForkWith(context);
445
574
  const runPromise = Effect.runPromiseWith(context);
446
575
  const mailbox = yield* Queue.unbounded();
576
+ let delivered;
577
+ const getDelivered = () => delivered;
447
578
  const timers = new Map();
448
579
  // `root` and `actor` are declared after the adapter below; its
449
580
  // callbacks only run once they are initialized.
@@ -454,7 +585,7 @@ function createEffectActor(logic, ...[options]) {
454
585
  Queue.offerUnsafe(mailbox, item);
455
586
  }
456
587
  };
457
- const timerKey = (source, id) => `${source.sessionId}:${id}`;
588
+ const timerKey = (source, timer) => `${source.sessionId}:${JSON.stringify(timer)}`;
458
589
  const inspectors = new Set();
459
590
  let rootAnnounced = false;
460
591
  const clock = Context.get(context, Clock.Clock);
@@ -470,14 +601,14 @@ function createEffectActor(logic, ...[options]) {
470
601
  if (result && typeof result.then === 'function') {
471
602
  void Promise.resolve(result).catch(error => {
472
603
  offer({
473
- [actionFailure]: true,
604
+ kind: 'actionFailure',
474
605
  error
475
606
  });
476
607
  });
477
608
  }
478
609
  } catch (error) {
479
610
  offer({
480
- [actionFailure]: true,
611
+ kind: 'actionFailure',
481
612
  error
482
613
  });
483
614
  }
@@ -501,7 +632,10 @@ function createEffectActor(logic, ...[options]) {
501
632
  },
502
633
  sendEvent: (source, target, event) => {
503
634
  if (isRoot(target)) {
504
- offer(event);
635
+ offer({
636
+ kind: 'event',
637
+ event: event
638
+ });
505
639
  return;
506
640
  }
507
641
  deliverEvent(source, target, event);
@@ -513,25 +647,28 @@ function createEffectActor(logic, ...[options]) {
513
647
  }
514
648
  source._emit(event);
515
649
  },
516
- scheduleTimer: (source, id, delay) => {
517
- const key = timerKey(source, id);
650
+ scheduleTimer: (source, timer, delay) => {
651
+ const key = timerKey(source, timer);
518
652
  timers.get(key)?.interruptUnsafe();
519
653
  const fiber = Fiber.runIn(runFork(Effect.andThen(Effect.sleep(Duration.millis(delay)), Effect.sync(() => {
520
654
  timers.delete(key);
521
655
  const timerEvent = {
522
656
  type: XSTATE_TIMER,
523
- id
657
+ ...timer
524
658
  };
525
659
  if (isRoot(source)) {
526
- offer(timerEvent);
660
+ offer({
661
+ kind: 'event',
662
+ event: timerEvent
663
+ });
527
664
  } else {
528
665
  deliverEvent(source, source, timerEvent);
529
666
  }
530
667
  }))), actorScope);
531
668
  timers.set(key, fiber);
532
669
  },
533
- cancelTimer: (source, id) => {
534
- const key = timerKey(source, id);
670
+ cancelTimer: (source, timer) => {
671
+ const key = timerKey(source, timer);
535
672
  timers.get(key)?.interruptUnsafe();
536
673
  timers.delete(key);
537
674
  },
@@ -543,8 +680,15 @@ function createEffectActor(logic, ...[options]) {
543
680
  }
544
681
  }
545
682
  },
546
- waitForEvent: () => runPromise(Queue.take(mailbox))
683
+ waitForEvent: () => runPromise(Queue.take(mailbox)).then(item => {
684
+ delivered = item;
685
+ return item.kind === 'event' ? item.event : {
686
+ type: 'xstate.effect.actionFailure'
687
+ };
688
+ })
547
689
  }, {
690
+ // A live actor needs one FIFO for child events, sends and timers.
691
+ captureRootEvents: false,
548
692
  inspect: event => {
549
693
  // The pure step scope re-materializes the root ref per step and
550
694
  // announces it again; observers should see the root once.
@@ -592,6 +736,7 @@ function createEffectActor(logic, ...[options]) {
592
736
  status: 'stopped'
593
737
  });
594
738
  }
739
+ actor._rejectReceipts();
595
740
  closeEffectHost(host);
596
741
  };
597
742
 
@@ -625,19 +770,23 @@ function createEffectActor(logic, ...[options]) {
625
770
  bindEffectHost(actor, host);
626
771
  const executeEffects = batch => Effect.promise(() => durable.executeEffects(batch).then(() => undefined, error => {
627
772
  offer({
628
- [actionFailure]: true,
773
+ kind: 'actionFailure',
629
774
  error
630
775
  });
631
776
  }));
632
777
  const loop = Effect.gen(function* () {
633
778
  yield* executeEffects(effects);
634
779
  while (snapshot.status === 'active' && !stopped) {
635
- const item = yield* Effect.promise(() => durable.waitForEvent().then(event => event, () => undefined));
636
- if (item === undefined || stopped) {
637
- break;
638
- }
780
+ delivered = undefined;
781
+ const received = yield* Effect.promise(() => durable.waitForEvent().catch(() => undefined));
782
+ if (received === undefined || stopped) break;
783
+ const item = getDelivered() ?? {
784
+ kind: 'event',
785
+ event: received
786
+ };
787
+ const receipt = item.kind === 'event' ? item.receipt : undefined;
639
788
  let event;
640
- if (isActionFailure(item)) {
789
+ if (item.kind === 'actionFailure') {
641
790
  const errorEvent = logic.getExecutionErrorEvent?.(snapshot, item.error);
642
791
  if (!errorEvent) {
643
792
  snapshot = errorSnapshot(snapshot, item.error);
@@ -645,16 +794,25 @@ function createEffectActor(logic, ...[options]) {
645
794
  }
646
795
  event = errorEvent;
647
796
  } else {
648
- event = item;
797
+ event = item.event;
649
798
  }
650
799
  try {
651
800
  [snapshot, effects] = durable.transition(snapshot, event);
652
801
  } catch (error) {
653
802
  snapshot = errorSnapshot(snapshot, error);
803
+ actor._publish(snapshot, receipt);
804
+ actor._completeReceipt(receipt, Exit.succeed(snapshot));
654
805
  break;
655
806
  }
656
- actor._publish(snapshot);
807
+ actor._publish(snapshot, receipt);
808
+ if (stopped) break;
809
+ const rejectedReason = effects.length === 1 && effects[0].effect.type === '@xstate.deadLetter' && 'reason' in effects[0].effect && effects[0].effect.reason === 'invalidEvent' ? effects[0].effect.reason : undefined;
657
810
  yield* executeEffects(effects);
811
+ actor._completeReceipt(receipt, rejectedReason !== undefined ? Exit.fail(new EventRejectedError({
812
+ actorId: actor.id,
813
+ event,
814
+ reason: rejectedReason
815
+ })) : Exit.succeed(snapshot));
658
816
  }
659
817
  if (!stopped) {
660
818
  actor._publish(snapshot);
@@ -684,45 +842,11 @@ function createEffectActor(logic, ...[options]) {
684
842
  }) => actor));
685
843
  }
686
844
 
687
- /**
688
- * The failure an Effect-backed actor reports when its Effect was interrupted
689
- * by something other than the actor being stopped, such as `Effect.interrupt`,
690
- * losing an `Effect.race`, or an `Effect.timeout` that interrupts.
691
- */
692
- class EffectInterruptedError extends Data.TaggedError('EffectInterruptedError') {
693
- get message() {
694
- return 'Effect was interrupted before the actor completed';
695
- }
696
- }
697
-
698
- /**
699
- * Reported by `waitFor` when the actor stops or errors before a snapshot
700
- * matches, and by `join` when the actor stops without output. `join` reports
701
- * an errored actor's own `snapshot.error` instead.
702
- */
703
- class ActorStoppedError extends Data.TaggedError('ActorStoppedError') {
704
- get message() {
705
- return `Actor "${this.actorId}" ${this.snapshot.status === 'error' ? 'errored' : 'stopped'} before completing`;
706
- }
707
- }
708
-
709
- /**
710
- * Reported by the `send` atom of `createActorAtoms` when an event is sent
711
- * before the actor's runtime has finished building.
712
- */
713
- class NotReadyError extends Data.TaggedError('NotReadyError') {
714
- get message() {
715
- return 'The actor is not ready yet';
716
- }
717
- }
718
-
719
- /** The tag of the machine itself, used when the root state is parallel. */
720
- const MACHINE_TAG = '(machine)';
721
-
722
845
  /**
723
846
  * The dotted path of a state value: `'idle'`, `'form.editing'`. A parallel
724
- * state has no single path, so its tag stops at the parallel state, or is
725
- * `'(machine)'` when the machine itself is parallel.
847
+ * state has no single path, so its tag stops at the parallel state. When the
848
+ * machine itself is parallel, the tag is `TRootTag`: the machine id, or `''`
849
+ * for a machine without one.
726
850
  */
727
851
 
728
852
  /**
@@ -734,16 +858,16 @@ const MACHINE_TAG = '(machine)';
734
858
  /** The tagged state union of a machine. */
735
859
 
736
860
  /** Computes the dotted path tag of a state value. */
737
- function stateTag(value, prefix = '') {
861
+ function stateTag(value, rootTag, prefix = '') {
738
862
  if (typeof value === 'string') {
739
863
  return prefix ? `${prefix}.${value}` : value;
740
864
  }
741
865
  const keys = Object.keys(value);
742
866
  if (keys.length !== 1) {
743
- return prefix || MACHINE_TAG;
867
+ return prefix || rootTag;
744
868
  }
745
869
  const key = keys[0];
746
- return stateTag(value[key], prefix ? `${prefix}.${key}` : key);
870
+ return stateTag(value[key], rootTag, prefix ? `${prefix}.${key}` : key);
747
871
  }
748
872
 
749
873
  /**
@@ -763,11 +887,11 @@ function stateTag(value, prefix = '') {
763
887
  */
764
888
  function taggedState(snapshot) {
765
889
  return {
766
- _tag: stateTag(snapshot.value),
890
+ _tag: stateTag(snapshot.value, snapshot.machine.id),
767
891
  value: snapshot.value,
768
892
  context: snapshot.context,
769
893
  snapshot
770
894
  };
771
895
  }
772
896
 
773
- export { ActorStoppedError as A, EffectInterruptedError as E, NotReadyError as N, runHostedEffect as a, EffectActor as b, createEffectActor as c, ActorScope as d, relayToParent as r, startHostedEffect as s, taggedState as t, withActorScope as w };
897
+ export { ActorStoppedError as A, EffectFailure as E, NotReadyError as N, causeFromError as a, runHostedEffect as b, createEffectActor as c, EffectActor as d, ActorScope as e, EventRejectedError as f, EffectInterruptedError as g, relayToParent as r, startHostedEffect as s, taggedState as t, withActorScope as w };
@@ -1,8 +1,8 @@
1
1
  import { Cause } from 'effect';
2
2
  import { Atom, AsyncResult } from 'effect/reactivity';
3
- import { c as createEffectActor, N as NotReadyError, t as taggedState } from './state-b3d9b3e2.js';
4
- export { N as NotReadyError } from './state-b3d9b3e2.js';
5
- import 'xstate';
3
+ import { StateMachine } from 'xstate';
4
+ import { c as createEffectActor, a as causeFromError, N as NotReadyError, t as taggedState } from './state-37b37ed4.js';
5
+ export { N as NotReadyError } from './state-37b37ed4.js';
6
6
  import 'xstate/durable';
7
7
 
8
8
  /**
@@ -55,7 +55,7 @@ function createActorAtoms(runtime, logic, ...[options]) {
55
55
  }
56
56
  const value = current.value;
57
57
  if (value.status === 'error') {
58
- return AsyncResult.failureWithPrevious(Cause.fail(value.error), {
58
+ return AsyncResult.failureWithPrevious(causeFromError(value.error), {
59
59
  previous: get.self()
60
60
  });
61
61
  }
@@ -75,15 +75,17 @@ function createActorAtoms(runtime, logic, ...[options]) {
75
75
  function select(selector) {
76
76
  return Atom.map(snapshot, result => AsyncResult.map(result, selector));
77
77
  }
78
- const state = select(current => taggedState(current));
79
- return {
78
+ const base = {
80
79
  actor,
81
80
  snapshot,
82
81
  result,
83
82
  send: sendAtom,
84
- select,
85
- state
83
+ select
86
84
  };
85
+ return logic instanceof StateMachine ? {
86
+ ...base,
87
+ state: select(current => taggedState(current))
88
+ } : base;
87
89
  }
88
90
 
89
91
  export { createActorAtoms };
@@ -0,0 +1,3 @@
1
+ export * from "./declarations/src/oxlint.js";
2
+ export { default } from "./declarations/src/oxlint.js";
3
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieHN0YXRlLWVmZmVjdC1veGxpbnQuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4vZGVjbGFyYXRpb25zL3NyYy9veGxpbnQuZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
@@ -0,0 +1,124 @@
1
+ import { defineRule, definePlugin } from '@oxlint/plugins';
2
+
3
+ const ENQUEUE_NAMES = new Set(['enq', 'enqueue']);
4
+ const EFFECT_LOGIC_CONSTRUCTORS = new Set(['createEffectLogic', 'createEffectStreamLogic', 'createEffectEventStreamLogic']);
5
+ const isNode = value => typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string';
6
+ const rootIdentifier = node => {
7
+ let current = node;
8
+ for (;;) {
9
+ if (current.type === 'CallExpression') {
10
+ current = current.callee;
11
+ } else if (current.type === 'MemberExpression') {
12
+ current = current.object;
13
+ } else if (current.type === 'Identifier') {
14
+ return current.name;
15
+ } else {
16
+ return undefined;
17
+ }
18
+ }
19
+ };
20
+ const returnedExpressions = fn => {
21
+ if (fn.body === null) {
22
+ return [];
23
+ }
24
+ if (fn.body.type !== 'BlockStatement') {
25
+ return [fn.body];
26
+ }
27
+ const found = [];
28
+ const visit = node => {
29
+ if (node.type === 'ReturnStatement') {
30
+ if (node.argument) {
31
+ found.push(node.argument);
32
+ }
33
+ return;
34
+ }
35
+ if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression' || node.type === 'FunctionDeclaration') {
36
+ return;
37
+ }
38
+ for (const [key, value] of Object.entries(node)) {
39
+ if (key === 'parent') {
40
+ continue;
41
+ }
42
+ if (Array.isArray(value)) {
43
+ for (const item of value) {
44
+ if (isNode(item)) {
45
+ visit(item);
46
+ }
47
+ }
48
+ } else if (isNode(value)) {
49
+ visit(value);
50
+ }
51
+ }
52
+ };
53
+ visit(fn.body);
54
+ return found;
55
+ };
56
+ const isPromiseResolve = node => node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name === 'Promise' && node.callee.property.type === 'Identifier' && node.callee.property.name === 'resolve';
57
+ const isEffectExpression = node => {
58
+ if (node.type !== 'CallExpression') {
59
+ return false;
60
+ }
61
+ if (isPromiseResolve(node)) {
62
+ const [argument] = node.arguments;
63
+ return argument !== undefined && isEffectExpression(argument);
64
+ }
65
+ return rootIdentifier(node) === 'Effect';
66
+ };
67
+ const isInlineEffectLogic = node => node.type === 'CallExpression' && node.callee.type === 'Identifier' && EFFECT_LOGIC_CONSTRUCTORS.has(node.callee.name);
68
+ const isFunctionNode = node => node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression';
69
+ const noInlineEffect = defineRule({
70
+ create(context) {
71
+ return {
72
+ CallExpression(node) {
73
+ const {
74
+ callee
75
+ } = node;
76
+ if (callee.type === 'MemberExpression' && callee.object.type === 'Identifier' && ENQUEUE_NAMES.has(callee.object.name) && callee.property.type === 'Identifier' && callee.property.name === 'spawn') {
77
+ const [logic] = node.arguments;
78
+ if (logic !== undefined && isInlineEffectLogic(logic)) {
79
+ context.report({
80
+ messageId: 'inlineSpawn',
81
+ node: logic
82
+ });
83
+ }
84
+ return;
85
+ }
86
+ if (callee.type !== 'Identifier' || !ENQUEUE_NAMES.has(callee.name)) {
87
+ return;
88
+ }
89
+ const [action] = node.arguments;
90
+ if (action === undefined || !isFunctionNode(action)) {
91
+ return;
92
+ }
93
+ for (const expression of returnedExpressions(action)) {
94
+ if (isEffectExpression(expression)) {
95
+ context.report({
96
+ messageId: 'inlineAction',
97
+ node: expression
98
+ });
99
+ }
100
+ }
101
+ }
102
+ };
103
+ },
104
+ meta: {
105
+ docs: {
106
+ description: 'Disallow Effects in inline enqueue callbacks and inline Effect logic in enq.spawn; declare them in setupEffect({ actions }) or actors.'
107
+ },
108
+ messages: {
109
+ inlineAction: 'An Effect returned from an inline action is discarded. Declare it in setupEffect({ actions }) and run it with enq(args.actions.name, args).',
110
+ inlineSpawn: 'Inline Effect logic passed to enq.spawn is invisible to RequirementsFrom and rejected at runtime. Declare it in actors and spawn args.actors.name.'
111
+ },
112
+ type: 'problem'
113
+ }
114
+ });
115
+ var oxlint = definePlugin({
116
+ meta: {
117
+ name: 'xstate-effect'
118
+ },
119
+ rules: {
120
+ 'no-inline-effect': noInlineEffect
121
+ }
122
+ });
123
+
124
+ export { oxlint as default };
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/test.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoieHN0YXRlLWVmZmVjdC10ZXN0LmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvdGVzdC5kLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBIn0=