@effect-app/vue 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 (50) hide show
  1. package/.eslintrc.cjs +10 -0
  2. package/.prettierignore +6 -0
  3. package/_cjs/_global.cjs +5 -0
  4. package/_cjs/_global.cjs.map +1 -0
  5. package/_cjs/form.cjs +43 -0
  6. package/_cjs/form.cjs.map +1 -0
  7. package/_cjs/hooks.cjs +228 -0
  8. package/_cjs/hooks.cjs.map +1 -0
  9. package/_cjs/index.cjs +54 -0
  10. package/_cjs/index.cjs.map +1 -0
  11. package/_cjs/internal.cjs +19 -0
  12. package/_cjs/internal.cjs.map +1 -0
  13. package/_cjs/routeParams.cjs +51 -0
  14. package/_cjs/routeParams.cjs.map +1 -0
  15. package/_cjs/runtime.cjs +70 -0
  16. package/_cjs/runtime.cjs.map +1 -0
  17. package/_src/_global.ts +10 -0
  18. package/_src/form.ts +116 -0
  19. package/_src/hooks.ts +300 -0
  20. package/_src/index.ts +3 -0
  21. package/_src/internal.ts +14 -0
  22. package/_src/routeParams.ts +71 -0
  23. package/_src/runtime.ts +50 -0
  24. package/dist/_global.d.ts +9 -0
  25. package/dist/_global.d.ts.map +1 -0
  26. package/dist/_global.js +10 -0
  27. package/dist/form.d.ts +26 -0
  28. package/dist/form.d.ts.map +1 -0
  29. package/dist/form.js +47 -0
  30. package/dist/hooks.d.ts +61 -0
  31. package/dist/hooks.d.ts.map +1 -0
  32. package/dist/hooks.js +204 -0
  33. package/dist/index.d.ts +4 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +4 -0
  36. package/dist/internal.d.ts +9 -0
  37. package/dist/internal.d.ts.map +1 -0
  38. package/dist/internal.js +11 -0
  39. package/dist/routeParams.d.ts +15 -0
  40. package/dist/routeParams.d.ts.map +1 -0
  41. package/dist/routeParams.js +45 -0
  42. package/dist/runtime.d.ts +19 -0
  43. package/dist/runtime.d.ts.map +1 -0
  44. package/dist/runtime.js +48 -0
  45. package/package.json +153 -0
  46. package/tsconfig.json +104 -0
  47. package/tsconfig.json.bak +36 -0
  48. package/tsplus.config.json +6 -0
  49. package/vitest.config.ts +5 -0
  50. package/wallaby.cjs +1 -0
