Coder Social home page Coder Social logo

robot's People

Contributors

acd02 avatar arjunyel avatar danburzo avatar denisstasyev avatar dependabot[bot] avatar drapegnik avatar emrysmyrddin avatar farskid avatar github-actions[bot] avatar gkiely avatar hipsterbrown avatar jamesopstad avatar jcquintas avatar josmardias avatar kayodebristol avatar kju2 avatar kybarg avatar markmichon avatar matthewp avatar matthewpblog avatar mitchreece avatar ninerian avatar rob-gordon avatar shazron avatar sytabaresa avatar t-var-s avatar theschmocker avatar willwillems avatar xar avatar zearin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

robot's Issues

Actions inside state?

I'll start with the disclaimer that I'm just dipping my toes with robot, but here goes.

Currently the documentation for actions states that:

action takes a function that will be run during a transition. The primary purpose of using action is to perform side-effects.

Question

Why are actions allowed only within transitions? What if I want to invoke an action every time the machine enters a specific state, regardless of the previous state? With the current API, I have to define that action in all the transitions that lead to that specific state.

Suggestion

Allow actions to be defined also as children of state.

const machine = createMachine({
  idle: state(transition("submit", "validate")),
  validate: state(
    action(ctx => log('Validation attempt', ctx))
  ),
  // ...gazillion other states that might lead to 'validate'
});

Now the action get's invoked every time the machine enters validate-state.

To take it one step further, there could also be an option to define whether the action should be triggered upon entry or exit of state.

Distribution in CJS / UMD format

Hi Matthew,

Following up on the Twitter conversation, I'm opening this issue about packaging for use in Node and (older) browsers.

I can add a Rollup config to build CJS / UMD bundles in a PR, but I noticed the use of "type": "module" in package.json (TIL!), and I'm not sure how making the main field point to CJS and module point to ES can impact people's setups...

`immediate` does not callback in `interpret`

Hello! I've been loving this library but I discovered some unexpected behavior with immediate (long post ahead).

Original code snippet:

import { createMachine, immediate, interpret, invoke, reduce, state, state as final, transition } from 'robot3';

const wait = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const load = async () => {
    await wait(1000);
    return 'content';
};

const machine = createMachine({
    ready: state(immediate("loading")),
    loading: invoke(load, transition('done', 'loaded')),
    loaded: final(),
});

console.log(`state: ${machine.current}`);

interpret(machine, service => {
    console.log(`state: ${service.machine.current}`);
});

Expected:

  • state: ready
  • state: loading
  • 1 second passes
  • state: loaded

What actually happens:

  • state: ready
  • 1 second passes
  • state: loaded

