@effect-app/vue 4.0.0-beta.32 → 4.0.0-beta.321

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 (148) hide show
  1. package/CHANGELOG.md +2404 -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 +634 -0
  6. package/dist/commander.d.ts.map +1 -0
  7. package/dist/commander.js +1068 -0
  8. package/dist/{experimental/confirm.d.ts → confirm.d.ts} +6 -4
  9. package/dist/confirm.d.ts.map +1 -0
  10. package/dist/confirm.js +26 -0
  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 +14 -5
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/form.js +51 -16
  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 +15 -0
  24. package/dist/intl.d.ts.map +1 -0
  25. package/dist/intl.js +9 -0
  26. package/dist/lib.d.ts +7 -9
  27. package/dist/lib.d.ts.map +1 -1
  28. package/dist/lib.js +35 -10
  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 +236 -336
  33. package/dist/makeClient.d.ts.map +1 -1
  34. package/dist/makeClient.js +417 -408
  35. package/dist/makeContext.d.ts.map +1 -1
  36. package/dist/makeIntl.d.ts.map +1 -1
  37. package/dist/{experimental/makeUseCommand.d.ts → makeUseCommand.d.ts} +4 -3
  38. package/dist/makeUseCommand.d.ts.map +1 -0
  39. package/dist/makeUseCommand.js +13 -0
  40. package/dist/mutate.d.ts +109 -40
  41. package/dist/mutate.d.ts.map +1 -1
  42. package/dist/mutate.js +235 -58
  43. package/dist/query.d.ts +158 -45
  44. package/dist/query.d.ts.map +1 -1
  45. package/dist/query.js +380 -122
  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 -14
  53. package/dist/runtime.d.ts.map +1 -1
  54. package/dist/runtime.js +2 -26
  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 +2 -0
  59. package/dist/toast.d.ts.map +1 -0
  60. package/dist/toast.js +2 -0
  61. package/dist/withToast.d.ts +2 -0
  62. package/dist/withToast.d.ts.map +1 -0
  63. package/dist/withToast.js +2 -0
  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 +36 -95
  69. package/src/atomQuery.ts +600 -0
  70. package/src/commander.ts +3408 -0
  71. package/src/{experimental/confirm.ts → confirm.ts} +13 -15
  72. package/src/dependencyMetadata.ts +84 -0
  73. package/src/errorReporter.ts +65 -75
  74. package/src/form.ts +66 -21
  75. package/src/index.ts +7 -7
  76. package/src/internal/tanstackQuery.ts +308 -0
  77. package/src/intl.ts +12 -0
  78. package/src/lib.ts +49 -21
  79. package/src/liveQueryInvalidation.ts +138 -0
  80. package/src/makeClient.ts +971 -1159
  81. package/src/makeIntl.ts +2 -2
  82. package/src/{experimental/makeUseCommand.ts → makeUseCommand.ts} +9 -6
  83. package/src/mutate.ts +406 -153
  84. package/src/query.ts +763 -260
  85. package/src/queryLifetime.ts +74 -0
  86. package/src/routeParams.ts +7 -7
  87. package/src/runtime.ts +1 -31
  88. package/src/suspense.ts +43 -0
  89. package/src/toast.ts +1 -0
  90. package/src/withToast.ts +1 -0
  91. package/test/Mutation.test.ts +181 -24
  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 +3753 -122
  104. package/test/dist/stubs.d.ts.map +1 -1
  105. package/test/dist/stubs.js +192 -32
  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 +25 -20
  109. package/test/form.test.ts +22 -3
  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 +437 -38
  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 +228 -43
  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 -2
  124. package/tsconfig.src.json +34 -34
  125. package/tsconfig.test.json +2 -2
  126. package/vitest.config.ts +5 -5
  127. package/dist/experimental/commander.d.ts +0 -359
  128. package/dist/experimental/commander.d.ts.map +0 -1
  129. package/dist/experimental/commander.js +0 -557
  130. package/dist/experimental/confirm.d.ts.map +0 -1
  131. package/dist/experimental/confirm.js +0 -28
  132. package/dist/experimental/intl.d.ts +0 -16
  133. package/dist/experimental/intl.d.ts.map +0 -1
  134. package/dist/experimental/intl.js +0 -5
  135. package/dist/experimental/makeUseCommand.d.ts.map +0 -1
  136. package/dist/experimental/makeUseCommand.js +0 -13
  137. package/dist/experimental/toast.d.ts +0 -47
  138. package/dist/experimental/toast.d.ts.map +0 -1
  139. package/dist/experimental/toast.js +0 -41
  140. package/dist/experimental/withToast.d.ts +0 -25
  141. package/dist/experimental/withToast.d.ts.map +0 -1
  142. package/dist/experimental/withToast.js +0 -45
  143. package/eslint.config.mjs +0 -24
  144. package/src/experimental/commander.ts +0 -1835
  145. package/src/experimental/intl.ts +0 -9
  146. package/src/experimental/toast.ts +0 -66
  147. package/src/experimental/withToast.ts +0 -99
  148. package/tsconfig.json.bak +0 -12