package/_src/form.ts ADDED
@@ -0,0 +1,116 @@
1
+ import type {
2
+ EncodedOf,
3
+ ParsedShapeOfCustom,
4
+ Property,
5
+ PropertyRecord
6
+ } from "@effect-app/prelude/schema"
7
+ import {
8
+ type AnyProperty,
9
+ getMetadataFromSchemaOrProp,
10
+ isSchema,
11
+ Parser,
12
+ type SchemaAny,
13
+ These
14
+ } from "@effect-app/prelude/schema"
15
+ import { capitalize } from "vue"
16
+
17
+ export function convertIn(v: string | null) {
18
+ return v === null ? "" : v
19
+ }
20
+
21
+ export function convertOut(v: string, set: (v: string | null) => void) {
22
+ v = v.trim()
23
+ return set(v === "" ? null : v)
24
+ }
25
+
26
+ export function buildFieldInfoFromProps<Props extends PropertyRecord>(
27
+ props: Props
28
+ ) {
29
+ return props.$$.keys.reduce(
30
+ (prev, cur) => {
31
+ prev[cur] = buildFieldInfo(props[cur] as AnyProperty, cur)
32
+ return prev
33
+ },
34
+ {} as {
35
+ [K in keyof Props]: FieldInfo<
36
+ EncodedOf<GetSchemaFromProp<Props[K]>>,
37
+ ParsedShapeOfCustom<GetSchemaFromProp<Props[K]>>
38
+ >
39
+ }
40
+ )
41
+ }
42
+
43
+ export interface FieldMetadata {
44
+ minLength: number | undefined
45
+ maxLength: number | undefined
46
+ required: boolean
47
+ }
48
+
49
+ const f = Symbol()
50
+ abstract class PhantomTypeParameter<
51
+ Identifier extends keyof any,
52
+ InstantiatedType
53
+ > {
54
+ protected abstract readonly _: {
55
+ readonly [NameP in Identifier]: (_: InstantiatedType) => InstantiatedType
56
+ }
57
+ }
58
+ export interface FieldInfo<Tin, Tout> extends PhantomTypeParameter<typeof f, { in: Tin; out: Tout }> {
59
+ rules: ((v: string) => boolean | string)[]
60
+ metadata: FieldMetadata
61
+ type: "text"
62
+ }
63
+
64
+ type GetSchemaFromProp<T> = T extends Property<infer S, any, any, any> ? S
65
+ : never
66
+
67
+ function buildFieldInfo(
68
+ propOrSchema: AnyProperty | SchemaAny,
69
+ fieldKey: PropertyKey
70
+ ): FieldInfo<any, any> {
71
+ const metadata = getMetadataFromSchemaOrProp(propOrSchema)
72
+ const schema = isSchema(propOrSchema) ? propOrSchema : propOrSchema._schema
73
+ const parse = Parser.for(schema)
74
+
75
+ const info = {
76
+ type: "text", // TODO: various types
77
+ rules: [
78
+ (v: string) => !metadata.required || v !== "" || "The field cannot be empty",
79
+ (v: string) =>
80
+ v === "" ||
81
+ metadata.minLength === undefined ||
82
+ v.length >= metadata.minLength ||
83
+ `The field requires at least ${metadata.minLength} characters`,
84
+ (v: string) =>
85
+ v === "" ||
86
+ metadata.maxLength === undefined ||
87
+ v.length <= metadata.maxLength ||
88
+ `The field cannot have more than ${metadata.maxLength} characters`,
89
+ (v: unknown) =>
90
+ pipe(
91
+ parse(v === "" ? null : v),
92
+ These.result,
93
+ _ =>
94
+ _.match(
95
+ () =>
96
+ `The entered value is not a valid ${
97
+ capitalize(
98
+ fieldKey.toString()
99
+ )
100
+ }`,
101
+ ([_, optErr]) =>
102
+ optErr.isSome()
103
+ ? `The entered value is not a valid ${
104
+ capitalize(
105
+ fieldKey.toString()
106
+ )
107
+ }`
108
+ : true
109
+ )
110
+ )
111
+ ],
112
+ metadata
113
+ }
114
+
115
+ return info as any
116
+ }
package/_src/hooks.ts ADDED
@@ -0,0 +1,300 @@
1
+ import type { ApiConfig, FetchResponse } from "@effect-app/prelude/client"
2
+ import { Done } from "@effect-app/prelude/client"
3
+ import type { Http } from "@effect-app/core/http/http-client"
4
+ import { InterruptedException } from "@effect/io/Cause"
5
+ import * as swrv from "swrv"
6
+ import type { fetcherFn, IKey, IResponse } from "swrv/dist/types.js"
7
+ import type { Ref } from "vue"
8
+ import { computed, ref, shallowRef } from "vue"
9
+ import { run } from "./internal.js"
10
+
11
+ export { isFailed, isInitializing, isSuccess } from "@effect-app/prelude/client"
12
+
13
+ type useSWRVType = {
14
+ <Data, Error>(key: IKey): IResponse<Data, Error>
15
+ <Data, Error>(
16
+ key: IKey,
17
+ fn?: fetcherFn<Data>,
18
+ config?: swrv.IConfig<Data, fetcherFn<Data>>
19
+ ): IResponse<Data, Error>
20
+ }
21
+ type MutateType = {
22
+ <Data>(
23
+ key: string,
24
+ res: Data | Promise<Data>,
25
+ cache?: swrv.SWRVCache<Omit<IResponse<any, any>, "mutate">>,
26
+ ttl?: number
27
+ ): Promise<{
28
+ data: any
29
+ error: any
30
+ isValidating: any
31
+ }>
32
+ }
33
+ // madness - workaround different import behavior on server and client
34
+ const useSWRV = (swrv.default.default ? swrv.default.default : swrv.default) as unknown as useSWRVType
35
+ export const mutate = (swrv.default.mutate ? swrv.default.mutate : swrv.mutate) as unknown as MutateType
36
+
37
+ function swrToQuery<E, A>(
38
+ r: { error: E | undefined; data: A | undefined; isValidating: boolean }
39
+ ): QueryResult<E, A> {
40
+ if (r.error) {
41
+ return r.isValidating
42
+ ? Refreshing.fail<E, A>(r.error, r.data)
43
+ : Done.fail<E, A>(r.error, r.data)
44
+ }
45
+ if (r.data !== undefined) {
46
+ return r.isValidating
47
+ ? Refreshing.succeed<A, E>(r.data)
48
+ : Done.succeed<A, E>(r.data)
49
+ }
50
+
51
+ return r.isValidating ? new Loading() : new Initial()
52
+ }
53
+
54
+ export function useMutate<E, A>(self: Effect<ApiConfig | Http, E, FetchResponse<A>> & { mapPath: string }) {
55
+ const fn = () => run.value(self).then(_ => _.body)
56
+ return () => mutate(self.mapPath, fn)
57
+ }
58
+
59
+ export function useMutateWithArg<Arg, E, A>(
60
+ self: ((arg: Arg) => Effect<ApiConfig | Http, E, FetchResponse<A>>) & { mapPath: (arg: Arg) => string }
61
+ ) {
62
+ const fn = (arg: Arg) => run.value(self(arg)).then(_ => _.body)
63
+ return (arg: Arg) => mutate(self.mapPath(arg), fn(arg))
64
+ }
65
+
66
+ // TODO: same trick with mutations/actions
67
+ export function useSafeQueryWithArg<Arg, E, A>(
68
+ self: ((arg: Arg) => Effect<ApiConfig | Http, E, FetchResponse<A>>) & { mapPath: (arg: Arg) => string },
69
+ arg: Arg,
70
+ config?: swrv.IConfig<A, fetcherFn<A>> | undefined
71
+ ) {
72
+ return useSafeQueryWithArg_(self, self.mapPath, arg, config)
73
+ }
74
+
75
+ export function useSafeQuery<E, A>(
76
+ self: Effect<ApiConfig | Http, E, FetchResponse<A>> & { mapPath: string },
77
+ config?: swrv.IConfig<A, fetcherFn<A>> | undefined
78
+ ) {
79
+ return useSafeQuery_(self.mapPath, self, config)
80
+ }
81
+
82
+ export function useSafeQueryWithArg_<Arg, E, A>(
83
+ self: (arg: Arg) => Effect<ApiConfig | Http, E, FetchResponse<A>>,
84
+ mapPath: (arg: Arg) => string,
85
+ arg: Arg,
86
+ config?: swrv.IConfig<A, fetcherFn<A>> | undefined
87
+ ) {
88
+ return useSafeQuery_(mapPath(arg), self(arg), config)
89
+ }
90
+
91
+ export function useSafeQuery_<E, A>(
92
+ key: string,
93
+ self: Effect<ApiConfig | Http, E, FetchResponse<A>>,
94
+ config?: swrv.IConfig<A, fetcherFn<A>> | undefined
95
+ ) {
96
+ // const [result, latestSuccess, execute] = make(self)
97
+
98
+ // TODO: support with interruption
99
+ // const sem = Semaphore.unsafeMake(1)
100
+ // const lock = sem.withPermits(1)
101
+ // let fib: Fiber.FiberContext<E, FetchResponse<A>> | undefined = undefined
102
+ // const execute = self
103
+ // const runNew = execute.fork()
104
+ // .tap(newFiber =>
105
+ // Effect(() => {
106
+ // fib = newFiber
107
+ // })
108
+ // )
109
+
110
+ // const ex = lock(
111
+ // Effect.suspend(() => {
112
+ // return fib
113
+ // ? Fiber.interrupt(fib).zipRight(runNew)
114
+ // : runNew
115
+ // })
116
+ // ).flatMap(Fiber.await)
117
+ // .flatMap(Effect.done)
118
+
119
+ // function execWithInterruption() {
120
+ // return ex.provideSomeLayer(Layers)
121
+ // .runPromise()
122
+ // .catch(err => {
123
+ // if (!Cause.isInterruptedException(err)) throw err
124
+ // return undefined
125
+ // })
126
+ // }
127
+
128
+ // const swr = useSWRV<A, E>(key, () => execWithInterruption().then(_ => _?.body as any)) // Effect.runPromise(self.provideSomeLayer(Layers))
129
+ const swr = useSWRV<A, E>(key, () => run.value(self).then(_ => _.body), config)
130
+ const result = computed(() =>
131
+ swrToQuery({ data: swr.data.value, error: swr.error.value, isValidating: swr.isValidating.value })
132
+ ) // ref<QueryResult<E, A>>()
133
+ const latestSuccess = computed(() => {
134
+ const value = result.value
135
+ return value.isSuccess() ?
136
+ value.current.isRight()
137
+ ? value.current.right
138
+ : value.previous.isSome()
139
+ ? value.previous.value
140
+ : undefined :
141
+ undefined
142
+ })
143
+
144
+ return tuple(result, latestSuccess, () => swr.mutate(), swr)
145
+ }
146
+
147
+ export function useSafeQueryLegacy<E, A>(self: Effect<ApiConfig | Http, E, FetchResponse<A>>) {
148
+ const [result, latestSuccess, execute] = make(self)
149
+
150
+ const sem = Semaphore.unsafeMake(1)
151
+ const withPermit = sem.withPermits(1)
152
+ let fib: Fiber.Runtime<unknown, unknown> | undefined = undefined
153
+ const runNew = execute.fork
154
+ .tap(newFiber =>
155
+ Effect(() => {
156
+ fib = newFiber
157
+ })
158
+ )
159
+
160
+ const ex = Effect.suspendSucceed(() => {
161
+ return fib
162
+ ? fib.interrupt.zipRight(runNew)
163
+ : runNew
164
+ }).apply(withPermit)
165
+ .flatMap(_ => _.await())
166
+ .flatMap(_ => _.done)
167
+
168
+ function exec() {
169
+ return run.value(ex)
170
+ .catch(err => {
171
+ // TODO
172
+ if (!JSON.stringify(err).includes("InterruptedException")) throw err
173
+ })
174
+ }
175
+
176
+ return tuple(result, latestSuccess, exec)
177
+ }
178
+
179
+ export function make<R, E, A>(self: Effect<R, E, FetchResponse<A>>) {
180
+ const result = shallowRef(new Initial() as QueryResult<E, A>)
181
+
182
+ const execute = Effect(() => {
183
+ result.value = result.value.isInitializing()
184
+ ? new Loading()
185
+ : new Refreshing(result.value)
186
+ })
187
+ .zipRight(self.map(_ => _.body).asQueryResult)
188
+ .flatMap(r => Effect(() => result.value = r))
189
+
190
+ const latestSuccess = computed(() => {
191
+ const value = result.value
192
+ return value.hasValue() ?
193
+ value.current.isRight()
194
+ ? value.current.right
195
+ : value.previous.isSome()
196
+ ? value.previous.value
197
+ : undefined :
198
+ undefined
199
+ })
200
+
201
+ return tuple(result, latestSuccess, execute)
202
+ }
203
+
204
+ /**
205
+ * Pass a function that returns an Effect, e.g from a client action.
206
+ * Returns a tuple with state ref and execution function which reports errors as Toast.
207
+ */
208
+ export function useMutation<I, E, A>(self: (i: I) => Effect<ApiConfig | Http, E, A>) {
209
+ const loading = ref(false)
210
+ const error = ref<E>()
211
+ const value = ref<A>()
212
+ const handle = handleExit(loading, error, value)
213
+ const exec = (i: I, abortSignal?: AbortSignal) =>
214
+ run.value(
215
+ Effect(() => {
216
+ loading.value = true
217
+ value.value = undefined
218
+ error.value = undefined
219
+ })
220
+ .zipRight(self(i))
221
+ .exit
222
+ .flatMap(exit => Effect(() => handle(exit)))
223
+ .fork
224
+ .flatMap(f => {
225
+ const cancel = () => run.value(f.interrupt)
226
+ abortSignal?.addEventListener("abort", () => void cancel().catch(console.error))
227
+ return f.join
228
+ })
229
+ )
230
+
231
+ const state = computed(() => ({ loading: loading.value, value: value.value, error: error.value }))
232
+
233
+ return tuple(
234
+ state,
235
+ exec
236
+ )
237
+ }
238
+
239
+ /**
240
+ * Parameter-less variant of @see {@link useMutation}
241
+ */
242
+ export function useAction<E, A>(self: Effect<ApiConfig | Http, E, A>) {
243
+ const loading = ref(false)
244
+ const error = ref<E>()
245
+ const value = ref<A>()
246
+
247
+ const handle = handleExit(loading, error, value)
248
+
249
+ const exec = (abortSignal?: AbortSignal) => {
250
+ loading.value = true
251
+ return run.value(
252
+ self
253
+ .exit
254
+ .flatMap(exit => Effect(() => handle(exit)))
255
+ .fork
256
+ .flatMap(f => {
257
+ const cancel = () => f.interrupt.unsafeRunPromise
258
+ abortSignal?.addEventListener("abort", () => void cancel().catch(console.error))
259
+ return f.join
260
+ })
261
+ )
262
+ }
263
+ const state = computed(() => ({ loading: loading.value, value: value.value, error: error.value }))
264
+ return tuple(
265
+ state,
266
+ exec
267
+ )
268
+ }
269
+
270
+ function handleExit<E, A>(
271
+ loading: Ref<boolean>,
272
+ error: Ref<E | undefined>,
273
+ value: Ref<A | undefined>
274
+ ) {
275
+ return (exit: Exit<E, A>): Either<E, A> => {
276
+ loading.value = false
277
+ if (exit.isSuccess()) {
278
+ value.value = exit.value
279
+ error.value = undefined
280
+ return Either(exit.value)
281
+ }
282
+
283
+ const err = exit.cause.failureOption
284
+ if (err.isSome()) {
285
+ error.value = err.value
286
+ value.value = undefined
287
+ return Either.left(err.value)
288
+ }
289
+
290
+ const died = exit.cause.dieOption
291
+ if (died.value) {
292
+ throw died.value
293
+ }
294
+ const interrupted = exit.cause.interruptOption
295
+ if (interrupted.value) {
296
+ throw InterruptedException()
297
+ }
298
+ throw new Error("Invalid state")
299
+ }
300
+ }
package/_src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { isFailed, isInitializing, isSuccess } from "@effect-app/prelude/client"
2
+ export * from "./hooks.js"
3
+ export * from "./runtime.js"
@@ -0,0 +1,14 @@
1
+ import type { ApiConfig } from "@effect-app/prelude/client"
2
+ import type { Http } from "@effect-app/core/http/http-client"
3
+ import type { Runtime } from "@effect/io/Runtime"
4
+
5
+ export const run = {
6
+ value<E, A>(_: Effect<ApiConfig | Http, E, A>): Promise<A> {
7
+ throw new Error("Runtime not initialized, please run `initRuntime` first")
8
+ }
9
+ }
10
+ export function initRuntime<A>(rt: Runtime<A | ApiConfig | Http>) {
11
+ run.value = function<E, A>(self: Effect<ApiConfig | Http, E, A>): Promise<A> {
12
+ return rt.unsafeRunPromise(self)
13
+ }
14
+ }
@@ -0,0 +1,71 @@
1
+ import type { ParsedShapeOfCustom, ReqRes, SchemaAny } from "@effect-app/prelude/schema"
2
+ import { EParserFor, Parser, unsafe } from "@effect-app/prelude/schema"
3
+ import type { ParsedQuery } from "query-string"
4
+
5
+ export function getQueryParam(search: ParsedQuery, param: string) {
6
+ const v = search[param]
7
+ if (Array.isArray(v)) {
8
+ return v[0]
9
+ }
10
+ return v ?? null
11
+ }
12
+
13
+ export const getQueryParamO = flow(getQueryParam, Opt.fromNullable)
14
+
15
+ export const parseOpt = <E, A>(t: ReqRes<E, A>) => {
16
+ const dec = flow(EParserFor(t), x =>
17
+ x.effect._tag === "Right"
18
+ ? x.effect.right[1]._tag === "None"
19
+ ? Opt(x.effect.right[0])
20
+ : Opt.none
21
+ : Opt.none)
22
+ return dec
23
+ }
24
+
25
+ export const parseOptUnknown = <E, A>(t: ReqRes<E, A>) => {
26
+ const dec = flow(Parser.for(t), x =>
27
+ x.effect._tag === "Right"
28
+ ? x.effect.right[1]._tag === "None"
29
+ ? Opt(x.effect.right[0])
30
+ : Opt.none
31
+ : Opt.none)
32
+ return dec
33
+ }
34
+
35
+ export function parseRouteParamsOption<NER extends Record<string, SchemaAny>>(
36
+ query: Record<string, any>,
37
+ t: NER // enforce non empty
38
+ ): {
39
+ [K in keyof NER]: Opt<ParsedShapeOfCustom<NER[K]>>
40
+ } {
41
+ return t.$$.keys.reduce(
42
+ (prev, cur) => {
43
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
44
+ prev[cur] = getQueryParamO(query, cur as string).flatMap(parseOpt(t[cur]!))
45
+
46
+ return prev
47
+ },
48
+ {} as {
49
+ [K in keyof NER]: Opt<ParsedShapeOfCustom<NER[K]>>
50
+ }
51
+ )
52
+ }
53
+
54
+ export function parseRouteParams<NER extends Record<string, SchemaAny>>(
55
+ query: Record<string, any>,
56
+ t: NER // enforce non empty
57
+ ): {
58
+ [K in keyof NER]: ParsedShapeOfCustom<NER[K]>
59
+ } {
60
+ return t.$$.keys.reduce(
61
+ (prev, cur) => {
62
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
63
+ prev[cur] = unsafe(EParserFor(t[cur]!))(query[cur as any])
64
+
65
+ return prev
66
+ },
67
+ {} as {
68
+ [K in keyof NER]: ParsedShapeOfCustom<NER[K]>
69
+ }
70
+ )
71
+ }
@@ -0,0 +1,50 @@
1
+ import type { Http } from "@effect-app/core/http/http-client"
2
+ import * as HF from "@effect-app/core/http/http-client-fetch"
3
+ import { ApiConfig } from "@effect-app/prelude/client"
4
+ import { fetch } from "cross-fetch"
5
+ import { initRuntime } from "./internal.js"
6
+
7
+ export { initRuntime } from "./internal.js"
8
+
9
+ const DefaultApiConfig = Config.struct({
10
+ apiUrl: Config.string("apiUrl").withDefault("/api"),
11
+ headers: Config.string()
12
+ .table("headers").optional
13
+ })
14
+
15
+ export function makeApiLayers(config: Config<ApiConfig> = DefaultApiConfig) {
16
+ return HF.Client(fetch) + ApiConfig.Live(config)
17
+ }
18
+
19
+ export function makeAppRuntime<R, E, A>(layer: Layer<R, E, A>) {
20
+ return Effect.gen(function*($) {
21
+ const scope = yield* $(Scope.make())
22
+ const env = yield* $(layer.buildWithScope(scope))
23
+ const runtime = yield* $(Effect.runtime<A>().scoped.provideEnvironment(env))
24
+
25
+ return {
26
+ runtime,
27
+ clean: scope.close(Exit.unit)
28
+ }
29
+ })
30
+ }
31
+
32
+ export function initializeSync<E, A>(layer: Layer<never, E, A | ApiConfig | Http>) {
33
+ const { clean, runtime } = makeAppRuntime(layer).unsafeRunSync
34
+ initRuntime(runtime)
35
+ return {
36
+ runtime,
37
+ clean: () => clean.unsafeRunSync
38
+ }
39
+ }
40
+
41
+ export function initializeAsync<E, A>(layer: Layer<never, E, A | ApiConfig | Http>) {
42
+ return makeAppRuntime(layer).unsafeRunPromise
43
+ .then(({ clean, runtime }) => {
44
+ initRuntime(runtime)
45
+ return {
46
+ runtime,
47
+ clean: () => clean.unsafeRunPromise
48
+ }
49
+ })
50
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @tsplus global
3
+ */
4
+ import { Initial, Loading, QueryResult, Refreshing } from "@effect-app/prelude/client";
5
+ /**
6
+ * @tsplus global
7
+ */
8
+ import "@effect-app/prelude/_global";
9
+ //# sourceMappingURL=_global.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_global.d.ts","sourceRoot":"","sources":["../_src/_global.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAA;AARtF;;GAEG;AACH,OAAO,6BAA6B,CAAA"}
@@ -0,0 +1,10 @@
1
+ /* eslint-disable unused-imports/no-unused-imports */
2
+ /**
3
+ * @tsplus global
4
+ */
5
+ import "@effect-app/prelude/_global";
6
+ /**
7
+ * @tsplus global
8
+ */
9
+ import "@effect-app/prelude/client";
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX2dsb2JhbC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL19zcmMvX2dsb2JhbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxxREFBcUQ7QUFDckQ7O0dBRUc7QUFDSCxPQUFPLDZCQUE2QixDQUFBO0FBRXBDOztHQUVHO0FBQ0gsT0FBMEQsNEJBQTRCLENBQUEifQ==
package/dist/form.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import type { EncodedOf, ParsedShapeOfCustom, Property, PropertyRecord } from "@effect-app/prelude/schema";
2
+ export declare function convertIn(v: string | null): string;
3
+ export declare function convertOut(v: string, set: (v: string | null) => void): void;
4
+ export declare function buildFieldInfoFromProps<Props extends PropertyRecord>(props: Props): { [K in keyof Props]: FieldInfo<EncodedOf<GetSchemaFromProp<Props[K]>>, ParsedShapeOfCustom<GetSchemaFromProp<Props[K]>>>; };
5
+ export interface FieldMetadata {
6
+ minLength: number | undefined;
7
+ maxLength: number | undefined;
8
+ required: boolean;
9
+ }
10
+ declare const f: unique symbol;
11
+ declare abstract class PhantomTypeParameter<Identifier extends keyof any, InstantiatedType> {
12
+ protected abstract readonly _: {
13
+ readonly [NameP in Identifier]: (_: InstantiatedType) => InstantiatedType;
14
+ };
15
+ }
16
+ export interface FieldInfo<Tin, Tout> extends PhantomTypeParameter<typeof f, {
17
+ in: Tin;
18
+ out: Tout;
19
+ }> {
20
+ rules: ((v: string) => boolean | string)[];
21
+ metadata: FieldMetadata;
22
+ type: "text";
23
+ }
24
+ type GetSchemaFromProp<T> = T extends Property<infer S, any, any, any> ? S : never;
25
+ export {};
26
+ //# sourceMappingURL=form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../_src/form.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACf,MAAM,4BAA4B,CAAA;AAWnC,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,UAEzC;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,QAGpE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,SAAS,cAAc,EAClE,KAAK,EAAE,KAAK,gIAcb;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,QAAA,MAAM,CAAC,eAAW,CAAA;AAClB,uBAAe,oBAAoB,CACjC,UAAU,SAAS,MAAM,GAAG,EAC5B,gBAAgB;IAEhB,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,EAAE;QAC7B,QAAQ,EAAE,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC,EAAE,gBAAgB,KAAK,gBAAgB;KAC1E,CAAA;CACF;AACD,MAAM,WAAW,SAAS,CAAC,GAAG,EAAE,IAAI,CAAE,SAAQ,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,EAAE,EAAE,GAAG,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,CAAC;IAClG,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,CAAC,EAAE,CAAA;IAC1C,QAAQ,EAAE,aAAa,CAAA;IACvB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,KAAK,iBAAiB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,GACtE,KAAK,CAAA"}
package/dist/form.js ADDED
@@ -0,0 +1,47 @@
1
+ import * as tsplus_module_1 from "@effect-app/prelude/utils";
2
+ import * as tsplus_module_2 from "@fp-ts/data/Option";
3
+ import * as tsplus_module_3 from "@fp-ts/data/Either";
4
+ import * as tsplus_module_4 from "@effect-app/core/Function";
5
+ import { getMetadataFromSchemaOrProp, isSchema, Parser, These } from "@effect-app/prelude/schema";
6
+ import { capitalize } from "vue";
7
+ export function convertIn(v) {
8
+ return v === null ? "" : v;
9
+ }
10
+ export function convertOut(v, set) {
11
+ v = v.trim();
12
+ return set(v === "" ? null : v);
13
+ }
14
+ export function buildFieldInfoFromProps(props) {
15
+ return tsplus_module_1.RecordKeys(tsplus_module_1.object$(props)).reduce((prev, cur) => {
16
+ prev[cur] = buildFieldInfo(props[cur], cur);
17
+ return prev;
18
+ }, {});
19
+ }
20
+ const f = Symbol();
21
+ class PhantomTypeParameter {
22
+ }
23
+ function buildFieldInfo(propOrSchema, fieldKey) {
24
+ const metadata = getMetadataFromSchemaOrProp(propOrSchema);
25
+ const schema = isSchema(propOrSchema) ? propOrSchema : propOrSchema._schema;
26
+ const parse = Parser.for(schema);
27
+ const info = {
28
+ type: "text",
29
+ rules: [
30
+ (v) => !metadata.required || v !== "" || "The field cannot be empty",
31
+ (v) => v === "" ||
32
+ metadata.minLength === undefined ||
33
+ v.length >= metadata.minLength ||
34
+ `The field requires at least ${metadata.minLength} characters`,
35
+ (v) => v === "" ||
36
+ metadata.maxLength === undefined ||
37
+ v.length <= metadata.maxLength ||
38
+ `The field cannot have more than ${metadata.maxLength} characters`,
39
+ (v) => tsplus_module_4.pipe(parse(v === "" ? null : v), These.result, _ => tsplus_module_3.match(() => `The entered value is not a valid ${capitalize(fieldKey.toString())}`, ([_, optErr]) => tsplus_module_2.isSome(optErr)
40
+ ? `The entered value is not a valid ${capitalize(fieldKey.toString())}`
41
+ : true)(_))
42
+ ],
43
+ metadata
44
+ };
45
+ return info;
46
+ }
47
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL19zcmMvZm9ybS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBTUEsT0FBTyxFQUVMLDJCQUEyQixFQUMzQixRQUFRLEVBQ1IsTUFBTSxFQUVOLEtBQUssRUFDTixNQUFNLDRCQUE0QixDQUFBO0FBQ25DLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxLQUFLLENBQUE7QUFFaEMsTUFBTSxVQUFVLFNBQVMsQ0FBQyxDQUFnQjtJQUN4QyxPQUFPLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFBO0FBQzVCLENBQUM7QUFFRCxNQUFNLFVBQVUsVUFBVSxDQUFDLENBQVMsRUFBRSxHQUErQjtJQUNuRSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFBO0lBQ1osT0FBTyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQTtBQUNqQyxDQUFDO0FBRUQsTUFBTSxVQUFVLHVCQUF1QixDQUNyQyxLQUFZO0lBRVosT0FBTyxtREFBQSxLQUFLLEdBQVMsTUFBTSxDQUN6QixDQUFDLElBQUksRUFBRSxHQUFHLEVBQUUsRUFBRTtRQUNaLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxjQUFjLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBZ0IsRUFBRSxHQUFHLENBQUMsQ0FBQTtRQUMxRCxPQUFPLElBQUksQ0FBQTtJQUNiLENBQUMsRUFDRCxFQUtDLENBQ0YsQ0FBQTtBQUNILENBQUM7QUFRRCxNQUFNLENBQUMsR0FBRyxNQUFNLEVBQUUsQ0FBQTtBQUNsQixNQUFlLG9CQUFvQjtDQU9sQztBQVVELFNBQVMsY0FBYyxDQUNyQixZQUFxQyxFQUNyQyxRQUFxQjtJQUVyQixNQUFNLFFBQVEsR0FBRywyQkFBMkIsQ0FBQyxZQUFZLENBQUMsQ0FBQTtJQUMxRCxNQUFNLE1BQU0sR0FBRyxRQUFRLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQTtJQUMzRSxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxDQUFBO0lBRWhDLE1BQU0sSUFBSSxHQUFHO1FBQ1gsSUFBSSxFQUFFLE1BQU07UUFDWixLQUFLLEVBQUU7WUFDTCxDQUFDLENBQVMsRUFBRSxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsUUFBUSxJQUFJLENBQUMsS0FBSyxFQUFFLElBQUksMkJBQTJCO1lBQzVFLENBQUMsQ0FBUyxFQUFFLEVBQUUsQ0FDWixDQUFDLEtBQUssRUFBRTtnQkFDUixRQUFRLENBQUMsU0FBUyxLQUFLLFNBQVM7Z0JBQ2hDLENBQUMsQ0FBQyxNQUFNLElBQUksUUFBUSxDQUFDLFNBQVM7Z0JBQzlCLCtCQUErQixRQUFRLENBQUMsU0FBUyxhQUFhO1lBQ2hFLENBQUMsQ0FBUyxFQUFFLEVBQUUsQ0FDWixDQUFDLEtBQUssRUFBRTtnQkFDUixRQUFRLENBQUMsU0FBUyxLQUFLLFNBQVM7Z0JBQ2hDLENBQUMsQ0FBQyxNQUFNLElBQUksUUFBUSxDQUFDLFNBQVM7Z0JBQzlCLG1DQUFtQyxRQUFRLENBQUMsU0FBUyxhQUFhO1lBQ3BFLENBQUMsQ0FBVSxFQUFFLEVBQUUsQ0FDYixnQkFBQSxJQUFJLENBQ0YsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQzFCLEtBQUssQ0FBQyxNQUFNLEVBQ1osQ0FBQyxDQUFDLEVBQUUsQ0FDRixzQkFDRSxHQUFHLEVBQUUsQ0FDSCxvQ0FDRSxVQUFVLENBQ1IsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUV2QixFQUFFLEVBQ0osQ0FBQyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsRUFBRSxFQUFFLENBQ2QsdUJBQUEsTUFBTSxDQUFTO2dCQUNiLENBQUMsQ0FBQyxvQ0FDQSxVQUFVLENBQ1IsUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUV2QixFQUFFO2dCQUNGLENBQUMsQ0FBQyxJQUFJLEVBZFosQ0FBQyxDQWVBLENBQ0o7U0FDSjtRQUNELFFBQVE7S0FDVCxDQUFBO0lBRUQsT0FBTyxJQUFXLENBQUE7QUFDcEIsQ0FBQyJ9