The state machine never actually has the loading state when transitioned to from an immediate state! All other kinds of transitions to loading would actually have loading be the state rather than skipping over it (I've actually had to retitle and rewrite this post a few times because I keep misidentifying what the pattern / problem is with the interaction of invoke and immediate).

One implication here is that, if you are using this to render a UI, your application can't know from the state machine that that 1 second is spent in loading; it'll still show its ready state. You would have to add a side effect (in the load function) that reaches outside the state machine to inform the outside world that there is loading taking place.

Without changing how immediate works (i.e. in the current version of the library), you can use a recipe like this to get my originally expected behavior (at least in this situation; I haven't tested it in the infinite other ways you could):

// all the codes from the original snippet except for the machine definition
const immediately = () => wait(0);

const machine = createMachine({
    ready: invoke(immediately, transition('done', 'loading')),
    loading: invoke(load, transition('done', 'loaded')),
    loaded: final(),
});
// same interpreter from the original snippet

However, this has problems like

  • requiring those helper functions (wait and immediately) to be written in userspace
  • leaving immediate without any purpose (as far as I'm aware?)

I think some solutions could be

  1. if this is the intended behavior: documenting that immediate works this way very explicitly
  2. if this is unintended behavior but not wanting to make a breaking change: having the library provide a shortcut like export const immediately = next => invoke(() => wait(0), transition('done', next));
  3. if this is unintended behavior that should be fixed with a breaking change: rewrite immediate to work in some way based off of the function in 2.

Using immediate AND transition

I apologize if this is a silly question, I just got started with robot.

I wrote this state machine (codesandbox here) and it didn't work as I expected:

const canSubmit = () => false; // will never approve!
const machine = createMachine({
  idle: state(transition("submit", "validate")),
  validate: state(
    immediate("submission", guard(canSubmit)),
    transition("revalidate", "submission", guard(canSubmit))
  ),
  submission: state()
});

What I thought would happen

After sending submit, I expected the machine to be in validate state, waiting for the revalidate transition.

What happened

Machine went back to idle

Why it's strange

In the guide, there's this code:

const machine = createMachine({
  idle: state(
    transition('submit', 'validate')
  ),
  validate: state(
    immediate('submission', guard(canSubmit)),
    immediate('idle') // <--- you can omit this line if you want to?
  ),
  submission: state()
});

...but to go back to idle then the guard prevents proceeding to 'submission', it's completely unnecessary to call immediate('idle'). The machine will go back to idle without it, too.

Is there a fault in my thinking, or a fault in the robot?๐Ÿค”

Import broken in Node.js

Hey there hello!

Package.json specifies "type": "module" so Node,js (>=13.5.0, which have esm support) tries to load robot3 as a module.

But in the build dist/machine.js file, the first line is Object.defineProperty(exports, '__esModule', { value: true });.

This breaks Node.js, which issues: Uncaught ReferenceError: exports is not defined.

Workaround

import("robot3/machine.js") works, as this file is esm.

Async transition guards

Pretty cool lib!

Is it possible to have guards returning promises, so it will only transition if the promise guard resolves?

e.g:

// Only allow submission of a login and password is entered.
async function canSubmit(ctx) {
  return fetch(`/api/verify/${ctx.login}`);
}

const machine = createMachine({
  idle: state(
    transition('submit', 'complete',
      guard(canSubmit)
    )
  ),
  complete: state()
});

I understand that Actions are the place where we are supposed to do side effects, but sometimes we have side effects which can fail and if we allow the transition, our machine would be in an invalid state.

Our use case is media playback. From idle to loaded you can try loading the video but if that fails you actually need to be back in the idle state.

Initial State

maybe there is already a way to do this, but how do I set the initial state for my machine ?
In the first toggle example in the docs(https://thisrobot.life), what to do if I want to start for example at the active state ?
right now it always starts at the first state the machine encounters, which might work in most cases

Typescript issue accessing the child in a nested scenario

I'm trying to run the scenario from the "Nested Guide" test, with Typescript.

Copying the exemple, I get some errors:

  1. I need to provide a context when calling createMachine, otherwise the compiler complains with:
   robot3.spec.ts:258:29 - error TS2345: Argument of type 'Machine<{ green: MachineState; yellow: MachineState; red: MachineState; }, unknown, "yellow" | "green" | "red">' is not assignable to parameter of type 'Machine<{}, {}, string>'.
      Type 'unknown' is not assignable to type '{}'.

    258     let service = interpret(stoplight, () => {});
  1. The compiler does not let me access the child by getting using the service.send function:
let child = service.send;
console.log(child.machine.current); // walk
   robot3.spec.ts:267:23 - error TS2339: Property 'machine' does not exist on type 'SendFunction<string>'.

    267     console.log(child.machine.current); // walk
                              ~~~~~~~
    robot3.spec.ts:269:11 - error TS2339: Property 'send' does not exist on type 'SendFunction<string>'.

    269     child.send('toggle');

However, I suspect this might be a typo in the Guide, since the child machine seems to be accessible at runtime by doing this:

let child = service.child;
console.log(child.machine.current); // walk

Is it a problem with the documentation ?

  1. However, in this case, the Service typescript type does not seem to have the child property:
robot3.spec.ts:267:23 - error TS2339: Property 'machine' does not exist on type 'SendFunction<string>'.

    267     console.log(child.machine.current); // walk
                              ~~~~~~~
  1. In the end, I can run the test successfully doing this:
it("can nest machines", async () => {

    const stopwalk = createMachine({
      walk: state(
        transition('toggle', 'dontWalk')
      ),
      dontWalk: state(
        transition('toggle', 'walk')
      )
    }, () : any => {} );
    
    const stoplight = createMachine({
      green: state(
        transition('next', 'yellow')
      ),
      yellow: state(
        transition('next', 'red')
      ),
      red: invoke(stopwalk,
        transition('done', 'green')
      )
    },() : any => {}); // Required "context" for typing

    let service = interpret(stoplight, () => {});

    service.send('next');
    console.log(service.machine.current); // yellow
    
    service.send('next');
    console.log(service.machine.current); // red
    
    let child = (service as any).child; // Need to cast, and access child instead of send
    console.log(child.machine.current); // walk
    
    child.send('toggle');
    console.log(child.machine.current); // dontWalk
    console.log(service.machine.current); // green

  })

Is there a cleaner way ?

Thanks !

[bug] Immediate doesn't trigger onChange event

const three = createMachine({
  threeInit: state(
    immediate('threeDone')
  ),
  threeDone: state(),
})

const two = createMachine({
  twoInit: state(
    immediate('twoStart'),
  ),
  twoStart: invoke(three,
    transition('done', 'twoDone'),
  ),
  twoDone: state(),
})

const one = createMachine({
  oneInit: state(
    transition('START', 'oneStart'),
  ),
  oneStart: invoke(two,
    transition('done', 'oneDone'),
  ),
  oneDone: state(),
})
const interpretedMachine = interpret(one, (service) => {
  console.log(service.machine.current)
});

interpretedMachine.send('START')

Output

oneStart

Expected output

oneStart
twoInit
twoStart
threeInit
threeDone
twoDone
oneDone

Nested Docs and Usage

Currently, the docs outline the current workflow for using the nested stoplight example:

let service = interpret(stoplight, () => {});

service.send('next');
console.log(service.machine.current); // yellow

service.send('next');
console.log(service.machine.current); // red

let child = service.send;
console.log(child.machine.current); // walk

child.send('toggle');
console.log(child.machine.current); // dontWalk
console.log(service.machine.current); // green

It seems like let child = service.send; should be let child = service.child;, right? If so, easy enough to change.

However, when applying the documented code, the final console.log returns red for me. https://stackblitz.com/edit/robot-stoplight?file=index.js There seems to be some possibilities here, but the ones I can see are lacking:

It is possible that the child machine should be:

const stopwalk = createMachine({
  walk: state(
    transition('toggle', 'dontWalk')
  ),
  dontWalk: state(),  // <--- makes it "final"?
});

And, the docs should run two paths through the machine, getting the "new" child the second time?

let service = interpret(stoplight, () => {});
service.send('next');
console.log(service.machine.current); // yellow

service.send('next');
console.log(service.machine.current); // red

let child = service.child;
console.log(child.machine.current); // walk

child.send('toggle');
console.log(child.machine.current); // dontWalk
console.log(service.machine.current); // green

service.send('next');
console.log(service.machine.current); // yellow

service.send('next');
console.log(service.machine.current); // red

child = service.child;     // <--- magic happens here when you get the NEW child machine 
console.log(child.machine.current); // walk

child.send('toggle');
console.log(child.machine.current); // dontWalk
console.log(service.machine.current); // green

Code for alternate versions: https://stackblitz.com/edit/robot-stoplight?file=alt.js

Fix mobile styles

Currently it doesn't look right in mobile view even though we have the breakpoints, not sure why.

Implement in typescript?

Would you be opposed to a PR that implements the library directly in TypeScript with an extra build step? It would ensure that the typings stay up to date and as exhaustive as it can be and add a level of safety to the actual library code. Wanted to ask before I put any time trying to do something like that.

Compile code for old browsers support

Hi, I've been using this module for a little while but just figured out that the app we are working on is not working on old browsers because the es2015+ syntax is not being compiled down to support those browsers, It would be possible to configure the build to generate es5 compatible code?, I can make a PR for that (this is also happening on the integrations libraries).

Thanks.

TypeScript support

I was feeling too guilty to open a "TypeScript herp derp?" issue when I found this project, but then I found this tweet, so here we are.

There are two main ways to add types โ€“ convert the source to TypeScript, and generate the JS and type file from that, or leave the source as the current JS, and add an index.d.ts with separate tests for the types.

Do you feel a preference for one or the other for this project?

Add API to get next possible transitions

I am building an API server to serve xState statecharts. For that I need to know which events are generally possible in the current state of the machine.
xState has a way to tell me with nextEvents. It seems robot does not provide such view on the state yet?
In xState I can also try to perform a state change without actually changing the state machines current state using service.machine.transition(current, event).changed
Is something like that possible with robot?

I'd like to add robot support to my API server.

Robot Logo

Hey everyone, I'm thinking about giving Robot a logo that's not just the robot emoji.

I created this, derived from an open license artwork I found online:

possible Robot logo

I like it, but if other more artistic people want to jump in that would be great too. Or just give your opinions.

Personally I like the idea of a robot head. A full robot would be nice too, but for stickers the head seems like the right dimensions to me. Would love to hear what you think.

[Question] Does robot support multiple nested machines?

const first = createMachine({
  start: state(
    transition('DONE', 'done'),
  ),
  done: state(),
})

const second = createMachine({
  start: state(
    transition('DONE', 'done'),
  ),
  done: state(),
})

const machine = createMachine({
  start: state(
    transition('FIRST', 'first'),
    transition('SECOND', 'second'),
  ),
  first: invoke(first,
    transition('done', 'start'),
  ),
  second: invoke(second,
    transition('done', 'start'),
  ),
})

when calling interpretedMachine.send('SECOND') get an error

uncaughtException TypeError: Cannot read property 'call' of undefined
    at Object.enter (\node_modules\robot3\dist\machine.js:90:13)
    at transitionTo (\node_modules\robot3\dist\machine.js:153:20)
    at send (\node_modules\robot3\dist\machine.js:164:12)
    at Object.send (\node_modules\robot3\dist\machine.js:171:20)

Pass the event into the initial context

This is particularly useful for child machines. Prevents the need for an initial state to handle receiving the event.

const child = createMachine({
  one: state()
}, (ctx, ev) => ( { files: ev.files }));

How to use the unpkg version of this?

I can't seem to get the exports from the published module at
import { createMachine } from "https://unpkg.com/[email protected]/dist/machine.js"

It errors with:

Uncaught SyntaxError: The requested module 'https://unpkg.com/[email protected]/dist/machine.js?module' does not provide an export named 'createMachine'

Maybe the module should export using module.exports instead of just exports?

I got it working using:
import { createMachine, state, transition } from "https://unpkg.com/[email protected]/machine.js"

However preact-robot is not properly hosted on unpkg (it tries to import from preact-hooks). So is there a way to get robot running with webmodules instead of transpiling?

Allow invoke to take machines

It would be nice of invoke could take a machine instead of a promise.

import { createMachine, invoke, state, transition } from '@matthewp/robot';

const machine = createMachine({
  idle: invoke(otherMachine,
    transition('done', 'next')
  ),
  next: state()
});

For this to work we need the concept of a final state.

Pass the event into the guard

I have a state machine where I want a a transition only to occur when the event has a specific form, e.g. a provided value is in a specific range. My understanding is that guards would be the right approach here, but it looks like events are currently not passed to guards.

Immutable State Machines

The goal of this issue is to find an immutable replacement for services.

Many parts of Robot are already immutable, machines and their state for example, but services are the one glaring exception. This causes a number of problems internally, mostly around services carrying around stale state in the middle of transitions.

I would like to research this topic a bit more and look to the wider software world for inspiration. It's important to first consider why do we have services in the first place? There could be a send() function that takes a machine and returns a new machine (this is kind of how it already works internally).

  • Services hold on to an onChange function that gets called when send() is called. How can we replace this?
  • Services hold the context object.
  • Services hold on to the child which is a child service. I think we can move this to the machine where child is a child machine but I'm not totally sure about this, this is sort of stateful.

Figure out a better way for matching the current state

In this branch I implemented matches() which works like in XState: https://github.com/matthewp/robot/tree/matches

That is, you can do something like this:

service.matches('foo.bar');

Which will return true of the state is foo and the child machine's state is bar. This is pretty nice.

However, one thing I've wanted for a while is the ability to do unions for matching. In JavaScript you can use bitwise operators to do union matching, like so:

const Red = 1;
const Yellow = 1 << 1;
const Green = 1 << 2;

const CanGo = Yellow | Green;

It would be cool to incorporate this into Robot some how. What if you could pass such a union into a service.matches() API like so:

service.machine.current; // "yellow";

service.matches(Yellow); // true
service.matches(Green); // false
service.matches(CanGo); // true

I'd like to explore possibly allowing for such a thing. This would likely mean creating the state bits so users don't have to. Here's a possibly api:

const { yellow, green } = machine.states;

service.matches(yellow | green);

This seems fine. What about child states though? I would want to incorporate these some how as well so you can do the sort of service.matches('foo.bar') check that's shown at the top of this issue.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.