Skip to content

Commit

Permalink
Unified arguments for all implementation types (#3939)
Browse files Browse the repository at this point in the history
* Unified args WIP

* Unify guards

* Ugh just 2 tests failing

* Fix tests

* Fix types

* The flattening

* More flattening

* More flattening

* Flatten pure

* Flatten input

* Small tweaks to the unified args work (#3941)

* Small tweaks to the unified args work

* fix types

* fixed a test

* fixed a last type error

* Add changesets

* Update .changeset/hot-zoos-destroy.md

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
davidkpiano and Andarist authored Apr 4, 2023
1 parent 3b13cb5 commit 91bc6fd
Show file tree
Hide file tree
Showing 75 changed files with 933 additions and 762 deletions.
14 changes: 14 additions & 0 deletions .changeset/hot-zoos-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'xstate': major
---

Action/actor/delay/guard arguments are now consolidated into a single object argument. This is a breaking change for all of those things that are called with arguments.

```diff
assign({
- count: (context, event) => {
+ count: ({ context, event }) => {
return context.count + event.value;
}
})
```
12 changes: 12 additions & 0 deletions .changeset/poor-badgers-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'xstate': major
---

Guard arguments are now consolidated into a single object argument. This is a breaking change for all guards that are called with arguments.

```diff
- guard: (context, event) => {
+ guard: ({ context, event }) => {
return context.count + event.value > 10;
}
```
8 changes: 6 additions & 2 deletions packages/core/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export function resolveActionObject(
type: actionObject.type,
params: actionObject.params,
execute: (actorCtx) => {
return dereferencedAction(state.context, state.event, {
return dereferencedAction({
context: state.context,
event: state.event,
action: a,
_event: state._event,
state,
Expand Down Expand Up @@ -98,7 +100,9 @@ export function toActionObject<
function: action
},
execute: (actorCtx) => {
return action(state.context as TContext, _event.data as TEvent, {
return action({
context: state.context as TContext,
event: _event.data as TEvent,
action: actionObject,
_event: _event as SCXML.Event<TEvent>,
state: state as AnyState,
Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/actions/assign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ export function assign<
);
}

const meta: AssignMeta<TContext, TExpressionEvent, TEvent> = {
const args: AssignMeta<TContext, TExpressionEvent, TEvent> & {
context: TContext;
event: TExpressionEvent;
} = {
context: state.context,
event: _event.data,
state: state as any,
action,
_event,
Expand All @@ -70,12 +75,12 @@ export function assign<

let partialUpdate: Partial<TContext> = {};
if (isFunction(assignment)) {
partialUpdate = assignment(state.context, _event.data, meta);
partialUpdate = assignment(args);
} else {
for (const key of Object.keys(assignment)) {
const propAssignment = assignment[key];
partialUpdate[key as keyof TContext] = isFunction(propAssignment)
? propAssignment(state.context, _event.data, meta)
? propAssignment(args)
: propAssignment;
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/actions/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export function cancel<
},
(_event, { state, actorContext }) => {
const resolvedSendId = isFunction(sendId)
? sendId(state.context, _event.data, {
? sendId({
context: state.context,
event: _event.data,
_event,
state: state as any, // TODO: fix types,
self: actorContext?.self ?? ({} as any),
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/actions/invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ export function invoke<
systemId: invokeDef.systemId,
input:
typeof input === 'function'
? input(state.context, _event.data as any, {
self: actorContext!.self
? input({
context: state.context,
event: _event.data as any,
self: actorContext?.self
})
: input
});
Expand Down
15 changes: 10 additions & 5 deletions packages/core/src/actions/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import { log as logActionType } from '../actionTypes.js';
import { createDynamicAction } from '../../actions/dynamicAction.js';
import { BaseDynamicActionObject, DynamicLogAction } from '../index.js';

const defaultLogExpr = <TContext, TEvent extends EventObject>(
context: TContext,
event: TEvent
) => ({
const defaultLogExpr = <TContext, TEvent extends EventObject>({
context,
event
}: {
context: TContext;
event: TEvent;
}) => ({
context,
event
});
Expand Down Expand Up @@ -44,7 +47,9 @@ export function log<
(_event, { state, actorContext }) => {
const resolvedValue =
typeof expr === 'function'
? expr(state.context, _event.data, {
? expr({
context: state.context,
event: _event.data,
_event,
state: state as any,
self: actorContext?.self ?? ({} as any),
Expand Down
15 changes: 10 additions & 5 deletions packages/core/src/actions/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ export function pure<
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
>(
getActions: (
context: TContext,
event: TExpressionEvent
) => SingleOrArray<BaseActionObject | string> | undefined
getActions: ({
context,
event
}: {
context: TContext;
event: TExpressionEvent;
}) => SingleOrArray<BaseActionObject | string> | undefined
): BaseDynamicActionObject<
TContext,
TExpressionEvent,
Expand All @@ -41,7 +44,9 @@ export function pure<
params: {
actions:
toArray(
toActionObjects(getActions(state.context, _event.data))
toActionObjects(
getActions({ context: state.context, event: _event.data })
)
) ?? []
}
}
Expand Down
20 changes: 10 additions & 10 deletions packages/core/src/actions/raise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
AnyInterpreter,
RaiseActionParams,
NoInfer,
StateMeta
StateMeta,
UnifiedArg
} from '../types.js';
import { toSCXMLEvent } from '../utils.js';

Expand Down Expand Up @@ -62,32 +63,31 @@ export function raise<
? eventOrExpr.name
: eventOrExpr.type
};
const meta: StateMeta<any, any> = {
const args: UnifiedArg<TContext, TExpressionEvent> &
StateMeta<TContext, TExpressionEvent> = {
context: state.context,
event: _event.data,
_event,
state,
state: state as any, // TODO: fix
self: actorContext?.self ?? ({} as any),
system: actorContext?.system
};
const delaysMap = state.machine.options.delays;

// TODO: helper function for resolving Expr
const resolvedEvent = toSCXMLEvent(
typeof eventOrExpr === 'function'
? eventOrExpr(state.context, _event.data, meta)
: eventOrExpr
typeof eventOrExpr === 'function' ? eventOrExpr(args) : eventOrExpr
);

let resolvedDelay: number | undefined;
if (typeof params.delay === 'string') {
const configDelay = delaysMap && delaysMap[params.delay];
resolvedDelay =
typeof configDelay === 'function'
? configDelay(state.context, _event.data, meta)
: configDelay;
typeof configDelay === 'function' ? configDelay(args) : configDelay;
} else {
resolvedDelay =
typeof params.delay === 'function'
? params.delay(state.context, _event.data, meta)
? params.delay(args)
: params.delay;
}

Expand Down
27 changes: 13 additions & 14 deletions packages/core/src/actions/send.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import {
SendActionObject,
SendActionOptions,
State,
StateMeta
StateMeta,
UnifiedArg
} from '../index.js';
import { actionTypes, error } from '../actions.js';

Expand Down Expand Up @@ -85,7 +86,9 @@ export function send<
? eventOrExpr.name
: eventOrExpr.type
};
const meta: StateMeta<TContext, TEvent> = {
const args: UnifiedArg<TContext, TEvent> & StateMeta<TContext, TEvent> = {
context: state.context,
event: _event.data,
_event,
state: state as State<TContext, TEvent>,
self: actorContext?.self ?? (null as any),
Expand All @@ -95,25 +98,23 @@ export function send<

// TODO: helper function for resolving Expr
const resolvedEvent = toSCXMLEvent(
isFunction(eventOrExpr)
? eventOrExpr(state.context, _event.data, meta)
: eventOrExpr
isFunction(eventOrExpr) ? eventOrExpr(args) : eventOrExpr
);

let resolvedDelay: number | undefined;
if (isString(params.delay)) {
const configDelay = delaysMap && delaysMap[params.delay];
resolvedDelay = isFunction(configDelay)
? configDelay(state.context, _event.data, meta)
? configDelay(args)
: configDelay;
} else {
resolvedDelay = isFunction(params.delay)
? params.delay(state.context, _event.data, meta)
? params.delay(args)
: params.delay;
}

const resolvedTarget = isFunction(params.to)
? params.to(state.context, _event.data, meta)
? params.to(args)
: params.to;
let targetActorRef: AnyActorRef | undefined;

Expand Down Expand Up @@ -214,7 +215,7 @@ export function respond<
) {
return send<TContext, TEvent>(event, {
...options,
to: (_, __, { _event }) => {
to: ({ _event }) => {
return _event.origin!; // TODO: handle when _event.origin is undefined
}
});
Expand Down Expand Up @@ -251,7 +252,7 @@ export function forwardTo<
return resolvedTarget;
};
}
return send<TContext, TEvent>((_, event) => event, {
return send<TContext, TEvent>(({ event }) => event, {
...options,
to: target
});
Expand All @@ -273,12 +274,10 @@ export function escalate<
options?: SendActionParams<TContext, TEvent>
) {
return sendParent<TContext, TEvent>(
(context, event, meta) => {
(arg) => {
return {
type: actionTypes.error,
data: isFunction(errorData)
? errorData(context, event, meta)
: errorData
data: isFunction(errorData) ? errorData(arg) : errorData
};
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/stop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function stop<
},
(_event, { state }) => {
const actorRefOrString = isFunction(actor)
? actor(state.context, _event.data)
? actor({ context: state.context, event: _event.data })
: actor;
const actorRef =
typeof actorRefOrString === 'string'
Expand Down
39 changes: 17 additions & 22 deletions packages/core/src/guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import type {
BooleanGuardDefinition,
GuardConfig,
GuardDefinition,
GuardMeta,
SCXML,
GuardPredicate,
MachineContext
Expand All @@ -20,7 +19,7 @@ export function stateIn<
return {
type: 'xstate.guard:in',
params: { stateValue },
predicate: (_, __, { state }) => {
predicate: ({ state }) => {
if (isString(stateValue) && isStateId(stateValue)) {
return state.configuration.some((sn) => sn.id === stateValue.slice(1));
}
Expand All @@ -40,13 +39,8 @@ export function not<
type: 'xstate.boolean',
params: { op: 'not' },
children: [toGuardDefinition(guard)],
predicate: (ctx, _, meta) => {
return !meta.evaluate(
meta.guard.children![0],
ctx,
meta._event,
meta.state
);
predicate: ({ evaluate, guard, context, _event, state }) => {
return !evaluate(guard.children![0], context, _event, state);
}
};
}
Expand All @@ -61,9 +55,9 @@ export function and<
type: 'xstate.boolean',
params: { op: 'and' },
children: guards.map((guard) => toGuardDefinition(guard)),
predicate: (ctx, _, meta) => {
return meta.guard.children!.every((childGuard) => {
return meta.evaluate(childGuard, ctx, meta._event, meta.state);
predicate: ({ evaluate, guard, context, _event, state }) => {
return guard.children!.every((childGuard) => {
return evaluate(childGuard, context, _event, state);
});
}
};
Expand All @@ -76,9 +70,9 @@ export function or<TContext extends MachineContext, TEvent extends EventObject>(
type: 'xstate.boolean',
params: { op: 'or' },
children: guards.map((guard) => toGuardDefinition(guard)),
predicate: (ctx, _, meta) => {
return meta.guard.children!.some((childGuard) => {
return meta.evaluate(childGuard, ctx, meta._event, meta.state);
predicate: ({ evaluate, guard, context, _event, state }) => {
return guard.children!.some((childGuard) => {
return evaluate(childGuard, context, _event, state);
});
}
};
Expand All @@ -94,20 +88,21 @@ export function evaluateGuard<
state: State<TContext, TEvent>
): boolean {
const { machine } = state;
const guardMeta: GuardMeta<TContext, TEvent> = {
state,
guard,
_event,
evaluate: evaluateGuard
};

const predicate = machine?.options?.guards?.[guard.type] ?? guard.predicate;

if (!predicate) {
throw new Error(`Guard '${guard.type}' is not implemented.'.`);
}

return predicate(context, _event.data, guardMeta);
return predicate({
context,
event: _event.data,
state,
guard,
_event,
evaluate: evaluateGuard
});
}

export function toGuardDefinition<
Expand Down
Loading

0 comments on commit 91bc6fd

Please sign in to comment.