@effect-app/vue 2.51.18 → 2.52.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/CHANGELOG.md +6 -0
- package/dist/experimental/makeExperimental.d.ts +67 -0
- package/dist/experimental/makeExperimental.d.ts.map +1 -0
- package/dist/experimental/makeExperimental.js +16 -0
- package/dist/experimental/useCommand.d.ts +108 -0
- package/dist/experimental/useCommand.d.ts.map +1 -0
- package/dist/experimental/useCommand.js +162 -0
- package/dist/experimental/useConfirm.d.ts +7 -0
- package/dist/experimental/useConfirm.d.ts.map +1 -0
- package/dist/experimental/useConfirm.js +9 -0
- package/dist/experimental/useWithToast.d.ts +23 -0
- package/dist/experimental/useWithToast.d.ts.map +1 -0
- package/dist/experimental/useWithToast.js +31 -0
- package/dist/form.d.ts +1 -1
- package/dist/form.d.ts.map +1 -1
- package/package.json +22 -3
- package/src/experimental/makeExperimental.ts +27 -0
- package/src/experimental/useCommand.ts +513 -0
- package/src/experimental/useConfirm.ts +17 -0
- package/src/experimental/useWithToast.ts +65 -0
- package/test/Mutation.test.ts +296 -0
- package/test/dist/Mutation.test.d.ts.map +1 -0
- package/test/dist/stubs.d.ts +68 -0
- package/test/dist/stubs.d.ts.map +1 -0
- package/test/dist/stubs.js +60 -0
- package/test/stubs.ts +75 -0
- package/tsconfig.src.json +2 -1
- package/vitest.config.ts +15 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-app/vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.52.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/effect-ts-app/libs/tree/main/packages/vue",
|
|
@@ -12,16 +12,19 @@
|
|
|
12
12
|
"effect-app": "3.2.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@effect/platform": "^0.90.0",
|
|
16
|
-
"@effect/platform-browser": "^0.70.0",
|
|
17
15
|
"@effect-atom/atom": "^0.1.4",
|
|
18
16
|
"@effect-atom/atom-vue": "^0.1.3",
|
|
17
|
+
"@effect/platform": "^0.90.0",
|
|
18
|
+
"@effect/platform-browser": "^0.70.0",
|
|
19
19
|
"@sentry/browser": "^10.2.0",
|
|
20
20
|
"effect": "^3.17.6",
|
|
21
21
|
"vue": "^3.5.18"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"@effect/vitest": "^0.25.1",
|
|
25
|
+
"@formatjs/icu-messageformat-parser": "^2.11.2",
|
|
24
26
|
"@types/node": "~24.2.0",
|
|
27
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
25
28
|
"intl-messageformat": "^10.7.16",
|
|
26
29
|
"json5": "^2.2.3",
|
|
27
30
|
"typescript": "~5.9.2",
|
|
@@ -44,6 +47,22 @@
|
|
|
44
47
|
"types": "./dist/errorReporter.d.ts",
|
|
45
48
|
"default": "./dist/errorReporter.js"
|
|
46
49
|
},
|
|
50
|
+
"./experimental/makeExperimental": {
|
|
51
|
+
"types": "./dist/experimental/makeExperimental.d.ts",
|
|
52
|
+
"default": "./dist/experimental/makeExperimental.js"
|
|
53
|
+
},
|
|
54
|
+
"./experimental/useCommand": {
|
|
55
|
+
"types": "./dist/experimental/useCommand.d.ts",
|
|
56
|
+
"default": "./dist/experimental/useCommand.js"
|
|
57
|
+
},
|
|
58
|
+
"./experimental/useConfirm": {
|
|
59
|
+
"types": "./dist/experimental/useConfirm.d.ts",
|
|
60
|
+
"default": "./dist/experimental/useConfirm.js"
|
|
61
|
+
},
|
|
62
|
+
"./experimental/useWithToast": {
|
|
63
|
+
"types": "./dist/experimental/useWithToast.d.ts",
|
|
64
|
+
"default": "./dist/experimental/useWithToast.js"
|
|
65
|
+
},
|
|
47
66
|
"./form": {
|
|
48
67
|
"types": "./dist/form.d.ts",
|
|
49
68
|
"default": "./dist/form.js"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Runtime } from "effect-app"
|
|
2
|
+
import { type MakeIntlReturn } from "../makeIntl.js"
|
|
3
|
+
import { makeUseCommand } from "./useCommand.js"
|
|
4
|
+
import { makeUseConfirm } from "./useConfirm.js"
|
|
5
|
+
import { makeUseWithToast, type UseToast } from "./useWithToast.js"
|
|
6
|
+
|
|
7
|
+
export const makeExperimental = <Locale extends string, R>(
|
|
8
|
+
// NOTE: underscores to not collide with auto exports in nuxt apps
|
|
9
|
+
_useIntl: MakeIntlReturn<Locale>["useIntl"],
|
|
10
|
+
_useToast: UseToast,
|
|
11
|
+
runtime: Runtime.Runtime<R>
|
|
12
|
+
) => {
|
|
13
|
+
const _useConfirm = makeUseConfirm(_useIntl)
|
|
14
|
+
const _useWithToast = makeUseWithToast(_useToast)
|
|
15
|
+
const _useCommand = makeUseCommand(
|
|
16
|
+
_useIntl,
|
|
17
|
+
_useConfirm,
|
|
18
|
+
_useWithToast,
|
|
19
|
+
runtime
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
useConfirm: _useConfirm,
|
|
24
|
+
useCommand: _useCommand,
|
|
25
|
+
useWithToast: _useWithToast
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
import { asResult, type MakeIntlReturn, reportRuntimeError } from "@effect-app/vue"
|
|
3
|
+
import { reportMessage } from "@effect-app/vue/errorReporter"
|
|
4
|
+
import { type Result } from "@effect-atom/atom/Result"
|
|
5
|
+
import { Cause, Context, Effect, type Exit, flow, Match, Option, Runtime, S } from "effect-app"
|
|
6
|
+
import { SupportedErrors } from "effect-app/client"
|
|
7
|
+
import { OperationFailure, OperationSuccess } from "effect-app/Operations"
|
|
8
|
+
import { type RuntimeFiber } from "effect/Fiber"
|
|
9
|
+
import { type YieldWrap } from "effect/Utils"
|
|
10
|
+
import { computed, type ComputedRef } from "vue"
|
|
11
|
+
import { type makeUseConfirm } from "./useConfirm.js"
|
|
12
|
+
import { type makeUseWithToast } from "./useWithToast.js"
|
|
13
|
+
|
|
14
|
+
export const DefaultIntl = {
|
|
15
|
+
de: {
|
|
16
|
+
"handle.confirmation": "{action} bestätigen?",
|
|
17
|
+
"handle.waiting": "{action} wird ausgeführt...",
|
|
18
|
+
"handle.success": "{action} erfolgreich",
|
|
19
|
+
"handle.with_errors": "{action} fehlgeschlagen",
|
|
20
|
+
"handle.with_warnings": "{action} erfolgreich, mit Warnungen",
|
|
21
|
+
"handle.error_response":
|
|
22
|
+
"Die Anfrage war nicht erfolgreich:\n{error}\nWir wurden benachrichtigt und werden das Problem in Kürze beheben.",
|
|
23
|
+
"handle.response_error": "Die Antwort konnte nicht verarbeitet werden:\n{error}",
|
|
24
|
+
"handle.request_error": "Die Anfrage konnte nicht gesendet werden:\n{error}",
|
|
25
|
+
"handle.unexpected_error2": "{action} unerwarteter Fehler, probieren sie es in kurze nochmals."
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
"handle.confirmation": "Confirm {action}?",
|
|
29
|
+
"handle.waiting": "{action} executing...",
|
|
30
|
+
"handle.success": "{action} Success",
|
|
31
|
+
"handle.with_errors": "{action} Failed",
|
|
32
|
+
"handle.with_warnings": "{action}, with warnings",
|
|
33
|
+
"handle.error_response":
|
|
34
|
+
"There was an error in processing the response:\n{error}\nWe have been notified and will fix the problem shortly.",
|
|
35
|
+
"handle.request_error": "There was an error in the request:\n{error}",
|
|
36
|
+
"handle.response_error": "The request was not successful:\n{error}",
|
|
37
|
+
"handle.unexpected_error2": "{action} unexpected error, please try again shortly."
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class CommandContext extends Context.Tag("CommandContext")<
|
|
42
|
+
CommandContext,
|
|
43
|
+
{ action: string }
|
|
44
|
+
>() {}
|
|
45
|
+
|
|
46
|
+
export const makeUseCommand = <Locale extends string, RT>(
|
|
47
|
+
// NOTE: underscores to not collide with auto exports in nuxt apps
|
|
48
|
+
_useIntl: MakeIntlReturn<Locale>["useIntl"],
|
|
49
|
+
_useConfirm: ReturnType<typeof makeUseConfirm>,
|
|
50
|
+
_useWithToast: ReturnType<typeof makeUseWithToast>,
|
|
51
|
+
runtime: Runtime.Runtime<RT>
|
|
52
|
+
) =>
|
|
53
|
+
() => {
|
|
54
|
+
const withToast = _useWithToast()
|
|
55
|
+
const { intl } = _useIntl()
|
|
56
|
+
const { confirmOrInterrupt } = _useConfirm()
|
|
57
|
+
|
|
58
|
+
const runFork = Runtime.runFork(runtime)
|
|
59
|
+
|
|
60
|
+
type CommandOut<Args extends Array<any>, A, E> = ComputedRef<
|
|
61
|
+
((...a: Args) => RuntimeFiber<Exit.Exit<A, E>, never>) & {
|
|
62
|
+
action: string
|
|
63
|
+
result: Result<A, E>
|
|
64
|
+
waiting: boolean
|
|
65
|
+
}
|
|
66
|
+
>
|
|
67
|
+
|
|
68
|
+
type CommandOutHelper<Args extends Array<any>, Eff extends Effect.Effect<any, any, any>> = CommandOut<
|
|
69
|
+
Args,
|
|
70
|
+
Effect.Effect.Success<Eff>,
|
|
71
|
+
Effect.Effect.Error<Eff>
|
|
72
|
+
>
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
/** Version of confirmOrInterrupt that automatically includes the action name in the default messages */
|
|
76
|
+
confirmOrInterrupt: Effect.fnUntraced(function*(
|
|
77
|
+
message: string | undefined = undefined
|
|
78
|
+
) {
|
|
79
|
+
const context = yield* CommandContext
|
|
80
|
+
yield* confirmOrInterrupt(
|
|
81
|
+
message
|
|
82
|
+
?? intl.value.formatMessage(
|
|
83
|
+
{ id: "handle.confirmation" },
|
|
84
|
+
{ action: context.action }
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
}),
|
|
88
|
+
/** Version of withDefaultToast that automatically includes the action name in the default messages and uses intl */
|
|
89
|
+
withDefaultToast: <A, E>(
|
|
90
|
+
self: Effect.Effect<A, E, CommandContext>,
|
|
91
|
+
errorRenderer?: (e: E) => string | undefined // undefined falls back to default?
|
|
92
|
+
) =>
|
|
93
|
+
Effect.gen(function*() {
|
|
94
|
+
const { action } = yield* CommandContext
|
|
95
|
+
|
|
96
|
+
const defaultWarnMessage = intl.value.formatMessage(
|
|
97
|
+
{ id: "handle.with_warnings" },
|
|
98
|
+
{ action }
|
|
99
|
+
)
|
|
100
|
+
const defaultErrorMessage = intl.value.formatMessage(
|
|
101
|
+
{ id: "handle.with_errors" },
|
|
102
|
+
{ action }
|
|
103
|
+
)
|
|
104
|
+
function renderError(e: E): string {
|
|
105
|
+
if (errorRenderer) {
|
|
106
|
+
const m = errorRenderer(e)
|
|
107
|
+
if (m) {
|
|
108
|
+
return m
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (!S.is(SupportedErrors)(e) && !S.ParseResult.isParseError(e)) {
|
|
112
|
+
if (typeof e === "object" && e !== null) {
|
|
113
|
+
if ("message" in e) {
|
|
114
|
+
return `${e.message}`
|
|
115
|
+
}
|
|
116
|
+
if ("_tag" in e) {
|
|
117
|
+
return `${e._tag}`
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return ""
|
|
121
|
+
}
|
|
122
|
+
const e2: SupportedErrors | S.ParseResult.ParseError = e
|
|
123
|
+
return Match.value(e2).pipe(
|
|
124
|
+
Match.tags({
|
|
125
|
+
ParseError: (e) => {
|
|
126
|
+
console.warn(e.toString())
|
|
127
|
+
return intl.value.formatMessage({ id: "validation.failed" })
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
Match.orElse((e) => `${e.message ?? e._tag ?? e}`)
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return yield* self.pipe(
|
|
135
|
+
withToast({
|
|
136
|
+
onWaiting: intl.value.formatMessage(
|
|
137
|
+
{ id: "handle.waiting" },
|
|
138
|
+
{ action }
|
|
139
|
+
),
|
|
140
|
+
onSuccess: (a) =>
|
|
141
|
+
intl.value.formatMessage({ id: "handle.success" }, { action })
|
|
142
|
+
+ (S.is(OperationSuccess)(a) && a.message ? "\n" + a.message : ""),
|
|
143
|
+
onFailure: Option.match({
|
|
144
|
+
onNone: () =>
|
|
145
|
+
intl.value.formatMessage(
|
|
146
|
+
{ id: "handle.unexpected_error2" },
|
|
147
|
+
{
|
|
148
|
+
action,
|
|
149
|
+
error: "" // TODO consider again Cause.pretty(cause), // will be reported to Sentry/Otel anyway.. and we shouldn't bother users with error dumps?
|
|
150
|
+
}
|
|
151
|
+
),
|
|
152
|
+
onSome: (e) =>
|
|
153
|
+
S.is(OperationFailure)(e)
|
|
154
|
+
? {
|
|
155
|
+
level: "warn",
|
|
156
|
+
message: defaultWarnMessage + e.message ? "\n" + e.message : ""
|
|
157
|
+
}
|
|
158
|
+
: `${defaultErrorMessage}:\n` + renderError(e)
|
|
159
|
+
})
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
}),
|
|
163
|
+
/**
|
|
164
|
+
* Define a Command
|
|
165
|
+
* @param actionName The internal name of the action. will be used as Span. will be used to lookup user facing name via intl. `action.${actionName}`
|
|
166
|
+
* @returns A function that can be called to execute the mutation, like directly in a `@click` handler. Error reporting is built-in.
|
|
167
|
+
* the Effects **only** have access to the `CommandContext` service, which contains the user-facing action name.
|
|
168
|
+
* The function also has the following properties:
|
|
169
|
+
* - action: The user-facing name of the action, as defined in the intl messages. Can be used e.g as Button label.
|
|
170
|
+
* - result: The Result of the mutation
|
|
171
|
+
* - waiting: Whether the mutation is currently in progress. (shorthand for .result.waiting). Can be used e.g as Button loading/disabled state.
|
|
172
|
+
* Reporting status to the user is recommended to use the `withDefaultToast` helper, or render the .result inline
|
|
173
|
+
*/
|
|
174
|
+
fn: (actionName: string): {
|
|
175
|
+
<Eff extends YieldWrap<Effect.Effect<any, any, RT | CommandContext>>, AEff, Args extends Array<any>>(
|
|
176
|
+
body: (...args: Args) => Generator<Eff, AEff, never>
|
|
177
|
+
): CommandOut<
|
|
178
|
+
Args,
|
|
179
|
+
AEff,
|
|
180
|
+
[Eff] extends [never] ? never
|
|
181
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
182
|
+
: never
|
|
183
|
+
>
|
|
184
|
+
<
|
|
185
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
186
|
+
AEff,
|
|
187
|
+
Args extends Array<any>,
|
|
188
|
+
A extends Effect.Effect<any, any, RT | CommandContext>
|
|
189
|
+
>(
|
|
190
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
191
|
+
a: (
|
|
192
|
+
_: Effect.Effect<
|
|
193
|
+
AEff,
|
|
194
|
+
[Eff] extends [never] ? never
|
|
195
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
196
|
+
: never,
|
|
197
|
+
[Eff] extends [never] ? never
|
|
198
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
199
|
+
: never
|
|
200
|
+
> /* TODO ,
|
|
201
|
+
...args: NoInfer<Args> */
|
|
202
|
+
) => A
|
|
203
|
+
): CommandOutHelper<Args, A>
|
|
204
|
+
<
|
|
205
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
206
|
+
AEff,
|
|
207
|
+
Args extends Array<any>,
|
|
208
|
+
A,
|
|
209
|
+
B extends Effect.Effect<any, any, RT | CommandContext>
|
|
210
|
+
>(
|
|
211
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
212
|
+
a: (
|
|
213
|
+
_: Effect.Effect<
|
|
214
|
+
AEff,
|
|
215
|
+
[Eff] extends [never] ? never
|
|
216
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
217
|
+
: never,
|
|
218
|
+
[Eff] extends [never] ? never
|
|
219
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
220
|
+
: never
|
|
221
|
+
> /* TODO ,
|
|
222
|
+
...args: NoInfer<Args> */
|
|
223
|
+
) => A,
|
|
224
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B
|
|
225
|
+
): CommandOutHelper<Args, B>
|
|
226
|
+
<
|
|
227
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
228
|
+
AEff,
|
|
229
|
+
Args extends Array<any>,
|
|
230
|
+
A,
|
|
231
|
+
B,
|
|
232
|
+
C extends Effect.Effect<any, any, RT | CommandContext>
|
|
233
|
+
>(
|
|
234
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
235
|
+
a: (
|
|
236
|
+
_: Effect.Effect<
|
|
237
|
+
AEff,
|
|
238
|
+
[Eff] extends [never] ? never
|
|
239
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
240
|
+
: never,
|
|
241
|
+
[Eff] extends [never] ? never
|
|
242
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
243
|
+
: never
|
|
244
|
+
> /* TODO ,
|
|
245
|
+
...args: NoInfer<Args> */
|
|
246
|
+
) => A,
|
|
247
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
248
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C
|
|
249
|
+
): CommandOutHelper<Args, C>
|
|
250
|
+
<
|
|
251
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
252
|
+
AEff,
|
|
253
|
+
Args extends Array<any>,
|
|
254
|
+
A,
|
|
255
|
+
B,
|
|
256
|
+
C,
|
|
257
|
+
D extends Effect.Effect<any, any, RT | CommandContext>
|
|
258
|
+
>(
|
|
259
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
260
|
+
a: (
|
|
261
|
+
_: Effect.Effect<
|
|
262
|
+
AEff,
|
|
263
|
+
[Eff] extends [never] ? never
|
|
264
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
265
|
+
: never,
|
|
266
|
+
[Eff] extends [never] ? never
|
|
267
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
268
|
+
: never
|
|
269
|
+
> /* TODO ,
|
|
270
|
+
...args: NoInfer<Args> */
|
|
271
|
+
) => A,
|
|
272
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
273
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
274
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D
|
|
275
|
+
): CommandOutHelper<Args, D>
|
|
276
|
+
<
|
|
277
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
278
|
+
AEff,
|
|
279
|
+
Args extends Array<any>,
|
|
280
|
+
A,
|
|
281
|
+
B,
|
|
282
|
+
C,
|
|
283
|
+
D,
|
|
284
|
+
E extends Effect.Effect<any, any, RT | CommandContext>
|
|
285
|
+
>(
|
|
286
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
287
|
+
a: (
|
|
288
|
+
_: Effect.Effect<
|
|
289
|
+
AEff,
|
|
290
|
+
[Eff] extends [never] ? never
|
|
291
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
292
|
+
: never,
|
|
293
|
+
[Eff] extends [never] ? never
|
|
294
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
295
|
+
: never
|
|
296
|
+
> /* TODO ,
|
|
297
|
+
...args: NoInfer<Args> */
|
|
298
|
+
) => A,
|
|
299
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
300
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
301
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D,
|
|
302
|
+
e: (_: D /* TODO , ...args: NoInfer<Args> */) => E
|
|
303
|
+
): CommandOutHelper<Args, E>
|
|
304
|
+
<
|
|
305
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
306
|
+
AEff,
|
|
307
|
+
Args extends Array<any>,
|
|
308
|
+
A,
|
|
309
|
+
B,
|
|
310
|
+
C,
|
|
311
|
+
D,
|
|
312
|
+
E,
|
|
313
|
+
F extends Effect.Effect<any, any, RT | CommandContext>
|
|
314
|
+
>(
|
|
315
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
316
|
+
a: (
|
|
317
|
+
_: Effect.Effect<
|
|
318
|
+
AEff,
|
|
319
|
+
[Eff] extends [never] ? never
|
|
320
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
321
|
+
: never,
|
|
322
|
+
[Eff] extends [never] ? never
|
|
323
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
324
|
+
: never
|
|
325
|
+
> /* TODO ,
|
|
326
|
+
...args: NoInfer<Args> */
|
|
327
|
+
) => A,
|
|
328
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
329
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
330
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D,
|
|
331
|
+
e: (_: D /* TODO , ...args: NoInfer<Args> */) => E,
|
|
332
|
+
f: (_: E /* TODO , ...args: NoInfer<Args> */) => F
|
|
333
|
+
): CommandOutHelper<Args, F>
|
|
334
|
+
<
|
|
335
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
336
|
+
AEff,
|
|
337
|
+
Args extends Array<any>,
|
|
338
|
+
A,
|
|
339
|
+
B,
|
|
340
|
+
C,
|
|
341
|
+
D,
|
|
342
|
+
E,
|
|
343
|
+
F,
|
|
344
|
+
G extends Effect.Effect<any, any, RT | CommandContext>
|
|
345
|
+
>(
|
|
346
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
347
|
+
a: (
|
|
348
|
+
_: Effect.Effect<
|
|
349
|
+
AEff,
|
|
350
|
+
[Eff] extends [never] ? never
|
|
351
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
352
|
+
: never,
|
|
353
|
+
[Eff] extends [never] ? never
|
|
354
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
355
|
+
: never
|
|
356
|
+
> /* TODO ,
|
|
357
|
+
...args: NoInfer<Args> */
|
|
358
|
+
) => A,
|
|
359
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
360
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
361
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D,
|
|
362
|
+
e: (_: D /* TODO , ...args: NoInfer<Args> */) => E,
|
|
363
|
+
f: (_: E /* TODO , ...args: NoInfer<Args> */) => F,
|
|
364
|
+
g: (_: F /* TODO , ...args: NoInfer<Args> */) => G
|
|
365
|
+
): CommandOutHelper<Args, G>
|
|
366
|
+
<
|
|
367
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
368
|
+
AEff,
|
|
369
|
+
Args extends Array<any>,
|
|
370
|
+
A,
|
|
371
|
+
B,
|
|
372
|
+
C,
|
|
373
|
+
D,
|
|
374
|
+
E,
|
|
375
|
+
F,
|
|
376
|
+
G,
|
|
377
|
+
H extends Effect.Effect<any, any, RT | CommandContext>
|
|
378
|
+
>(
|
|
379
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
380
|
+
a: (
|
|
381
|
+
_: Effect.Effect<
|
|
382
|
+
AEff,
|
|
383
|
+
[Eff] extends [never] ? never
|
|
384
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
385
|
+
: never,
|
|
386
|
+
[Eff] extends [never] ? never
|
|
387
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
388
|
+
: never
|
|
389
|
+
> /* TODO ,
|
|
390
|
+
...args: NoInfer<Args> */
|
|
391
|
+
) => A,
|
|
392
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
393
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
394
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D,
|
|
395
|
+
e: (_: D /* TODO , ...args: NoInfer<Args> */) => E,
|
|
396
|
+
f: (_: E /* TODO , ...args: NoInfer<Args> */) => F,
|
|
397
|
+
g: (_: F /* TODO , ...args: NoInfer<Args> */) => G,
|
|
398
|
+
h: (_: G /* TODO , ...args: NoInfer<Args> */) => H
|
|
399
|
+
): CommandOutHelper<Args, H>
|
|
400
|
+
<
|
|
401
|
+
Eff extends YieldWrap<Effect.Effect<any, any, any>>,
|
|
402
|
+
AEff,
|
|
403
|
+
Args extends Array<any>,
|
|
404
|
+
A,
|
|
405
|
+
B,
|
|
406
|
+
C,
|
|
407
|
+
D,
|
|
408
|
+
E,
|
|
409
|
+
F,
|
|
410
|
+
G,
|
|
411
|
+
H,
|
|
412
|
+
I extends Effect.Effect<any, any, RT | CommandContext>
|
|
413
|
+
>(
|
|
414
|
+
body: (...args: Args) => Generator<Eff, AEff, never>,
|
|
415
|
+
a: (
|
|
416
|
+
_: Effect.Effect<
|
|
417
|
+
AEff,
|
|
418
|
+
[Eff] extends [never] ? never
|
|
419
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer E, infer _R>>] ? E
|
|
420
|
+
: never,
|
|
421
|
+
[Eff] extends [never] ? never
|
|
422
|
+
: [Eff] extends [YieldWrap<Effect.Effect<infer _A, infer _E, infer R>>] ? R
|
|
423
|
+
: never
|
|
424
|
+
> /* TODO ,
|
|
425
|
+
...args: NoInfer<Args> */
|
|
426
|
+
) => A,
|
|
427
|
+
b: (_: A /* TODO , ...args: NoInfer<Args> */) => B,
|
|
428
|
+
c: (_: B /* TODO , ...args: NoInfer<Args> */) => C,
|
|
429
|
+
d: (_: C /* TODO , ...args: NoInfer<Args> */) => D,
|
|
430
|
+
e: (_: D /* TODO , ...args: NoInfer<Args> */) => E,
|
|
431
|
+
f: (_: E /* TODO , ...args: NoInfer<Args> */) => F,
|
|
432
|
+
g: (_: F /* TODO , ...args: NoInfer<Args> */) => G,
|
|
433
|
+
h: (_: G /* TODO , ...args: NoInfer<Args> */) => H,
|
|
434
|
+
i: (_: H /* TODO , ...args: NoInfer<Args> */) => I
|
|
435
|
+
): CommandOutHelper<Args, I>
|
|
436
|
+
} =>
|
|
437
|
+
// TODO constrain/type combinators
|
|
438
|
+
(
|
|
439
|
+
fn: any,
|
|
440
|
+
// TODO: combinators can freely take A, E, R and change it to whatever they want, as long as the end result Requires not more than CommandContext | R
|
|
441
|
+
...combinators: any[]
|
|
442
|
+
): any => {
|
|
443
|
+
const action = intl.value.formatMessage({
|
|
444
|
+
id: `action.${actionName}`,
|
|
445
|
+
defaultMessage: actionName
|
|
446
|
+
})
|
|
447
|
+
const context = { action }
|
|
448
|
+
|
|
449
|
+
const errorReporter = <A, E, R>(self: Effect.Effect<A, E, R>) =>
|
|
450
|
+
self.pipe(
|
|
451
|
+
Effect.tapErrorCause(
|
|
452
|
+
Effect.fnUntraced(function*(cause) {
|
|
453
|
+
if (Cause.isInterruptedOnly(cause)) {
|
|
454
|
+
console.info(`Interrupted while trying to ${actionName}`)
|
|
455
|
+
return
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const fail = Cause.failureOption(cause)
|
|
459
|
+
if (Option.isSome(fail)) {
|
|
460
|
+
// if (fail.value._tag === "SuppressErrors") {
|
|
461
|
+
// console.info(
|
|
462
|
+
// `Suppressed error trying to ${action}`,
|
|
463
|
+
// fail.value,
|
|
464
|
+
// )
|
|
465
|
+
// return
|
|
466
|
+
// }
|
|
467
|
+
const message = `Failure trying to ${actionName}`
|
|
468
|
+
yield* reportMessage(message, {
|
|
469
|
+
action: actionName,
|
|
470
|
+
error: fail.value
|
|
471
|
+
})
|
|
472
|
+
return
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const extra = {
|
|
476
|
+
action,
|
|
477
|
+
message: `Unexpected Error trying to ${actionName}`
|
|
478
|
+
}
|
|
479
|
+
yield* reportRuntimeError(cause, extra)
|
|
480
|
+
})
|
|
481
|
+
)
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
// TODO: override span stack set by Effect.fn as it points here instead of to the caller of Command.fn.
|
|
485
|
+
// perhaps copying Effect.fn implementation is better than using it?
|
|
486
|
+
const handler = Effect.fn(actionName)(
|
|
487
|
+
fn,
|
|
488
|
+
...(combinators as [any]),
|
|
489
|
+
// all must be within the Effect.fn to fit within the Span
|
|
490
|
+
Effect.provideService(CommandContext, context) as any, /* TODO */
|
|
491
|
+
((_: any) => Effect.annotateCurrentSpan({ action }).pipe(Effect.zipRight(_))) as any, /* TODO */
|
|
492
|
+
errorReporter as any /* TODO */
|
|
493
|
+
) as any // (...args: Args) => Effect.Effect<AEff, $WrappedEffectError, R>
|
|
494
|
+
|
|
495
|
+
const [result, mut] = asResult(handler)
|
|
496
|
+
|
|
497
|
+
return computed(() =>
|
|
498
|
+
Object.assign(
|
|
499
|
+
flow(
|
|
500
|
+
mut as any,
|
|
501
|
+
runFork
|
|
502
|
+
// (_) => {}
|
|
503
|
+
), /* make sure always create a new one, or the state won't properly propagate */
|
|
504
|
+
{
|
|
505
|
+
action,
|
|
506
|
+
result: result.value,
|
|
507
|
+
waiting: result.value.waiting
|
|
508
|
+
}
|
|
509
|
+
)
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect } from "effect-app"
|
|
2
|
+
import { type MakeIntlReturn } from "../makeIntl.js"
|
|
3
|
+
|
|
4
|
+
export const makeUseConfirm = <Locale extends string>(_useIntl: MakeIntlReturn<Locale>["useIntl"]) => () => {
|
|
5
|
+
const { intl } = _useIntl()
|
|
6
|
+
const getDefaultMessage = () =>
|
|
7
|
+
intl.value.formatMessage({ id: "confirm.default", defaultMessage: "Sind sie Sicher?" })
|
|
8
|
+
|
|
9
|
+
const confirm = (message = getDefaultMessage()) => Effect.sync(() => window.confirm(message))
|
|
10
|
+
|
|
11
|
+
const confirmOrInterrupt = (message = getDefaultMessage()) =>
|
|
12
|
+
confirm(message).pipe(
|
|
13
|
+
Effect.flatMap((result) => (result ? Effect.void : Effect.interrupt))
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
return { confirm, confirmOrInterrupt }
|
|
17
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Cause, Effect, type Option } from "effect-app"
|
|
2
|
+
|
|
3
|
+
export interface ToastOptions<A, E, Args extends ReadonlyArray<unknown>> {
|
|
4
|
+
onWaiting: string | ((...args: Args) => string)
|
|
5
|
+
onSuccess: string | ((a: A, ...args: Args) => string)
|
|
6
|
+
onFailure:
|
|
7
|
+
| string
|
|
8
|
+
| ((
|
|
9
|
+
error: Option.Option<E>,
|
|
10
|
+
...args: Args
|
|
11
|
+
) => string | { level: "warn" | "error"; message: string })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ToastId = string | number
|
|
15
|
+
export type ToastOpts = { id?: ToastId; timeout?: number }
|
|
16
|
+
|
|
17
|
+
export type UseToast = () => {
|
|
18
|
+
error: (message: string, options?: ToastOpts) => ToastId
|
|
19
|
+
warning: (message: string, options?: ToastOpts) => ToastId
|
|
20
|
+
success: (message: string, options?: ToastOpts) => ToastId
|
|
21
|
+
info: (message: string, options?: ToastOpts) => ToastId
|
|
22
|
+
dismiss: (id: ToastId) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const makeUseWithToast = (useToast: UseToast) => () => {
|
|
26
|
+
const toast = useToast()
|
|
27
|
+
return <A, E, Args extends ReadonlyArray<unknown>, R>(
|
|
28
|
+
options: ToastOptions<A, E, Args>
|
|
29
|
+
) =>
|
|
30
|
+
Effect.fnUntraced(function*(self: Effect.Effect<A, E, R>, ...args: Args) {
|
|
31
|
+
const toastId = toast.info(
|
|
32
|
+
// .loading
|
|
33
|
+
typeof options.onWaiting === "string"
|
|
34
|
+
? options.onWaiting
|
|
35
|
+
: options.onWaiting(...args)
|
|
36
|
+
)
|
|
37
|
+
return yield* self.pipe(
|
|
38
|
+
Effect.tap((a) => {
|
|
39
|
+
toast.success(
|
|
40
|
+
typeof options.onSuccess === "string"
|
|
41
|
+
? options.onSuccess
|
|
42
|
+
: options.onSuccess(a, ...args),
|
|
43
|
+
{ id: toastId, timeout: 3_000 }
|
|
44
|
+
)
|
|
45
|
+
}),
|
|
46
|
+
Effect.tapErrorCause((cause) =>
|
|
47
|
+
Effect.sync(() => {
|
|
48
|
+
if (Cause.isInterruptedOnly(cause)) {
|
|
49
|
+
toast.dismiss(toastId)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
const t = typeof options.onFailure === "string"
|
|
53
|
+
? options.onFailure
|
|
54
|
+
: options.onFailure(Cause.failureOption(cause), ...args)
|
|
55
|
+
if (typeof t === "object") {
|
|
56
|
+
return t.level === "warn"
|
|
57
|
+
? toast.warning(t.message, { id: toastId, timeout: 5_000 })
|
|
58
|
+
: toast.error(t.message, { id: toastId, timeout: 5_000 })
|
|
59
|
+
}
|
|
60
|
+
toast.error(t, { id: toastId, timeout: 5_000 })
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
})
|
|
65
|
+
}
|