effect 4.0.0-beta.105 → 4.0.0-beta.107

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 (181) hide show
  1. package/dist/Channel.d.ts +26 -0
  2. package/dist/Channel.d.ts.map +1 -1
  3. package/dist/Channel.js +47 -0
  4. package/dist/Channel.js.map +1 -1
  5. package/dist/Config.js +3 -3
  6. package/dist/Config.js.map +1 -1
  7. package/dist/ConfigProvider.d.ts +35 -3
  8. package/dist/ConfigProvider.d.ts.map +1 -1
  9. package/dist/ConfigProvider.js +41 -8
  10. package/dist/ConfigProvider.js.map +1 -1
  11. package/dist/Duration.d.ts.map +1 -1
  12. package/dist/Duration.js +13 -1
  13. package/dist/Duration.js.map +1 -1
  14. package/dist/Fiber.d.ts +1 -1
  15. package/dist/Fiber.d.ts.map +1 -1
  16. package/dist/Function.d.ts +26 -0
  17. package/dist/Function.d.ts.map +1 -1
  18. package/dist/Function.js +36 -0
  19. package/dist/Function.js.map +1 -1
  20. package/dist/JsonSchema.d.ts.map +1 -1
  21. package/dist/JsonSchema.js +34 -17
  22. package/dist/JsonSchema.js.map +1 -1
  23. package/dist/Schema.d.ts +32 -84
  24. package/dist/Schema.d.ts.map +1 -1
  25. package/dist/Schema.js +58 -54
  26. package/dist/Schema.js.map +1 -1
  27. package/dist/SchemaAST.d.ts +2 -17
  28. package/dist/SchemaAST.d.ts.map +1 -1
  29. package/dist/SchemaAST.js +39 -28
  30. package/dist/SchemaAST.js.map +1 -1
  31. package/dist/SchemaRepresentation.d.ts +44 -7
  32. package/dist/SchemaRepresentation.d.ts.map +1 -1
  33. package/dist/SchemaRepresentation.js +15 -4
  34. package/dist/SchemaRepresentation.js.map +1 -1
  35. package/dist/ScopedRef.d.ts.map +1 -1
  36. package/dist/ScopedRef.js +1 -1
  37. package/dist/ScopedRef.js.map +1 -1
  38. package/dist/Stream.d.ts +10 -0
  39. package/dist/Stream.d.ts.map +1 -1
  40. package/dist/Stream.js +12 -23
  41. package/dist/Stream.js.map +1 -1
  42. package/dist/TxQueue.js +1 -1
  43. package/dist/TxQueue.js.map +1 -1
  44. package/dist/internal/effect.js +2 -4
  45. package/dist/internal/effect.js.map +1 -1
  46. package/dist/internal/executionPlan.js +3 -2
  47. package/dist/internal/executionPlan.js.map +1 -1
  48. package/dist/internal/rcRef.js +18 -13
  49. package/dist/internal/rcRef.js.map +1 -1
  50. package/dist/internal/schema/fromJsonSchemaDocument.js +32 -16
  51. package/dist/internal/schema/fromJsonSchemaDocument.js.map +1 -1
  52. package/dist/internal/schema/toArbitrary.d.ts +1 -4
  53. package/dist/internal/schema/toArbitrary.d.ts.map +1 -1
  54. package/dist/internal/schema/toArbitrary.js +0 -74
  55. package/dist/internal/schema/toArbitrary.js.map +1 -1
  56. package/dist/internal/schema/toJsonSchemaDocument.js +18 -4
  57. package/dist/internal/schema/toJsonSchemaDocument.js.map +1 -1
  58. package/dist/internal/schema/toRepresentation.js +45 -43
  59. package/dist/internal/schema/toRepresentation.js.map +1 -1
  60. package/dist/testing/TestSchema.js +2 -2
  61. package/dist/testing/TestSchema.js.map +1 -1
  62. package/dist/unstable/ai/McpSchema.d.ts +128 -128
  63. package/dist/unstable/ai/McpSchema.d.ts.map +1 -1
  64. package/dist/unstable/ai/McpServer.d.ts.map +1 -1
  65. package/dist/unstable/ai/McpServer.js +23 -11
  66. package/dist/unstable/ai/McpServer.js.map +1 -1
  67. package/dist/unstable/ai/Prompt.d.ts +4 -3
  68. package/dist/unstable/ai/Prompt.d.ts.map +1 -1
  69. package/dist/unstable/ai/Prompt.js +38 -13
  70. package/dist/unstable/ai/Prompt.js.map +1 -1
  71. package/dist/unstable/cli/Argument.d.ts +6 -3
  72. package/dist/unstable/cli/Argument.d.ts.map +1 -1
  73. package/dist/unstable/cli/Argument.js +2 -1
  74. package/dist/unstable/cli/Argument.js.map +1 -1
  75. package/dist/unstable/cli/CliError.d.ts +20 -1
  76. package/dist/unstable/cli/CliError.d.ts.map +1 -1
  77. package/dist/unstable/cli/CliError.js +26 -2
  78. package/dist/unstable/cli/CliError.js.map +1 -1
  79. package/dist/unstable/cli/Command.d.ts +23 -0
  80. package/dist/unstable/cli/Command.d.ts.map +1 -1
  81. package/dist/unstable/cli/Command.js +23 -4
  82. package/dist/unstable/cli/Command.js.map +1 -1
  83. package/dist/unstable/cluster/Entity.d.ts +2 -2
  84. package/dist/unstable/cluster/Entity.d.ts.map +1 -1
  85. package/dist/unstable/cluster/Entity.js.map +1 -1
  86. package/dist/unstable/cluster/EntityProxy.d.ts +3 -3
  87. package/dist/unstable/cluster/EntityProxy.d.ts.map +1 -1
  88. package/dist/unstable/cluster/EntityProxy.js +4 -3
  89. package/dist/unstable/cluster/EntityProxy.js.map +1 -1
  90. package/dist/unstable/cluster/Sharding.d.ts +2 -2
  91. package/dist/unstable/cluster/Sharding.d.ts.map +1 -1
  92. package/dist/unstable/cluster/Sharding.js +14 -5
  93. package/dist/unstable/cluster/Sharding.js.map +1 -1
  94. package/dist/unstable/eventlog/EventLogEncryption.d.ts +6 -5
  95. package/dist/unstable/eventlog/EventLogEncryption.d.ts.map +1 -1
  96. package/dist/unstable/eventlog/EventLogEncryption.js +14 -12
  97. package/dist/unstable/eventlog/EventLogEncryption.js.map +1 -1
  98. package/dist/unstable/eventlog/EventLogMessage.d.ts +3 -3
  99. package/dist/unstable/eventlog/EventLogMessage.d.ts.map +1 -1
  100. package/dist/unstable/eventlog/EventLogMessage.js +2 -3
  101. package/dist/unstable/eventlog/EventLogMessage.js.map +1 -1
  102. package/dist/unstable/eventlog/EventLogRemote.js +4 -4
  103. package/dist/unstable/eventlog/EventLogRemote.js.map +1 -1
  104. package/dist/unstable/eventlog/EventLogServerEncrypted.js +3 -2
  105. package/dist/unstable/eventlog/EventLogServerEncrypted.js.map +1 -1
  106. package/dist/unstable/http/HttpServerRequest.d.ts.map +1 -1
  107. package/dist/unstable/http/HttpServerRequest.js +7 -1
  108. package/dist/unstable/http/HttpServerRequest.js.map +1 -1
  109. package/dist/unstable/http/Multipart.d.ts +2 -0
  110. package/dist/unstable/http/Multipart.d.ts.map +1 -1
  111. package/dist/unstable/http/Multipart.js +4 -12
  112. package/dist/unstable/http/Multipart.js.map +1 -1
  113. package/dist/unstable/http/MultipartParser/internal/headers.d.ts.map +1 -1
  114. package/dist/unstable/http/MultipartParser/internal/headers.js +16 -2
  115. package/dist/unstable/http/MultipartParser/internal/headers.js.map +1 -1
  116. package/dist/unstable/http/MultipartParser/internal/multipart.d.ts.map +1 -1
  117. package/dist/unstable/http/MultipartParser/internal/multipart.js +23 -8
  118. package/dist/unstable/http/MultipartParser/internal/multipart.js.map +1 -1
  119. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  120. package/dist/unstable/httpapi/OpenApi.js +1 -1
  121. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  122. package/dist/unstable/process/ChildProcess.d.ts +8 -0
  123. package/dist/unstable/process/ChildProcess.d.ts.map +1 -1
  124. package/dist/unstable/process/ChildProcess.js.map +1 -1
  125. package/dist/unstable/rpc/RpcClient.d.ts.map +1 -1
  126. package/dist/unstable/rpc/RpcClient.js +5 -0
  127. package/dist/unstable/rpc/RpcClient.js.map +1 -1
  128. package/dist/unstable/sql/Migrator.d.ts +9 -1
  129. package/dist/unstable/sql/Migrator.d.ts.map +1 -1
  130. package/dist/unstable/sql/Migrator.js +15 -2
  131. package/dist/unstable/sql/Migrator.js.map +1 -1
  132. package/dist/unstable/sql/SqlResolver.d.ts.map +1 -1
  133. package/dist/unstable/sql/SqlResolver.js +20 -16
  134. package/dist/unstable/sql/SqlResolver.js.map +1 -1
  135. package/dist/unstable/workers/Worker.d.ts.map +1 -1
  136. package/dist/unstable/workers/Worker.js +11 -12
  137. package/dist/unstable/workers/Worker.js.map +1 -1
  138. package/package.json +1 -1
  139. package/src/Channel.ts +58 -0
  140. package/src/Config.ts +3 -3
  141. package/src/ConfigProvider.ts +43 -8
  142. package/src/Duration.ts +12 -1
  143. package/src/Fiber.ts +1 -1
  144. package/src/Function.ts +37 -0
  145. package/src/JsonSchema.ts +43 -42
  146. package/src/Schema.ts +68 -146
  147. package/src/SchemaAST.ts +50 -30
  148. package/src/SchemaRepresentation.ts +44 -7
  149. package/src/ScopedRef.ts +3 -1
  150. package/src/Stream.ts +12 -30
  151. package/src/TxQueue.ts +1 -1
  152. package/src/internal/effect.ts +3 -6
  153. package/src/internal/executionPlan.ts +3 -2
  154. package/src/internal/rcRef.ts +24 -16
  155. package/src/internal/schema/fromJsonSchemaDocument.ts +36 -18
  156. package/src/internal/schema/toArbitrary.ts +0 -72
  157. package/src/internal/schema/toJsonSchemaDocument.ts +24 -4
  158. package/src/internal/schema/toRepresentation.ts +66 -48
  159. package/src/testing/TestSchema.ts +2 -2
  160. package/src/unstable/ai/McpServer.ts +23 -11
  161. package/src/unstable/ai/Prompt.ts +37 -15
  162. package/src/unstable/cli/Argument.ts +6 -3
  163. package/src/unstable/cli/CliError.ts +28 -2
  164. package/src/unstable/cli/Command.ts +44 -4
  165. package/src/unstable/cluster/Entity.ts +7 -2
  166. package/src/unstable/cluster/EntityProxy.ts +12 -5
  167. package/src/unstable/cluster/Sharding.ts +29 -17
  168. package/src/unstable/eventlog/EventLogEncryption.ts +15 -16
  169. package/src/unstable/eventlog/EventLogMessage.ts +2 -3
  170. package/src/unstable/eventlog/EventLogRemote.ts +4 -4
  171. package/src/unstable/eventlog/EventLogServerEncrypted.ts +2 -2
  172. package/src/unstable/http/HttpServerRequest.ts +8 -3
  173. package/src/unstable/http/Multipart.ts +4 -13
  174. package/src/unstable/http/MultipartParser/internal/headers.ts +16 -387
  175. package/src/unstable/http/MultipartParser/internal/multipart.ts +25 -8
  176. package/src/unstable/httpapi/OpenApi.ts +4 -1
  177. package/src/unstable/process/ChildProcess.ts +8 -0
  178. package/src/unstable/rpc/RpcClient.ts +5 -0
  179. package/src/unstable/sql/Migrator.ts +22 -9
  180. package/src/unstable/sql/SqlResolver.ts +28 -20
  181. package/src/unstable/workers/Worker.ts +13 -14
