@effect-app/vue 4.0.0-beta.33 → 4.0.0-beta.331
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 +2463 -0
- package/dist/atomQuery.d.ts +120 -0
- package/dist/atomQuery.d.ts.map +1 -0
- package/dist/atomQuery.js +434 -0
- package/dist/commander.d.ts +634 -0
- package/dist/commander.d.ts.map +1 -0
- package/dist/commander.js +1068 -0
- package/dist/{experimental/confirm.d.ts → confirm.d.ts} +6 -4
- package/dist/confirm.d.ts.map +1 -0
- package/dist/confirm.js +26 -0
- package/dist/dependencyMetadata.d.ts +18 -0
- package/dist/dependencyMetadata.d.ts.map +1 -0
- package/dist/dependencyMetadata.js +67 -0
- package/dist/errorReporter.d.ts +6 -4
- package/dist/errorReporter.d.ts.map +1 -1
- package/dist/errorReporter.js +14 -19
- package/dist/form.d.ts +5 -5
- package/dist/form.d.ts.map +1 -1
- package/dist/form.js +42 -16
- package/dist/internal/tanstackQuery.d.ts +9 -0
- package/dist/internal/tanstackQuery.d.ts.map +1 -0
- package/dist/internal/tanstackQuery.js +204 -0
- package/dist/intl.d.ts +15 -0
- package/dist/intl.d.ts.map +1 -0
- package/dist/intl.js +9 -0
- package/dist/lib.d.ts +7 -9
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +35 -10
- package/dist/liveQueryInvalidation.d.ts +16 -0
- package/dist/liveQueryInvalidation.d.ts.map +1 -0
- package/dist/liveQueryInvalidation.js +115 -0
- package/dist/makeClient.d.ts +236 -336
- package/dist/makeClient.d.ts.map +1 -1
- package/dist/makeClient.js +417 -408
- package/dist/makeContext.d.ts.map +1 -1
- package/dist/makeIntl.d.ts.map +1 -1
- package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +4 -3
- package/dist/makeUseCommand.d.ts.map +1 -0
- package/dist/makeUseCommand.js +13 -0
- package/dist/mutate.d.ts +109 -40
- package/dist/mutate.d.ts.map +1 -1
- package/dist/mutate.js +235 -58
- package/dist/query.d.ts +158 -45
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +380 -122
- package/dist/queryLifetime.d.ts +25 -0
- package/dist/queryLifetime.d.ts.map +1 -0
- package/dist/queryLifetime.js +54 -0
- package/dist/routeParams.d.ts +4 -4
- package/dist/routeParams.d.ts.map +1 -1
- package/dist/routeParams.js +4 -3
- package/dist/runtime.d.ts +1 -14
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +2 -26
- package/dist/suspense.d.ts +19 -0
- package/dist/suspense.d.ts.map +1 -0
- package/dist/suspense.js +38 -0
- package/dist/toast.d.ts +2 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/toast.js +2 -0
- package/dist/withToast.d.ts +2 -0
- package/dist/withToast.d.ts.map +1 -0
- package/dist/withToast.js +2 -0
- package/docs/atom-query-api-redesign.md +207 -0
- package/docs/mutation-command-atoms.md +40 -0
- package/docs/query-key-invalidation.md +107 -0
- package/examples/streamMutation.ts +72 -0
- package/package.json +36 -95
- package/src/atomQuery.ts +600 -0
- package/src/commander.ts +3408 -0
- package/src/{experimental/confirm.ts → confirm.ts} +13 -15
- package/src/dependencyMetadata.ts +84 -0
- package/src/errorReporter.ts +65 -75
- package/src/form.ts +57 -21
- package/src/index.ts +7 -7
- package/src/internal/tanstackQuery.ts +308 -0
- package/src/intl.ts +12 -0
- package/src/lib.ts +49 -21
- package/src/liveQueryInvalidation.ts +138 -0
- package/src/makeClient.ts +971 -1159
- package/src/makeIntl.ts +2 -2
- package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +9 -6
- package/src/mutate.ts +406 -153
- package/src/query.ts +763 -260
- package/src/queryLifetime.ts +74 -0
- package/src/routeParams.ts +7 -7
- package/src/runtime.ts +1 -31
- package/src/suspense.ts +43 -0
- package/src/toast.ts +1 -0
- package/src/withToast.ts +1 -0
- package/test/Mutation.test.ts +181 -24
- package/test/dependencyInvalidation.test.ts +444 -0
- package/test/dist/dependencyInvalidation.test.d.ts.map +1 -0
- package/test/dist/form.test.d.ts.map +1 -1
- package/test/dist/inactiveInvalidation.test.d.ts.map +1 -0
- package/test/dist/interrupt-refetch-repro.test.d.ts.map +1 -0
- package/test/dist/lib.test.d.ts.map +1 -0
- package/test/dist/liveQueryInvalidation.test.d.ts.map +1 -0
- package/test/dist/query-span.test.d.ts.map +1 -0
- package/test/dist/queryOptions.test.d.ts.map +1 -0
- package/test/dist/streamFinal.test.d.ts.map +1 -0
- package/test/dist/streamFn.test.d.ts.map +1 -0
- package/test/dist/stubs.d.ts +3751 -120
- package/test/dist/stubs.d.ts.map +1 -1
- package/test/dist/stubs.js +190 -30
- package/test/dist/suspense-regression.test.d.ts.map +1 -0
- package/test/dist/suspense.test.d.ts.map +1 -0
- package/test/form-validation-errors.test.ts +24 -19
- package/test/form.test.ts +25 -6
- package/test/inactiveInvalidation.test.ts +172 -0
- package/test/interrupt-refetch-repro.test.ts +625 -0
- package/test/lib.test.ts +240 -0
- package/test/liveQueryInvalidation.test.ts +108 -0
- package/test/makeClient.test.ts +437 -38
- package/test/query-span.test.ts +92 -0
- package/test/queryOptions.test.ts +88 -0
- package/test/streamFinal.test.ts +64 -0
- package/test/streamFn.test.ts +457 -0
- package/test/stubs.ts +227 -42
- package/test/suspense-regression.test.ts +168 -0
- package/test/suspense.test.ts +160 -0
- package/tsconfig.examples.json +20 -0
- package/tsconfig.json +9 -2
- package/tsconfig.src.json +34 -34
- package/tsconfig.test.json +2 -2
- package/vitest.config.ts +5 -5
- package/dist/experimental/commander.d.ts +0 -359
- package/dist/experimental/commander.d.ts.map +0 -1
- package/dist/experimental/commander.js +0 -557
- package/dist/experimental/confirm.d.ts.map +0 -1
- package/dist/experimental/confirm.js +0 -28
- package/dist/experimental/intl.d.ts +0 -16
- package/dist/experimental/intl.d.ts.map +0 -1
- package/dist/experimental/intl.js +0 -5
- package/dist/experimental/makeUseCommand.d.ts.map +0 -1
- package/dist/experimental/makeUseCommand.js +0 -13
- package/dist/experimental/toast.d.ts +0 -47
- package/dist/experimental/toast.d.ts.map +0 -1
- package/dist/experimental/toast.js +0 -41
- package/dist/experimental/withToast.d.ts +0 -25
- package/dist/experimental/withToast.d.ts.map +0 -1
- package/dist/experimental/withToast.js +0 -45
- package/eslint.config.mjs +0 -24
- package/src/experimental/commander.ts +0 -1835
- package/src/experimental/intl.ts +0 -9
- package/src/experimental/toast.ts +0 -66
- package/src/experimental/withToast.ts +0 -99
- package/tsconfig.json.bak +0 -12
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as Context from "effect-app/Context"
|
|
2
|
+
import * as Effect from "effect-app/Effect"
|
|
3
|
+
import * as Layer from "effect-app/Layer"
|
|
4
|
+
import { I18n } from "./intl.ts"
|
|
3
5
|
|
|
4
6
|
// @effect-diagnostics-next-line missingEffectServiceDependency:off
|
|
5
|
-
export class Confirm extends
|
|
7
|
+
export class Confirm extends Context.Service<Confirm>()("Confirm", {
|
|
6
8
|
make: Effect.gen(function*() {
|
|
7
9
|
const { intl } = yield* I18n
|
|
8
10
|
|
|
@@ -21,16 +23,12 @@ export class Confirm extends ServiceMap.Service<Confirm>()("Confirm", {
|
|
|
21
23
|
static readonly DefaultWithoutDependencies = Layer.effect(this, this.make)
|
|
22
24
|
static readonly Default = this.DefaultWithoutDependencies
|
|
23
25
|
|
|
24
|
-
static confirm(message?: string) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const c = yield* Confirm
|
|
33
|
-
return yield* c.confirmOrInterrupt(message)
|
|
34
|
-
})
|
|
35
|
-
}
|
|
26
|
+
static readonly confirm = Effect.fnUntraced(function*(message?: string) {
|
|
27
|
+
const c = yield* Confirm
|
|
28
|
+
return yield* c.confirm(message)
|
|
29
|
+
})
|
|
30
|
+
static readonly confirmOrInterrupt = Effect.fnUntraced(function*(message?: string) {
|
|
31
|
+
const c = yield* Confirm
|
|
32
|
+
return yield* c.confirmOrInterrupt(message)
|
|
33
|
+
})
|
|
36
34
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { DataDependencies } from "effect-app/client"
|
|
2
|
+
import * as Hash from "effect/Hash"
|
|
3
|
+
|
|
4
|
+
// Maps a live query's reactivity key (its full `[...queryKey, input]` key) to the
|
|
5
|
+
// data dependencies it read while fetching. Populated when the query handler resolves and
|
|
6
|
+
// cleared when the query atom is GC'd, so the set mirrors the live query atoms (mounted or
|
|
7
|
+
// cached-within-ttl) — the atom equivalent of the former tanstack query cache.
|
|
8
|
+
type Entry = { readonly key: ReadonlyArray<unknown>; readonly reads: DataDependencies.DataDependencies }
|
|
9
|
+
const readDependencies = new Map<number, Entry>()
|
|
10
|
+
export type QueryInvalidationMode = "await" | "soft"
|
|
11
|
+
type InvalidationModeEntry = { awaitSubscribers: number; softSubscribers: number }
|
|
12
|
+
const invalidationModes = new Map<number, InvalidationModeEntry>()
|
|
13
|
+
|
|
14
|
+
export const registerQueryInvalidationMode = (
|
|
15
|
+
key: ReadonlyArray<unknown>,
|
|
16
|
+
mode: QueryInvalidationMode
|
|
17
|
+
): () => void => {
|
|
18
|
+
const hash = Hash.hash(key)
|
|
19
|
+
const entry = invalidationModes.get(hash) ?? { awaitSubscribers: 0, softSubscribers: 0 }
|
|
20
|
+
if (mode === "soft") entry.softSubscribers++
|
|
21
|
+
else entry.awaitSubscribers++
|
|
22
|
+
invalidationModes.set(hash, entry)
|
|
23
|
+
let active = true
|
|
24
|
+
return () => {
|
|
25
|
+
if (!active) return
|
|
26
|
+
active = false
|
|
27
|
+
if (mode === "soft") entry.softSubscribers--
|
|
28
|
+
else entry.awaitSubscribers--
|
|
29
|
+
if (entry.awaitSubscribers === 0 && entry.softSubscribers === 0) invalidationModes.delete(hash)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const getQueryInvalidationMode = (key: ReadonlyArray<unknown>): QueryInvalidationMode => {
|
|
34
|
+
const entry = invalidationModes.get(Hash.hash(key))
|
|
35
|
+
return entry !== undefined && entry.awaitSubscribers === 0 && entry.softSubscribers > 0 ? "soft" : "await"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const setQueryReadDependencies = (
|
|
39
|
+
key: ReadonlyArray<unknown>,
|
|
40
|
+
reads: DataDependencies.DataDependencies
|
|
41
|
+
) => {
|
|
42
|
+
const h = Hash.hash(key)
|
|
43
|
+
if (!DataDependencies.isNonEmpty(reads)) readDependencies.delete(h)
|
|
44
|
+
else readDependencies.set(h, { key, reads })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const clearQueryReadDependencies = (key: ReadonlyArray<unknown>) => {
|
|
48
|
+
readDependencies.delete(Hash.hash(key))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const getQueryReadDependencies = (
|
|
52
|
+
key: ReadonlyArray<unknown>
|
|
53
|
+
): DataDependencies.DataDependencies => readDependencies.get(Hash.hash(key))?.reads ?? DataDependencies.empty()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Reactivity keys of every live query whose recorded read-dependencies intersect this
|
|
57
|
+
* mutation's `writeDependencies`. Returned keys are passed to `invalidateAndAwait`, refreshing
|
|
58
|
+
* exactly those queries.
|
|
59
|
+
*/
|
|
60
|
+
export const getDerivedInvalidationKeys = (
|
|
61
|
+
writeDependencies: DataDependencies.DataDependencies
|
|
62
|
+
): ReadonlyArray<ReadonlyArray<unknown>> => {
|
|
63
|
+
if (!DataDependencies.isNonEmpty(writeDependencies)) return []
|
|
64
|
+
const keys: Array<ReadonlyArray<unknown>> = []
|
|
65
|
+
for (const { key, reads } of readDependencies.values()) {
|
|
66
|
+
if (DataDependencies.intersects(reads, writeDependencies)) keys.push(key)
|
|
67
|
+
}
|
|
68
|
+
return keys
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const partitionInvalidationKeys = (
|
|
72
|
+
keys: ReadonlyArray<ReadonlyArray<unknown>>
|
|
73
|
+
): {
|
|
74
|
+
readonly awaitKeys: ReadonlyArray<ReadonlyArray<unknown>>
|
|
75
|
+
readonly softKeys: ReadonlyArray<ReadonlyArray<unknown>>
|
|
76
|
+
} => {
|
|
77
|
+
const awaitKeys: Array<ReadonlyArray<unknown>> = []
|
|
78
|
+
const softKeys: Array<ReadonlyArray<unknown>> = []
|
|
79
|
+
for (const key of keys) {
|
|
80
|
+
if (getQueryInvalidationMode(key) === "soft") softKeys.push(key)
|
|
81
|
+
else awaitKeys.push(key)
|
|
82
|
+
}
|
|
83
|
+
return { awaitKeys, softKeys }
|
|
84
|
+
}
|
package/src/errorReporter.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
3
|
import * as Sentry from "@sentry/browser"
|
|
4
|
-
import { Cause, Effect, type LogLevel } from "effect-app"
|
|
5
4
|
import { CauseException, tryToJson, tryToReport } from "effect-app/client/errors"
|
|
5
|
+
import * as Effect from "effect-app/Effect"
|
|
6
6
|
import { dropUndefined, LogLevelToSentry } from "effect-app/utils"
|
|
7
|
+
import * as Cause from "effect/Cause"
|
|
8
|
+
import type * as LogLevel from "effect/LogLevel"
|
|
7
9
|
|
|
8
10
|
export const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): CauseException<E> => {
|
|
9
11
|
try {
|
|
@@ -13,45 +15,41 @@ export const tryCauseException = <E>(cause: Cause.Cause<E>, name: string): Cause
|
|
|
13
15
|
}
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
export function reportError(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
|
|
28
|
-
return Cause.squash(cause)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const error = tryCauseException(cause, name)
|
|
32
|
-
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
33
|
-
yield* Effect
|
|
34
|
-
.logWithLevel(level)("Reporting error", cause)
|
|
35
|
-
.pipe(
|
|
36
|
-
Effect.annotateLogs(dropUndefined({
|
|
37
|
-
extras,
|
|
38
|
-
error: tryToReport(error),
|
|
39
|
-
cause: tryToJson(cause),
|
|
40
|
-
__error_name__: name
|
|
41
|
-
})),
|
|
42
|
-
Effect.catchCause((cause) => Effect.logWarning("Failed to log error", cause)),
|
|
43
|
-
Effect.catchCause(() => Effect.logFatal("Failed to log error cause"))
|
|
44
|
-
)
|
|
18
|
+
export function reportError(name: string) {
|
|
19
|
+
return Effect.fnUntraced(
|
|
20
|
+
function*(
|
|
21
|
+
cause: Cause.Cause<unknown>,
|
|
22
|
+
extras?: Record<string, unknown>,
|
|
23
|
+
level: LogLevel.Severity = "Error"
|
|
24
|
+
) {
|
|
25
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
26
|
+
yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs("extras", JSON.stringify(extras ?? {})))
|
|
27
|
+
return Cause.squash(cause)
|
|
28
|
+
}
|
|
45
29
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
30
|
+
const error = tryCauseException(cause, name)
|
|
31
|
+
yield* reportSentry(error, extras, LogLevelToSentry(level))
|
|
32
|
+
yield* Effect
|
|
33
|
+
.logWithLevel(level)("Reporting error", cause)
|
|
34
|
+
.pipe(
|
|
35
|
+
Effect.annotateLogs(dropUndefined({
|
|
36
|
+
extras,
|
|
37
|
+
error: tryToReport(error),
|
|
38
|
+
cause: tryToJson(cause),
|
|
39
|
+
__error_name__: name
|
|
40
|
+
})),
|
|
41
|
+
Effect.catchCause((cause) => Effect.logWarning("Failed to log error", cause)),
|
|
42
|
+
Effect.catchCause(() => Effect.logFatal("Failed to log error cause"))
|
|
53
43
|
)
|
|
54
|
-
|
|
44
|
+
|
|
45
|
+
return error
|
|
46
|
+
},
|
|
47
|
+
(effect) =>
|
|
48
|
+
Effect.tapCause(effect, (cause) =>
|
|
49
|
+
Effect.logError("Failed to report error", cause).pipe(
|
|
50
|
+
Effect.tapCause(() => Effect.logFatal("Failed to log error cause"))
|
|
51
|
+
))
|
|
52
|
+
)
|
|
55
53
|
}
|
|
56
54
|
|
|
57
55
|
function reportSentry(
|
|
@@ -63,39 +61,33 @@ function reportSentry(
|
|
|
63
61
|
const scope = new Sentry.Scope()
|
|
64
62
|
scope.setLevel(level)
|
|
65
63
|
if (extras) scope.setContext("extras", extras)
|
|
66
|
-
scope.setContext("error", tryToReport(error)
|
|
67
|
-
scope.setContext("cause", tryToJson(error.originalCause)
|
|
64
|
+
scope.setContext("error", tryToReport(error))
|
|
65
|
+
scope.setContext("cause", tryToJson(error.originalCause))
|
|
68
66
|
Sentry.captureException(error, scope)
|
|
69
67
|
})
|
|
70
68
|
}
|
|
71
69
|
|
|
72
|
-
export function logError<E>(
|
|
73
|
-
|
|
74
|
-
) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
Effect.tapCause((cause) =>
|
|
94
|
-
Effect.logError("Failed to log error", cause).pipe(
|
|
95
|
-
Effect.tapCause(() => Effect.logFatal("Failed to log error cause"))
|
|
96
|
-
)
|
|
97
|
-
)
|
|
98
|
-
)
|
|
70
|
+
export function logError<E>(name: string) {
|
|
71
|
+
return Effect.fnUntraced(
|
|
72
|
+
function*(cause: Cause.Cause<E>, extras?: Record<string, unknown>) {
|
|
73
|
+
if (Cause.hasInterruptsOnly(cause)) {
|
|
74
|
+
yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
yield* Effect
|
|
78
|
+
.logWarning("Logging error", cause)
|
|
79
|
+
.pipe(Effect.annotateLogs(dropUndefined({
|
|
80
|
+
extras,
|
|
81
|
+
cause: tryToJson(cause),
|
|
82
|
+
__error_name__: name
|
|
83
|
+
})))
|
|
84
|
+
},
|
|
85
|
+
(effect) =>
|
|
86
|
+
Effect.tapCause(effect, (cause) =>
|
|
87
|
+
Effect.logError("Failed to log error", cause).pipe(
|
|
88
|
+
Effect.tapCause(() => Effect.logFatal("Failed to log error cause"))
|
|
89
|
+
))
|
|
90
|
+
)
|
|
99
91
|
}
|
|
100
92
|
|
|
101
93
|
export function captureException(error: unknown, extras?: Record<string, unknown>) {
|
|
@@ -105,12 +97,10 @@ export function captureException(error: unknown, extras?: Record<string, unknown
|
|
|
105
97
|
console.error(error, extras)
|
|
106
98
|
}
|
|
107
99
|
|
|
108
|
-
export
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Sentry.captureMessage(message, scope)
|
|
100
|
+
export const reportMessage = Effect.fnUntraced(function*(message: string, extras?: Record<string, unknown>) {
|
|
101
|
+
const scope = new Sentry.Scope()
|
|
102
|
+
if (extras) scope.setContext("extras", extras)
|
|
103
|
+
Sentry.captureMessage(message, scope)
|
|
113
104
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
105
|
+
console.warn(message, extras)
|
|
106
|
+
})
|
package/src/form.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { createIntl, type IntlFormatters } from "@formatjs/intl"
|
|
2
|
-
|
|
3
|
-
import
|
|
2
|
+
|
|
3
|
+
import * as Option from "effect-app/Option"
|
|
4
|
+
import * as S from "effect-app/Schema"
|
|
4
5
|
import type { Unbranded } from "effect-app/Schema/brand"
|
|
5
6
|
import type { IsUnion } from "effect-app/utils"
|
|
7
|
+
import * as Cause from "effect/Cause"
|
|
8
|
+
import * as Exit from "effect/Exit"
|
|
9
|
+
import { pipe } from "effect/Function"
|
|
6
10
|
import { capitalize, ref } from "vue"
|
|
7
11
|
|
|
8
12
|
// type GetSchemaFromProp<T> = T extends Field<infer S, any, any, any> ? S
|
|
@@ -23,7 +27,7 @@ function getObjectsAST(ast: S.AST.AST): S.AST.Objects | null {
|
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
/** @deprecated Use OmegaForm instead */
|
|
26
|
-
export function convertIn(v: string | null, type?: "text" | "float" | "int") {
|
|
30
|
+
export function convertIn(v: number | string | null, type?: "text" | "float" | "int") {
|
|
27
31
|
return v === null ? "" : type === "text" ? v : `${v}`
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -76,7 +80,8 @@ export interface DiscriminatedUnionFieldInfo<T> {
|
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
export type NestedFieldInfoKey<Key> = [Key] extends [Record<PropertyKey, any>]
|
|
79
|
-
? Unbranded<Key> extends
|
|
83
|
+
? Unbranded<Key> extends (string | number | boolean | bigint | symbol) ? FieldInfo<Key>
|
|
84
|
+
: Unbranded<Key> extends Record<PropertyKey, any> ? NestedFieldInfo<Key>
|
|
80
85
|
: FieldInfo<Key>
|
|
81
86
|
: FieldInfo<Key>
|
|
82
87
|
|
|
@@ -147,10 +152,18 @@ function handlePropertySignature(
|
|
|
147
152
|
const typeLiteral = getObjectsAST(ps.type)
|
|
148
153
|
|
|
149
154
|
const tagPropertySignature = typeLiteral?.propertySignatures.find((_) => _.name === "_tag")
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
// unwrap single-element Union to Literal (S.Struct({ _tag: S.Literal("x") }) wraps as Union([Literal("x")]))
|
|
156
|
+
const tagType = tagPropertySignature
|
|
157
|
+
? S.AST.isUnion(tagPropertySignature.type)
|
|
158
|
+
&& tagPropertySignature.type.types.length === 1
|
|
159
|
+
&& S.AST.isLiteral(tagPropertySignature.type.types[0]!)
|
|
160
|
+
? tagPropertySignature.type.types[0]
|
|
161
|
+
: tagPropertySignature.type
|
|
162
|
+
: undefined
|
|
163
|
+
const tagLiteral = tagType
|
|
164
|
+
&& S.AST.isLiteral(tagType)
|
|
165
|
+
&& typeof tagType.literal === "string"
|
|
166
|
+
? tagType.literal
|
|
154
167
|
: void 0
|
|
155
168
|
|
|
156
169
|
const toRet = handlePropertySignature(ps)
|
|
@@ -254,12 +267,12 @@ function buildFieldInfo(
|
|
|
254
267
|
const propertyKey = property.name
|
|
255
268
|
const schema = S.make<S.Codec<unknown>>(property.type)
|
|
256
269
|
const metadata = getMetadataFromSchema(property.type)
|
|
257
|
-
const parse = S.decodeUnknownExit(schema
|
|
270
|
+
const parse = S.decodeUnknownExit(schema)
|
|
258
271
|
|
|
259
272
|
const nullableOrUndefined = S.AST.isUnion(property.type)
|
|
260
273
|
&& (property.type.types.includes(S.Null.ast) || property.type.types.some((_) => _._tag === "Undefined"))
|
|
261
274
|
const realSelf = nullableOrUndefined && S.AST.isUnion(property.type)
|
|
262
|
-
? property.type.types.
|
|
275
|
+
? property.type.types.find((_) => _ !== S.Null.ast && _._tag !== "Undefined")!
|
|
263
276
|
: property.type
|
|
264
277
|
const id = S.AST.resolveIdentifier(property.type)
|
|
265
278
|
const id2 = S.AST.resolveIdentifier(realSelf)
|
|
@@ -276,20 +289,22 @@ function buildFieldInfo(
|
|
|
276
289
|
return custom(err, e, v)
|
|
277
290
|
}
|
|
278
291
|
|
|
279
|
-
// parse specific error types for better translation support
|
|
280
|
-
|
|
292
|
+
// parse specific error types for better translation support.
|
|
293
|
+
// beta.107 formatters often omit ", actual/got <value>" from the issue string;
|
|
294
|
+
// fall back to the rejected input `v` when the regex has no capture.
|
|
295
|
+
const integerMatch = err.match(/Expected.*integer(?:.*(?:actual|got)\s+([^)\s]+))?/i)
|
|
281
296
|
if (integerMatch) {
|
|
282
297
|
return translate.value(
|
|
283
298
|
{ defaultMessage: "Expected an integer, actual {actualValue}", id: "validation.integer.expected" },
|
|
284
|
-
{ actualValue: integerMatch[1] }
|
|
299
|
+
{ actualValue: integerMatch[1] ?? String(v) }
|
|
285
300
|
)
|
|
286
301
|
}
|
|
287
302
|
|
|
288
|
-
const numberMatch = err.match(/Expected.*number
|
|
303
|
+
const numberMatch = err.match(/Expected.*number(?:.*(?:actual|got)\s+([^)\s]+))?/i)
|
|
289
304
|
if (numberMatch) {
|
|
290
305
|
return translate.value(
|
|
291
306
|
{ defaultMessage: "Expected a number, actual {actualValue}", id: "validation.number.expected" },
|
|
292
|
-
{ actualValue: numberMatch[1] }
|
|
307
|
+
{ actualValue: numberMatch[1] ?? String(v) }
|
|
293
308
|
)
|
|
294
309
|
}
|
|
295
310
|
|
|
@@ -376,7 +391,7 @@ function buildFieldInfo(
|
|
|
376
391
|
: metadata.type === "float" || metadata.type === "int"
|
|
377
392
|
? numberRules
|
|
378
393
|
: []) as UnknownRule[],
|
|
379
|
-
parseRule
|
|
394
|
+
parseRule
|
|
380
395
|
]
|
|
381
396
|
|
|
382
397
|
const info = {
|
|
@@ -420,22 +435,43 @@ export function getMetadataFromSchema(
|
|
|
420
435
|
required: boolean
|
|
421
436
|
description?: string
|
|
422
437
|
} {
|
|
438
|
+
const findJsonSchemaType = (
|
|
439
|
+
schema: any,
|
|
440
|
+
target: "number" | "integer"
|
|
441
|
+
): boolean => {
|
|
442
|
+
if (!schema || typeof schema !== "object") {
|
|
443
|
+
return false
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (schema.type === target) {
|
|
447
|
+
return true
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (Array.isArray(schema.type) && schema.type.includes(target)) {
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return ["anyOf", "oneOf", "allOf"].some((key) =>
|
|
455
|
+
Array.isArray(schema[key]) && schema[key].some((member: any) => findJsonSchemaType(member, target))
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
|
|
423
459
|
const nullable = S.AST.isUnion(ast) && ast.types.includes(S.Null.ast)
|
|
424
460
|
const realSelf = nullable && S.AST.isUnion(ast)
|
|
425
|
-
? ast.types.
|
|
461
|
+
? ast.types.find((_) => _ !== S.Null.ast)!
|
|
426
462
|
: ast
|
|
427
463
|
|
|
428
464
|
let jschema: any
|
|
429
465
|
try {
|
|
430
466
|
const doc = S.toJsonSchemaDocument(S.make<S.Codec<unknown>>(realSelf))
|
|
431
467
|
jschema = doc.schema as any
|
|
432
|
-
const defs = doc.definitions
|
|
468
|
+
const defs = doc.definitions
|
|
433
469
|
// resolve $ref against definitions
|
|
434
470
|
while (jschema["$ref"] && jschema["$ref"].startsWith("#/$defs/")) {
|
|
435
471
|
const { $ref: _, ...rest } = jschema
|
|
436
472
|
jschema = { ...defs[jschema["$ref"].replace("#/$defs/", "")], ...rest }
|
|
437
473
|
}
|
|
438
|
-
} catch
|
|
474
|
+
} catch {
|
|
439
475
|
jschema = {}
|
|
440
476
|
}
|
|
441
477
|
// or we need to add these info directly in the refinement like the minimum
|
|
@@ -449,8 +485,8 @@ export function getMetadataFromSchema(
|
|
|
449
485
|
// "title": "Int"
|
|
450
486
|
// }
|
|
451
487
|
// }
|
|
452
|
-
const
|
|
453
|
-
const
|
|
488
|
+
const isInt = findJsonSchemaType(jschema, "integer")
|
|
489
|
+
const isNumber = isInt || findJsonSchemaType(jschema, "number")
|
|
454
490
|
return {
|
|
455
491
|
type: isInt ? "int" as const : isNumber ? "float" as const : "text" as const,
|
|
456
492
|
minimum: jschema.minimum,
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from "./lib.
|
|
1
|
+
export * from "./lib.ts"
|
|
2
2
|
|
|
3
|
-
export * from "./makeClient.
|
|
4
|
-
export * from "./makeContext.
|
|
5
|
-
export * from "./makeIntl.
|
|
6
|
-
export * from "./mutate.
|
|
7
|
-
export * from "./query.
|
|
8
|
-
export * from "./runtime.
|
|
3
|
+
export * from "./makeClient.ts"
|
|
4
|
+
export * from "./makeContext.ts"
|
|
5
|
+
export * from "./makeIntl.ts"
|
|
6
|
+
export * from "./mutate.ts"
|
|
7
|
+
export * from "./query.ts"
|
|
8
|
+
export * from "./runtime.ts"
|