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

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