@effect-app/vue 4.0.0-beta.98 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/CHANGELOG.md +2707 -0
  2. package/dist/atomQuery.d.ts +120 -0
  3. package/dist/atomQuery.d.ts.map +1 -0
  4. package/dist/atomQuery.js +434 -0
  5. package/dist/commander.d.ts +318 -43
  6. package/dist/commander.d.ts.map +1 -1
  7. package/dist/commander.js +593 -81
  8. package/dist/confirm.d.ts +5 -3
  9. package/dist/confirm.d.ts.map +1 -1
  10. package/dist/confirm.js +12 -14
  11. package/dist/dependencyMetadata.d.ts +18 -0
  12. package/dist/dependencyMetadata.d.ts.map +1 -0
  13. package/dist/dependencyMetadata.js +67 -0
  14. package/dist/errorReporter.d.ts +6 -4
  15. package/dist/errorReporter.d.ts.map +1 -1
  16. package/dist/errorReporter.js +14 -19
  17. package/dist/form.d.ts +5 -5
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/form.js +16 -10
  20. package/dist/internal/tanstackQuery.d.ts +9 -0
  21. package/dist/internal/tanstackQuery.d.ts.map +1 -0
  22. package/dist/internal/tanstackQuery.js +204 -0
  23. package/dist/intl.d.ts +4 -4
  24. package/dist/intl.d.ts.map +1 -1
  25. package/dist/intl.js +2 -2
  26. package/dist/lib.d.ts +8 -10
  27. package/dist/lib.d.ts.map +1 -1
  28. package/dist/lib.js +37 -12
  29. package/dist/liveQueryInvalidation.d.ts +16 -0
  30. package/dist/liveQueryInvalidation.d.ts.map +1 -0
  31. package/dist/liveQueryInvalidation.js +115 -0
  32. package/dist/makeClient.d.ts +232 -109
  33. package/dist/makeClient.d.ts.map +1 -1
  34. package/dist/makeClient.js +416 -84
  35. package/dist/makeContext.d.ts.map +1 -1
  36. package/dist/makeIntl.d.ts.map +1 -1
  37. package/dist/makeUseCommand.d.ts +3 -2
  38. package/dist/makeUseCommand.d.ts.map +1 -1
  39. package/dist/makeUseCommand.js +2 -2
  40. package/dist/mutate.d.ts +110 -41
  41. package/dist/mutate.d.ts.map +1 -1
  42. package/dist/mutate.js +236 -59
  43. package/dist/query.d.ts +161 -44
  44. package/dist/query.d.ts.map +1 -1
  45. package/dist/query.js +382 -113
  46. package/dist/queryLifetime.d.ts +25 -0
  47. package/dist/queryLifetime.d.ts.map +1 -0
  48. package/dist/queryLifetime.js +54 -0
  49. package/dist/routeParams.d.ts +4 -4
  50. package/dist/routeParams.d.ts.map +1 -1
  51. package/dist/routeParams.js +4 -3
  52. package/dist/runtime.d.ts +1 -17
  53. package/dist/runtime.d.ts.map +1 -1
  54. package/dist/runtime.js +2 -38
  55. package/dist/suspense.d.ts +19 -0
  56. package/dist/suspense.d.ts.map +1 -0
  57. package/dist/suspense.js +38 -0
  58. package/dist/toast.d.ts +1 -45
  59. package/dist/toast.d.ts.map +1 -1
  60. package/dist/toast.js +2 -32
  61. package/dist/withToast.d.ts +1 -24
  62. package/dist/withToast.d.ts.map +1 -1
  63. package/dist/withToast.js +2 -45
  64. package/docs/atom-query-api-redesign.md +207 -0
  65. package/docs/mutation-command-atoms.md +40 -0
  66. package/docs/query-key-invalidation.md +107 -0
  67. package/examples/streamMutation.ts +72 -0
  68. package/package.json +35 -94
  69. package/src/atomQuery.ts +600 -0
  70. package/src/commander.ts +1064 -116
  71. package/src/confirm.ts +12 -14
  72. package/src/dependencyMetadata.ts +84 -0
  73. package/src/errorReporter.ts +65 -75
  74. package/src/form.ts +22 -15
  75. package/src/index.ts +7 -7
  76. package/src/internal/tanstackQuery.ts +308 -0
  77. package/src/intl.ts +2 -2
  78. package/src/lib.ts +51 -23
  79. package/src/liveQueryInvalidation.ts +138 -0
  80. package/src/makeClient.ts +972 -263
  81. package/src/makeIntl.ts +2 -2
  82. package/src/makeUseCommand.ts +8 -5
  83. package/src/mutate.ts +407 -154
  84. package/src/query.ts +758 -242
  85. package/src/queryLifetime.ts +74 -0
  86. package/src/routeParams.ts +7 -7
  87. package/src/runtime.ts +1 -54
  88. package/src/suspense.ts +43 -0
  89. package/src/toast.ts +1 -52
  90. package/src/withToast.ts +1 -99
  91. package/test/Mutation.test.ts +104 -17
  92. package/test/dependencyInvalidation.test.ts +444 -0
  93. package/test/dist/dependencyInvalidation.test.d.ts.map +1 -0
  94. package/test/dist/form.test.d.ts.map +1 -1
  95. package/test/dist/inactiveInvalidation.test.d.ts.map +1 -0
  96. package/test/dist/interrupt-refetch-repro.test.d.ts.map +1 -0
  97. package/test/dist/lib.test.d.ts.map +1 -0
  98. package/test/dist/liveQueryInvalidation.test.d.ts.map +1 -0
  99. package/test/dist/query-span.test.d.ts.map +1 -0
  100. package/test/dist/queryOptions.test.d.ts.map +1 -0
  101. package/test/dist/streamFinal.test.d.ts.map +1 -0
  102. package/test/dist/streamFn.test.d.ts.map +1 -0
  103. package/test/dist/stubs.d.ts +3681 -256
  104. package/test/dist/stubs.d.ts.map +1 -1
  105. package/test/dist/stubs.js +166 -25
  106. package/test/dist/suspense-regression.test.d.ts.map +1 -0
  107. package/test/dist/suspense.test.d.ts.map +1 -0
  108. package/test/form-validation-errors.test.ts +2 -1
  109. package/test/form.test.ts +2 -1
  110. package/test/inactiveInvalidation.test.ts +172 -0
  111. package/test/interrupt-refetch-repro.test.ts +625 -0
  112. package/test/lib.test.ts +240 -0
  113. package/test/liveQueryInvalidation.test.ts +108 -0
  114. package/test/makeClient.test.ts +414 -32
  115. package/test/query-span.test.ts +92 -0
  116. package/test/queryOptions.test.ts +88 -0
  117. package/test/streamFinal.test.ts +64 -0
  118. package/test/streamFn.test.ts +457 -0
  119. package/test/stubs.ts +194 -36
  120. package/test/suspense-regression.test.ts +168 -0
  121. package/test/suspense.test.ts +160 -0
  122. package/tsconfig.examples.json +20 -0
  123. package/tsconfig.json +9 -1
  124. package/tsconfig.src.json +34 -34
  125. package/tsconfig.test.json +2 -2
  126. package/vitest.config.ts +5 -5
  127. package/eslint.config.mjs +0 -24
  128. package/tsconfig.json.bak +0 -12