package/src/ScopedRef.ts CHANGED
@@ -219,7 +219,9 @@ export const set: {
219
219
  Scope.provide(scope),
220
220
  Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause)))
221
221
  )
222
- yield* Scope.close(self.backing.backing.ref.current[0], Exit.void)
222
+ yield* Scope.close(self.backing.backing.ref.current[0], Exit.void).pipe(
223
+ Effect.tapCause((cause) => Scope.close(scope, Exit.failCause(cause)))
224
+ )
223
225
  self.backing.backing.ref.current = [scope, value]
224
226
  },
225
227
  Effect.uninterruptible,
package/src/Stream.ts CHANGED
@@ -19270,39 +19270,16 @@ export const mkString = <E, R>(self: Stream<string, E, R>): Effect.Effect<string
19270
19270
  * await Effect.runPromise(program) // => [1, 2, 3, 4]
19271
19271
  * ```
19272
19272
  *
19273
+ * **Gotchas**
19274
+ *
19275
+ * This materializes the full content in memory. The source stream must not
19276
+ * reuse or mutate emitted buffers, which are retained until collection completes.
19277
+ *
19273
19278
  * @category destructors
19274
19279
  * @since 4.0.0
19275
19280
  */
19276
19281
  export const mkArrayBuffer = <E, R>(self: Stream<Uint8Array, E, R>): Effect.Effect<ArrayBuffer, E, R> =>
19277
- Effect.map(
19278
- Channel.runFold(
19279
- self.channel,
19280
- (): {
19281
- bytes: number
19282
- readonly arrays: Array<Uint8Array>
19283
- } => ({
19284
- bytes: 0,
19285
- arrays: []
19286
- }),
19287
- (acc, chunk) => {
19288
- for (let i = 0; i < chunk.length; i++) {
19289
- acc.bytes += chunk[i].length
19290
- acc.arrays.push(chunk[i])
19291
- }
19292
- return acc
19293
- }
19294
- ),
19295
- ({ arrays, bytes }) => {
19296
- const result = new Uint8Array(bytes)
19297
- let offset = 0
19298
- for (let i = 0; i < arrays.length; i++) {
19299
- const array = arrays[i]
19300
- result.set(array, offset)
19301
- offset += array.length
19302
- }
19303
- return result.buffer
19304
- }
19305
- )
19282
+ Effect.map(Channel.mkUint8Array(self.channel), (bytes) => bytes.buffer)
19306
19283
 
19307
19284
  /**
19308
19285
  * Concatenates the stream's `Uint8Array` chunks into a single `Uint8Array`.
@@ -19321,11 +19298,16 @@ export const mkArrayBuffer = <E, R>(self: Stream<Uint8Array, E, R>): Effect.Effe
19321
19298
  * await Effect.runPromise(program)
19322
19299
  * ```
