Giter VIP home page Giter VIP logo

xsystem's Introduction

XSystem

GitHub issues latest release dependencies

Building Blocks for XState-based Actor Systems.

This package aims to extend the actor concepts that are present in XState and provide "plug 'n play" components. These come in the form of ready-to-use behavior, composable higher-order behavior, and utility functions.

At its core, XSystem provides mechanisms for actors to subscribe to events from other actors. An actor is able to publish events to all subscribers. Natively in XState, actors are only able to subscribe to state changes of another actor.

Installation

XSystem has a peer dependency to XState, which has to be installed as well.

npm i xstate xsystem

Documentation

All features that are available in XSystem are documented in the wiki of this repository.

This package is spearheading actor-system concepts for the XState community. Based on community feedback and new developments in XState, the functionality provided in XSystem is open to change as the community settles on best practices around actor systems. These might also include radical changes when it significantly improves the developer experience.

Related Work

Similar concepts for XState are explored by the following packages:

License

This project is published under the MIT license. All contributions are welcome.

xsystem's People

Contributors

christoph-fricke 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

Watchers

 avatar  avatar

xsystem's Issues

Improve DX around `withPubSub`

The idea of withPubSub is to extend a given behavior with the ability to publish event. Most importantly, the type for published events should be different from the type for received event, because an actor does not have to only publish events it can receive.

The current generics make it rather awkward to type the publishable events. As soon as the generic P gets defined, a user has to also provide types for the other generics. When defaults are used for the remainder, the wrapped behavior does not correctly infer its types from the given behavior.

To avoid this, a user currently has to type the published events like this: withPubSub((pub: Publish<Event>) => behavior).

Ideally, this should work: withPubSub<Event>((pub) => behavior) and should correctly infer the receive-able events and state from behavior.

I tried this, but it does not work as the resulting behavior will be typed as Behavior<any, any>:

export function withPubSub<P extends EventObject>(
	getBehavior: (publish: Publish<P>) => Behavior<any>
): ReturnType<typeof getBehavior> extends Behavior<infer E, infer S>
	? WithPubSub<P, Behavior<E, S>>
	: never {
	const subscribers = createSubscriberStructure<P>();
	const behavior = getBehavior(createPublishFunction(subscribers));

	return {
		...behavior,
		transition: (state, event, ctx) => {
			if (handleSubscribeEvent(subscribers, event)) return state;

			return behavior.transition(state, event, ctx);
		},
	};
}

Stop relevant Behaviors

Currently, this repository waits for statelyai/xstate#2560 to be merged. When it is finally merged, all behaviors that are marked with TODO should add their stop behavior.

The relevant behaviors are:

  • fromMachine
  • event-bus
  • withSubscriptions
  • withPubSub (? should it empty its subscribers on stop ?)

Error Handling / Supervision

Elixir's and Akka actor system follows a "let it crash" mentality and use supervisors to create actors trees, which are partly restarted if an actor in the tree crashes12. XState is currently not really concerned with proper error handling and it is handled rather inconsistently, although errors in the system are inevitable. There have been a few discussions on the Discord3 and in the RFCs4, which can inspire a solution.

We might try to tackle the problem in XSystem first, before forces changes in XState, since XSystem is smaller and easier to change. A solution probably requires our own spawning implementation and potential extensions to the Behavior and ActorRef interface.

An initial thought is to wrap any behavior transition in a spawn function in a try/catch block and "restart" the behavior in case the catch block is reached.

I am open for ideas and suggestions regarding this topic. 😄

Footnotes

  1. https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html

  2. https://doc.akka.io/docs/akka/current/typed/fault-tolerance.html

  3. https://discord.com/channels/795785288994652170/877478623202906132/895041466538266704

  4. https://github.com/statelyai/rfcs/blob/0001-errors/text/0001-errors.md

Typings for `fromMachine` are Incompatible

fromMachine is used to convert a state machine to a behavior so it can be used with higher-order-behavior defined by this library.

However, it is currently not possible to pass a machine that is created with createMachine or model.createMachine to fromMachine. TypeScript produces some type errors that I don't fully understand yet.

Relevant source: https://github.com/christoph-fricke/xsystem/blob/main/src/utils/from_machine.ts
Test that should not produce errors if TS comment is removed: https://github.com/christoph-fricke/xsystem/blob/main/src/utils/from_machine.test.ts

`ActorRef` Registry

I am starting to explore an implementation of a registry for XState-based actor systems. This is still very much in the exploration phase and will evolve once we start to gather feedback in the XState community.

Related work:

