@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.
Files changed (48) hide show
  1. package/dist/definition.js +5 -2
  2. package/dist/definition.js.map +1 -1
  3. package/dist/dts/definition.d.ts +4 -1
  4. package/dist/dts/definition.d.ts.map +1 -1
  5. package/dist/dts/machines/Form/Error.d.ts +9 -0
  6. package/dist/dts/machines/Form/Error.d.ts.map +1 -0
  7. package/dist/dts/machines/Form/Field/index.d.ts +56 -0
  8. package/dist/dts/machines/Form/Field/index.d.ts.map +1 -0
  9. package/dist/dts/machines/{Form.d.ts → Form/index.d.ts} +6 -42
  10. package/dist/dts/machines/Form/index.d.ts.map +1 -0
  11. package/dist/dts/machines/basic.d.ts +8 -0
  12. package/dist/dts/machines/basic.d.ts.map +1 -0
  13. package/dist/dts/machines/index copy.d.ts +10 -0
  14. package/dist/dts/machines/index copy.d.ts.map +1 -0
  15. package/dist/dts/machines/index.d.ts +2 -3
  16. package/dist/dts/machines/index.d.ts.map +1 -1
  17. package/dist/dts/machines/index.test copy.d.ts +2 -0
  18. package/dist/dts/machines/index.test copy.d.ts.map +1 -0
  19. package/dist/machines/Form/Error.js +4 -0
  20. package/dist/machines/Form/Error.js.map +1 -0
  21. package/dist/machines/Form/Field/index.js +102 -0
  22. package/dist/machines/Form/Field/index.js.map +1 -0
  23. package/dist/machines/{Form.js → Form/index.js} +9 -55
  24. package/dist/machines/Form/index.js.map +1 -0
  25. package/dist/machines/QueryState.js +2 -2
  26. package/dist/machines/QueryState.js.map +1 -1
  27. package/dist/machines/basic.js +37 -0
  28. package/dist/machines/basic.js.map +1 -0
  29. package/dist/machines/index copy.js +39 -0
  30. package/dist/machines/index copy.js.map +1 -0
  31. package/dist/machines/index.js +2 -3
  32. package/dist/machines/index.js.map +1 -1
  33. package/dist/machines/index.test copy.js +37 -0
  34. package/dist/machines/index.test copy.js.map +1 -0
  35. package/package.json +13 -24
  36. package/src/definition.ts +9 -5
  37. package/src/machines/Form/Error.ts +5 -0
  38. package/src/machines/Form/Field/index.ts +234 -0
  39. package/src/machines/{Form.test.ts → Form/index.test.ts} +10 -11
  40. package/src/machines/{Form.ts → Form/index.ts} +9 -146
  41. package/src/machines/QueryState.ts +2 -2
  42. package/src/machines/{Struct.ts → basic.ts} +23 -7
  43. package/src/machines/{Struct.test.ts → index.test.ts} +13 -1
  44. package/src/machines/index.ts +2 -3
  45. package/dist/dts/machines/Form.d.ts.map +0 -1
  46. package/dist/machines/Form.js.map +0 -1
  47. package/src/machines/of.test.ts +0 -15
  48. 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.6.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.2.0",
34
- "@matheuspuel/query-state": "^0.2.0",
35
- "effect": "^3.18.1",
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.1",
41
- "@effect/vitest": "^0.26.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": "^3.2.4",
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.6.2",
55
- "tsx": "^4.20.6",
42
+ "prettier": "^3.8.1",
43
+ "tsx": "^4.21.0",
56
44
  "typescript": "^5.9.3",
57
- "vite": "^6.3.5",
58
- "vitest": "^3.2.4"
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
- <Actions extends AnyStateActions>(args: StateMachine<State, Actions>) =>
49
- args
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<State>()({
70
+ make({
67
71
  ...self,
68
72
  actions: machine => ({ ...f(self.actions(machine), machine) }),
69
73
  })
@@ -0,0 +1,5 @@
1
+ import { Data } from 'effect'
2
+
3
+ export class ValidationError<E> extends Data.TaggedError(
4
+ 'FormValidationError',
5
+ )<{ error: E }> {}
@@ -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 './Form.js'
5
+ import { ValidationError } from './index.js'
6
6
 
7
7
  describe('Form', () => {
8
8
  it('should work', () => {
9
- const formField = Form.transformField(Form.field(0), {
10
- validate: _ =>
11
- _ > 1 ? Effect.succeed({ n: _ }) : Effect.fail('low' as const),
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.field(0), x: Form.field('') }),
70
- B: Form.Struct({ b: Form.field(0), x: Form.field('') }),
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.field(''),
139
+ oldPassword: Form.Field.of(''),
141
140
  newPassword: StateMachine.mapActions(
142
141
  Form.Struct({
143
- password: Form.field(''),
144
- confirmation: Form.fieldWithError<'not-match'>()(Form.field('')),
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
- } from '../definition.js'
20
- import { Struct as StateMachineStruct } from '../machines/Struct.js'
21
-
22
- export class ValidationError<E> extends Data.TaggedError(
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 const field = <A>(initial: A): FormField<A, A, never> =>
81
- makeField<A, A, never>({
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
- make<InternalState>()({
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 make<ReadonlyArray<ItemState>>()({
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 './of.js'
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<typeof _.initialState>()({
80
+ make({
81
81
  ..._,
82
82
  start: machine =>
83
83
  _.actions(machine)