From 0387332f130f1331583f11fc345335c8c7634e17 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Thu, 5 Aug 2021 20:13:21 +0530 Subject: [PATCH 1/8] feat(dx): instantiate all boundary types and other minor refactors --- src/index.ts | 2 +- src/types.ts | 64 +++++++++++++------------------------ test/types.twoslash-test.ts | 2 +- 3 files changed, 24 insertions(+), 44 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7ab82e3..a82f081 100644 --- a/src/index.ts +++ b/src/index.ts @@ -144,4 +144,4 @@ const createLogger = (definition: Machine.Definition.Impl) => (groupLabel: strin const useStateMachine = useStateMachineImpl as unknown as UseStateMachine; export default useStateMachine; -export const t = () => ({ [$$t]: undefined as T }) +export const t = () => ({ [$$t]: undefined as unknown as T }) diff --git a/src/types.ts b/src/types.ts index 5fb5090..1ac40de 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,14 +3,13 @@ import { R } from "./extras" export type UseStateMachine = >(definition: A.InferNarrowestObject) => - [ state: Machine.State> - , send: Machine.Send> + [ state: A.Instantiate>> + , send: A.Instantiate>> ] export const $$t = Symbol("$$t"); type $$t = typeof $$t; -export type CreateType = - () => { [$$t]: T } +export type CreateType = () => { [$$t]: T } export namespace Machine { export type Definition< @@ -150,9 +149,9 @@ export namespace Machine { export type Effect>> = - (parameter: EffectParameterForStateValue) => + (parameter: A.Instantiate>) => | void - | ((parameter: EffectCleanupParameterForStateValue) => void) + | ((parameter: A.Instantiate>) => void) type EffectImpl = (parameter: EffectParameter.Impl) => @@ -240,7 +239,7 @@ export namespace Machine { export type Event> = | O.Value<{ [T in U.Exclude]: A.Get extends infer E - ? E extends any ? O.ShallowClean<{ type: T } & E> : never + ? E extends any ? O.ShallowMerge<{ type: T } & E> : never : never }> | ( A.Get extends true ? never : @@ -271,26 +270,10 @@ export namespace Machine { } export namespace EffectParameter { - export interface EffectParameterForStateValue - extends Base - { event: Machine.EntryEventForStateValue - } - export namespace Cleanup { - export interface ForStateValue - extends Base - { event: Machine.ExitEventForStateValue - } - export type Impl = EffectParameter.Impl } - export interface Base - { send: Machine.Send - , context: Machine.Context - , setContext: Machine.SetContext - } - export type Impl = EffectParameterImpl; } export interface EffectParameterImpl @@ -446,11 +429,6 @@ export namespace S { : false; } -export namespace F { - export type Call = F extends (...args: any[]) => infer R ? R : never; - export type Parameters = F extends (...args: infer A) => any ? A : never; -} - export namespace U { export type Extract = T extends U ? T : never; export type Exclude = T extends U ? never : T; @@ -458,12 +436,11 @@ export namespace U { export namespace O { export type Value = T[keyof T]; - export type ShallowClean = { [K in keyof T]: T[K] } + export type ShallowMerge = { [K in keyof T]: T[K] } } export namespace A { export type Cast = T extends U ? T : U; - export type Fallback = T extends U ? T : U; export type Tuple = T[] | [T]; export type Object = object; export type String = string; @@ -508,10 +485,6 @@ export namespace A { P extends [infer K1, ...infer Kr] ? K1 extends keyof T ? _Get : - K1 extends Get.Returned$$ ? - _Get infer R ? R : undefined, Kr, F> : - K1 extends Get.Parameters$$ ? - _Get any ? A : undefined, Kr, F> : F : never @@ -520,14 +493,6 @@ export namespace A { ? A.Cast : never - export namespace Get { - const Returned$$ = Symbol("Returned$$"); - export type Returned$$ = typeof Returned$$; - - const Parameters$$ = Symbol("Parameters$$"); - export type Parameters$$ = typeof Parameters$$; - } - export type CustomError = Place extends (S.IsLiteral extends true ? Error : A.String) ? Place extends `${S.Assert} ` @@ -535,6 +500,21 @@ export namespace A { : `${S.Assert} ` : Error + export type Instantiate = + T extends any + ? T extends A.Function + ? T extends { (...a: infer A1): infer R1, (...a: infer A2): infer R2 } + ? { (...a: Instantiate): Instantiate + , (...a: Instantiate): Instantiate + } : + T extends (...a: infer A1) => infer R1 + ? (...a1: Instantiate) => Instantiate : + never : + T extends A.Object + ? { [K in keyof T]: Instantiate } : + T + : never + export type Tag = { [_ in N]: void } diff --git a/test/types.twoslash-test.ts b/test/types.twoslash-test.ts index f45be5e..7dfdcfa 100644 --- a/test/types.twoslash-test.ts +++ b/test/types.twoslash-test.ts @@ -2,7 +2,7 @@ import { A, LS, UseStateMachine, CreateType } from "../src/types"; const useStateMachine = (() => []) as any as UseStateMachine; -const t = (() => {}) as CreateType +const t = (() => undefined) as unknown as CreateType const query = () => ((global as any).twoSlashQueries.shift()) as { completions: string[], text: string } From 1d89f1fc5d82c5da1fae60b07982b29eeb5280b9 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Fri, 6 Aug 2021 06:53:25 +0530 Subject: [PATCH 2/8] don't instantiate built-in object (list taken from ts-toolbelt/Misc/BuiltIn) --- src/types.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/types.ts b/src/types.ts index 1ac40de..afd8edc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -501,6 +501,7 @@ export namespace A { : Error export type Instantiate = + T extends InstantiateBlockList ? T : T extends any ? T extends A.Function ? T extends { (...a: infer A1): infer R1, (...a: infer A2): infer R2 } @@ -515,6 +516,13 @@ export namespace A { T : never + type InstantiateBlockList = + | { [Symbol.toStringTag]: string } + | Error + | Date + | RegExp + | Generator + export type Tag = { [_ in N]: void } From c0d6d52b51e718c37fb7a241389de76e9aebac60 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Fri, 6 Aug 2021 07:57:57 +0530 Subject: [PATCH 3/8] instantiate guard parameter too --- src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/types.ts b/src/types.ts index afd8edc..b9ee28d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -127,9 +127,10 @@ export namespace Machine { | { target: TargetString , guard?: ( parameter: + A.Instantiate< { context: Machine.Context , event: U.Extract, Event> - } + }> ) => boolean } From 9702986305482144431ef0cafd768f1e07326307 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Fri, 6 Aug 2021 07:58:57 +0530 Subject: [PATCH 4/8] don't instantiate context, event and builtins --- src/types.ts | 56 ++++++++++++++++++++----------------- test/index.test.ts | 2 +- test/types.twoslash-test.ts | 35 +++++++++++++++++++++++ 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/src/types.ts b/src/types.ts index b9ee28d..f05cbe8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,8 +3,8 @@ import { R } from "./extras" export type UseStateMachine = >(definition: A.InferNarrowestObject) => - [ state: A.Instantiate>> - , send: A.Instantiate>> + [ state: A.Instantiated>> + , send: A.Instantiated>> ] export const $$t = Symbol("$$t"); @@ -127,9 +127,9 @@ export namespace Machine { | { target: TargetString , guard?: ( parameter: - A.Instantiate< - { context: Machine.Context - , event: U.Extract, Event> + A.Instantiated< + { context: A.Uninstantiated> + , event: A.Uninstantiated, Event>> }> ) => boolean } @@ -150,9 +150,9 @@ export namespace Machine { export type Effect>> = - (parameter: A.Instantiate>) => + (parameter: A.Instantiated>) => | void - | ((parameter: A.Instantiate>) => void) + | ((parameter: A.Instantiated>) => void) type EffectImpl = (parameter: EffectParameter.Impl) => @@ -239,8 +239,8 @@ export namespace Machine { export type Event> = | O.Value<{ [T in U.Exclude]: - A.Get extends infer E - ? E extends any ? O.ShallowMerge<{ type: T } & E> : never + A.Get extends infer P + ? P extends any ? O.ShallowMerge<{ type: T } & P> : never : never }> | ( A.Get extends true ? never : @@ -286,17 +286,17 @@ export namespace Machine { export interface EffectParameterForStateValue extends BaseEffectParameter - { event: Machine.EntryEventForStateValue + { event: A.Uninstantiated> } export interface EffectCleanupParameterForStateValue extends BaseEffectParameter - { event: Machine.ExitEventForStateValue + { event: A.Uninstantiated> } export interface BaseEffectParameter { send: Machine.Send - , context: Machine.Context + , context: A.Uninstantiated> , setContext: Machine.SetContext } @@ -352,8 +352,8 @@ export namespace Machine { } export type Send = - { (sendable: U.Exclude, A.String>): void - , (sendable: U.Extract, A.String>): void + { (sendable: A.Uninstantiated, A.String>>): void + , (sendable: A.Uninstantiated, A.String>>): void } type SendImpl = (send: Sendable.Impl) => void @@ -370,7 +370,9 @@ export namespace Machine { export type Impl = SetContextImpl; } - export type ContextUpdater = (context: Context) => Context + export type ContextUpdater = + (context: A.Uninstantiated>) => + A.Uninstantiated> type ContextUpdaterImpl = (context: Context.Impl) => Context.Impl export namespace ContextUpdater { @@ -387,8 +389,8 @@ export namespace Machine { > = Value extends any ? { value: Value - , context: Context - , event: EntryEventForStateValue + , context: A.Uninstantiated> + , event: A.Uninstantiated> , nextEventsT: A.Get, "type">[] , nextEvents: NextEvents } @@ -437,7 +439,7 @@ export namespace U { export namespace O { export type Value = T[keyof T]; - export type ShallowMerge = { [K in keyof T]: T[K] } + export type ShallowMerge = { [K in keyof T]: T[K] } & unknown } export namespace A { @@ -501,29 +503,33 @@ export namespace A { : `${S.Assert} ` : Error - export type Instantiate = - T extends InstantiateBlockList ? T : + export type Instantiated = + T extends Uninstantiated ? U : + T extends Builtin ? T : T extends any ? T extends A.Function ? T extends { (...a: infer A1): infer R1, (...a: infer A2): infer R2 } - ? { (...a: Instantiate): Instantiate - , (...a: Instantiate): Instantiate + ? { (...a: Instantiated): Instantiated + , (...a: Instantiated): Instantiated } : T extends (...a: infer A1) => infer R1 - ? (...a1: Instantiate) => Instantiate : + ? (...a1: Instantiated) => Instantiated : never : T extends A.Object - ? { [K in keyof T]: Instantiate } : + ? { [K in keyof T]: Instantiated } : T : never - type InstantiateBlockList = + type Builtin = | { [Symbol.toStringTag]: string } | Error | Date | RegExp | Generator + export type Uninstantiated = T & { [$$uninstantiated]: true } + declare const $$uninstantiated: unique symbol; + export type Tag = { [_ in N]: void } diff --git a/test/index.test.ts b/test/index.test.ts index 570dbcf..f3defc6 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -13,7 +13,7 @@ const clearLog = () => const useStateMachine = ((d: any) => _useStateMachine({ ...d, console: { log: logger } }) - ) as typeof _useStateMachine + ) as any as typeof _useStateMachine describe("useStateMachine", () => { describe("States & Transitions", () => { diff --git a/test/types.twoslash-test.ts b/test/types.twoslash-test.ts index 7dfdcfa..72bca5d 100644 --- a/test/types.twoslash-test.ts +++ b/test/types.twoslash-test.ts @@ -1273,3 +1273,38 @@ describe("workaround for #65", () => { } >()) }) + +describe("A.Instantiated", () => { + it("does not instantiate builtin objects", () => { + let _x: A.Instantiated = new Date() + _x; +// ^? + expect(query().text).toContain("Date") + }) + + it("does not instantiate context", () => { + interface Something { foo: string } + let [_state] = useStateMachine({ + // ^? + context: { foo: "" } as Something, + initial: "a", + states: { a: {} } + }) + + expect(query().text).toContain("Something") + }) + + it("does not instantiate event payloads deeply", () => { + interface Something { foo: string } + let [_, _send] = useStateMachine({ + // ^? + schema: { + events: { A: t<{ bar: Something }>() } + }, + initial: "a", + states: { a: { on: { A: "a" } } } + }) + + expect(query().text).toContain("Something") + }) +}) From 270e178f306bbda4ff187b51e38f3c42ca8a0e08 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Wed, 4 Aug 2021 00:11:58 +0530 Subject: [PATCH 5/8] api changes for `sendT` machine = { ...machineInstant, send } Machine.State -> Machine machineState -> machineInstant machineState.value -> machineInstant.state Machine.StateValue -> Machine.State --- src/index.ts | 54 ++++++++------- src/types.ts | 130 ++++++++++++++++++++---------------- test/index.test.ts | 89 +++++++++++++----------- test/types.twoslash-test.ts | 102 +++++++++++++--------------- 4 files changed, 198 insertions(+), 177 deletions(-) diff --git a/src/index.ts b/src/index.ts index a82f081..f9189a6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import { useEffect, useReducer } from "react"; -import { UseStateMachine, Machine, $$t } from "./types"; +import { UseStateMachine, Machine, $$t, O } from "./types"; import { assertNever, R, useConstant } from "./extras"; + const useStateMachineImpl = (definition: Machine.Definition.Impl) => { - const [state, dispatch] = useReducer(createReducer(definition), createInitialState(definition)); + const [machineInstant, dispatch] = useReducer(createReducer(definition), createInitialState(definition)); const send = useConstant(() => (sendable: Machine.Sendable.Impl) => dispatch({ type: "SEND", sendable })); @@ -13,30 +14,33 @@ const useStateMachineImpl = (definition: Machine.Definition.Impl) => { }; useEffect(() => { - const entry = R.get(definition.states, state.value)!.effect; + const entry = R.get(definition.states, machineInstant.state)!.effect; let exit = entry?.({ send, setContext, - event: state.event, - context: state.context, + event: machineInstant.event, + context: machineInstant.context, }); return typeof exit === "function" - ? () => exit?.({ send, setContext, event: state.event, context: state.context }) + ? () => exit?.({ send, setContext, event: machineInstant.event, context: machineInstant.context }) : undefined; // eslint-disable-next-line react-hooks/exhaustive-deps - }, [state.value, state.event]); + }, [machineInstant.state, machineInstant.event]); - return [state, send]; + return { ...machineInstant, send }; }; -const createInitialState = (definition: Machine.Definition.Impl): Machine.State.Impl => { +type MachineInstant = + O.OmitKey + +const createInitialState = (definition: Machine.Definition.Impl): MachineInstant => { let nextEvents = R.keys(R.concat( R.fromMaybe(R.get(definition.states, definition.initial)!.on), R.fromMaybe(definition.on) )) return { - value: definition.initial, + state: definition.initial, context: definition.context as Machine.Context.Impl, event: { type: "$$initial" } as Machine.Event.Impl, nextEvents: nextEvents, @@ -46,56 +50,56 @@ const createInitialState = (definition: Machine.Definition.Impl): Machine.State. const createReducer = (definition: Machine.Definition.Impl) => { let log = createLogger(definition); - return (machineState: Machine.State.Impl, internalEvent: InternalEvent): Machine.State.Impl => { + return (machineInstant: MachineInstant, internalEvent: InternalEvent): MachineInstant => { if (internalEvent.type === "SET_CONTEXT") { - let nextContext = internalEvent.updater(machineState.context); - log("Context update", ["Previous Context", machineState.context], ["Next Context", nextContext]); + let nextContext = internalEvent.updater(machineInstant.context); + log("Context update", ["Previous Context", machineInstant.context], ["Next Context", nextContext]); - return { ...machineState, context: nextContext }; + return { ...machineInstant, context: nextContext }; } if (internalEvent.type === "SEND") { let sendable = internalEvent.sendable; let event = typeof sendable === "string" ? { type: sendable } : sendable; - let context = machineState.context; - let stateNode = R.get(definition.states, machineState.value)!; + let context = machineInstant.context; + let stateNode = R.get(definition.states, machineInstant.state)!; let resolvedTransition = R.get(R.fromMaybe(stateNode.on), event.type) ?? R.get(R.fromMaybe(definition.on), event.type); if (!resolvedTransition) { log( `Current state doesn't listen to event type "${event.type}".`, - ["Current State", machineState], + ["Current State", machineInstant], ["Event", event] ); - return machineState; + return machineInstant; } - let [nextStateValue, didGuardDeny = false] = (() => { + let [nextState, didGuardDeny = false] = (() => { if (typeof resolvedTransition === "string") return [resolvedTransition]; if (resolvedTransition.guard === undefined) return [resolvedTransition.target]; if (resolvedTransition.guard({ context, event })) return [resolvedTransition.target]; return [resolvedTransition.target, true] - })() as [Machine.StateValue.Impl, true?] + })() as [Machine.State.Impl, true?] if (didGuardDeny) { log( - `Transition from "${machineState.value}" to "${nextStateValue}" denied by guard`, + `Transition from "${machineInstant.state}" to "${nextState}" denied by guard`, ["Event", event], ["Context", context] ); - return machineState; + return machineInstant; } - log(`Transition from "${machineState.value}" to "${nextStateValue}"`, ["Event", event]); + log(`Transition from "${machineInstant.state}" to "${nextState}"`, ["Event", event]); - let resolvedStateNode = R.get(definition.states, nextStateValue)!; + let resolvedStateNode = R.get(definition.states, nextState)!; let nextEvents = R.keys(R.concat( R.fromMaybe(resolvedStateNode.on), R.fromMaybe(definition.on) )); return { - value: nextStateValue, + state: nextState, context, event, nextEvents, diff --git a/src/types.ts b/src/types.ts index f05cbe8..65c6e0a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,15 +3,44 @@ import { R } from "./extras" export type UseStateMachine = >(definition: A.InferNarrowestObject) => - [ state: A.Instantiated>> - , send: A.Instantiated>> - ] + Machine> export const $$t = Symbol("$$t"); type $$t = typeof $$t; export type CreateType = () => { [$$t]: T } +export type Machine, + NextEvents = + ( State extends any + ? A.Get, "type"> + : never + )[] + > = + & { nextEvents: NextEvents + , send: Machine.Send + } + & ( State extends any + ? { state: State + , context: Machine.Context + , event: Machine.EntryEventForState + , nextEventsT: A.Get, "type">[] + } + : never + ) + +interface MachineImpl + { state: Machine.State.Impl + , context: Machine.Context.Impl + , event: Machine.Event.Impl + , nextEvents: Machine.Event.Impl["type"][] + , nextEventsT: Machine.Event.Impl["type"][] + , send: Machine.Send.Impl + } + export namespace Machine { + export type Impl = MachineImpl + export type Definition< Self, States = A.Get, @@ -52,8 +81,8 @@ export namespace Machine { ) interface DefinitionImp - { initial: StateValue.Impl - , states: R.Of + { initial: State.Impl + , states: R.Of , on?: Definition.On.Impl , schema?: { context?: null, events?: R.Of } , verbose?: boolean @@ -64,7 +93,7 @@ export namespace Machine { export namespace Definition { export type Impl = DefinitionImp - export type FromTypeParamter = + export type FromTypeParameter = "$$internalIsConstraint" extends keyof D ? D extends infer X ? X extends Definition ? X : never : never : D @@ -120,7 +149,7 @@ export namespace Machine { } export type Transition, + TargetString = Machine.State, Event = { type: L.Pop

} > = | TargetString @@ -135,12 +164,12 @@ export namespace Machine { } type TransitionImpl = - | State.Impl["value"] - | { target: State.Impl["value"] + | Machine.Impl["state"] + | { target: Machine.Impl["state"] , guard?: ( parameter: - { context: State.Impl["context"] - , event: State.Impl["event"] + { context: Machine.Impl["context"] + , event: Machine.Impl["event"] } ) => boolean } @@ -149,10 +178,10 @@ export namespace Machine { } - export type Effect>> = - (parameter: A.Instantiated>) => + export type Effect>> = + (parameter: A.Instantiated>) => | void - | ((parameter: A.Instantiated>) => void) + | ((parameter: A.Instantiated>) => void) type EffectImpl = (parameter: EffectParameter.Impl) => @@ -218,15 +247,15 @@ export namespace Machine { export type InitialEventType = "$$initial"; } - export type StateValue = + export type State = keyof A.Get - export type InitialStateValue = + export type InitialState = A.Get - type StateValueImpl = string & A.Tag<"Machine.StateValue"> - export namespace StateValue { - export type Impl = StateValueImpl; + type StateImpl = string & A.Tag<"Machine.State"> + export namespace State { + export type Impl = StateImpl; } export type Context = @@ -240,7 +269,7 @@ export namespace Machine { export type Event> = | O.Value<{ [T in U.Exclude]: A.Get extends infer P - ? P extends any ? O.ShallowMerge<{ type: T } & P> : never + ? P extends any ? O.ShallowClean<{ type: T } & P> : never : never }> | ( A.Get extends true ? never : @@ -271,7 +300,17 @@ export namespace Machine { } export namespace EffectParameter { + export interface EffectParameterForState + extends BaseEffectParameter + { event: Machine.EntryEventForState + } + export namespace Cleanup { + export interface ForState + extends BaseEffectParameter + { event: Machine.ExitEventForState + } + export type Impl = EffectParameter.Impl } @@ -284,14 +323,14 @@ export namespace Machine { , setContext: SetContext.Impl } - export interface EffectParameterForStateValue + export interface EffectParameterForState extends BaseEffectParameter - { event: A.Uninstantiated> + { event: A.Uninstantiated> } - export interface EffectCleanupParameterForStateValue + export interface EffectCleanupParameterForState extends BaseEffectParameter - { event: A.Uninstantiated> + { event: A.Uninstantiated> } export interface BaseEffectParameter @@ -300,8 +339,8 @@ export namespace Machine { , setContext: Machine.SetContext } - export type EntryEventForStateValue = - | ( StateValue extends InitialStateValue + export type EntryEventForState = + | ( State extends InitialState ? { type: Definition.InitialEventType } : never ) @@ -311,7 +350,7 @@ export namespace Machine { | O.Value<{ [S in keyof A.Get]: O.Value<{ [E in keyof A.Get]: A.Get extends infer T - ? (T extends A.String ? T : A.Get) extends StateValue + ? (T extends A.String ? T : A.Get) extends State ? E : never : never @@ -319,7 +358,7 @@ export namespace Machine { }> | O.Value<{ [E in keyof A.Get]: A.Get extends infer T - ? (T extends A.String ? T : A.Get) extends StateValue + ? (T extends A.String ? T : A.Get) extends State ? E : never : never @@ -327,11 +366,11 @@ export namespace Machine { } > - export type ExitEventForStateValue = + export type ExitEventForState = U.Extract< Event, { type: - | keyof A.Get + | keyof A.Get | keyof A.Get } > @@ -378,34 +417,6 @@ export namespace Machine { export namespace ContextUpdater { export type Impl = ContextUpdaterImpl; } - - export type State, - NextEvents = - ( Value extends any - ? A.Get, "type"> - : never - )[] - > = - Value extends any - ? { value: Value - , context: A.Uninstantiated> - , event: A.Uninstantiated> - , nextEventsT: A.Get, "type">[] - , nextEvents: NextEvents - } - : never - - interface StateImpl - { value: StateValue.Impl - , context: Context.Impl - , event: Event.Impl - , nextEvents: Event.Impl["type"][] - , nextEventsT: Event.Impl["type"][] - } - export namespace State { - export type Impl = StateImpl - } } export namespace L { @@ -439,7 +450,8 @@ export namespace U { export namespace O { export type Value = T[keyof T]; - export type ShallowMerge = { [K in keyof T]: T[K] } & unknown + export type ShallowClean = { [K in keyof T]: T[K] } + export type OmitKey = { [P in U.Exclude]: T[P] } } export namespace A { diff --git a/test/index.test.ts b/test/index.test.ts index f3defc6..783e118 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -32,12 +32,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["ACTIVATE"], nextEventsT: ["ACTIVATE"], + send: expect.any(Function) }); }); @@ -57,17 +58,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("ACTIVATE"); + result.current.send("ACTIVATE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE"], nextEventsT: ["DEACTIVATE"], + send: expect.any(Function) }); }); @@ -90,17 +92,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("FORCE_ACTIVATE"); + result.current.send("FORCE_ACTIVATE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "FORCE_ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE", "FORCE_ACTIVATE"], nextEventsT: ["DEACTIVATE", "FORCE_ACTIVATE"], + send: expect.any(Function), }); }); @@ -120,17 +123,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]({ type: "ACTIVATE" }); + result.current.send({ type: "ACTIVATE" }); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "ACTIVATE", }, - value: "active", + state: "active", nextEvents: ["DEACTIVATE"], nextEventsT: ["DEACTIVATE"], + send: expect.any(Function), }); }); @@ -152,15 +156,16 @@ describe("useStateMachine", () => { act(() => { // TypeScript won"t allow me to type "ON" because it knows it"s not a valid event // @ts-expect-error - result.current[1]("ON"); + result.current.send("ON"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -188,17 +193,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); it("should invoke effect callbacks", () => { @@ -227,7 +233,7 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(entry.mock.calls.length).toBe(2); @@ -260,14 +266,15 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -295,7 +302,7 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]({ type: "ACTIVATE", number: 10 }); + result.current.send({ type: "ACTIVATE", number: 10 }); }); expect(effect.mock.calls[0][0]["event"]).toStrictEqual({ type: "ACTIVATE", number: 10 }); }); @@ -352,16 +359,17 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(guard).toHaveBeenCalled(); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "$$initial" }, - value: "inactive", + state: "inactive", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -388,18 +396,19 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); expect(guard).toHaveBeenCalled(); - expect(result.current[0]).toStrictEqual({ + expect(result.current).toStrictEqual({ context: undefined, event: { type: "TOGGLE", }, - value: "active", + state: "active", nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); }); @@ -420,12 +429,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ - value: "inactive", + expect(result.current).toStrictEqual({ + state: "inactive", context: { foo: "bar" }, event: { type: "$$initial" }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -453,12 +463,13 @@ describe("useStateMachine", () => { }) ); - expect(result.current[0]).toStrictEqual({ - value: "inactive", + expect(result.current).toStrictEqual({ + state: "inactive", context: { foo: "bar" }, event: { type: "$$initial" }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); @@ -482,17 +493,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ - value: "active", + expect(result.current).toStrictEqual({ + state: "active", context: { toggleCount: 1 }, event: { type: "TOGGLE", }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); it("should update context on exit", () => { @@ -515,17 +527,18 @@ describe("useStateMachine", () => { ); act(() => { - result.current[1]("TOGGLE"); + result.current.send("TOGGLE"); }); - expect(result.current[0]).toStrictEqual({ - value: "active", + expect(result.current).toStrictEqual({ + state: "active", context: { toggleCount: 1 }, event: { type: "TOGGLE", }, nextEvents: ["TOGGLE"], nextEventsT: ["TOGGLE"], + send: expect.any(Function), }); }); }); @@ -592,7 +605,7 @@ describe("useStateMachine", () => { if (result.all[0] instanceof Error) throw result.all[0]; else if (result.all[1] instanceof Error) throw result.all[1]; - else expect(result.all[0][1]).toBe(result.all[1][1]); + else expect(result.all[0].send).toBe(result.all[1].send); }); }); }); diff --git a/test/types.twoslash-test.ts b/test/types.twoslash-test.ts index 72bca5d..c6b48e0 100644 --- a/test/types.twoslash-test.ts +++ b/test/types.twoslash-test.ts @@ -432,13 +432,13 @@ describe("Machine.Definition", () => { }) it("doesn't infer narrowest", () => { - let [state] = useStateMachine({ + let machine = useStateMachine({ schema: {}, context: { foo: "hello" }, initial: "a", states: { a: {} } }) - A.test(A.areEqual()) + A.test(A.areEqual()) }) }) @@ -1123,8 +1123,8 @@ describe("Machine.Definition", () => { }) }) -describe("UseStateMachine", () => { - let [state, send] = useStateMachine({ +describe("Machine", () => { + let machine = useStateMachine({ schema: { events: { X: t<{ foo: number }>(), @@ -1152,46 +1152,40 @@ describe("UseStateMachine", () => { } }) - describe("Machine.State", () => { - A.test(A.areEqual< - typeof state, - | { value: "a" + A.test(A.areEqual< + typeof machine, + & { nextEvents: ("X" | "Y" | "Z")[] + , send: + { ( sendable: + | { type: "X", foo: number } + | { type: "Y", bar?: number } + | { type: "Z" } + ): void + , ( sendable: + | "Y" + | "Z" + ): void + } + } + & ( { state: "a" , context: { foo?: number } , event: | { type: "$$initial" } | { type: "Y", bar?: number } | { type: "Z" } , nextEventsT: ("X" | "Z")[] - , nextEvents: ("X" | "Y" | "Z")[] } - | { value: "b" + | { state: "b" , context: { foo?: number } , event: { type: "X", foo: number } , nextEventsT: ("Y" | "Z")[] - , nextEvents: ("X" | "Y" | "Z")[] } - >()) - }) - - describe("Machine.Send", () => { - A.test(A.areEqual< - typeof send, - { ( sendable: - | { type: "X", foo: number } - | { type: "Y", bar?: number } - | { type: "Z" } - ): void - , ( sendable: - | "Y" - | "Z" - ): void - } - >()) - }) + ) + >()) }) describe("Machine.Definition.FromTypeParamter", () => { - let [state, send] = useStateMachine({ + let machine = useStateMachine({ context: { toggleCount: 0 }, initial: "inactive", states: { @@ -1208,33 +1202,31 @@ describe("Machine.Definition.FromTypeParamter", () => { }) A.test(A.areEqual< - typeof state, - | { value: "inactive" - , context: { toggleCount: number } - , event: - | { type: "$$initial" } - | { type: "TOGGLE" } - , nextEventsT: "TOGGLE"[] - , nextEvents: "TOGGLE"[] - } - | { value: "active" - , context: { toggleCount: number } - , event: { type: "TOGGLE" } - , nextEventsT: "TOGGLE"[] - , nextEvents: "TOGGLE"[] + typeof machine, + & { nextEvents: "TOGGLE"[] + , send: + { (sendable: { type: "TOGGLE" }): void + , (sendable: "TOGGLE"): void + } } - >()) - - A.test(A.areEqual< - typeof send, - { (sendable: { type: "TOGGLE" }): void - , (sendable: "TOGGLE"): void - } + & ( { state: "inactive" + , context: { toggleCount: number } + , event: + | { type: "$$initial" } + | { type: "TOGGLE" } + , nextEventsT: "TOGGLE"[] + } + | { state: "active" + , context: { toggleCount: number } + , event: { type: "TOGGLE" } + , nextEventsT: "TOGGLE"[] + } + ) >()) }) describe("fix(Machine.State['nextEvents']): only normalize don't widen", () => { - let [state] = useStateMachine({ + let machine = useStateMachine({ schema: { events: { Y: t<{}>() } }, @@ -1246,11 +1238,11 @@ describe("fix(Machine.State['nextEvents']): only normalize don't widen", () => { } }) - A.test(A.areEqual()) + A.test(A.areEqual()) }) describe("workaround for #65", () => { - let [_, send] = useStateMachine({ + let machine = useStateMachine({ schema: { events: { A: t<{ value: string }>() @@ -1267,7 +1259,7 @@ describe("workaround for #65", () => { }) A.test(A.areEqual< - typeof send, + typeof machine.send, { (sendable: { type: "A", value: string } | { type: "B" }): void , (sendable: "B"): void } From 68eeffe19b6fa56bcfb193a3d10870f3f6874060 Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Wed, 4 Aug 2021 00:14:08 +0530 Subject: [PATCH 6/8] make test logger add linebreaks --- test/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/index.test.ts b/test/index.test.ts index 783e118..62d3d2f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -6,7 +6,7 @@ const logger: Console["log"] = (...xs) => log += xs.reduce( (a, x) => a + (typeof x === "string" ? x : JSON.stringify(x)), "" - ) + ) + "\n" const clearLog = () => log = ""; From ed4b424b71c51a3e83e51739af13c0e4dbdf31bd Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Wed, 22 Dec 2021 07:43:34 +0530 Subject: [PATCH 7/8] fix tests --- src/types.ts | 10 ++++++---- test/types.twoslash-test.ts | 8 +++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/types.ts b/src/types.ts index 65c6e0a..f2a2d37 100644 --- a/src/types.ts +++ b/src/types.ts @@ -17,15 +17,17 @@ export type Machine = - & { nextEvents: NextEvents + & A.Instantiated< + { nextEvents: NextEvents , send: Machine.Send - } + }> & ( State extends any - ? { state: State + ? A.Instantiated< + { state: State , context: Machine.Context , event: Machine.EntryEventForState , nextEventsT: A.Get, "type">[] - } + }> : never ) diff --git a/test/types.twoslash-test.ts b/test/types.twoslash-test.ts index c6b48e0..512985a 100644 --- a/test/types.twoslash-test.ts +++ b/test/types.twoslash-test.ts @@ -1276,26 +1276,28 @@ describe("A.Instantiated", () => { it("does not instantiate context", () => { interface Something { foo: string } - let [_state] = useStateMachine({ + let _machine = useStateMachine({ // ^? context: { foo: "" } as Something, initial: "a", states: { a: {} } }) + _machine; expect(query().text).toContain("Something") }) it("does not instantiate event payloads deeply", () => { interface Something { foo: string } - let [_, _send] = useStateMachine({ - // ^? + let _machine = useStateMachine({ + // ^? schema: { events: { A: t<{ bar: Something }>() } }, initial: "a", states: { a: { on: { A: "a" } } } }) + _machine; expect(query().text).toContain("Something") }) From 8206f1a6fe3b3b64e72b9cefd7dc94868cd23a5c Mon Sep 17 00:00:00 2001 From: Devansh Jethmalani Date: Fri, 4 Feb 2022 02:33:55 +0530 Subject: [PATCH 8/8] bump major --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9fbbf97..1b61998 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cassiozen/usestatemachine", - "version": "1.0.1", + "version": "2.0.0", "license": "MIT", "author": "Cassio Zen", "main": "dist/index.js",