@effect-app/vue 4.0.0-beta.18 → 4.0.0-beta.180

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 (100) hide show
  1. package/CHANGELOG.md +1224 -0
  2. package/dist/commander.d.ts +370 -0
  3. package/dist/commander.d.ts.map +1 -0
  4. package/dist/commander.js +591 -0
  5. package/dist/confirm.d.ts +19 -0
  6. package/dist/confirm.d.ts.map +1 -0
  7. package/dist/confirm.js +24 -0
  8. package/dist/errorReporter.d.ts +4 -4
  9. package/dist/errorReporter.d.ts.map +1 -1
  10. package/dist/errorReporter.js +12 -18
  11. package/dist/form.d.ts +13 -4
  12. package/dist/form.d.ts.map +1 -1
  13. package/dist/form.js +41 -12
  14. package/dist/index.d.ts +1 -1
  15. package/dist/intl.d.ts +15 -0
  16. package/dist/intl.d.ts.map +1 -0
  17. package/dist/intl.js +9 -0
  18. package/dist/lib.d.ts +6 -8
  19. package/dist/lib.d.ts.map +1 -1
  20. package/dist/lib.js +34 -7
  21. package/dist/makeClient.d.ts +148 -290
  22. package/dist/makeClient.d.ts.map +1 -1
  23. package/dist/makeClient.js +205 -361
  24. package/dist/makeContext.d.ts +1 -1
  25. package/dist/makeContext.d.ts.map +1 -1
  26. package/dist/makeIntl.d.ts +1 -1
  27. package/dist/makeIntl.d.ts.map +1 -1
  28. package/dist/makeUseCommand.d.ts +8 -0
  29. package/dist/makeUseCommand.d.ts.map +1 -0
  30. package/dist/makeUseCommand.js +13 -0
  31. package/dist/mutate.d.ts +57 -25
  32. package/dist/mutate.d.ts.map +1 -1
  33. package/dist/mutate.js +160 -33
  34. package/dist/query.d.ts +11 -15
  35. package/dist/query.d.ts.map +1 -1
  36. package/dist/query.js +19 -27
  37. package/dist/routeParams.d.ts +1 -1
  38. package/dist/runtime.d.ts +5 -2
  39. package/dist/runtime.d.ts.map +1 -1
  40. package/dist/runtime.js +27 -17
  41. package/dist/toast.d.ts +46 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/toast.js +32 -0
  44. package/dist/withToast.d.ts +26 -0
  45. package/dist/withToast.d.ts.map +1 -0
  46. package/dist/withToast.js +49 -0
  47. package/eslint.config.mjs +2 -2
  48. package/examples/streamMutation.ts +83 -0
  49. package/package.json +48 -48
  50. package/src/{experimental/commander.ts → commander.ts} +930 -255
  51. package/src/{experimental/confirm.ts → confirm.ts} +10 -14
  52. package/src/errorReporter.ts +62 -74
  53. package/src/form.ts +55 -16
  54. package/src/intl.ts +12 -0
  55. package/src/lib.ts +46 -13
  56. package/src/makeClient.ts +570 -1038
  57. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +3 -3
  58. package/src/mutate.ts +306 -72
  59. package/src/query.ts +39 -50
  60. package/src/runtime.ts +39 -18
  61. package/src/{experimental/toast.ts → toast.ts} +11 -25
  62. package/src/{experimental/withToast.ts → withToast.ts} +15 -6
  63. package/test/Mutation.test.ts +130 -10
  64. package/test/dist/form.test.d.ts.map +1 -1
  65. package/test/dist/lib.test.d.ts.map +1 -0
  66. package/test/dist/streamFinal.test.d.ts.map +1 -0
  67. package/test/dist/stubs.d.ts +3144 -117
  68. package/test/dist/stubs.d.ts.map +1 -1
  69. package/test/dist/stubs.js +132 -25
  70. package/test/form-validation-errors.test.ts +23 -19
  71. package/test/form.test.ts +20 -2
  72. package/test/lib.test.ts +240 -0
  73. package/test/makeClient.test.ts +241 -38
  74. package/test/streamFinal.test.ts +110 -0
  75. package/test/stubs.ts +172 -42
  76. package/tsconfig.examples.json +20 -0
  77. package/tsconfig.json +0 -1
  78. package/tsconfig.json.bak +5 -2
  79. package/tsconfig.src.json +34 -34
  80. package/tsconfig.test.json +2 -2
  81. package/vitest.config.ts +5 -5
  82. package/dist/experimental/commander.d.ts +0 -359
  83. package/dist/experimental/commander.d.ts.map +0 -1
  84. package/dist/experimental/commander.js +0 -557
  85. package/dist/experimental/confirm.d.ts +0 -19
  86. package/dist/experimental/confirm.d.ts.map +0 -1
  87. package/dist/experimental/confirm.js +0 -28
  88. package/dist/experimental/intl.d.ts +0 -16
  89. package/dist/experimental/intl.d.ts.map +0 -1
  90. package/dist/experimental/intl.js +0 -5
  91. package/dist/experimental/makeUseCommand.d.ts +0 -8
  92. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  93. package/dist/experimental/makeUseCommand.js +0 -13
  94. package/dist/experimental/toast.d.ts +0 -47
  95. package/dist/experimental/toast.d.ts.map +0 -1
  96. package/dist/experimental/toast.js +0 -41
  97. package/dist/experimental/withToast.d.ts +0 -25
  98. package/dist/experimental/withToast.d.ts.map +0 -1
  99. package/dist/experimental/withToast.js +0 -45
  100. package/src/experimental/intl.ts +0 -9