@@ -1,8 +1,10 @@
1
- import { Effect, Layer, ServiceMap } from "effect-app"
2
- import { I18n } from "./intl.js"
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 ServiceMap.Service<Confirm>()("Confirm", {
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
- return Effect.gen(function*() {
26
- const c = yield* Confirm
27
- return yield* c.confirm(message)
28
- })
29
- }
30
- static confirmOrInterrupt(message?: string) {
31
- return Effect.gen(function*() {
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
+ }
@@ -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
- name: string
18
- ) {
19
- return (
20
- cause: Cause.Cause<unknown>,
21
- extras?: Record<string, unknown>,
22
- level: LogLevel.Severity = "Error"
23
- ): Effect.Effect<unknown, never, never> =>
24
- Effect
25
- .gen(function*() {
26
- if (Cause.hasInterruptsOnly(cause)) {
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
- return error
47
- })
48
- .pipe(
49
- Effect.tapCause((cause) =>
50
- Effect.logError("Failed to report error", cause).pipe(
51
- Effect.tapCause(() => Effect.logFatal("Failed to log error cause"))
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) as any)
67
- scope.setContext("cause", tryToJson(error.originalCause) as any)
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
- name: string
74
- ) {
75
- return (cause: Cause.Cause<E>, extras?: Record<string, unknown>) =>
76
- Effect
77
- .gen(function*() {
78
- if (Cause.hasInterruptsOnly(cause)) {
79
- yield* Effect.logDebug("Interrupted").pipe(Effect.annotateLogs(dropUndefined({ extras })))
80
- return
81
- }
82
- yield* Effect
83
- .logWarning("Logging error", cause)
84
- .pipe(
85
- Effect.annotateLogs(dropUndefined({
86
- extras,
87
- cause: tryToJson(cause),
88
- __error_name__: name
89
- }))
90
- )
91
- })
92
- .pipe(
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 function reportMessage(message: string, extras?: Record<string, unknown>) {
109
- return Effect.gen(function*() {
110
- const scope = new Sentry.Scope()
111
- if (extras) scope.setContext("extras", extras)
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
- console.warn(message, extras)
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
- import type {} from "intl-messageformat"
3
- import { Cause, Exit, Option, pipe, S } from "effect-app"
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
@@ -22,7 +26,8 @@ function getObjectsAST(ast: S.AST.AST): S.AST.Objects | null {
22
26
  return null
23
27
  }
24
28
 
25
- export function convertIn(v: string | null, type?: "text" | "float" | "int") {
29
+ /** @deprecated Use OmegaForm instead */
30
+ export function convertIn(v: number | string | null, type?: "text" | "float" | "int") {
26
31
  return v === null ? "" : type === "text" ? v : `${v}`
27
32
  }
28
33
 
@@ -30,8 +35,10 @@ export function convertIn(v: string | null, type?: "text" | "float" | "int") {
30
35
  * Makes sure our international number format is converted to js int/float format.
31
36
  * Right now assumes . for thousands and , for decimal.
32
37
  */
38
+ /** @deprecated Use OmegaForm instead */
33
39
  export const prepareNumberForLocale = (v: string) => v.replace(/\./g, "").replace(/,/g, ".")
34
40
 
41
+ /** @deprecated Use OmegaForm instead */
35
42
  export function convertOutInt(v: string, type?: "text" | "float" | "int") {
36
43
  v = v == null ? v : v.trim()
37
44
  const c = v === ""
@@ -49,6 +56,7 @@ export function convertOutInt(v: string, type?: "text" | "float" | "int") {
49
56
  return c
50
57
  }
51
58
 
59
+ /** @deprecated Use OmegaForm instead */
52
60
  export function convertOut(v: string, set: (v: {} | null) => void, type?: "text" | "float" | "int") {
53
61
  return set(convertOutInt(v, type))
54
62
  }
@@ -72,7 +80,8 @@ export interface DiscriminatedUnionFieldInfo<T> {
72
80
  }
73
81
 
74
82
  export type NestedFieldInfoKey<Key> = [Key] extends [Record<PropertyKey, any>]
75
- ? Unbranded<Key> extends Record<PropertyKey, any> ? NestedFieldInfo<Key>
83
+ ? Unbranded<Key> extends (string | number | boolean | bigint | symbol) ? FieldInfo<Key>
84
+ : Unbranded<Key> extends Record<PropertyKey, any> ? NestedFieldInfo<Key>
76
85
  : FieldInfo<Key>
77
86
  : FieldInfo<Key>
78
87
 
@@ -143,10 +152,18 @@ function handlePropertySignature(
143
152
  const typeLiteral = getObjectsAST(ps.type)
144
153
 
145
154
  const tagPropertySignature = typeLiteral?.propertySignatures.find((_) => _.name === "_tag")
146
- const tagLiteral = tagPropertySignature
147
- && S.AST.isLiteral(tagPropertySignature.type)
148
- && typeof tagPropertySignature.type.literal === "string"
149
- ? tagPropertySignature.type.literal
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
150
167
  : void 0
151
168
 
152
169
  const toRet = handlePropertySignature(ps)
@@ -189,6 +206,7 @@ function handlePropertySignature(
189
206
  }
190
207
  }
191
208
 
209
+ /** @deprecated Use OmegaForm instead */
192
210
  export function buildFieldInfoFromFields<
193
211
  From extends Record<PropertyKey, any>,
194
212
  To extends Record<PropertyKey, any>
@@ -198,6 +216,7 @@ export function buildFieldInfoFromFields<
198
216
  return buildFieldInfoFromFieldsRoot(schema).fields
199
217
  }
200
218
 
219
+ /** @deprecated Use OmegaForm instead */
201
220
  export function buildFieldInfoFromFieldsRoot<
202
221
  From extends Record<PropertyKey, any>,
203
222
  To extends Record<PropertyKey, any>,
@@ -235,7 +254,9 @@ abstract class PhantomTypeParameter<
235
254
 
236
255
  const defaultIntl = createIntl({ locale: "en" })
237
256
 
257
+ /** @deprecated Use OmegaForm instead */
238
258
  export const translate = ref<IntlFormatters["formatMessage"]>(defaultIntl.formatMessage)
259
+ /** @deprecated Use OmegaForm instead */
239
260
  export const customSchemaErrors = ref<Map<S.AST.AST | string, (message: string, e: unknown, v: unknown) => string>>(
240
261
  new Map()
241
262
  )
@@ -246,12 +267,12 @@ function buildFieldInfo(
246
267
  const propertyKey = property.name
247
268
  const schema = S.make<S.Codec<unknown>>(property.type)
248
269
  const metadata = getMetadataFromSchema(property.type)
249
- const parse = S.decodeUnknownExit(schema as S.Codec<unknown> & { readonly DecodingServices: never })
270
+ const parse = S.decodeUnknownExit(schema)
250
271
 
251
272
  const nullableOrUndefined = S.AST.isUnion(property.type)
252
273
  && (property.type.types.includes(S.Null.ast) || property.type.types.some((_) => _._tag === "Undefined"))
253
274
  const realSelf = nullableOrUndefined && S.AST.isUnion(property.type)
254
- ? property.type.types.filter((_) => _ !== S.Null.ast && _._tag !== "Undefined")[0]!
275
+ ? property.type.types.find((_) => _ !== S.Null.ast && _._tag !== "Undefined")!
255
276
  : property.type
256
277
  const id = S.AST.resolveIdentifier(property.type)
257
278
  const id2 = S.AST.resolveIdentifier(realSelf)
@@ -268,20 +289,22 @@ function buildFieldInfo(
268
289
  return custom(err, e, v)
269
290
  }
270
291
 
271
- // parse specific error types for better translation support
272
- const integerMatch = err.match(/Expected.*integer.*actual\s+(.+)/i)
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)
273
296
  if (integerMatch) {
274
297
  return translate.value(
275
298
  { defaultMessage: "Expected an integer, actual {actualValue}", id: "validation.integer.expected" },
276
- { actualValue: integerMatch[1] }
299
+ { actualValue: integerMatch[1] ?? String(v) }
277
300
  )
278
301
  }
279
302
 
280
- const numberMatch = err.match(/Expected.*number.*actual\s+(.+)/i)
303
+ const numberMatch = err.match(/Expected.*number(?:.*(?:actual|got)\s+([^)\s]+))?/i)
281
304
  if (numberMatch) {
282
305
  return translate.value(
283
306
  { defaultMessage: "Expected a number, actual {actualValue}", id: "validation.number.expected" },
284
- { actualValue: numberMatch[1] }
307
+ { actualValue: numberMatch[1] ?? String(v) }
285
308
  )
286
309
  }
287
310
 
@@ -368,7 +391,7 @@ function buildFieldInfo(
368
391
  : metadata.type === "float" || metadata.type === "int"
369
392
  ? numberRules
370
393
  : []) as UnknownRule[],
371
- parseRule as UnknownRule
394
+ parseRule
372
395
  ]
373
396
 
374
397
  const info = {
@@ -398,6 +421,7 @@ function buildFieldInfo(
398
421
  return info as any
399
422
  }
400
423
 
424
+ /** @deprecated Use OmegaForm instead */
401
425
  export function getMetadataFromSchema(
402
426
  ast: S.AST.AST
403
427
  ): {
@@ -411,22 +435,43 @@ export function getMetadataFromSchema(
411
435
  required: boolean
412
436
  description?: string
413
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
+
414
459
  const nullable = S.AST.isUnion(ast) && ast.types.includes(S.Null.ast)
415
460
  const realSelf = nullable && S.AST.isUnion(ast)
416
- ? ast.types.filter((_) => _ !== S.Null.ast)[0]!
461
+ ? ast.types.find((_) => _ !== S.Null.ast)!
417
462
  : ast
418
463
 
419
464
  let jschema: any
420
465
  try {
421
466
  const doc = S.toJsonSchemaDocument(S.make<S.Codec<unknown>>(realSelf))
422
467
  jschema = doc.schema as any
423
- const defs = doc.definitions as Record<string, any>
468
+ const defs = doc.definitions
424
469
  // resolve $ref against definitions
425
470
  while (jschema["$ref"] && jschema["$ref"].startsWith("#/$defs/")) {
426
471
  const { $ref: _, ...rest } = jschema
427
472
  jschema = { ...defs[jschema["$ref"].replace("#/$defs/", "")], ...rest }
428
473
  }
429
- } catch (_err) {
474
+ } catch {
430
475
  jschema = {}
431
476
  }
432
477
  // or we need to add these info directly in the refinement like the minimum
@@ -440,8 +485,8 @@ export function getMetadataFromSchema(
440
485
  // "title": "Int"
441
486
  // }
442
487
  // }
443
- const isNumber = jschema.type === "number" || jschema.type === "integer"
444
- const isInt = jschema.type === "integer"
488
+ const isInt = findJsonSchemaType(jschema, "integer")
489
+ const isNumber = isInt || findJsonSchemaType(jschema, "number")
445
490
  return {
446
491
  type: isInt ? "int" as const : isNumber ? "float" as const : "text" as const,
447
492
  minimum: jschema.minimum,
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
- export * from "./lib.js"
1
+ export * from "./lib.ts"
2
2
 
3
- export * from "./makeClient.js"
4
- export * from "./makeContext.js"
5
- export * from "./makeIntl.js"
6
- export * from "./mutate.js"
7
- export * from "./query.js"
8
- export * from "./runtime.js"
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"