19323
19300
  *
19301
+ * **Gotchas**
19302
+ *
19303
+ * This materializes the full content in memory. The source stream must not
19304
+ * reuse or mutate emitted buffers, which are retained until collection completes.
19305
+ *
19324
19306
  * @category destructors
19325
19307
  * @since 4.0.0
19326
19308
  */
19327
19309
  export const mkUint8Array = <E, R>(self: Stream<Uint8Array, E, R>): Effect.Effect<Uint8Array, E, R> =>
19328
- Effect.map(mkArrayBuffer(self), (buffer) => new Uint8Array(buffer))
19310
+ Channel.mkUint8Array(self.channel)
19329
19311
 
19330
19312
  /**
19331
19313
  * Converts the stream to a `ReadableStream` using the provided services.
package/src/TxQueue.ts CHANGED
@@ -1700,7 +1700,7 @@ export const clear = <A, E>(self: TxEnqueue<A, E>): Effect.Effect<Array<A>, Excl
1700
1700
  */
1701
1701
  export const shutdown = <A, E>(self: TxEnqueue<A, E>): Effect.Effect<boolean> =>
1702
1702
  Effect.gen(function*() {
1703
- yield* Effect.ignore(clear(self))
1703
+ yield* Effect.ignoreCause(clear(self))
1704
1704
  return yield* interrupt(self)
1705
1705
  }).pipe(Effect.tx)