@@ -0,0 +1,240 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { computed, isProxy, isReactive, isRef, reactive, ref } from "vue"
3
+ import { deepToRaw } from "../src/lib.js"
4
+
5
+ type DeepMapKey = { id: string } | "list"
6
+ type DeepMapValue = { nestedSet: Set<{ ok: boolean } | Date> } | Array<{ count: number }>
7
+ type DeepSetValue = Map<string, { value: number }> | Array<{ value: number }>
8
+
9
+ const expectPlainDeep = (value: unknown): void => {
10
+ expect(isRef(value)).toBe(false)
11
+ expect(isReactive(value)).toBe(false)
12
+ expect(isProxy(value)).toBe(false)
13
+
14
+ if (Array.isArray(value)) {
15
+ value.forEach(expectPlainDeep)
16
+ return
17
+ }
18
+
19
+ if (value instanceof Map) {
20
+ value.forEach((entryValue, entryKey) => {
21
+ expectPlainDeep(entryKey)
22
+ expectPlainDeep(entryValue)
23
+ })
24
+ return
25
+ }
26
+
27
+ if (value instanceof Set) {
28
+ value.forEach((entry) => {
29
+ expectPlainDeep(entry)
30
+ })
31
+ return
32
+ }
33
+
34
+ if (value instanceof Date) {
35
+ return
36
+ }
37
+
38
+ if (value && typeof value === "object") {
39
+ Object.values(value).forEach(expectPlainDeep)
40
+ }
41
+ }
42
+
43
+ describe("deepToRaw", () => {
44
+ it("supports non-object root inputs", () => {
45
+ expect(deepToRaw(1)).toBe(1)
46
+ expect(deepToRaw("x")).toBe("x")
47
+ expect(deepToRaw(null)).toBe(null)
48
+ expect(deepToRaw(undefined)).toBe(undefined)
49
+ expect(deepToRaw(ref(123))).toBe(123)
50
+
51
+ const rootArray = deepToRaw(reactive([reactive({ n: 1 }), ref(2)]))
52
+ expect(rootArray).toEqual([{ n: 1 }, 2])
53
+ expect(Array.isArray(rootArray)).toBe(true)
54
+
55
+ const rootMap = deepToRaw(
56
+ reactive(new Map<string, unknown>([["k", reactive({ n: 1 })], ["r", ref(2)]]))
57
+ )
58
+ expect(rootMap).toBeInstanceOf(Map)
59
+ expect(rootMap.get("k")).toEqual({ n: 1 })
60
+ expect(rootMap.get("r")).toBe(2)
61
+
62
+ const rootSet = deepToRaw(reactive(new Set([reactive({ n: 1 }), ref(2)])))
63
+ expect(rootSet).toBeInstanceOf(Set)
64
+ expect(Array.from(rootSet)).toEqual([{ n: 1 }, 2])
65
+
66
+ const date = new Date("2024-02-03T00:00:00.000Z")
67
+ const rootDate = deepToRaw(date)
68
+ expect(rootDate).toBeInstanceOf(Date)
69
+ expect(rootDate).not.toBe(date)
70
+ expect(rootDate.toISOString()).toBe(date.toISOString())
71
+ })
72
+
73
+ it("unwraps nested objects and arrays without leaving vue proxies behind", () => {
74
+ const source = reactive({
75
+ list: [
76
+ reactive({
77
+ nested: reactive({
78
+ count: 1,
79
+ items: [reactive({ label: "a" }), reactive({ label: "b" })]
80
+ })
81
+ })
82
+ ],
83
+ plain: reactive({ ok: true })
84
+ })
85
+
86
+ const result = deepToRaw(source)
87
+
88
+ expect(Array.isArray(result.list)).toBe(true)
89
+ expect(Array.isArray(result.list[0]?.nested.items)).toBe(true)
90
+ expect(result).toEqual({
91
+ list: [{ nested: { count: 1, items: [{ label: "a" }, { label: "b" }] } }],
92
+ plain: { ok: true }
93
+ })
94
+ expectPlainDeep(result)
95
+ })
96
+
97
+ it("preserves maps and sets while deeply unwrapping nested entries", () => {
98
+ const key = reactive({ id: "key" })
99
+ const nestedDate = new Date("2024-01-02T03:04:05.000Z")
100
+ const map = reactive(
101
+ new Map<DeepMapKey, DeepMapValue>([
102
+ [key, reactive({ nestedSet: reactive(new Set([{ ok: true }, nestedDate])) })],
103
+ ["list", reactive([{ count: 2 }])]
104
+ ])
105
+ )
106
+ const set = reactive(
107
+ new Set<DeepSetValue>([
108
+ reactive(new Map([["deep", reactive({ value: 3 })]])),
109
+ reactive([{ value: 4 }])
110
+ ])
111
+ )
112
+ const source = reactive({
113
+ map,
114
+ set
115
+ })
116
+
117
+ const result = deepToRaw(source)
118
+
119
+ expect(result.map).toBeInstanceOf(Map)
120
+ expect(result.set).toBeInstanceOf(Set)
121
+
122
+ const entries = Array.from(result.map.entries())
123
+ expect(entries[0]?.[0]).toEqual({ id: "key" })
124
+ expect(entries[0]?.[0]).not.toBe(key)
125
+ expect(entries[0]?.[1]).toEqual({ nestedSet: new Set([{ ok: true }, nestedDate]) })
126
+ expect(entries[1]?.[1]).toEqual([{ count: 2 }])
127
+
128
+ const setValues = Array.from(result.set.values())
129
+ expect(setValues[0]).toBeInstanceOf(Map)
130
+ expect(setValues[1]).toEqual([{ value: 4 }])
131
+ expect((setValues[0] as Map<string, { value: number }>).get("deep")).toEqual({ value: 3 })
132
+
133
+ expectPlainDeep(result)
134
+ })
135
+
136
+ it("keeps nested dates as dates, including dates reached through refs", () => {
137
+ const date = new Date("2025-06-07T08:09:10.000Z")
138
+ const source = reactive({
139
+ createdAt: date,
140
+ nested: reactive({
141
+ updatedAt: ref(date),
142
+ list: [ref(date)],
143
+ map: reactive(new Map([["at", ref(date)]])),
144
+ set: reactive(new Set([ref(date)]))
145
+ })
146
+ })
147
+
148
+ const result = deepToRaw(source)
149
+
150
+ expect(result.createdAt).toBeInstanceOf(Date)
151
+ expect(result.nested.updatedAt).toBeInstanceOf(Date)
152
+ expect(result.nested.list[0]).toBeInstanceOf(Date)
153
+ expect(result.nested.map).toBeInstanceOf(Map)
154
+ expect(result.nested.set).toBeInstanceOf(Set)
155
+
156
+ const updatedAt = result.nested.updatedAt
157
+ const firstListDate = result.nested.list[0]
158
+ const mappedDate = result.nested.map.get("at")
159
+ const firstSetDate = Array.from(result.nested.set)[0]
160
+
161
+ if (!(updatedAt instanceof Date)) {
162
+ throw new Error("expected updatedAt to be a Date")
163
+ }
164
+
165
+ if (!(firstListDate instanceof Date)) {
166
+ throw new Error("expected first list item to be a Date")
167
+ }
168
+
169
+ if (!(mappedDate instanceof Date)) {
170
+ throw new Error("expected mapped date to be a Date")
171
+ }
172
+
173
+ if (!(firstSetDate instanceof Date)) {
174
+ throw new Error("expected first set item to be a Date")
175
+ }
176
+
177
+ expect(result.createdAt.toISOString()).toBe(date.toISOString())
178
+ expect(updatedAt.toISOString()).toBe(date.toISOString())
179
+ expect(firstListDate.toISOString()).toBe(date.toISOString())
180
+ expect(mappedDate.toISOString()).toBe(date.toISOString())
181
+ expect(firstSetDate.toISOString()).toBe(date.toISOString())
182
+
183
+ expectPlainDeep(result)
184
+ })
185
+
186
+ it("unwraps computed values nested in refs/plain objects and deepToRawes the computed result", () => {
187
+ const source = {
188
+ innerRef: ref({
189
+ computedValue: computed(() =>
190
+ reactive({
191
+ list: [reactive({ n: 1 }), reactive({ n: 2 })],
192
+ map: reactive(new Map([["k", reactive({ nested: true })]])),
193
+ set: reactive(new Set([reactive({ fromSet: true })]))
194
+ })
195
+ )
196
+ }),
197
+ plainComputed: computed(() => reactive({ date: ref(new Date("2025-01-01T00:00:00.000Z")) }))
198
+ }
199
+
200
+ const result = deepToRaw(source)
201
+
202
+ expect(result).toEqual({
203
+ innerRef: {
204
+ computedValue: {
205
+ list: [{ n: 1 }, { n: 2 }],
206
+ map: new Map([["k", { nested: true }]]),
207
+ set: new Set([{ fromSet: true }])
208
+ }
209
+ },
210
+ plainComputed: {
211
+ date: new Date("2025-01-01T00:00:00.000Z")
212
+ }
213
+ })
214
+
215
+ const innerRefValue = Reflect.get(result, "innerRef")
216
+ if (!innerRefValue || typeof innerRefValue !== "object") {
217
+ throw new Error("expected innerRef to be an object")
218
+ }
219
+
220
+ const computedValue = Reflect.get(innerRefValue, "computedValue")
221
+ if (!computedValue || typeof computedValue !== "object") {
222
+ throw new Error("expected computedValue to be an object")
223
+ }
224
+
225
+ const computedMap = Reflect.get(computedValue, "map")
226
+ const computedSet = Reflect.get(computedValue, "set")
227
+
228
+ const plainComputedValue = Reflect.get(result, "plainComputed")
229
+ if (!plainComputedValue || typeof plainComputedValue !== "object") {
230
+ throw new Error("expected plainComputed to be an object")
231
+ }
232
+
233
+ const computedDate = Reflect.get(plainComputedValue, "date")
234
+
235
+ expect(computedMap).toBeInstanceOf(Map)
236
+ expect(computedSet).toBeInstanceOf(Set)
237
+ expect(computedDate).toBeInstanceOf(Date)
238
+ expectPlainDeep(result)
239
+ })
240
+ })
@@ -0,0 +1,108 @@
1
+ import { DataDependencies } from "effect-app/client"
2
+ import { afterEach, describe, expect, it, vi } from "vitest"
3
+ import { beginLiveQueryFetch, configureLiveQueryInvalidation, endLiveQueryFetch, registerLiveQuery } from "../src/liveQueryInvalidation.js"
4
+
5
+ const repo = DataDependencies.repo("Inventory", ["item-1"])
6
+ const otherId = DataDependencies.repo("Inventory", ["item-2"])
7
+
8
+ describe("live query invalidation", () => {
9
+ const cleanups: Array<() => void> = []
10
+
11
+ afterEach(() => {
12
+ while (cleanups.length > 0) cleanups.pop()?.()
13
+ vi.useRealTimers()
14
+ })
15
+
16
+ it("invalidates only live queries whose recorded reads intersect", () => {
17
+ const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
18
+ let receive = (_writes: DataDependencies.DataDependencies) => {}
19
+ configureLiveQueryInvalidation({
20
+ ready: () => Promise.resolve(),
21
+ subscribe: (onWrites) => {
22
+ receive = onWrites
23
+ return () => {}
24
+ },
25
+ invalidate: (keys) => invalidations.push(keys)
26
+ })
27
+
28
+ const key = ["$Inventory", "List", undefined]
29
+ cleanups.push(registerLiveQuery(key, () => new Set([repo]), {}))
30
+ receive(new Set([otherId]))
31
+ expect(invalidations).toEqual([])
32
+
33
+ receive(new Set([repo]))
34
+ expect(invalidations).toEqual([[key]])
35
+ })
36
+
37
+ it("replays writes received while the initial query discovers its dependencies", async () => {
38
+ const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
39
+ let receive = (_writes: DataDependencies.DataDependencies) => {}
40
+ let open = () => {}
41
+ const ready = new Promise<void>((resolve) => open = resolve)
42
+ configureLiveQueryInvalidation({
43
+ ready: () => ready,
44
+ subscribe: (onWrites) => {
45
+ receive = onWrites
46
+ return () => {}
47
+ },
48
+ invalidate: (keys) => invalidations.push(keys)
49
+ })
50
+
51
+ const key = ["$Inventory", "Get", { id: "item-1" }]
52
+ let reads = DataDependencies.empty()
53
+ cleanups.push(registerLiveQuery(key, () => reads, {}))
54
+
55
+ const starting = beginLiveQueryFetch(key)
56
+ receive(new Set([repo]))
57
+ open()
58
+ const wasLive = await starting
59
+ reads = new Set([repo])
60
+ endLiveQueryFetch(wasLive)
61
+
62
+ expect(invalidations).toEqual([[key]])
63
+ })
64
+
65
+ it("coalesces matching writes using the query's maximum delay", () => {
66
+ vi.useFakeTimers()
67
+ const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
68
+ let receive = (_writes: DataDependencies.DataDependencies) => {}
69
+ configureLiveQueryInvalidation({
70
+ ready: () => Promise.resolve(),
71
+ subscribe: (onWrites) => {
72
+ receive = onWrites
73
+ return () => {}
74
+ },
75
+ invalidate: (keys) => invalidations.push(keys)
76
+ })
77
+
78
+ const key = ["$Inventory", "List", undefined]
79
+ cleanups.push(registerLiveQuery(key, () => new Set([repo]), { maxDelayMs: 2_000 }))
80
+ receive(new Set([repo]))
81
+ receive(new Set([repo]))
82
+ vi.advanceTimersByTime(1_999)
83
+ expect(invalidations).toEqual([])
84
+ vi.advanceTimersByTime(1)
85
+ expect(invalidations).toEqual([[key]])
86
+ })
87
+
88
+ it("fails safe by invalidating every live query when the source resets", () => {
89
+ const invalidations: Array<ReadonlyArray<ReadonlyArray<unknown>>> = []
90
+ let reset = () => {}
91
+ configureLiveQueryInvalidation({
92
+ ready: () => Promise.resolve(),
93
+ subscribe: (_onWrites, onReset) => {
94
+ reset = onReset
95
+ return () => {}
96
+ },
97
+ invalidate: (keys) => invalidations.push(keys)
98
+ })
99
+
100
+ const first = ["$Inventory", "List", undefined]
101
+ const second = ["$Orders", "List", undefined]
102
+ cleanups.push(registerLiveQuery(first, () => new Set([repo]), {}))
103
+ cleanups.push(registerLiveQuery(second, () => new Set([otherId]), {}))
104
+ reset()
105
+
106
+ expect(invalidations).toEqual([[first], [second]])
107
+ })
108
+ })