@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.
- package/.eslintrc.cjs +10 -0
- package/.prettierignore +6 -0
- package/_cjs/_global.cjs +5 -0
- package/_cjs/_global.cjs.map +1 -0
- package/_cjs/form.cjs +43 -0
- package/_cjs/form.cjs.map +1 -0
- package/_cjs/hooks.cjs +228 -0
- package/_cjs/hooks.cjs.map +1 -0
- package/_cjs/index.cjs +54 -0
- package/_cjs/index.cjs.map +1 -0
- package/_cjs/internal.cjs +19 -0
- package/_cjs/internal.cjs.map +1 -0
- package/_cjs/routeParams.cjs +51 -0
- package/_cjs/routeParams.cjs.map +1 -0
- package/_cjs/runtime.cjs +70 -0
- package/_cjs/runtime.cjs.map +1 -0
- package/_src/_global.ts +10 -0
- package/_src/form.ts +116 -0
- package/_src/hooks.ts +300 -0
- package/_src/index.ts +3 -0
- package/_src/internal.ts +14 -0
- package/_src/routeParams.ts +71 -0
- package/_src/runtime.ts +50 -0
- package/dist/_global.d.ts +9 -0
- package/dist/_global.d.ts.map +1 -0
- package/dist/_global.js +10 -0
- package/dist/form.d.ts +26 -0
- package/dist/form.d.ts.map +1 -0
- package/dist/form.js +47 -0
- package/dist/hooks.d.ts +61 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +204 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/internal.d.ts +9 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +11 -0
- package/dist/routeParams.d.ts +15 -0
- package/dist/routeParams.d.ts.map +1 -0
- package/dist/routeParams.js +45 -0
- package/dist/runtime.d.ts +19 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +48 -0
- package/package.json +153 -0
- package/tsconfig.json +104 -0
- package/tsconfig.json.bak +36 -0
- package/tsplus.config.json +6 -0
- package/vitest.config.ts +5 -0
- 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
package/_src/internal.ts
ADDED
|
@@ -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
|
+
}
|
package/_src/runtime.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|
package/dist/_global.js
ADDED
|
@@ -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
|