1706
1706
 
@@ -822,7 +822,7 @@ export const fiberJoin = <A, E>(self: Fiber.Fiber<A, E>): Effect.Effect<A, E> =>
822
822
  /** @internal */
823
823
  export const fiberJoinAll = <A extends Iterable<Fiber.Fiber<any, any>>>(self: A): Effect.Effect<
824
824
  Arr.ReadonlyArray.With<A, A extends Iterable<Fiber.Fiber<infer _A, infer _E>> ? _A : never>,
825
- A extends Fiber.Fiber<infer _A, infer _E> ? _E : never
825
+ A extends Iterable<Fiber.Fiber<infer _A, infer _E>> ? _E : never
826
826
  > =>
827
827
  callback((resume) => {
828
828
  const fibers = Array.from(self)
@@ -5862,11 +5862,8 @@ export const useSpan: {
5862
5862
  return withFiber((fiber) => {
5863
5863
  const span = makeSpanUnsafe(fiber, name, options)
5864
5864
  const clock = fiber.getRef(ClockRef)
5865
- return onExit(internalCall(() => evaluate(span)), (exit) =>
5866
- sync(() => {
5867
- if (span.status._tag === "Ended") return
5868
- span.end(clock.currentTimeNanosUnsafe(), exit)
5869
- }))
5865
+ const timingEnabled = fiber.getRef(TracerTimingEnabled)
5866
+ return onExit(internalCall(() => evaluate(span)), (exit) => endSpan(span, exit, clock, timingEnabled))
5870
5867
  })
5871
5868
  }
5872
5869
 
@@ -31,6 +31,7 @@ export const makeEventEmitter = (
31
31
  ): EventEmitter => {
32
32
  let lastStepIndex = -1
33
33
  let stepAttempt = 0
34
+ const emit = (event: Api.Event<any>) => effect.ignoreCause(onEvent(event))
34
35
  return {
35
36
  begin: effect.clockWith((clock) =>
36
37
  effect.suspend(() => {
@@ -47,7 +48,7 @@ export const makeEventEmitter = (
47
48
  startNanos: clock.monotonicTimeNanosUnsafe()
48
49
  }
49
50
  return effect.as(
50
- onEvent({
51
+ emit({
51
52
  _tag: "AttemptStart",
52
53
  attempt: state.attempt,
53
54
  stepAttempt: state.stepAttempt,
@@ -60,7 +61,7 @@ export const makeEventEmitter = (
60
61
  end: (state, exit) =>
61
62
  effect.clockWith((clock) => {
62
63
  const duration = Duration.nanos(clock.monotonicTimeNanosUnsafe() - state.startNanos)
63
- return onEvent(
64
+ return emit(
64
65
  exit._tag === "Success"
65
66
  ? {
66
67
  _tag: "AttemptSuccess",
@@ -94,7 +94,7 @@ export const make = <A, E, R>(options: {
94
94
  })
95
95
 
96
96
  const getState = <A, E>(self: RcRefImpl<A, E>) =>
97
- Effect.uninterruptibleMask((restore) => {
97
+ Effect.uninterruptibleMask(function loop(restore): Effect.Effect<State.Acquired<A>, E> {
98
98
  switch (self.state._tag) {
99
99
  case "Closed": {
100
100
  return Effect.interrupt
@@ -107,22 +107,30 @@ const getState = <A, E>(self: RcRefImpl<A, E>) =>
107
107
  }
108
108
  case "Empty": {
109
109
  const scope = Scope.makeUnsafe()
110
- return self.semaphore.withPermits(1)(
111
- restore(Effect.provideContext(
112
- self.acquire as Effect.Effect<A, E>,
113
- Context.add(self.context, Scope.Scope, scope)
114
- )).pipe(Effect.map((value) => {
115
- const state: State.Acquired<A> = {
116
- _tag: "Acquired",
117
- value,
118
- scope,
119
- fiber: undefined,
120
- refCount: 1,
121
- invalidated: false
110
+ return self.semaphore.withPermit(
111
+ Effect.suspend(() => {
112
+ if (self.state._tag !== "Empty") {
113
+ return loop(restore)
122
114
  }
123
- self.state = state
124
- return state
125
- }))
115
+ return restore(Effect.provideContext(
116
+ self.acquire as Effect.Effect<A, E>,
117
+ Context.add(self.context, Scope.Scope, scope)
118
+ )).pipe(
119
+ Effect.map((value) => {
120
+ const state: State.Acquired<A> = {
121
+ _tag: "Acquired",
122
+ value,
123
+ scope,
124
+ fiber: undefined,
125
+ refCount: 1,
126
+ invalidated: false
127
+ }
128
+ self.state = state
129
+ return state
130
+ }),
131
+ Effect.onExit((exit) => Exit.isFailure(exit) ? Scope.close(scope, exit) : Effect.void)
132
+ )
133
+ })
126
134
  )
127
135
  }
128
136
  }
@@ -743,7 +743,7 @@ function translateJsonSchemaMultiDocument(
743
743
  case "string":
744
744
  return {
745
745
  _tag: "String",
746
- checks: collectStringChecks(schema)
746
+ checks: collectStringChecks(schema, path)
747
747
  }
748
748
  case "number":
749
749
  case "integer":
@@ -757,23 +757,26 @@ function translateJsonSchemaMultiDocument(
757
757
  case "boolean":
758
758
  return { _tag: "Boolean", checks: [] }
759
759
  case "array": {
760
+ const prefixItems = Array.isArray(schema.prefixItems) ? schema.prefixItems : undefined
760
761
  const minItems = typeof schema.minItems === "number" ? schema.minItems : 0
761
- const elements = Array.isArray(schema.prefixItems)
762
- ? schema.prefixItems.map((element, index) => ({
763
- isOptional: index + 1 > minItems,
764
- type: recur(element, [...path, "prefixItems", index])
765
- }))
766
- : []
767
- const rest = schema.items !== undefined
768
- ? [recur(schema.items, [...path, "items"])]
769
- : schema.prefixItems !== undefined && typeof schema.maxItems === "number"
770
- ? []
771
- : [unknown]
762
+ const elements = prefixItems?.map((element, index) => ({
763
+ isOptional: index + 1 > minItems,
764
+ type: recur(element, [...path, "prefixItems", index])
765
+ })) ?? []
766
+ const isTupleClosed = schema.items === false ||
767
+ (schema.items === undefined &&
768
+ prefixItems !== undefined &&
769
+ schema.maxItems === prefixItems.length)
770
+ const isMaxItemsRedundant = isTupleClosed &&
771
+ typeof schema.maxItems === "number" &&
772
+ schema.maxItems >= elements.length
772
773
  return {
773
774
  _tag: "Arrays",
774
775
  elements,
775
- rest,
776
- checks: collectArrayChecks(schema)
776
+ rest: isTupleClosed
777
+ ? []
778
+ : [schema.items === undefined ? unknown : recur(schema.items, [...path, "items"])],
779
+ checks: collectArrayChecks(schema, isMaxItemsRedundant)
777
780
  }
778
781
  }
779
782
  case "object":
@@ -788,12 +791,23 @@ function translateJsonSchemaMultiDocument(
788
791
  }
789
792
  }
790
793
 
791
- function collectStringChecks(schema: JsonSchema.JsonSchema): Array<Check> {
794
+ function importPatternChecks(pattern: string, path: Path): Array<Check> {
795
+ switch (options?.patterns ?? "error") {
796
+ case "error":
797
+ throw errorWithPath(`Pattern encountered while patterns is set to "error"`, path)
798
+ case "ignore":
799
+ return []
800
+ case "apply":
801
+ return [jsonSchemaFilter("effect/schema/isPattern", { source: pattern, flags: "" })]
802
+ }
803
+ }
804
+
805
+ function collectStringChecks(schema: JsonSchema.JsonSchema, path: Path): Array<Check> {
792
806
  const checks: Array<Check> = []
793
807
  addNumberCheck(checks, schema.minLength, "effect/schema/isMinLength", "minLength")
794
808
  addNumberCheck(checks, schema.maxLength, "effect/schema/isMaxLength", "maxLength")
795
809
  if (typeof schema.pattern === "string") {
796
- checks.push(jsonSchemaFilter("effect/schema/isPattern", { source: schema.pattern, flags: "" }))
810
+ checks.push(...importPatternChecks(schema.pattern, [...path, "pattern"]))
797
811
  }
798
812
  return checks
799
813
  }
@@ -808,10 +822,12 @@ function translateJsonSchemaMultiDocument(
808
822
  return checks
809
823
  }
810
824
 
811
- function collectArrayChecks(schema: JsonSchema.JsonSchema): Array<Check> {
825
+ function collectArrayChecks(schema: JsonSchema.JsonSchema, isMaxItemsRedundant: boolean): Array<Check> {
812
826
  const checks: Array<Check> = []
813
827
  if (schema.prefixItems === undefined) {
814
828
  addNumberCheck(checks, schema.minItems, "effect/schema/isMinLength", "minLength")
829
+ }
830
+ if (!isMaxItemsRedundant) {
815
831
  addNumberCheck(checks, schema.maxItems, "effect/schema/isMaxLength", "maxLength")
816
832
  }
817
833
  if (schema.uniqueItems === true) {
@@ -851,10 +867,12 @@ function translateJsonSchemaMultiDocument(
851
867
  !Array.isArray(schema.patternProperties)
852
868
  ) {
853
869
  for (const [pattern, value] of Object.entries(schema.patternProperties)) {
870
+ const checks = importPatternChecks(pattern, [...path, "patternProperties", pattern])
871
+ if (checks.length === 0) return [{ parameter: string, type: unknown }]
854
872
  signatures.push({
855
873
  parameter: {
856
874
  _tag: "String",
857
- checks: [jsonSchemaFilter("effect/schema/isPattern", { source: pattern, flags: "" })]
875
+ checks
858
876
  },
859
877
  type: recur(value, [...path, "patternProperties", pattern])
860
878
  })
@@ -34,20 +34,6 @@ type LazyOption<T> = (
34
34
  recursionStack: RecursionStack
35
35
  ) => FastCheck.Arbitrary<T> | undefined
36
36
 
37
- export interface MutableReport {
38
- readonly warnings: Array<Schema.Annotations.ToArbitrary.Warning>
39
- }
40
-
41
- /** @internal */
42
- export function makeReport(): MutableReport {
43
- return { warnings: [] }
44
- }
45
-
46
- /** @internal */
47
- export function toReport(report: MutableReport): Schema.Annotations.ToArbitrary.Report {
48
- return { warnings: report.warnings.slice() }
49
- }
50
-
51
37
  function arbitraryError(what: string) {
52
38
  return new Error(`Unable to derive an arbitrary for ${what}`)
53
39
  }
@@ -394,64 +380,6 @@ function finiteNumberContext(ctx: Context): Context {
394
380
  }
395
381
  }
396
382
 
397
- function reportChecks(report: MutableReport, checks: SchemaAST.Checks | undefined, path: ReadonlyArray<PropertyKey>) {
398
- function visit(check: SchemaAST.Check<any>, covered: boolean) {
399
- const arbitrary = check.annotations?.arbitrary
400
- const nextCovered = covered || arbitrary?.constraint !== undefined || arbitrary?.candidate !== undefined
401
- if (check._tag !== "Filter") {
402
- for (const child of check.checks) {
403
- visit(child, nextCovered)
404
- }
405
- } else if (!nextCovered) {
406
- const description = check.annotations?.representation?.id ?? check.annotations?.identifier ??
407
- check.annotations?.expected
408
- report.warnings.push({ _tag: "OpaqueFilter", path, ...(description === undefined ? {} : { description }) })
409
- }
410
- }
411
- checks?.forEach((check) => visit(check, false))
412
- }
413
-
414
- /** @internal */
415
- export function collectReport(ast: SchemaAST.AST, report: MutableReport) {
416
- const stack = new WeakSet<SchemaAST.AST>()
417
- function visit(ast: SchemaAST.AST, path: ReadonlyArray<PropertyKey>) {
418
- if (stack.has(ast)) {
419
- return
420
- }
421
- stack.add(ast)
422
- reportChecks(report, ast.checks, path)
423
- switch (ast._tag) {
424
- case "Declaration":
425
- ast.typeParameters.forEach((tp) => visit(tp, path))
426
- break
427
- case "Arrays": {
428
- for (const [i, type] of [...ast.elements, ...ast.rest].entries()) {
429
- visit(type, [...path, i])
430
- }
431
- break
432
- }
433
- case "Objects":
434
- ast.propertySignatures.forEach((ps) => visit(ps.type, [...path, ps.name]))
435
- ast.indexSignatures.forEach((is) => {
436
- visit(is.parameter, path)
437
- visit(is.type, path)
438
- })
439
- break
440
- case "Union":
441
- ast.types.forEach((type) => visit(type, path))
442
- break
443
- case "TemplateLiteral":
444
- ast.parts.forEach((part, i) => visit(SchemaAST.toEncoded(part), [...path, i]))
445
- break
446
- case "Suspend":
447
- visit(ast.thunk(), path)
448
- break
449
- }
450
- stack.delete(ast)
451
- }
452
- visit(ast, [])
453
- }
454
-
455
383
  function applyCandidates(
456
384
  fc: typeof FastCheck,
457
385
  ctx: Context,
@@ -1,7 +1,8 @@
1
1
  import * as Arr from "../../Array.ts"
2
2
  import * as Equal from "../../Equal.ts"
3
- import { escapeToken } from "../../JsonPointer.ts"
3
+ import { escapeToken, unescapeToken } from "../../JsonPointer.ts"
4
4
  import type * as JsonSchema from "../../JsonSchema.ts"
5
+ import { rewriteRefs } from "../../JsonSchema.ts"
5
6
  import * as RegEx from "../../RegExp.ts"
6
7
  import type * as Schema from "../../Schema.ts"
7
8
  import * as SchemaAST from "../../SchemaAST.ts"
@@ -21,6 +22,11 @@ const jsonSchemaAnnotationExcludedKeys = new Set([
21
22
  ...InternalAnnotations.jsonSchemaAnnotationKeys
22
23
  ])
23
24
 
25
+ /** @internal */
26
+ export const toRepresentationOptions = {
27
+ isAnonymousReferenceAllowed: (ast: SchemaAST.AST): boolean => !SchemaAST.isDeclaration(ast)
28
+ }
29
+
24
30
  function collectJsonSchemaAnnotations(
25
31
  annotations: Schema.Annotations.Annotations | undefined,
26
32
  options: Schema.ToJsonSchemaOptions | undefined
@@ -142,22 +148,26 @@ function compileJsonSchema(
142
148
  references: SchemaRepresentation.References,
143
149
  options: Schema.ToJsonSchemaOptions | undefined
144
150
  ): JsonSchema.MultiDocument<"draft-2020-12"> {
145
- const definitions: Record<string, JsonSchema.JsonSchema> = {}
146
151
  // null = compiling, string = canonical key, object = compiled schema
147
152
  const definitionStates = new Map<string, JsonSchema.JsonSchema | string | null>()
148
153
  const compiledRepresentations = new WeakMap<SchemaRepresentation.Representation, JsonSchema.JsonSchema>()
149
154
  const fallbackDefinitions = new Map<string, Array<string>>()
155
+ let hasAliases = false
150
156
  const referenceKeys = Object.keys(references)
151
157
  for (const key of referenceKeys) {
152
158
  compileDefinition(key, ["references", key])
153
159
  }
160
+ const schemas = Arr.map(
161
+ representations,
162
+ (representation, index) => finalizeJsonSchema(recur(representation, rootPaths[index]))
163
+ )
164
+ const definitions: Record<string, JsonSchema.JsonSchema> = {}
154
165
  for (const key of referenceKeys) {
155
166
  const compiled = definitionStates.get(key)!
156
167
  if (typeof compiled !== "string") {
157
- InternalRecord.assignProperty(definitions, key, compiled)
168
+ InternalRecord.assignProperty(definitions, key, finalizeJsonSchema(compiled))
158
169
  }
159
170
  }
160
- const schemas = Arr.map(representations, (representation, index) => recur(representation, rootPaths[index]))
161
171
  return { dialect: "draft-2020-12", schemas, definitions }
162
172
 
163
173
  function compileDefinition(key: string, path: Path): string {
@@ -179,6 +189,7 @@ function compileJsonSchema(
179
189
  if (candidates === undefined) fallbackDefinitions.set(fallback, [key])
180
190
  else candidates.push(key)
181
191
  } else {
192
+ hasAliases = true
182
193
  definitionStates.set(key, match)
183
194
  return match
184
195
  }
@@ -187,6 +198,15 @@ function compileJsonSchema(
187
198
  return key
188
199
  }
189
200
 
201
+ function finalizeJsonSchema(schema: JsonSchema.JsonSchema): JsonSchema.JsonSchema {
202
+ if (!hasAliases) return schema
203
+ return rewriteRefs(schema, ($ref) =>
204
+ $ref.replace(/^#\/\$defs\/([^/]*)/, (match, token) => {
205
+ const canonical = definitionStates.get(unescapeToken(token))
206
+ return typeof canonical === "string" ? `#/$defs/${escapeToken(canonical)}` : match
207
+ }))
208
+ }
209
+
190
210
  function getIdentifierFallback(
191
211
  representation: SchemaRepresentation.Representation
192
212
  ): string | undefined {
@@ -7,17 +7,24 @@ import * as InternalAnnotations from "./annotations.ts"
7
7
 
8
8
  /** @internal */
9
9
  export function toRepresentation(
10
- ast: SchemaAST.AST
10
+ ast: SchemaAST.AST,
11
+ options?: Options
11
12
  ): SchemaRepresentation.Document {
12
- const { references, representations } = toRepresentations([ast])
13
+ const { references, representations } = toRepresentations([ast], options)
13
14
  return { representation: representations[0], references }
14
15
  }
15
16
 
16
17
  /** @internal */
17
18
  export function toRepresentations(
18
- asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>]
19
+ asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>],
20
+ options?: Options
19
21
  ): SchemaRepresentation.MultiDocument {
20
- return fromASTs(asts)
22
+ return fromASTs(asts, options)
23
+ }
24
+
25
+ /** @internal */
26
+ export interface Options {
27
+ readonly isAnonymousReferenceAllowed?: ((ast: SchemaAST.AST) => boolean) | undefined
21
28
  }
22
29
 
23
30
  type CheckRepresentationAnnotation = SchemaRepresentation.CheckRepresentationAnnotation<
@@ -28,11 +35,49 @@ function annotationsField<A>(annotations: A | undefined): { readonly annotations
28
35
  return annotations === undefined ? undefined : { annotations }
29
36
  }
30
37
 
31
- // Preserve repeated structural nodes as references without adding noise for leaf nodes.
32
- function isShareable(ast: SchemaAST.AST): boolean {
33
- return SchemaAST.isArrays(ast) ||
34
- SchemaAST.isObjects(ast) ||
35
- (SchemaAST.isUnion(ast) && ast.types.some(isShareable))
38
+ function hasShareableStructure(
39
+ ast: SchemaAST.AST,
40
+ isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"]
41
+ ): boolean {
42
+ if (isAnonymousReferenceAllowed?.(ast) === false) return false
43
+ switch (ast._tag) {
44
+ case "Arrays":
45
+ case "Objects":
46
+ case "Suspend":
47
+ return true
48
+ case "Declaration":
49
+ return true
50
+ case "Union":
51
+ return ast.types.some((ast) => hasShareableStructure(ast, isAnonymousReferenceAllowed))
52
+ default:
53
+ return false
54
+ }
55
+ }
56
+
57
+ function isWorthReferencing(bodyCost: number, occurrences: number): boolean {
58
+ return occurrences * bodyCost > bodyCost + occurrences + 1
59
+ }
60
+
61
+ function isAnonymousReferenceEligible(
62
+ ast: SchemaAST.AST,
63
+ occurrences: number,
64
+ isAnonymousReferenceAllowed: Options["isAnonymousReferenceAllowed"]
65
+ ): boolean {
66
+ if (isAnonymousReferenceAllowed?.(ast) === false) return false
67
+ if (hasShareableStructure(ast, isAnonymousReferenceAllowed)) return true
68
+ switch (ast._tag) {
69
+ case "Union":
70
+ return isWorthReferencing(ast.types.length + 1, occurrences)
71
+ case "Enum":
72
+ return isWorthReferencing(ast.enums.length + 1, occurrences)
73
+ case "TemplateLiteral":
74
+ return isWorthReferencing(ast.parts.length + 1, occurrences)
75
+ case "Literal":
76
+ return typeof ast.literal === "string" &&
77
+ isWorthReferencing(ast.literal.length / 32 + 1, occurrences)
78
+ default:
79
+ return false
80
+ }
36
81
  }
37
82
 
38
83
  interface ReferenceIdentifier {
@@ -54,17 +99,15 @@ function resolveReferenceIdentifier(
54
99
  }
55
100
 
56
101
  function fromASTs(
57
- asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>]
102
+ asts: readonly [SchemaAST.AST, ...Array<SchemaAST.AST>],
103
+ options: Options | undefined
58
104
  ): SchemaRepresentation.MultiDocument {
59
105
  const references: Record<string, SchemaRepresentation.Representation> = {}
60
106
  const anonymousReferences = new Map<SchemaAST.AST, string>()
61
107
  const referenceOwners = new Map<string, SchemaAST.AST>()
62
- const valueIds = new Map<unknown, number>()
63
- const canonicalByKey = new Map<string, SchemaAST.AST>()
64
- let nextValueId = 0
65
108
  const buildingReferences = new Set<string>()
66
109
  const visiting = new Set<SchemaAST.AST>()
67
- const visited = new Set<SchemaAST.AST>()
110
+ const occurrences = new Map<SchemaAST.AST, number>()
68
111
  const shared = new Set<SchemaAST.AST>()
69
112
 
70
113
  for (const ast of asts) visit(ast)
@@ -84,35 +127,6 @@ function fromASTs(
84
127
  return candidate
85
128
  }
86
129
 
87
- function getValueId(value: unknown): number {
88
- if (typeof value === "number" && globalThis.Number.isNaN(value)) {
89
- return nextValueId++
90
- }
91
- const found = valueIds.get(value)
92
- if (found !== undefined) return found
93
- const id = nextValueId++
94
- valueIds.set(value, id)
95
- return id
96
- }
97
-
98
- function getIdentityKey(ast: SchemaAST.AST): string {
99
- let identity = ast._tag
100
- for (const [key, value] of Object.entries(ast)) {
101
- if (key !== "_tag" && key !== "context") identity += `:${getValueId(value)}`
102
- }
103
- return identity
104
- }
105
-
106
- function getCanonicalAST(ast: SchemaAST.AST): SchemaAST.AST {
107
- const key = getIdentityKey(ast)
108
- const canonical = canonicalByKey.get(key)
109
- if (canonical === undefined) {
110
- canonicalByKey.set(key, ast)
111
- return ast
112
- }
113
- return canonical
114
- }
115
-
116
130
  function annotateReference(
117
131
  ast: SchemaAST.AST,
118
132
  referenceIdentifier: ReferenceIdentifier,
@@ -143,12 +157,16 @@ function fromASTs(
143
157
 
144
158
  function visit(input: SchemaAST.AST): void {
145
159
  const ast = SchemaAST.getLastEncoding(input)
146
- const owner = getCanonicalAST(ast)
147
- if (visited.has(owner)) {
148
- if (isShareable(ast)) shared.add(owner)
160
+ const owner = SchemaAST.getContextOwner(ast)
161
+ const count = (occurrences.get(owner) ?? 0) + 1
162
+ occurrences.set(owner, count)
163
+ if (count > 1) {
164
+ if (
165
+ !shared.has(owner) &&
166
+ isAnonymousReferenceEligible(owner, count, options?.isAnonymousReferenceAllowed)
167
+ ) shared.add(owner)
149
168
  return
150
169
  }
151
- visited.add(owner)
152
170
  visitChecks(ast.checks)
153
171
  switch (ast._tag) {
154
172
  case "Declaration":
@@ -178,7 +196,7 @@ function fromASTs(
178
196
 
179
197
  function recur(input: SchemaAST.AST): SchemaRepresentation.Representation {
180
198
  const ast = SchemaAST.getLastEncoding(input)
181
- const owner = getCanonicalAST(ast)
199
+ const owner = SchemaAST.getContextOwner(ast)
182
200
  const referenceIdentifier = resolveReferenceIdentifier(input, ast)
183
201
  if (referenceIdentifier !== undefined) {
184
202
  const reference = getReference(referenceIdentifier.identifier, owner)
@@ -172,7 +172,7 @@ export class Asserts<S extends Schema.Constraint> {
172
172
  }) {
173
173
  const decodeUnknownEffect = SchemaParser.decodeUnknownEffect(this.schema)
174
174
  const encodeEffect = SchemaParser.encodeEffect(this.schema)
175
- const arbitrary = Schema.toArbitrary(this.schema)
175
+ const arbitrary = Schema.toArbitrary(this.schema)(FastCheck)
176
176
  return FastCheck.assert(
177
177
  FastCheck.asyncProperty(arbitrary, async (t) => {
178
178
  const r = await Effect.runPromise(
@@ -280,7 +280,7 @@ export class Asserts<S extends Schema.Constraint> {
280
280
  }) {
281
281
  const params = options?.params
282
282
  const is = Schema.is(schema)
283
- const arb = Schema.toArbitrary(schema)
283
+ const arb = Schema.toArbitrary(schema)(FastCheck)
284
284
  FastCheck.assert(FastCheck.property(arb, (a) => is(a)), { numRuns: 20, ...params })
285
285
  }
286
286
  }