@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.
Files changed (148) hide show
  1. package/CHANGELOG.md +2463 -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 +5 -5
  18. package/dist/form.d.ts.map +1 -1
  19. package/dist/form.js +42 -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 +57 -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 +3751 -120
  104. package/test/dist/stubs.d.ts.map +1 -1
  105. package/test/dist/stubs.js +190 -30
  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 +24 -19
  109. package/test/form.test.ts +25 -6
  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 +227 -42
  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
@@ -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 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>
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
- const tagLiteral = tagPropertySignature
151
- && S.AST.isLiteral(tagPropertySignature.type)
152
- && typeof tagPropertySignature.type.literal === "string"
153
- ? 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
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 as S.Codec<unknown> & { readonly DecodingServices: never })
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.filter((_) => _ !== S.Null.ast && _._tag !== "Undefined")[0]!
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
- 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)
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.*actual\s+(.+)/i)
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 as UnknownRule
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.filter((_) => _ !== S.Null.ast)[0]!
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 as Record<string, any>
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 (_err) {
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 isNumber = jschema.type === "number" || jschema.type === "integer"
453
- const isInt = jschema.type === "integer"
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.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"