Skip to content

Commit

Permalink
feat: vanilla react integration
Browse files Browse the repository at this point in the history
  • Loading branch information
prncss-xyz committed Nov 3, 2024
1 parent b8abb49 commit f0539a5
Show file tree
Hide file tree
Showing 13 changed files with 325 additions and 111 deletions.
42 changes: 11 additions & 31 deletions packages/libs/core/src/machines/callback.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,29 @@
import { Typed } from '../utils'
import { IMachine, Sendable } from './core'
import { IMachine } from './core'
import { isDisabled, nextState } from './utils'

export type Spiced<Event extends Typed, Transformed, SubState, Final> = {
export type Spiced<Event, Transformed, SubState, Final> = {
final: Final | undefined
isDisabled: (event: Sendable<Event>) => boolean
next: (event: Sendable<Event>) => Transformed
isDisabled: (event: Event) => boolean
next: (event: Event) => Transformed
visit: <Acc>(
fold: (subState: SubState, acc: Acc, index: string) => Acc,
acc: Acc,
) => Acc
} & Transformed

export function machineCb<
Event extends Typed,
State,
Message,
Transformed,
SubState,
Final,
>(
machine: IMachine<
Sendable<Event>,
State,
Message,
Transformed,
SubState,
Final
>,
export function machineCb<Event, State, Message, Transformed, SubState, Final>(
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
) {
return function (state: State) {
const transformed = machine.transform(state)
return {
...transformed,
final: machine.getFinal(transformed),
isDisabled: (event: Sendable<Event>) => {
let touched = false
return (
machine.reducer(event, transformed, () => (touched = true)) ===
undefined && !touched
)
isDisabled: (event: Event) => {
return isDisabled(machine, transformed, event)
},
next: (event: Sendable<Event>, send: (e: Message) => void = () => {}) => {
const nextState = machine.reducer(event, transformed, send)
if (nextState === undefined) return transformed
return machine.transform(nextState)
next: (event: Event) => {
return nextState(machine, transformed, event)
},
visit: <Acc>(
fold: (subState: SubState, acc: Acc, index: string) => Acc,
Expand Down
1 change: 1 addition & 0 deletions packages/libs/core/src/machines/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from './listener'
export * from './multi-state'
export * from './object'
export * from './simple-state'
export * from './utils'
12 changes: 3 additions & 9 deletions packages/libs/core/src/machines/object.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IMachine } from './core'
import { Interpreter, MachineEffects } from './effects'
import { isDisabled, nextState } from './utils'

class ObjectMachine<Event, State, Message, Transformed, SubState, Final> {
private effects: MachineEffects<Event, SubState> | undefined
Expand Down Expand Up @@ -47,17 +48,10 @@ class ObjectMachine<Event, State, Message, Transformed, SubState, Final> {
this.effects?.flush()
}
isDisabled(event: Event) {
let touched = false
return (
this.machine.reducer(event, this.state, () => {
touched = true
}) === undefined && !touched
)
return isDisabled(this.machine, this.state, event)
}
next(event: Event) {
const nextState = this.machine.reducer(event, this.state, () => {})
if (nextState === undefined) return this.state
return this.machine.transform(nextState)
return nextState(this.machine, this.state, event)
}
send(event: Event) {
const state = this.machine.reducer(event, this.state, this.listener)
Expand Down
23 changes: 23 additions & 0 deletions packages/libs/core/src/machines/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IMachine } from './core'

export function isDisabled<Event, State, Message, Transformed, SubState, Final>(
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
transformed: Transformed,
event: Event,
) {
let touched = false
const res = machine.reducer(event, transformed, () => {
touched = true
})
return !touched && res === undefined
}

export function nextState<Event, State, Message, Transformed, SubState, Final>(
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
transformed: Transformed,
event: Event,
) {
const nextState = machine.reducer(event, transformed, () => {})
if (nextState === undefined) return transformed
return machine.transform(nextState)
}
33 changes: 1 addition & 32 deletions packages/libs/jotai/src/machine.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
import {
Listener,
multiStateMachine,
simpleStateMachine,
} from '@constellar/core'
import { multiStateMachine, simpleStateMachine } from '@constellar/core'
import { fireEvent, render, screen } from '@testing-library/react'
import { atom, createStore, useAtom } from 'jotai'
import { useState } from 'react'
Expand Down Expand Up @@ -252,30 +248,3 @@ describe('messages', () => {
expect(store.get(countAtom)).toBe(1)
})
})

/*
describe('listener', () => {
type Message = { p: number; type: 'out' } | { q: string; type: 'in' }
const x: Listener<Message, [number, string]> = {
in: ({ q }, x, y) => console.log(e.type, x, q),
out: ({ p }, x, y) => console.log(e.type, x, p),
}
type State = { type: 'a' }
type Event = { q: string; type: 'a' }
const someMachine = multiStateMachine<
Event,
State,
object,
object,
Message
>()({
init: 'a',
states: {
a: {},
},
})
const someAtom = machineAtom(someMachine(), {
listener: (x) => console.log(x),
})
})
*/
52 changes: 13 additions & 39 deletions packages/libs/jotai/src/machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
Listener,
machineCb,
MachineEffects,
Sendable,
Spiced,
toListener,
Typed,
Expand All @@ -17,22 +16,15 @@ import { useEffect, useRef } from 'react'
import { unwrap } from './utils'

export function machineAtom<
Event extends Typed,
Event,
State,
Message extends Typed,
Transformed,
SubState,
Final,
R,
>(
machine: IMachine<
Sendable<Event>,
State,
Message,
Transformed,
SubState,
Final
>,
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
opts: {
atomFactory: (
init: State,
Expand All @@ -42,53 +34,35 @@ export function machineAtom<
},
): WritableAtom<
Promise<Spiced<Event, Transformed, SubState, Final>>,
[Sendable<Event>],
[Event],
R
>
export function machineAtom<
Event extends Typed,
Event,
State,
Message extends Typed,
Transformed,
SubState,
Final,
R = void,
>(
machine: IMachine<
Sendable<Event>,
State,
Message,
Transformed,
SubState,
Final
>,
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
opts?: {
atomFactory?: (init: State) => WritableAtom<State, [State], R>
interpreter?: Interpreter<Event, SubState>
listener?: Listener<Message, [Getter, Setter]>
},
): WritableAtom<
Spiced<Event, Transformed, SubState, Final>,
[Sendable<Event>],
R
>
): WritableAtom<Spiced<Event, Transformed, SubState, Final>, [Event], R>
export function machineAtom<
Event extends Typed,
Event,
State,
Message extends Typed,
Transformed,
SubState,
Final,
R,
>(
machine: IMachine<
Sendable<Event>,
State,
Message,
Transformed,
SubState,
Final
>,
machine: IMachine<Event, State, Message, Transformed, SubState, Final>,
opts?: {
atomFactory?: (init: State) => WritableAtom<State, [State], R>
interpreter?: Interpreter<Event, SubState>
Expand All @@ -99,7 +73,7 @@ export function machineAtom<
opts?.atomFactory ? opts.atomFactory(machine.init) : atom(machine.init)
) as WritableAtom<State, [State], R>
const reducer = machine.reducer
const cb = machineCb(machine)
const toSpiced = machineCb(machine)
const listener = opts?.listener ? toListener(opts.listener) : () => {}
const effAtom = opts?.interpreter
? atom<MachineEffects<Event, SubState>>()
Expand All @@ -111,8 +85,8 @@ export function machineAtom<
return () => effects.flush()
}
const resAtom = atom(
(get) => unwrap(get(stateAtom), cb),
(get, set, event: Sendable<Event>) =>
(get) => unwrap(get(stateAtom), toSpiced),
(get, set, event: Event) =>
unwrap(get(stateAtom), (state) => {
const nextState = reducer(event, machine.transform(state), (message) =>
listener(message, get, set),
Expand All @@ -134,12 +108,12 @@ export function machineAtom<
}

export function useMachineEffects<
Event extends Typed,
Event,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Transformed extends Spiced<Event, unknown, any, unknown>,
>(
transformed: Transformed,
send: (event: Sendable<Event>) => void,
send: (event: Event) => void,
interpreter: Interpreter<Event, Transformed>,
) {
const machineEffects = useRef<MachineEffects<Event, Transformed>>()
Expand Down
50 changes: 50 additions & 0 deletions packages/libs/react/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"name": "@constellar/react",
"repository": {
"type": "git",
"url": "git+https://github.com/prncss-xyz/constellar.git"
},
"keywords": [
"react",
"optics",
"state"
],
"private": false,
"type": "module",
"version": "2.2.4",
"description": "",
"main": "dist/index.cjs",
"module": "dist/index.js",
"types": "dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"scripts": {
"dev:test": "vitest watch",
"build": "tsc -p ./tsconfig.json && vite build",
"test:unit": "vitest run",
"test:coverage": "vitest run --coverage",
"test:lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix",
"test:types": "tsc --noEmit",
"format": "prettier --write ./src"
},
"author": "Juliette Lamarche",
"license": "MIT",
"peerDependencies": {
"@constellar/core": "workspace:^",
"react": ">=18.0.0"
},
"devDependencies": {
"@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.0.0",
"@types/node": "^22.2.0",
"@types/react": "^18.3.3",
"@vitest/coverage-v8": "^2.0.5",
"jsdom": "^24.1.1",
"react": "18",
"react-dom": "18",
"vite": "^5.4.0",
"vite-plugin-dts": "^4.0.2",
"vitest": "^2.0.5"
}
}
2 changes: 2 additions & 0 deletions packages/libs/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './machine'
export * from './optics'
Loading

0 comments on commit f0539a5

Please sign in to comment.