Features / Use-Cases that should be supported by an implementation (uncertainty is marked with ???):

  • Registry should be an ActorRef itself
  • Register a existing ActorRef in the registry
  • Receive an ActorRef from the registry for a given ID
  • Spawn Behavior and StateMachine definitions directly with the registry as their parent
    • Pass the registry to their factories (Passing via context would require XState core support)
  • (???) Query the children of stored references for a given ID if it is not found in the direct references (walk the actor hierarchy)
  • (???) Broadcast an event to all references, which contains an example in the Elixir docs. Necessary when an event bus exists?
  • (???) Isomorphic Actors: Spawn actors from Web-Workers and sync the registry (and calls to references) with a wrapper in the worker
    • (???) Same but for other contexts as well (iFrame, WebSocket, ...)

Revisit typing of `fromActor`

The fromActor service creator should be used to connect a machine to an actor that accepts subscribe events, i.e. a publisher.

The current implementation of fromActor creates some type errors when the helper function is actually used. Some thinks to keep in mind:

  • A received actor might publish more event than the machine is interested in.
  • A machine can handle more events than the events published by the actor.
  • Can we fully type the events that are receivable by the actor?

These have an influence on the type definitions for the machine and actor.

Furthermore, it might be a good idea to send a state update of the actor as an event back to the machine.

Compilation error while compiling for Angular Nativescript

I ran into this error while compiling for Angular Nativescript:
ERROR in �[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m87�[0m - �[91merror�[0m�[90m TS1110: �[0mType expected.

Angular Version: 10.1.2
Nativescript Version: 8.0.8
xsystem version: 0.4.0
xstate version: 4.26.0

Here's the detailed log:
Preparing project...

File change detected. Starting incremental webpack compilation...

webpack is watching the files…

Hash: 593690d783b1d5f15c6c
Version: webpack 4.44.1
Time: 23585ms
Built at: 26/01/2022 10:29:51 am
Asset Size Chunks Chunk Names
assets/image/samplelogo.jpg 46.2 KiB [emitted]
assets/login/google/btn_google_signin_dark_disabled_web.png 2.25 KiB [emitted]
assets/login/google/btn_google_signin_dark_focus_web.png 4.09 KiB [emitted]
assets/login/google/btn_google_signin_dark_normal_web.png 3.89 KiB [emitted]
assets/login/google/btn_google_signin_dark_pressed_web.png 4.14 KiB [emitted]
assets/login/google/btn_google_signin_light_disabled_web.png 2.25 KiB [emitted]
assets/login/google/btn_google_signin_light_focus_web.png 4.23 KiB [emitted]
assets/login/google/btn_google_signin_light_normal_web.png 4 KiB [emitted]
assets/login/google/btn_google_signin_light_pressed_web.png 4.04 KiB [emitted]
bundle.js 15 KiB bundle [emitted] bundle
fonts/fa-brands-400.ttf 131 KiB [emitted]
fonts/fa-regular-400.ttf 39.1 KiB [emitted]
fonts/fa-solid-900.ttf 204 KiB [emitted]
package.json 133 bytes [emitted]
runtime.js 77.1 KiB runtime [emitted] runtime
tns-java-classes.js 0 bytes [emitted]
vendor.js 6.11 MiB vendor [emitted] vendor
Entrypoint bundle = runtime.js vendor.js bundle.js
[./app.css] 3.68 KiB {bundle} [built]
[./main.ts] 1.13 KiB {bundle} [built]
[/package.json] external "/package.json" 42 bytes {bundle} [optional] [built]
+ 335 hidden modules

ERROR in �[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m87�[0m - �[91merror�[0m�[90m TS1110: �[0mType expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~~~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m96�[0m - �[91merror�[0m�[90m TS1005: �[0m'}' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~~~~~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m101�[0m - �[91merror�[0m�[90m TS1128: �[0mDeclaration or statement expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m103�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m115�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m122�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~~~~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m132�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m133�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m141�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m152�[0m - �[91merror�[0m�[90m TS1109: �[0mExpression expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m158�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m159�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m167�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m179�[0m - �[91merror�[0m�[90m TS1005: �[0m';' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m2�[0m:�[93m205�[0m - �[91merror�[0m�[90m TS1005: �[0m'(' expected.

�[7m2�[0m export declare type Wildcard<S extends string, Separator extends string> = (S extends ${infer Start}${Separator}${infer Rest} ? ${Start}${Separator}* | ${Start}${Separator}${Wildcard<Rest, Separator>} : never) | "*";
�[7m �[0m �[91m ~�[0m
�[96mnode_modules/xsystem/dist/subscriptions/wildcard.d.ts�[0m:�[93m5�[0m:�[93m1�[0m - �[91merror�[0m�[90m TS1160: �[0mUnterminated template literal.

�[7m5�[0m
�[7m �[0m �[91m�[0m

Webpack compilation complete. Watching for file changes.

Webpack build done!

Updating runtime package.json with configuration values...

Project successfully prepared (android)

Attached compressed package.json file
package.json.zip

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.