@@ -1,95 +1,298 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { type Effect } from "effect-app"
3
- import { Something, useClient, useExperimental } from "./stubs.js"
4
-
5
- it.skip("works2", () => {
6
- const { legacy } = useClient()
7
- const n = legacy.useQuery({
8
- Request: null as any,
9
- handler: null as any as (a: string) => Effect.Effect<number>,
10
- id: "id"
2
+ import { S } from "effect-app"
3
+ import { configureInvalidation, makeQueryKey } from "effect-app/client"
4
+ import * as Exit from "effect/Exit"
5
+ import { Something, SomethingElse, SomethingElseReq, SomethingReq, useClient, useExperimental } from "./stubs.js"
6
+
7
+ const somethingInvalidationResources = {
8
+ Something: {
9
+ GetSomething2: Something.GetSomething2,
10
+ GetSomething2WithDependencies: Something.GetSomething2WithDependencies,
11
+ GetSomething3: Something.GetSomething3,
12
+ GetSomething4: Something.GetSomething4
13
+ }
14
+ }
15
+
16
+ it("TaggedRequestFor .moduleName and request .id / .moduleName", () => {
17
+ expectTypeOf(SomethingReq.moduleName).toEqualTypeOf<"Something">()
18
+ expectTypeOf(SomethingElseReq.moduleName).toEqualTypeOf<"SomethingElse">()
19
+
20
+ expectTypeOf(Something.GetSomething2.moduleName).toEqualTypeOf<"Something">()
21
+ expectTypeOf(Something.GetSomething2.id).toEqualTypeOf<"Something.GetSomething2">()
22
+ expectTypeOf(Something.GetSomething2.type).toEqualTypeOf<"query">()
23
+ expectTypeOf(Something.DoSomething.type).toEqualTypeOf<"command">()
24
+
25
+ expectTypeOf(SomethingElse.GetSomething2.moduleName).toEqualTypeOf<"SomethingElse">()
26
+ expectTypeOf(SomethingElse.GetSomething2.id).toEqualTypeOf<"SomethingElse.GetSomething2">()
27
+
28
+ const invalidates = configureInvalidation<{
29
+ Something: typeof Something
30
+ SomethingElse: typeof SomethingElse
31
+ }>()((queryKey, { Something, SomethingElse }) => [
32
+ { filters: { queryKey } },
33
+ { filters: { queryKey: makeQueryKey(Something.GetSomething2) } },
34
+ { filters: { queryKey: makeQueryKey(SomethingElse.GetSomething2) } }
35
+ ])
36
+
37
+ expectTypeOf(invalidates.invalidatesQueries).toBeFunction()
38
+ configureInvalidation<{ Something: typeof Something }>()((_queryKey, { Something }) => {
39
+ // @ts-expect-error commands are intentionally excluded from configured resources
40
+ void Something.DoSomething
41
+ return []
11
42
  })
12
43
 
13
- const [, z] = n("a")
44
+ const { clientFor } = useClient()
45
+ const client = clientFor(
46
+ Something,
47
+ undefined,
48
+ somethingInvalidationResources
49
+ )
50
+
51
+ // only queries, no commands, and no commands who require resources; shouldn't require invalidation resources args!
52
+ clientFor({ GetSomething: Something.GetSomething2 })
53
+
54
+ // @ts-expect-error invalidation resources should be required when any command configures them
55
+ clientFor(Something)
56
+
57
+ // @ts-expect-error invalidation resources for this module reject extra top-level resources
58
+ clientFor(Something, undefined, { ...somethingInvalidationResources, SomethingElse })
59
+
60
+ const doSomethingInvalidation = client.DoSomething.Request.config["invalidatesQueries"]
61
+ if (doSomethingInvalidation) {
62
+ const entries = doSomethingInvalidation(
63
+ ["$Something"],
64
+ somethingInvalidationResources,
65
+ { id: "abc" },
66
+ Exit.succeed(123)
67
+ )
68
+ expect(Array.isArray(entries)).toBe(true)
69
+ }
70
+
71
+ const SomethingCommand = SomethingReq.Command
72
+
73
+ class TypeInferenceWithSuccess extends SomethingCommand<TypeInferenceWithSuccess>()("TypeInferenceWithSuccess", {
74
+ id: S.String
75
+ }, {
76
+ success: S.FiniteFromString
77
+ }, (_queryKey, _resources, input, result) => {
78
+ expectTypeOf(input).toEqualTypeOf<{ readonly id: string }>()
79
+ expectTypeOf(result).toEqualTypeOf<Exit.Exit<number, never>>()
80
+ return []
81
+ }) {}
82
+ void TypeInferenceWithSuccess
83
+
84
+ class TypeInferenceWithoutSuccess extends SomethingCommand<TypeInferenceWithoutSuccess>()(
85
+ "TypeInferenceWithoutSuccess",
86
+ {
87
+ id: S.String
88
+ },
89
+ {},
90
+ (_queryKey, _resources, input, result) => {
91
+ expectTypeOf(input).toEqualTypeOf<{ readonly id: string }>()
92
+ expectTypeOf(result).toEqualTypeOf<Exit.Exit<void, never>>()
93
+ return []
94
+ }
95
+ ) {}
96
+ void TypeInferenceWithoutSuccess
97
+
98
+ type MixedResources = {
99
+ Something: typeof Something
100
+ Misc: {
101
+ value: number
102
+ GetSomething2: typeof Something.GetSomething2
103
+ }
104
+ }
105
+
106
+ class TypeInferenceResourceFiltering extends SomethingCommand<
107
+ TypeInferenceResourceFiltering,
108
+ MixedResources
109
+ >()("TypeInferenceResourceFiltering", {
110
+ id: S.String
111
+ }, {
112
+ success: S.FiniteFromString
113
+ }, (_queryKey, resources, _input, _result) => {
114
+ expectTypeOf(resources.Something.GetSomething2).toEqualTypeOf<typeof Something.GetSomething2>()
115
+ expectTypeOf(resources.Misc.GetSomething2).toEqualTypeOf<typeof Something.GetSomething2>()
116
+
117
+ // @ts-expect-error commands must be filtered from invalidation resources
118
+ void resources.Something.DoSomething
119
+ // @ts-expect-error non-query values must be filtered from invalidation resources
120
+ void resources.Misc.value
121
+
122
+ return []
123
+ }) {}
124
+ void TypeInferenceResourceFiltering
125
+
126
+ type WithSuccessInvalidation = NonNullable<typeof TypeInferenceWithSuccess.config.invalidatesQueries> // @ts-expect-error input should be required when command payload is non-empty
127
+ ;((_queryKey, _resources) => []) satisfies WithSuccessInvalidation
128
+ })
129
+
130
+ it.skip("query type tests", () => {
131
+ const { clientFor } = useClient()
132
+ const client = clientFor(
133
+ Something,
134
+ () => ({
135
+ GetSomething2WithDependencies: (queryKey) => [
136
+ { filters: { queryKey } },
137
+ {
138
+ filters: {
139
+ queryKey: makeQueryKey(
140
+ SomethingElse
141
+ .GetSomething2
142
+ )
143
+ }
144
+ }
145
+ ]
146
+ }),
147
+ somethingInvalidationResources
148
+ )
149
+
150
+ const q = client.GetSomething2.query
151
+
152
+ const [, z] = q({ id: "a" })
14
153
  const valz = z.value
15
154
  expectTypeOf(valz).toEqualTypeOf<number | undefined>()
16
155
 
17
- const [, a] = n("a", { placeholderData: () => 123 })
156
+ const [, a] = q({ id: "a" }, { placeholderData: () => 123 })
18
157
  const val1 = a.value
19
158
  expectTypeOf(val1).toEqualTypeOf<number>()
20
159
 
21
- const [, bbbb] = n("a", { select: (data) => data.toString() })
160
+ const [, bbbb] = q({ id: "a" }, { select: (data) => data.toString() })
22
161
  const val = bbbb.value
23
162
  expectTypeOf(val).toEqualTypeOf<string | undefined>()
24
163
 
25
- const [, ccc] = n("a", { placeholderData: () => 123, select: (data) => data.toString() })
164
+ const [, ccc] = q({ id: "a" }, { placeholderData: () => 123, select: (data) => data.toString() })
26
165
  const val2 = ccc.value
27
166
  expectTypeOf(val2).toEqualTypeOf<string>()
28
167
 
29
- const [, ddd] = n("a", { initialData: 123, select: (data) => data.toString() })
168
+ const [, ddd] = q({ id: "a" }, { initialData: 123, select: (data) => data.toString() })
30
169
  const val3 = ddd.value
31
170
  expectTypeOf(val3).toEqualTypeOf<string>()
32
171
 
33
- const [, eee] = n("a", { initialData: 123, placeholderData: () => 123, select: (data) => data.toString() })
172
+ const [, eee] = q({ id: "a" }, { initialData: 123, placeholderData: () => 123, select: (data) => data.toString() })
34
173
  const val4 = eee.value
35
174
  expectTypeOf(val4).toEqualTypeOf<string>()
36
175
  })
37
176
 
38
177
  it.skip("works", () => {
39
- const { clientFor, legacy } = useClient()
40
- const client = clientFor(Something)
178
+ const { clientFor } = useClient()
179
+ const client = clientFor(Something, undefined, somethingInvalidationResources)
41
180
  const Command = useExperimental()
42
181
 
43
182
  // just for jsdoc / type testing.
44
- const a0 = client.GetSomething2(null as any)
45
- const a00 = client.GetSomething2.mutate(null as any)
183
+ const a0 = client.GetSomething2.request(null as any)
184
+ const a00 = client.DoSomething.mutate(null as any)
46
185
  const a = client.GetSomething2.suspense(null as any)
47
186
  const b = client.GetSomething2.query(null as any)
48
187
 
49
- const c0 = legacy.useSafeMutation(null as any)
50
- const c = legacy.useQuery(null as any)
51
- const d = legacy.useSuspenseQuery(null as any)
188
+ const de = client.GetSomething3.handler(null as any)
189
+ const de2 = client.GetSomething3.handler({ id: null })
190
+
191
+ // @ts-expect-error not callable as it requires no input
192
+ const de3 = client.GetSomething4.handler(null as any)
193
+ void client.GetSomething4.handler
52
194
 
195
+ // @ts-expect-error query requests no longer expose command helpers
53
196
  const e = client.GetSomething2.wrap(null as any)
197
+ // @ts-expect-error query requests no longer expose command helpers
54
198
  const f = client.GetSomething2.fn(null as any)
55
199
 
56
- // @ts-expect-error dependencies required that are not provided
57
- const e0 = client.GetSomething2WithDependencies.wrap().handle // not available as we require dependencies not provided by the runtime
58
- // @ts-expect-error dependencies required that are not provided
59
- const e000 = Command.wrap(client.GetSomething2WithDependencies)().handle // not available as we require dependencies not provided by the runtime
60
- const e00 = client.GetSomething2WithDependencies.wrap((_) => _ as Effect.Effect<number, never, never>).handle(
61
- null as any
62
- )
63
- const e0000 =
64
- Command.wrap(client.GetSomething2WithDependencies)((_) => _ as Effect.Effect<number, never, never>).handle
200
+ // @ts-expect-error query requests no longer expose command helpers
201
+ const e0 = client.GetSomething2WithDependencies.wrap
202
+ // @ts-expect-error query request does not match Command.wrap mutation signature
203
+ const e000 = Command.wrap(client.GetSomething2WithDependencies)
204
+ const e00 = client.GetSomething2WithDependencies.request(null as any)
65
205
  // @ts-expect-error dependencies required that are not provided
66
206
  const e1 = client.GetSomething2WithDependencies.suspense(null as any)
67
207
  // @ts-expect-error dependencies required that are not provided
68
208
  const e2 = client.GetSomething2WithDependencies.query(null as any)
209
+ // @ts-expect-error query requests no longer expose command helpers
69
210
  const f0 = client.GetSomething2WithDependencies.fn(null as any)
70
211
 
71
- const g = client.GetSomething2.mutate.wrap(null as any)
72
- const h = client.GetSomething2.mutate.fn(null as any)
212
+ const g0 = client.DoSomething.wrap(null as any)
213
+ const g = client.DoSomething.mutate.wrap(null as any)
214
+ const g1 = client.DoSomething.mutate.project(S.String)
215
+ const g2 = g1(null as any)
216
+ const g3 = g1.wrap(null as any)
217
+ const g4 = client.helpers.doSomethingMutation.project(S.String)
218
+ const g5 = g4(null as any)
219
+ const g6 = g4.wrap(null as any)
220
+ // @ts-expect-error mutate no longer exposes fn, use client.DoSomething.fn
221
+ const h = client.DoSomething.mutate.fn(null as any)
222
+
223
+ // projection
224
+ // GetSomething2 uses FiniteFromString, that means Codec is String -> Number
225
+ // when we project that to S.String, it should work as the encoded shapes are identical
226
+ // aka, when we project, we skip decoding with the original codec, and instead use the provided one
227
+ // we have to make sure the Encoded shape of the provided projection schema matches the Encoded Shape of the original codec.
228
+ const projected = client.GetSomething2.project(S.String)
229
+ // @ts-expect-error encoded type mismatch: original encodes to string, S.Number encodes to number
230
+ client.GetSomething2.project(S.Number)
231
+ const p0 = projected.request(null as any)
232
+
233
+ // struct example: success schema encodes to { a: string | null }
234
+ // good: projection schema also expects { a: string | null } on the encoded side
235
+ const projectedStruct = client.GetStructNullable.project(S.Struct({ a: S.NullOr(S.String) }))
236
+ // bad: { a: S.String } has encoded type { a: string } — does not accept null
237
+ // @ts-expect-error encoded type mismatch: original encodes to { a: string | null }, projection expects { a: string }
238
+ client.GetStructNullable.project(S.Struct({ a: S.String }))
239
+
240
+ const p00 = projected.query(null as any)
241
+ const p = projected.suspense(null as any)
73
242
 
74
243
  expect(true).toBe(true)
75
244
  console.log({
76
245
  a,
77
246
  a0,
78
247
  a00,
79
- c0,
80
248
  b,
81
- c,
82
- d,
83
249
  e,
250
+ de,
251
+ de2,
252
+ de3,
84
253
  e0,
85
254
  e00,
86
255
  e000,
87
- e0000,
88
256
  e1,
89
257
  e2,
90
258
  f,
91
259
  f0,
260
+ g0,
92
261
  g,
93
- h
262
+ g1,
263
+ g2,
264
+ g3,
265
+ g4,
266
+ g5,
267
+ g6,
268
+ h,
269
+ p0,
270
+ p00,
271
+ p,
272
+ projectedStruct
94
273
  })
95
274
  })
275
+
276
+ it.skip("stream final type tests", () => {
277
+ const { clientFor } = useClient()
278
+ const client = clientFor(Something, undefined, somethingInvalidationResources)
279
+
280
+ const [_refNoFinal, execNoFinal] = client.StreamWithoutFinal.mutateStream
281
+ const [_refWithFinal, execWithFinal] = client.StreamWithFinal.mutateStream
282
+
283
+ // Without `final`: execute input is {id: string} and resolves with void
284
+ const _execNoFinalResult: ReturnType<typeof execNoFinal> = execNoFinal({ id: "test" })
285
+ // @ts-expect-error result of execNoFinal should be void-typed, not ExportComplete
286
+ const _badAssign: import("effect").Effect.Effect<import("./stubs.js").ExportComplete, never, never> =
287
+ _execNoFinalResult
288
+
289
+ // With `final: ExportComplete`: execute resolves with ExportComplete
290
+ const _execWithFinalResult: ReturnType<typeof execWithFinal> = execWithFinal({ id: "test" })
291
+ // Assignment should compile — result IS Effect<ExportComplete, ...>
292
+ const _goodAssign: import("effect").Effect.Effect<import("./stubs.js").ExportComplete, never, never> =
293
+ _execWithFinalResult
294
+ void _execNoFinalResult
295
+ void _execWithFinalResult
296
+ void _goodAssign
297
+ void _badAssign
298
+ })
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Runtime and type tests for the `final` schema on stream requests.
3
+ *
4
+ * The `final` option on a stream request schema lets callers model which type
5
+ * the last emitted stream element is. When present, the execute effect returned
6
+ * by `mutateStream` resolves with that final value instead of `void`.
7
+ */
8
+ import { it } from "@effect/vitest"
9
+ import { Effect, S } from "effect-app"
10
+ import * as Stream from "effect/Stream"
11
+ import { asStreamResult } from "../src/mutate.js"
12
+ import { ExportComplete, OperationProgress, Something, useClient } from "./stubs.js"
13
+
14
+ const somethingInvalidationResources = {
15
+ Something: {
16
+ GetSomething2: Something.GetSomething2,
17
+ GetSomething2WithDependencies: Something.GetSomething2WithDependencies,
18
+ GetSomething3: Something.GetSomething3,
19
+ GetSomething4: Something.GetSomething4
20
+ }
21
+ }
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // asStreamResult — low-level primitive, always returns void
25
+ // ---------------------------------------------------------------------------
26
+
27
+ it.live("asStreamResult returns void and updates ref with each element", () =>
28
+ Effect.gen(function*() {
29
+ const events: number[] = [1, 2, 3]
30
+ const [ref, execute] = asStreamResult(() => Stream.fromIterable(events))
31
+
32
+ yield* execute()
33
+
34
+ // ref should hold the last emitted value
35
+ expect(ref.value._tag).toBe("Success")
36
+ if (ref.value._tag === "Success") {
37
+ expect(ref.value.value).toBe(3)
38
+ expect(ref.value.waiting).toBe(false)
39
+ }
40
+ }))
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // mutateStream with no `final` — execute resolves with void (type-level)
44
+ // ---------------------------------------------------------------------------
45
+
46
+ it.skip("mutateStream without final: execute resolves void (type-level)", () => {
47
+ const { clientFor } = useClient()
48
+ const client = clientFor(Something, undefined, somethingInvalidationResources)
49
+
50
+ const [_ref, execute] = client.StreamWithoutFinal.mutateStream
51
+
52
+ // execute returns void — assigning to ExportComplete Effect should fail
53
+ const result = execute({ id: "test" })
54
+ // @ts-expect-error result should be void-typed, not ExportComplete
55
+ const _bad: import("effect").Effect.Effect<ExportComplete, never, never> = result
56
+ void _bad
57
+ })
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // mutateStream with `final` — execute resolves with Final type (type-level)
61
+ // ---------------------------------------------------------------------------
62
+
63
+ it.skip("mutateStream with final: execute resolves with ExportComplete (type-level)", () => {
64
+ const { clientFor } = useClient()
65
+ const client = clientFor(Something, undefined, somethingInvalidationResources)
66
+
67
+ const [_ref, execute] = client.StreamWithFinal.mutateStream
68
+
69
+ // execute returns ExportComplete — assignment should compile cleanly
70
+ const result = execute({ id: "test" })
71
+ const _ok: import("effect").Effect.Effect<ExportComplete, never, never> = result
72
+ void _ok
73
+ })
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Request class — final schema stored on class
77
+ // ---------------------------------------------------------------------------
78
+
79
+ it("stream request without final: .final is undefined", () => {
80
+ const req = Something.StreamWithoutFinal
81
+ expect((req as any).final).toBeUndefined()
82
+ })
83
+
84
+ it("stream request with final: .final holds the ExportComplete schema", () => {
85
+ const req = Something.StreamWithFinal
86
+ expect((req as any).final).toBeDefined()
87
+ // Verify the schema decodes correctly
88
+ const decoded = S.decodeUnknownSync((req as any).final)({ _tag: "ExportComplete", fileUrl: "https://x.com" })
89
+ expect(decoded).toBeInstanceOf(ExportComplete)
90
+ })
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Runtime: last stream value is accessible via the reactive ref after stream ends
94
+ // ---------------------------------------------------------------------------
95
+
96
+ it.live("last stream value is accessible via reactive ref after stream ends", () =>
97
+ Effect.gen(function*() {
98
+ const progress = new OperationProgress({ completed: 1 as S.NonNegativeInt, total: 2 as S.NonNegativeInt })
99
+ const complete = new ExportComplete({ fileUrl: "https://example.com/file.csv" as S.NonEmptyString })
100
+
101
+ const [ref, execute] = asStreamResult(() => Stream.make(progress, complete))
102
+
103
+ yield* execute()
104
+
105
+ expect(ref.value._tag).toBe("Success")
106
+ if (ref.value._tag === "Success") {
107
+ expect(ref.value.value).toBe(complete)
108
+ expect(ref.value.waiting).toBe(false)
109
+ }
110
+ }))