@matheuspuel/state-machine 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/definition.js +5 -2
- package/dist/definition.js.map +1 -1
- package/dist/dts/definition.d.ts +4 -1
- package/dist/dts/definition.d.ts.map +1 -1
- package/dist/dts/machines/Form/Error.d.ts +9 -0
- package/dist/dts/machines/Form/Error.d.ts.map +1 -0
- package/dist/dts/machines/Form/Field/index.d.ts +56 -0
- package/dist/dts/machines/Form/Field/index.d.ts.map +1 -0
- package/dist/dts/machines/{Form.d.ts → Form/index.d.ts} +6 -42
- package/dist/dts/machines/Form/index.d.ts.map +1 -0
- package/dist/dts/machines/basic.d.ts +8 -0
- package/dist/dts/machines/basic.d.ts.map +1 -0
- package/dist/dts/machines/index copy.d.ts +10 -0
- package/dist/dts/machines/index copy.d.ts.map +1 -0
- package/dist/dts/machines/index.d.ts +2 -3
- package/dist/dts/machines/index.d.ts.map +1 -1
- package/dist/dts/machines/index.test copy.d.ts +2 -0
- package/dist/dts/machines/index.test copy.d.ts.map +1 -0
- package/dist/machines/Form/Error.js +4 -0
- package/dist/machines/Form/Error.js.map +1 -0
- package/dist/machines/Form/Field/index.js +102 -0
- package/dist/machines/Form/Field/index.js.map +1 -0
- package/dist/machines/{Form.js → Form/index.js} +9 -55
- package/dist/machines/Form/index.js.map +1 -0
- package/dist/machines/QueryState.js +2 -2
- package/dist/machines/QueryState.js.map +1 -1
- package/dist/machines/basic.js +37 -0
- package/dist/machines/basic.js.map +1 -0
- package/dist/machines/index copy.js +39 -0
- package/dist/machines/index copy.js.map +1 -0
- package/dist/machines/index.js +2 -3
- package/dist/machines/index.js.map +1 -1
- package/dist/machines/index.test copy.js +37 -0
- package/dist/machines/index.test copy.js.map +1 -0
- package/package.json +13 -24
- package/src/definition.ts +9 -5
- package/src/machines/Form/Error.ts +5 -0
- package/src/machines/Form/Field/index.ts +234 -0
- package/src/machines/{Form.test.ts → Form/index.test.ts} +10 -11
- package/src/machines/{Form.ts → Form/index.ts} +9 -146
- package/src/machines/QueryState.ts +2 -2
- package/src/machines/{Struct.ts → basic.ts} +23 -7
- package/src/machines/{Struct.test.ts → index.test.ts} +13 -1
- package/src/machines/index.ts +2 -3
- package/dist/dts/machines/Form.d.ts.map +0 -1
- package/dist/machines/Form.js.map +0 -1
- package/src/machines/of.test.ts +0 -15
- package/src/machines/of.ts +0 -11
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { describe, expect, it } from '@effect/vitest';
|
|
2
|
+
import { StateMachine } from '@matheuspuel/state-machine';
|
|
3
|
+
import { Effect, pipe } from 'effect';
|
|
4
|
+
describe('of', () => {
|
|
5
|
+
it('should work', () => {
|
|
6
|
+
const machine = StateMachine.of('');
|
|
7
|
+
const instance = StateMachine.run(machine);
|
|
8
|
+
instance.actions.set('a');
|
|
9
|
+
const state = instance.ref.get.pipe(Effect.runSync);
|
|
10
|
+
expect(state).toStrictEqual('a');
|
|
11
|
+
const data = instance.actions.get();
|
|
12
|
+
expect(data).toStrictEqual('a');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
describe('Struct', () => {
|
|
16
|
+
it('should work', () => {
|
|
17
|
+
const machine = pipe(StateMachine.Struct({
|
|
18
|
+
a: StateMachine.of(0),
|
|
19
|
+
b: StateMachine.of(''),
|
|
20
|
+
}), base => StateMachine.make()({
|
|
21
|
+
...base,
|
|
22
|
+
actions: ({ Store }) => ({
|
|
23
|
+
...base.actions({ Store }),
|
|
24
|
+
get: () => Store.get(),
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
const instance = StateMachine.run(machine);
|
|
28
|
+
const getState = () => instance.ref.get.pipe(Effect.runSync);
|
|
29
|
+
expect(getState()).toStrictEqual({ a: 0, b: '' });
|
|
30
|
+
instance.actions.a.set(1);
|
|
31
|
+
expect(getState()).toStrictEqual({ a: 1, b: '' });
|
|
32
|
+
instance.actions.b.set('a');
|
|
33
|
+
expect(getState()).toStrictEqual({ a: 1, b: 'a' });
|
|
34
|
+
expect(instance.actions.get()).toStrictEqual({ a: 1, b: 'a' });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
//# sourceMappingURL=index.test%20copy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.test copy.js","sourceRoot":"","sources":["../../src/machines/index.test copy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAErC,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE;IAClB,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACrB,MAAM,OAAO,GAAG,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;QACnC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC1C,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACzB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QACnD,MAAM,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;QAChC,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAA;QACnC,MAAM,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,aAAa,EAAE,GAAG,EAAE;QACrB,MAAM,OAAO,GAAG,IAAI,CAClB,YAAY,CAAC,MAAM,CAAC;YAClB,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC;YACrB,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,EAAE,CAAC;SACvB,CAAC,EACF,IAAI,CAAC,EAAE,CACL,YAAY,CAAC,IAAI,EAAiC,CAAC;YACjD,GAAG,IAAI;YACP,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;gBACvB,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;gBAC1B,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;aACvB,CAAC;SACH,CAAC,CACL,CAAA;QACD,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QAC1C,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;QAC5D,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QACjD,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;QACzB,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAA;QACjD,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC3B,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;QAClD,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;IAChE,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matheuspuel/state-machine",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"/dist",
|
|
@@ -12,50 +12,38 @@
|
|
|
12
12
|
"types": "./dist/dts/index.d.ts",
|
|
13
13
|
"default": "./dist/index.js"
|
|
14
14
|
},
|
|
15
|
-
"./definition": {
|
|
16
|
-
"types": "./dist/dts/definition.d.ts",
|
|
17
|
-
"default": "./dist/definition.js"
|
|
18
|
-
},
|
|
19
|
-
"./machines": {
|
|
20
|
-
"types": "./dist/dts/machines/index.d.ts",
|
|
21
|
-
"default": "./dist/machines/index.js"
|
|
22
|
-
},
|
|
23
15
|
"./react": {
|
|
24
16
|
"types": "./dist/dts/react/index.d.ts",
|
|
25
17
|
"default": "./dist/react/index.js"
|
|
26
|
-
},
|
|
27
|
-
"./runtime": {
|
|
28
|
-
"types": "./dist/dts/runtime/index.d.ts",
|
|
29
|
-
"default": "./dist/runtime/index.js"
|
|
30
18
|
}
|
|
31
19
|
},
|
|
32
20
|
"dependencies": {
|
|
33
|
-
"@matheuspuel/optic": "^0.
|
|
34
|
-
"@matheuspuel/query-state": "^0.
|
|
35
|
-
"effect": "^3.
|
|
21
|
+
"@matheuspuel/optic": "^0.3.0",
|
|
22
|
+
"@matheuspuel/query-state": "^0.3.0",
|
|
23
|
+
"effect": "^3.19.16",
|
|
36
24
|
"react": "19.1.0",
|
|
37
25
|
"use-sync-external-store": "^1.5.0"
|
|
38
26
|
},
|
|
39
27
|
"devDependencies": {
|
|
40
|
-
"@effect/language-service": "^0.
|
|
41
|
-
"@effect/vitest": "^0.
|
|
28
|
+
"@effect/language-service": "^0.73.1",
|
|
29
|
+
"@effect/vitest": "^0.27.0",
|
|
42
30
|
"@matheuspuel/state-machine": "file:.",
|
|
43
31
|
"@types/node": "^25.0.8",
|
|
44
32
|
"@types/react": "~19.1.17",
|
|
45
33
|
"@types/use-sync-external-store": "^1.5.0",
|
|
46
34
|
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
47
35
|
"@typescript-eslint/parser": "^8.55.0",
|
|
48
|
-
"@vitest/coverage-v8": "^
|
|
36
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
49
37
|
"cross-env": "^7.0.3",
|
|
50
38
|
"eslint": "^8.57.1",
|
|
51
39
|
"eslint-config-prettier": "^10.1.1",
|
|
52
40
|
"eslint-plugin-prettier": "^5.2.6",
|
|
53
41
|
"eslint-plugin-react-hooks": "^7.0.1",
|
|
54
|
-
"prettier": "^3.
|
|
55
|
-
"tsx": "^4.
|
|
42
|
+
"prettier": "^3.8.1",
|
|
43
|
+
"tsx": "^4.21.0",
|
|
56
44
|
"typescript": "^5.9.3",
|
|
57
|
-
"vite": "^
|
|
58
|
-
"vitest": "^
|
|
45
|
+
"vite": "^7.3.1",
|
|
46
|
+
"vitest": "^4.0.18"
|
|
59
47
|
},
|
|
60
48
|
"scripts": {
|
|
61
49
|
"build": "tsc --project tsconfig.build.json",
|
|
@@ -64,6 +52,7 @@
|
|
|
64
52
|
"lint:fix": "eslint . --fix --config .eslintrc.production.cjs",
|
|
65
53
|
"typecheck": "tsc",
|
|
66
54
|
"test": "vitest",
|
|
67
|
-
"check": "pnpm build && pnpm typecheck && pnpm lint && pnpm test --run"
|
|
55
|
+
"check": "pnpm build && pnpm typecheck && pnpm lint && pnpm test --run",
|
|
56
|
+
"upload": "pnpm check && pnpm publish"
|
|
68
57
|
}
|
|
69
58
|
}
|
package/src/definition.ts
CHANGED
|
@@ -43,10 +43,14 @@ export type AnyStateMachineWithActions<Actions extends AnyStateActions> = {
|
|
|
43
43
|
onUpdate?: (state: any) => void | Promise<void>
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
export const make =
|
|
47
|
-
<State
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
export const make = <State, Actions extends AnyStateActions>(
|
|
47
|
+
args: StateMachine<State, Actions>,
|
|
48
|
+
) => args
|
|
49
|
+
|
|
50
|
+
export const withState = <State>() => ({
|
|
51
|
+
make: <Actions extends AnyStateActions>(args: StateMachine<State, Actions>) =>
|
|
52
|
+
args,
|
|
53
|
+
})
|
|
50
54
|
|
|
51
55
|
export type PreparedStateActions<Actions extends AnyStateActions> = Actions
|
|
52
56
|
|
|
@@ -63,7 +67,7 @@ export const mapActions = <
|
|
|
63
67
|
self: StateMachine<State, Actions>,
|
|
64
68
|
f: (actions: Actions, machine: { Store: Store<State> }) => NextActions,
|
|
65
69
|
): StateMachine<State, NextActions> =>
|
|
66
|
-
make
|
|
70
|
+
make({
|
|
67
71
|
...self,
|
|
68
72
|
actions: machine => ({ ...f(self.actions(machine), machine) }),
|
|
69
73
|
})
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
|
|
3
|
+
import { Effect, Either, flow, Option, pipe, Schema } from 'effect'
|
|
4
|
+
import { NoSuchElementException } from 'effect/Cause'
|
|
5
|
+
import { ParseError } from 'effect/ParseResult'
|
|
6
|
+
import * as StateMachine from '../../../definition.js'
|
|
7
|
+
import { ValidationError } from '../Error.js'
|
|
8
|
+
|
|
9
|
+
type FormFieldBase<A, I, E> = StateMachine.StateMachine<
|
|
10
|
+
{ value: I; error: E | null },
|
|
11
|
+
{
|
|
12
|
+
set: (value: I) => void
|
|
13
|
+
update: (f: (previous: I) => I) => void
|
|
14
|
+
error: { set: (error: E | null) => void }
|
|
15
|
+
validate: () => Effect.Effect<A, ValidationError<E>>
|
|
16
|
+
check: () => Promise<Either.Either<A, ValidationError<E>>>
|
|
17
|
+
setStateFromData: (data: A) => void
|
|
18
|
+
}
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
export type FormField<A, I, E> = FormFieldBase<A, I, E> & {
|
|
22
|
+
withError: <E2>() => FormField<A, I, E2 | E>
|
|
23
|
+
parse: {
|
|
24
|
+
<A2 extends A, E2>(
|
|
25
|
+
to: (value: NoInfer<A>) => Effect.Effect<A2, E2>,
|
|
26
|
+
): FormField<A2, I, E2 | E>
|
|
27
|
+
<A2, E2>(args: {
|
|
28
|
+
to: (value: NoInfer<A>) => Effect.Effect<A2, E2>
|
|
29
|
+
from: (data: A2) => NoInfer<A>
|
|
30
|
+
}): FormField<A2, I, E2 | E>
|
|
31
|
+
}
|
|
32
|
+
transform: {
|
|
33
|
+
<A2 extends A>(to: (value: NoInfer<A>) => A2): FormField<A2, I, E>
|
|
34
|
+
<A2>(args: {
|
|
35
|
+
to: (value: NoInfer<A>) => A2
|
|
36
|
+
from: (data: A2) => NoInfer<A>
|
|
37
|
+
}): FormField<A2, I, E>
|
|
38
|
+
<A2>(schema: Schema.Schema<A2, A>): FormField<A2, I, E | ParseError>
|
|
39
|
+
<A2, E2>(
|
|
40
|
+
schema: Schema.Schema<A2, A>,
|
|
41
|
+
makeError: (error: ParseError) => E2,
|
|
42
|
+
): FormField<A2, I, E | E2>
|
|
43
|
+
}
|
|
44
|
+
filter: <E2>(
|
|
45
|
+
predicate: (value: A) => boolean,
|
|
46
|
+
onFail: (value: A) => E2,
|
|
47
|
+
) => FormField<A, I, E | E2>
|
|
48
|
+
required: () => FormField<NonNullable<A>, I, E | NoSuchElementException>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type AnyFormField = FormField<any, any, any>
|
|
52
|
+
|
|
53
|
+
const addExtraProperties = <A, I, E>(
|
|
54
|
+
self: FormFieldBase<A, I, E>,
|
|
55
|
+
): FormField<A, I, E> => {
|
|
56
|
+
const withError = <E2>(): FormField<A, I, E2 | E> =>
|
|
57
|
+
self as FormField<A, I, E2 | E>
|
|
58
|
+
|
|
59
|
+
const parse: FormField<A, I, E>['parse'] = <A2, E2>(
|
|
60
|
+
args:
|
|
61
|
+
| ((value: NoInfer<A>) => Effect.Effect<A2, E2>)
|
|
62
|
+
| {
|
|
63
|
+
to: (value: NoInfer<A>) => Effect.Effect<A2, E2>
|
|
64
|
+
from: (data: A2) => NoInfer<A>
|
|
65
|
+
},
|
|
66
|
+
): FormField<A2, I, E2 | E> => {
|
|
67
|
+
const to = typeof args === 'function' ? args : args.to
|
|
68
|
+
const from =
|
|
69
|
+
typeof args === 'function' ? (_: A2) => _ as unknown as A : args.from
|
|
70
|
+
return addExtraProperties<A2, I, E2 | E>(
|
|
71
|
+
StateMachine.mapActions(withError<E2>(), (actions, { Store }) => {
|
|
72
|
+
const validate = (): Effect.Effect<
|
|
73
|
+
A2,
|
|
74
|
+
ValidationError<E2 | E>,
|
|
75
|
+
never
|
|
76
|
+
> =>
|
|
77
|
+
actions.validate().pipe(
|
|
78
|
+
Effect.flatMap(
|
|
79
|
+
flow(
|
|
80
|
+
to,
|
|
81
|
+
Effect.tap(() =>
|
|
82
|
+
Effect.sync(() =>
|
|
83
|
+
Store.update(_ => ({ value: _.value, error: null })),
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
Effect.tapError(error =>
|
|
87
|
+
Effect.sync(() =>
|
|
88
|
+
Store.update(_ => ({ value: _.value, error })),
|
|
89
|
+
),
|
|
90
|
+
),
|
|
91
|
+
Effect.mapError(error => new ValidationError({ error })),
|
|
92
|
+
),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
return {
|
|
96
|
+
...actions,
|
|
97
|
+
validate,
|
|
98
|
+
check: () => validate().pipe(Effect.either, Effect.runPromise),
|
|
99
|
+
setStateFromData: data => actions.setStateFromData(from(data)),
|
|
100
|
+
}
|
|
101
|
+
}),
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const transform: FormField<A, I, E>['transform'] = <A2, E2>(
|
|
106
|
+
args:
|
|
107
|
+
| ((value: NoInfer<A>) => A2)
|
|
108
|
+
| {
|
|
109
|
+
to: (value: NoInfer<A>) => A2
|
|
110
|
+
from: (data: A2) => NoInfer<A>
|
|
111
|
+
}
|
|
112
|
+
| Schema.Schema<A2, A>,
|
|
113
|
+
makeError?: (error: ParseError) => E2,
|
|
114
|
+
): FormField<A2, I, any> => {
|
|
115
|
+
if (Schema.isSchema(args)) {
|
|
116
|
+
const schema = args as Schema.Schema<A2, A>
|
|
117
|
+
return parse({
|
|
118
|
+
to: value =>
|
|
119
|
+
Effect.mapError(Schema.decode(schema)(value), parseError =>
|
|
120
|
+
(makeError ?? (_ => _))(parseError),
|
|
121
|
+
),
|
|
122
|
+
from: Schema.encodeSync(schema),
|
|
123
|
+
})
|
|
124
|
+
} else if (typeof args === 'function') {
|
|
125
|
+
const to = args
|
|
126
|
+
return parse({
|
|
127
|
+
to: _ => Effect.succeed(to(_)),
|
|
128
|
+
from: (_: A2) => _ as unknown as A,
|
|
129
|
+
})
|
|
130
|
+
} else if ('to' in args) {
|
|
131
|
+
const { to, from } = args
|
|
132
|
+
return parse({
|
|
133
|
+
to: _ => Effect.succeed(to(_)),
|
|
134
|
+
from: from,
|
|
135
|
+
})
|
|
136
|
+
} else {
|
|
137
|
+
throw new Error('Invalid arguments')
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const filter: FormField<A, I, E>['filter'] = (predicate, onFail) =>
|
|
142
|
+
parse(value =>
|
|
143
|
+
predicate(value) ? Effect.succeed(value) : Effect.fail(onFail(value)),
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
...self,
|
|
148
|
+
withError,
|
|
149
|
+
parse,
|
|
150
|
+
transform,
|
|
151
|
+
filter,
|
|
152
|
+
required: () => parse(Option.fromNullable),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export const make = <A, I, E>(args: {
|
|
157
|
+
initial: I
|
|
158
|
+
validate: (value: I) => Effect.Effect<A, E>
|
|
159
|
+
fromData: (data: A) => I
|
|
160
|
+
}): FormField<A, I, E> =>
|
|
161
|
+
addExtraProperties(
|
|
162
|
+
StateMachine.withState<{ value: I; error: E | null }>().make({
|
|
163
|
+
initialState: { value: args.initial, error: null },
|
|
164
|
+
actions: ({ Store }) => {
|
|
165
|
+
const validate = () =>
|
|
166
|
+
pipe(
|
|
167
|
+
Store.get().value,
|
|
168
|
+
args.validate,
|
|
169
|
+
Effect.tap(() =>
|
|
170
|
+
Effect.sync(() =>
|
|
171
|
+
Store.update(_ => ({ value: _.value, error: null })),
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
Effect.tapError(error =>
|
|
175
|
+
Effect.sync(() => Store.update(_ => ({ value: _.value, error }))),
|
|
176
|
+
),
|
|
177
|
+
Effect.mapError(error => new ValidationError({ error })),
|
|
178
|
+
)
|
|
179
|
+
return {
|
|
180
|
+
set: value => Store.update(() => ({ value, error: null })),
|
|
181
|
+
update: f => Store.update(_ => ({ value: f(_.value), error: null })),
|
|
182
|
+
error: {
|
|
183
|
+
set: error => Store.update(_ => ({ value: _.value, error })),
|
|
184
|
+
},
|
|
185
|
+
validate,
|
|
186
|
+
check: () => validate().pipe(Effect.either, Effect.runPromise),
|
|
187
|
+
setStateFromData: data =>
|
|
188
|
+
Store.update(_ => ({
|
|
189
|
+
..._,
|
|
190
|
+
value: args.fromData(data),
|
|
191
|
+
error: null,
|
|
192
|
+
})),
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
}),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
export const of = <A>(initial: A): FormField<A, A, never> =>
|
|
199
|
+
make<A, A, never>({
|
|
200
|
+
initial: initial,
|
|
201
|
+
validate: _ => Effect.succeed(_),
|
|
202
|
+
fromData: _ => _,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
export const nullOr = <A>(initial?: A | null) => of<A | null>(initial ?? null)
|
|
206
|
+
|
|
207
|
+
// eslint-disable-next-line @typescript-eslint/no-duplicate-type-constituents
|
|
208
|
+
export const undefinedOr = <A>(initial?: A | undefined) =>
|
|
209
|
+
of<A | undefined>(initial)
|
|
210
|
+
|
|
211
|
+
export const String = of('')
|
|
212
|
+
|
|
213
|
+
export const TrimString = String.transform(_ => _.trim())
|
|
214
|
+
|
|
215
|
+
export const TrimStringOrNull = TrimString.transform({
|
|
216
|
+
to: _ => _ || null,
|
|
217
|
+
from: _ => _ ?? '',
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
export const TrimStringOrUndefined = TrimString.transform({
|
|
221
|
+
to: _ => _ || undefined,
|
|
222
|
+
from: _ => _ ?? '',
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
export const NonEmptyString = String.parse({
|
|
226
|
+
to: _ => Schema.decodeOption(Schema.NonEmptyString)(_),
|
|
227
|
+
from: _ => _,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
export const TrimNonEmptyString = String.parse({
|
|
231
|
+
to: _ =>
|
|
232
|
+
Schema.decodeOption(Schema.compose(Schema.Trim, Schema.NonEmptyString))(_),
|
|
233
|
+
from: _ => _,
|
|
234
|
+
})
|
|
@@ -2,14 +2,13 @@ import { describe, expect, it } from '@effect/vitest'
|
|
|
2
2
|
import { Form, StateMachine } from '@matheuspuel/state-machine'
|
|
3
3
|
import { Effect } from 'effect'
|
|
4
4
|
import { NoSuchElementException } from 'effect/Cause'
|
|
5
|
-
import { ValidationError } from './
|
|
5
|
+
import { ValidationError } from './index.js'
|
|
6
6
|
|
|
7
7
|
describe('Form', () => {
|
|
8
8
|
it('should work', () => {
|
|
9
|
-
const formField = Form.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
fromData: (data: { n: number }) => data.n,
|
|
9
|
+
const formField = Form.Field.of(0).parse({
|
|
10
|
+
to: _ => (_ > 1 ? Effect.succeed({ n: _ }) : Effect.fail('low' as const)),
|
|
11
|
+
from: (data: { n: number }) => data.n,
|
|
13
12
|
})
|
|
14
13
|
const machine = Form.Struct({
|
|
15
14
|
a: formField,
|
|
@@ -66,8 +65,8 @@ describe('Form', () => {
|
|
|
66
65
|
|
|
67
66
|
it('TaggedUnion', () => {
|
|
68
67
|
const stateMachine = Form.TaggedUnion('_tag', {
|
|
69
|
-
A: Form.Struct({ a: Form.
|
|
70
|
-
B: Form.Struct({ b: Form.
|
|
68
|
+
A: Form.Struct({ a: Form.Field.of(0), x: Form.Field.of('') }),
|
|
69
|
+
B: Form.Struct({ b: Form.Field.of(0), x: Form.Field.of('') }),
|
|
71
70
|
})
|
|
72
71
|
const form = StateMachine.run(stateMachine)
|
|
73
72
|
const getState = () => form.ref.get.pipe(Effect.runSync)
|
|
@@ -96,7 +95,7 @@ describe('Form', () => {
|
|
|
96
95
|
|
|
97
96
|
it('Array', () => {
|
|
98
97
|
const stateMachine = Form.Struct({
|
|
99
|
-
list: Form.Array(Form.Struct({ a: Form.NonEmptyString })),
|
|
98
|
+
list: Form.Array(Form.Struct({ a: Form.Field.NonEmptyString })),
|
|
100
99
|
})
|
|
101
100
|
const form = StateMachine.run(stateMachine)
|
|
102
101
|
const getState = () => form.ref.get.pipe(Effect.runSync)
|
|
@@ -137,11 +136,11 @@ describe('Form', () => {
|
|
|
137
136
|
|
|
138
137
|
it('change password example', () => {
|
|
139
138
|
const stateMachine = Form.Struct({
|
|
140
|
-
oldPassword: Form.
|
|
139
|
+
oldPassword: Form.Field.of(''),
|
|
141
140
|
newPassword: StateMachine.mapActions(
|
|
142
141
|
Form.Struct({
|
|
143
|
-
password: Form.
|
|
144
|
-
confirmation: Form.
|
|
142
|
+
password: Form.Field.of(''),
|
|
143
|
+
confirmation: Form.Field.of('').withError<'not-match'>(),
|
|
145
144
|
}),
|
|
146
145
|
actions => ({
|
|
147
146
|
...actions,
|
|
@@ -1,141 +1,17 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
Data,
|
|
5
|
-
Effect,
|
|
6
|
-
Either,
|
|
7
|
-
flow,
|
|
8
|
-
Option,
|
|
9
|
-
pipe,
|
|
10
|
-
Record,
|
|
11
|
-
Schema,
|
|
12
|
-
} from 'effect'
|
|
13
|
-
import { ParseError } from 'effect/ParseResult'
|
|
3
|
+
import { Effect, Either, Option, pipe, Record } from 'effect'
|
|
14
4
|
import {
|
|
15
5
|
AnyStateMachineWithActions,
|
|
16
|
-
make,
|
|
17
6
|
mapActions,
|
|
18
7
|
StateMachine,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'FormValidationError',
|
|
24
|
-
)<{
|
|
25
|
-
error: E
|
|
26
|
-
}> {}
|
|
27
|
-
|
|
28
|
-
export type FormField<A, I, E> = StateMachine<
|
|
29
|
-
{ value: I; error: E | null },
|
|
30
|
-
{
|
|
31
|
-
set: (value: I) => void
|
|
32
|
-
update: (f: (previous: I) => I) => void
|
|
33
|
-
error: { set: (error: E | null) => void }
|
|
34
|
-
validate: () => Effect.Effect<A, ValidationError<E>>
|
|
35
|
-
check: () => Promise<Either.Either<A, ValidationError<E>>>
|
|
36
|
-
setStateFromData: (data: A) => void
|
|
37
|
-
}
|
|
38
|
-
>
|
|
39
|
-
|
|
40
|
-
export type AnyFormField = FormField<any, any, any>
|
|
41
|
-
|
|
42
|
-
export const makeField = <A, I, E>(args: {
|
|
43
|
-
initial: I
|
|
44
|
-
validate: (value: I) => Effect.Effect<A, E>
|
|
45
|
-
fromData: (data: A) => I
|
|
46
|
-
}): FormField<A, I, E> =>
|
|
47
|
-
make<{ value: I; error: E | null }>()({
|
|
48
|
-
initialState: { value: args.initial, error: null },
|
|
49
|
-
actions: ({ Store }) => {
|
|
50
|
-
const validate = () =>
|
|
51
|
-
pipe(
|
|
52
|
-
Store.get().value,
|
|
53
|
-
args.validate,
|
|
54
|
-
Effect.tap(() =>
|
|
55
|
-
Effect.sync(() =>
|
|
56
|
-
Store.update(_ => ({ value: _.value, error: null })),
|
|
57
|
-
),
|
|
58
|
-
),
|
|
59
|
-
Effect.tapError(error =>
|
|
60
|
-
Effect.sync(() => Store.update(_ => ({ value: _.value, error }))),
|
|
61
|
-
),
|
|
62
|
-
Effect.mapError(error => new ValidationError({ error })),
|
|
63
|
-
)
|
|
64
|
-
return {
|
|
65
|
-
set: value => Store.update(() => ({ value, error: null })),
|
|
66
|
-
update: f => Store.update(_ => ({ value: f(_.value), error: null })),
|
|
67
|
-
error: { set: error => Store.update(_ => ({ value: _.value, error })) },
|
|
68
|
-
validate,
|
|
69
|
-
check: () => validate().pipe(Effect.either, Effect.runPromise),
|
|
70
|
-
setStateFromData: data =>
|
|
71
|
-
Store.update(_ => ({
|
|
72
|
-
..._,
|
|
73
|
-
value: args.fromData(data),
|
|
74
|
-
error: null,
|
|
75
|
-
})),
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
})
|
|
8
|
+
withState,
|
|
9
|
+
} from '../../definition.js'
|
|
10
|
+
import { Struct as StateMachineStruct } from '../../machines/basic.js'
|
|
11
|
+
import { ValidationError } from './Error.js'
|
|
79
12
|
|
|
80
|
-
export
|
|
81
|
-
|
|
82
|
-
initial: initial,
|
|
83
|
-
validate: _ => Effect.succeed(_),
|
|
84
|
-
fromData: _ => _,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
export const fieldWithError =
|
|
88
|
-
<E>() =>
|
|
89
|
-
<A, I, E1>(self: FormField<A, I, E1>): FormField<A, I, E | E1> =>
|
|
90
|
-
self as FormField<A, I, E | E1>
|
|
91
|
-
|
|
92
|
-
export const transformField = <A, A1, I, E, E1>(
|
|
93
|
-
self: FormField<A1, I, E1>,
|
|
94
|
-
args: {
|
|
95
|
-
validate: (value: NoInfer<A1>) => Effect.Effect<A, E>
|
|
96
|
-
fromData: (data: A) => NoInfer<A1>
|
|
97
|
-
},
|
|
98
|
-
): FormField<A, I, E | E1> =>
|
|
99
|
-
mapActions(fieldWithError<E>()(self), (actions, { Store }) => {
|
|
100
|
-
const validate = (): Effect.Effect<A, ValidationError<E | E1>, never> =>
|
|
101
|
-
actions.validate().pipe(
|
|
102
|
-
Effect.flatMap(
|
|
103
|
-
flow(
|
|
104
|
-
args.validate,
|
|
105
|
-
Effect.tap(() =>
|
|
106
|
-
Effect.sync(() =>
|
|
107
|
-
Store.update(_ => ({ value: _.value, error: null })),
|
|
108
|
-
),
|
|
109
|
-
),
|
|
110
|
-
Effect.tapError(error =>
|
|
111
|
-
Effect.sync(() => Store.update(_ => ({ value: _.value, error }))),
|
|
112
|
-
),
|
|
113
|
-
Effect.mapError(error => new ValidationError({ error })),
|
|
114
|
-
),
|
|
115
|
-
),
|
|
116
|
-
)
|
|
117
|
-
return {
|
|
118
|
-
...actions,
|
|
119
|
-
validate,
|
|
120
|
-
check: () => validate().pipe(Effect.either, Effect.runPromise),
|
|
121
|
-
setStateFromData: data => actions.setStateFromData(args.fromData(data)),
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
export const transformFieldSchema = <A, A1, I, E1>(
|
|
126
|
-
self: FormField<A1, I, E1>,
|
|
127
|
-
schema: Schema.Schema<A, A1>,
|
|
128
|
-
): FormField<A, I, E1 | ParseError> =>
|
|
129
|
-
transformField(self, {
|
|
130
|
-
validate: Schema.decode(schema),
|
|
131
|
-
fromData: Schema.encodeSync(schema),
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
export const validateField = <A1, A extends A1, I, E, E1>(
|
|
135
|
-
self: FormField<A1, I, E1>,
|
|
136
|
-
validate: (value: NoInfer<A1>) => Effect.Effect<A, E>,
|
|
137
|
-
): FormField<A, I, E | E1> =>
|
|
138
|
-
transformField(self, { validate, fromData: _ => _ })
|
|
13
|
+
export * from './Error.js'
|
|
14
|
+
export * as Field from './Field/index.js'
|
|
139
15
|
|
|
140
16
|
export const Struct = <
|
|
141
17
|
Fields extends Record<
|
|
@@ -296,7 +172,7 @@ export const TaggedUnion = <
|
|
|
296
172
|
}
|
|
297
173
|
|
|
298
174
|
return mapActions(
|
|
299
|
-
|
|
175
|
+
withState<InternalState>().make({
|
|
300
176
|
initialState: {
|
|
301
177
|
[tagKey]: { value: initialTag, error: null },
|
|
302
178
|
...Object.fromEntries(
|
|
@@ -410,7 +286,7 @@ export const Array = <
|
|
|
410
286
|
Item extends StateMachine<infer State, infer Actions> ? State : never
|
|
411
287
|
type ItemActions =
|
|
412
288
|
Item extends StateMachine<infer State, infer Actions> ? Actions : never
|
|
413
|
-
return
|
|
289
|
+
return withState<ReadonlyArray<ItemState>>().make({
|
|
414
290
|
initialState: [],
|
|
415
291
|
actions: ({ Store }) => {
|
|
416
292
|
const getItemActions = (index: number): ItemActions =>
|
|
@@ -452,16 +328,3 @@ export const Array = <
|
|
|
452
328
|
},
|
|
453
329
|
}) as any
|
|
454
330
|
}
|
|
455
|
-
|
|
456
|
-
export const String = field('')
|
|
457
|
-
|
|
458
|
-
export const NonEmptyString = transformField(String, {
|
|
459
|
-
validate: _ => Schema.decodeOption(Schema.NonEmptyString)(_),
|
|
460
|
-
fromData: _ => _,
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
export const TrimNonEmptyString = transformField(String, {
|
|
464
|
-
validate: _ =>
|
|
465
|
-
Schema.decodeOption(Schema.compose(Schema.Trim, Schema.NonEmptyString))(_),
|
|
466
|
-
fromData: _ => _,
|
|
467
|
-
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { QueryState as QueryState_ } from '@matheuspuel/query-state'
|
|
2
2
|
import { DateTime, Effect, pipe, Schedule } from 'effect'
|
|
3
3
|
import { make, mapActions, StateMachine } from '../definition.js'
|
|
4
|
-
import { of } from './
|
|
4
|
+
import { of } from './basic.js'
|
|
5
5
|
|
|
6
6
|
export const QueryState = <A, E, P = undefined>() =>
|
|
7
7
|
mapActions(of(QueryState_.initial<A, E, P>()), actions => ({
|
|
@@ -77,7 +77,7 @@ export const trackEffect: {
|
|
|
77
77
|
_ =>
|
|
78
78
|
options?.runOnStart
|
|
79
79
|
? pipe(options.runOnStart, options =>
|
|
80
|
-
make
|
|
80
|
+
make({
|
|
81
81
|
..._,
|
|
82
82
|
start: machine =>
|
|
83
83
|
_.actions(machine)
|