effect 4.0.0-beta.14 → 4.0.0-beta.16

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 (82) hide show
  1. package/dist/Channel.d.ts +6 -6
  2. package/dist/Channel.d.ts.map +1 -1
  3. package/dist/Channel.js +4 -4
  4. package/dist/Channel.js.map +1 -1
  5. package/dist/Effect.d.ts +7 -7
  6. package/dist/Effect.d.ts.map +1 -1
  7. package/dist/Effect.js.map +1 -1
  8. package/dist/Schema.d.ts +43 -0
  9. package/dist/Schema.d.ts.map +1 -1
  10. package/dist/Schema.js +37 -0
  11. package/dist/Schema.js.map +1 -1
  12. package/dist/SchemaParser.d.ts +5 -0
  13. package/dist/SchemaParser.d.ts.map +1 -1
  14. package/dist/SchemaParser.js +10 -0
  15. package/dist/SchemaParser.js.map +1 -1
  16. package/dist/SchemaTransformation.d.ts +70 -3
  17. package/dist/SchemaTransformation.d.ts.map +1 -1
  18. package/dist/SchemaTransformation.js +79 -4
  19. package/dist/SchemaTransformation.js.map +1 -1
  20. package/dist/Stream.d.ts +7 -7
  21. package/dist/Stream.d.ts.map +1 -1
  22. package/dist/Stream.js +8 -6
  23. package/dist/Stream.js.map +1 -1
  24. package/dist/Types.d.ts +70 -0
  25. package/dist/Types.d.ts.map +1 -1
  26. package/dist/internal/effect.js +4 -4
  27. package/dist/internal/effect.js.map +1 -1
  28. package/dist/internal/schema/schema.js +1 -0
  29. package/dist/internal/schema/schema.js.map +1 -1
  30. package/dist/unstable/ai/LanguageModel.d.ts +2 -0
  31. package/dist/unstable/ai/LanguageModel.d.ts.map +1 -1
  32. package/dist/unstable/ai/LanguageModel.js.map +1 -1
  33. package/dist/unstable/cli/Command.d.ts +34 -4
  34. package/dist/unstable/cli/Command.d.ts.map +1 -1
  35. package/dist/unstable/cli/Command.js +73 -24
  36. package/dist/unstable/cli/Command.js.map +1 -1
  37. package/dist/unstable/cli/GlobalFlag.d.ts +21 -56
  38. package/dist/unstable/cli/GlobalFlag.d.ts.map +1 -1
  39. package/dist/unstable/cli/GlobalFlag.js +9 -48
  40. package/dist/unstable/cli/GlobalFlag.js.map +1 -1
  41. package/dist/unstable/cli/internal/command.d.ts +3 -0
  42. package/dist/unstable/cli/internal/command.d.ts.map +1 -1
  43. package/dist/unstable/cli/internal/command.js +2 -0
  44. package/dist/unstable/cli/internal/command.js.map +1 -1
  45. package/dist/unstable/cli/internal/help.d.ts +18 -4
  46. package/dist/unstable/cli/internal/help.d.ts.map +1 -1
  47. package/dist/unstable/cli/internal/help.js +61 -7
  48. package/dist/unstable/cli/internal/help.js.map +1 -1
  49. package/dist/unstable/httpapi/HttpApiBuilder.d.ts +10 -4
  50. package/dist/unstable/httpapi/HttpApiBuilder.d.ts.map +1 -1
  51. package/dist/unstable/httpapi/HttpApiBuilder.js +17 -6
  52. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  53. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts +7 -2
  54. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts.map +1 -1
  55. package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
  56. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  57. package/dist/unstable/httpapi/OpenApi.js +11 -1
  58. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  59. package/dist/unstable/reactivity/AtomHttpApi.d.ts +2 -2
  60. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  61. package/dist/unstable/reactivity/AtomRegistry.js +2 -6
  62. package/dist/unstable/reactivity/AtomRegistry.js.map +1 -1
  63. package/package.json +1 -1
  64. package/src/Channel.ts +24 -14
  65. package/src/Effect.ts +30 -8
  66. package/src/Schema.ts +61 -0
  67. package/src/SchemaParser.ts +11 -0
  68. package/src/SchemaTransformation.ts +85 -4
  69. package/src/Stream.ts +46 -22
  70. package/src/Types.ts +66 -0
  71. package/src/internal/effect.ts +41 -14
  72. package/src/internal/schema/schema.ts +1 -0
  73. package/src/unstable/ai/LanguageModel.ts +9 -6
  74. package/src/unstable/cli/Command.ts +119 -31
  75. package/src/unstable/cli/GlobalFlag.ts +36 -114
  76. package/src/unstable/cli/internal/command.ts +5 -0
  77. package/src/unstable/cli/internal/help.ts +103 -22
  78. package/src/unstable/httpapi/HttpApiBuilder.ts +68 -13
  79. package/src/unstable/httpapi/HttpApiEndpoint.ts +13 -4
  80. package/src/unstable/httpapi/OpenApi.ts +18 -1
  81. package/src/unstable/reactivity/AtomHttpApi.ts +2 -2
  82. package/src/unstable/reactivity/AtomRegistry.ts +2 -6
package/src/Stream.ts CHANGED
@@ -48,7 +48,9 @@ import type {
48
48
  ExcludeTag,
49
49
  ExtractReason,
50
50
  ExtractTag,
51
+ NarrowReason,
51
52
  NoInfer,
53
+ OmitReason,
52
54
  ReasonTags,
53
55
  Tags,
54
56
  unassigned
@@ -8092,8 +8094,16 @@ export const catchReason: {
8092
8094
  >(
8093
8095
  errorTag: K,
8094
8096
  reasonTag: RK,
8095
- f: (reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>) => Stream<A2, E2, R2>,
8096
- orElse?: ((reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>) => Stream<A3, E3, R3>) | undefined
8097
+ f: (
8098
+ reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
8099
+ error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
8100
+ ) => Stream<A2, E2, R2>,
8101
+ orElse?:
8102
+ | ((
8103
+ reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>,
8104
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, RK>
8105
+ ) => Stream<A3, E3, R3>)
8106
+ | undefined
8097
8107
  ): <A, R>(
8098
8108
  self: Stream<A, E, R>
8099
8109
  ) => Stream<A | A2 | Exclude<A3, unassigned>, (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3, R | R2 | R3>
@@ -8156,8 +8166,10 @@ export const catchReason: {
8156
8166
  self: Stream<A, E, R>,
8157
8167
  errorTag: K,
8158
8168
  reasonTag: RK,
8159
- f: (reason: ExtractReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>,
8160
- orElse?: ((reason: ExcludeReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>) | undefined
8169
+ f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: NarrowReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>,
8170
+ orElse?:
8171
+ | ((reason: ExcludeReason<ExtractTag<E, K>, RK>, error: OmitReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>)
8172
+ | undefined
8161
8173
  ): Stream<A | A2 | Exclude<A3, unassigned>, (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3, R | R2 | R3>
8162
8174
  } = dual(
8163
8175
  (args) => isStream(args[0]),
@@ -8177,16 +8189,18 @@ export const catchReason: {
8177
8189
  self: Stream<A, E, R>,
8178
8190
  errorTag: K,
8179
8191
  reasonTag: RK,
8180
- f: (reason: ExtractReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>,
8181
- orElse?: ((reason: ExcludeReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>) | undefined
8192
+ f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: NarrowReason<ExtractTag<E, K>, RK>) => Stream<A2, E2, R2>,
8193
+ orElse?:
8194
+ | ((reason: ExcludeReason<ExtractTag<E, K>, RK>, error: OmitReason<ExtractTag<E, K>, RK>) => Stream<A3, E3, R3>)
8195
+ | undefined
8182
8196
  ): Stream<A | A2 | Exclude<A3, unassigned>, (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3, R | R2 | R3> =>
8183
8197
  fromChannel(
8184
8198
  Channel.catchReason(
8185
8199
  toChannel(self),
8186
8200
  errorTag,
8187
8201
  reasonTag,
8188
- (reason) => f(reason).channel,
8189
- orElse && ((reason) => orElse(reason).channel)
8202
+ (reason, error) => f(reason, error).channel,
8203
+ orElse && ((reason, error) => orElse(reason, error).channel)
8190
8204
  )
8191
8205
  ) as any
8192
8206
  )
@@ -8279,7 +8293,8 @@ export const catchReasons: {
8279
8293
  E,
8280
8294
  Cases extends {
8281
8295
  [RK in ReasonTags<ExtractTag<NoInfer<E>, K>>]+?: (
8282
- reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>
8296
+ reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
8297
+ error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
8283
8298
  ) => Stream<any, any, any>
8284
8299
  },
8285
8300
  A2 = unassigned,
@@ -8289,7 +8304,10 @@ export const catchReasons: {
8289
8304
  errorTag: K,
8290
8305
  cases: Cases,
8291
8306
  orElse?:
8292
- | ((reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>) => Stream<A2, E2, R2>)
8307
+ | ((
8308
+ reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
8309
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
8310
+ ) => Stream<A2, E2, R2>)
8293
8311
  | undefined
8294
8312
  ): <A, R>(self: Stream<A, E, R>) => Stream<
8295
8313
  | A
@@ -8356,7 +8374,8 @@ export const catchReasons: {
8356
8374
  K extends Tags<E>,
8357
8375
  Cases extends {
8358
8376
  [RK in ReasonTags<ExtractTag<E, K>>]+?: (
8359
- reason: ExtractReason<ExtractTag<E, K>, RK>
8377
+ reason: ExtractReason<ExtractTag<E, K>, RK>,
8378
+ error: NarrowReason<ExtractTag<E, K>, RK>
8360
8379
  ) => Stream<any, any, any>
8361
8380
  },
8362
8381
  A2 = unassigned,
@@ -8367,7 +8386,10 @@ export const catchReasons: {
8367
8386
  errorTag: K,
8368
8387
  cases: Cases,
8369
8388
  orElse?:
8370
- | ((reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>) => Stream<A2, E2, R2>)
8389
+ | ((
8390
+ reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
8391
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
8392
+ ) => Stream<A2, E2, R2>)
8371
8393
  | undefined
8372
8394
  ): Stream<
8373
8395
  | A
@@ -8387,12 +8409,12 @@ export const catchReasons: {
8387
8409
  }[keyof Cases]
8388
8410
  >
8389
8411
  } = dual((args) => isStream(args[0]), (self, errorTag, cases, orElse) => {
8390
- const handlers: Record<string, (reason: any) => Channel.Channel<any, any, any, any, any, any, any>> = {}
8412
+ const handlers: Record<string, (reason: any, error: any) => Channel.Channel<any, any, any, any, any, any, any>> = {}
8391
8413
  for (const key of Object.keys(cases)) {
8392
8414
  const handler = (cases as any)[key]
8393
- handlers[key] = (reason) => handler(reason).channel
8415
+ handlers[key] = (reason, error) => handler(reason, error).channel
8394
8416
  }
8395
- const orElseHandler = orElse && ((reason: any) => orElse(reason).channel)
8417
+ const orElseHandler = orElse && ((reason: any, error: any) => orElse(reason, error).channel)
8396
8418
  return fromChannel(
8397
8419
  Channel.catchReasons(self.channel, errorTag as any, handlers as any, orElseHandler as any) as Channel.Channel<
8398
8420
  Arr.NonEmptyReadonlyArray<any>,
@@ -13638,12 +13660,15 @@ export const aggregateWithin: {
13638
13660
  * @category Aggregation
13639
13661
  */
13640
13662
  B>()
13663
+ let leftover: Arr.NonEmptyReadonlyArray<A2> | undefined
13641
13664
  const step = yield* Schedule.toStepWithSleep(schedule)
13642
- const stepToBuffer = Effect.suspend(() => step(lastOutput)).pipe(
13643
- Effect.flatMap(() => Queue.offer(buffer, scheduleStep)),
13644
- Effect.flatMap(() => Effect.never),
13645
- Pull.catchDone(() => Cause.done())
13646
- )
13665
+ const stepToBuffer = Effect.suspend(function loop(): Pull.Pull<never, E3, void, R3> {
13666
+ return step(lastOutput).pipe(
13667
+ Effect.flatMap(() => !hadChunk && leftover === undefined ? loop() : Queue.offer(buffer, scheduleStep)),
13668
+ Effect.flatMap(() => Effect.never),
13669
+ Pull.catchDone(() => Cause.done())
13670
+ )
13671
+ })
13647
13672
 
13648
13673
  // buffer -> sink
13649
13674
  const pullFromBuffer: Pull.Pull<
@@ -13653,7 +13678,6 @@ export const aggregateWithin: {
13653
13678
  Effect.flatMap((arr) => arr === scheduleStep ? Cause.done() : Effect.succeed(arr))
13654
13679
  )
13655
13680
 
13656
- let leftover: Arr.NonEmptyReadonlyArray<A2> | undefined
13657
13681
  const sinkUpstream = Effect.suspend((): Pull.Pull<Arr.NonEmptyReadonlyArray<A | A2>, E> => {
13658
13682
  if (leftover !== undefined) {
13659
13683
  const chunk = leftover
@@ -13665,7 +13689,7 @@ export const aggregateWithin: {
13665
13689
  return pullFromBuffer
13666
13690
  })
13667
13691
  const catchSinkHalt = Effect.flatMap(([value, leftover_]: Sink.End<B, A2>) => {
13668
- // ignore the last output if the upsteam only pulled a halt
13692
+ // ignore the last output if the upstream only pulled a halt
13669
13693
  if (!hadChunk && buffer.state._tag === "Done") return Cause.done()
13670
13694
  lastOutput = Option.some(value)
13671
13695
  leftover = leftover_
package/src/Types.ts CHANGED
@@ -927,6 +927,72 @@ export type ExtractReason<E, K extends string> = E extends { readonly reason: in
927
927
  : never
928
928
  : never
929
929
 
930
+ /**
931
+ * Narrows a specific reason variant by its `_tag` from an error's `reason`
932
+ * field.
933
+ *
934
+ * - Use to narrow down to a single reason variant from a nested error type.
935
+ * - Returns `never` if `E` has no matching reason variant.
936
+ *
937
+ * **Example** (Narrowing a reason variant)
938
+ *
939
+ * ```ts
940
+ * import type { Types } from "effect"
941
+ *
942
+ * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number }
943
+ * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number }
944
+ * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError }
945
+ *
946
+ * type Result = Types.NarrowReason<ApiError, "RateLimitError">
947
+ * // ApiError & { readonly reason: { readonly _tag: "RateLimitError"; readonly retryAfter: number } }
948
+ * ```
949
+ *
950
+ * @see {@link ExcludeReason}
951
+ * @see {@link ReasonOf}
952
+ * @see {@link ReasonTags}
953
+ *
954
+ * @since 4.0.0
955
+ * @category types
956
+ */
957
+ export type NarrowReason<E, K extends string> = E extends { readonly reason: infer R }
958
+ ? R extends { readonly _tag: infer T } ? K extends T ? E & { readonly reason: R } : never
959
+ : never
960
+ : never
961
+
962
+ /**
963
+ * Narrows an error's `reason` field to exclude a specific reason variant by
964
+ * its `_tag`.
965
+ *
966
+ * - Use to narrow the error to only the remaining reason variants after
967
+ * excluding the matched one.
968
+ * - Returns `never` if `E` has no `reason` field or no remaining variants.
969
+ *
970
+ * **Example** (Omitting a reason variant)
971
+ *
972
+ * ```ts
973
+ * import type { Types } from "effect"
974
+ *
975
+ * type RateLimitError = { readonly _tag: "RateLimitError"; readonly retryAfter: number }
976
+ * type QuotaError = { readonly _tag: "QuotaError"; readonly limit: number }
977
+ * type ApiError = { readonly _tag: "ApiError"; readonly reason: RateLimitError | QuotaError }
978
+ *
979
+ * type Result = Types.OmitReason<ApiError, "RateLimitError">
980
+ * // ApiError & { readonly reason: { readonly _tag: "QuotaError"; readonly limit: number } }
981
+ * ```
982
+ *
983
+ * @see {@link NarrowReason}
984
+ * @see {@link ExcludeReason}
985
+ * @see {@link ReasonOf}
986
+ * @see {@link ReasonTags}
987
+ *
988
+ * @since 4.0.0
989
+ * @category types
990
+ */
991
+ export type OmitReason<E, K extends string> = E extends { readonly reason: infer R }
992
+ ? R extends { readonly _tag: infer T } ? K extends T ? never : E & { readonly reason: R }
993
+ : never
994
+ : never
995
+
930
996
  /**
931
997
  * Excludes a specific reason variant by its `_tag` from an error's `reason`
932
998
  * field.
@@ -50,7 +50,9 @@ import type {
50
50
  ExcludeTag,
51
51
  ExtractReason,
52
52
  ExtractTag,
53
+ NarrowReason,
53
54
  NoInfer,
55
+ OmitReason,
54
56
  ReasonOf,
55
57
  ReasonTags,
56
58
  Simplify,
@@ -2824,8 +2826,16 @@ export const catchReason: {
2824
2826
  >(
2825
2827
  errorTag: K,
2826
2828
  reasonTag: RK,
2827
- f: (reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>) => Effect.Effect<A2, E2, R2>,
2828
- orElse?: ((reasons: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>) => Effect.Effect<A3, E3, R3>) | undefined
2829
+ f: (
2830
+ reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
2831
+ error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
2832
+ ) => Effect.Effect<A2, E2, R2>,
2833
+ orElse?:
2834
+ | ((
2835
+ reasons: ExcludeReason<ExtractTag<NoInfer<E>, K>, RK>,
2836
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, RK>
2837
+ ) => Effect.Effect<A3, E3, R3>)
2838
+ | undefined
2829
2839
  ): <A, R>(
2830
2840
  self: Effect.Effect<A, E, R>
2831
2841
  ) => Effect.Effect<
@@ -2849,8 +2859,16 @@ export const catchReason: {
2849
2859
  self: Effect.Effect<A, E, R>,
2850
2860
  errorTag: K,
2851
2861
  reasonTag: RK,
2852
- f: (reason: ExtractReason<ExtractTag<E, K>, RK>) => Effect.Effect<A2, E2, R2>,
2853
- orElse?: ((reasons: ExcludeReason<ExtractTag<E, K>, RK>) => Effect.Effect<A3, E3, R3>) | undefined
2862
+ f: (
2863
+ reason: ExtractReason<ExtractTag<E, K>, RK>,
2864
+ error: NarrowReason<ExtractTag<E, K>, RK>
2865
+ ) => Effect.Effect<A2, E2, R2>,
2866
+ orElse?:
2867
+ | ((
2868
+ reasons: ExcludeReason<ExtractTag<E, K>, RK>,
2869
+ error: OmitReason<ExtractTag<E, K>, RK>
2870
+ ) => Effect.Effect<A3, E3, R3>)
2871
+ | undefined
2854
2872
  ): Effect.Effect<
2855
2873
  A | A2 | Exclude<A3, unassigned>,
2856
2874
  (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3,
@@ -2874,8 +2892,13 @@ export const catchReason: {
2874
2892
  self: Effect.Effect<A, E, R>,
2875
2893
  errorTag: K,
2876
2894
  reasonTag: RK,
2877
- f: (reason: ExtractReason<ExtractTag<E, K>, RK>) => Effect.Effect<A2, E2, R2>,
2878
- orElse?: ((reasons: ExcludeReason<ExtractTag<E, K>, RK>) => Effect.Effect<A3, E3, R3>) | undefined
2895
+ f: (reason: ExtractReason<ExtractTag<E, K>, RK>, error: ExtractTag<E, K>) => Effect.Effect<A2, E2, R2>,
2896
+ orElse?:
2897
+ | ((
2898
+ reasons: ExcludeReason<ExtractTag<E, K>, RK>,
2899
+ error: OmitReason<ExtractTag<E, K>, RK>
2900
+ ) => Effect.Effect<A3, E3, R3>)
2901
+ | undefined
2879
2902
  ): Effect.Effect<
2880
2903
  A | A2 | Exclude<A3, unassigned>,
2881
2904
  (A3 extends unassigned ? E : ExcludeTag<E, K>) | E2 | E3,
@@ -2886,8 +2909,8 @@ export const catchReason: {
2886
2909
  ((e: any) => isTagged(e, errorTag) && hasProperty(e, "reason")) as any,
2887
2910
  (e: any): Effect.Effect<A2 | A3, E | E2 | E3, R2 | R3> => {
2888
2911
  const reason = e.reason as any
2889
- if (isTagged(reason, reasonTag)) return f(reason as any)
2890
- return orElse ? internalCall(() => orElse(reason)) : fail(e)
2912
+ if (isTagged(reason, reasonTag)) return f(reason as any, e)
2913
+ return orElse ? internalCall(() => orElse(reason, e)) : fail(e)
2891
2914
  }
2892
2915
  ) as any
2893
2916
  )
@@ -2899,7 +2922,8 @@ export const catchReasons: {
2899
2922
  E,
2900
2923
  Cases extends {
2901
2924
  [RK in ReasonTags<ExtractTag<NoInfer<E>, K>>]+?: (
2902
- reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>
2925
+ reason: ExtractReason<ExtractTag<NoInfer<E>, K>, RK>,
2926
+ error: NarrowReason<ExtractTag<NoInfer<E>, K>, RK>
2903
2927
  ) => Effect.Effect<any, any, any>
2904
2928
  },
2905
2929
  A2 = unassigned,
@@ -2910,7 +2934,8 @@ export const catchReasons: {
2910
2934
  cases: Cases,
2911
2935
  orElse?:
2912
2936
  | ((
2913
- reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
2937
+ reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
2938
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
2914
2939
  ) => Effect.Effect<A2, E2, R2>)
2915
2940
  | undefined
2916
2941
  ): <A, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<
@@ -2937,7 +2962,8 @@ export const catchReasons: {
2937
2962
  K extends Tags<E>,
2938
2963
  Cases extends {
2939
2964
  [RK in ReasonTags<ExtractTag<E, K>>]+?: (
2940
- reason: ExtractReason<ExtractTag<E, K>, RK>
2965
+ reason: ExtractReason<ExtractTag<E, K>, RK>,
2966
+ error: NarrowReason<ExtractTag<E, K>, RK>
2941
2967
  ) => Effect.Effect<any, any, any>
2942
2968
  },
2943
2969
  A2 = unassigned,
@@ -2949,7 +2975,8 @@ export const catchReasons: {
2949
2975
  cases: Cases,
2950
2976
  orElse?:
2951
2977
  | ((
2952
- reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
2978
+ reason: ExcludeReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>,
2979
+ error: OmitReason<ExtractTag<NoInfer<E>, K>, Extract<keyof Cases, string>>
2953
2980
  ) => Effect.Effect<A2, E2, R2>)
2954
2981
  | undefined
2955
2982
  ): Effect.Effect<
@@ -2982,9 +3009,9 @@ export const catchReasons: {
2982
3009
  const reason = e.reason
2983
3010
  keys ??= Object.keys(cases)
2984
3011
  if (keys.includes(reason._tag)) {
2985
- return internalCall(() => (cases as any)[reason._tag](reason))
3012
+ return internalCall(() => (cases as any)[reason._tag](reason, e))
2986
3013
  }
2987
- return orElse ? internalCall(() => orElse(reason)) : fail(e)
3014
+ return orElse ? internalCall(() => orElse(reason, e)) : fail(e)
2988
3015
  }
2989
3016
  )
2990
3017
  })
@@ -31,5 +31,6 @@ export function make<S extends Schema.Top>(ast: S["ast"], options?: object): S {
31
31
  self.ast = ast
32
32
  self.rebuild = (ast: AST.AST) => make(ast, options)
33
33
  self.makeUnsafe = Parser.makeUnsafe(self)
34
+ self.makeOption = Parser.makeOption(self)
34
35
  return self
35
36
  }
@@ -527,13 +527,16 @@ export type ExtractError<Options> = Options extends {
527
527
  * @category utility types
528
528
  */
529
529
  export type ExtractServices<Options> = Options extends {
530
- readonly toolkit: Toolkit.WithHandler<infer _Tools>
531
- }
530
+ readonly disableToolCallResolution: true
531
+ } ? never
532
+ : Options extends {
533
+ readonly toolkit: Toolkit.WithHandler<infer _Tools>
534
+ }
532
535
  // Required for tool call execution
533
- ?
534
- | Tool.ResultEncodingServices<_Tools[keyof _Tools]>
535
- // Required for decoding large language model responses
536
- | Tool.ResultDecodingServices<_Tools[keyof _Tools]>
536
+ ?
537
+ | Tool.ResultEncodingServices<_Tools[keyof _Tools]>
538
+ // Required for decoding large language model responses
539
+ | Tool.ResultDecodingServices<_Tools[keyof _Tools]>
537
540
  : Options extends {
538
541
  readonly toolkit: Effect.Yieldable<
539
542
  Toolkit.Toolkit<infer _Tools>,
@@ -6,7 +6,7 @@ import * as Console from "../../Console.ts"
6
6
  import * as Effect from "../../Effect.ts"
7
7
  import type * as FileSystem from "../../FileSystem.ts"
8
8
  import { dual } from "../../Function.ts"
9
- import * as Layer from "../../Layer.ts"
9
+ import type * as Layer from "../../Layer.ts"
10
10
  import * as Option from "../../Option.ts"
11
11
  import type * as Path from "../../Path.ts"
12
12
  import type { Pipeable } from "../../Pipeable.ts"
@@ -22,7 +22,7 @@ import * as CliOutput from "./CliOutput.ts"
22
22
  import * as GlobalFlag from "./GlobalFlag.ts"
23
23
  import { checkForDuplicateFlags, makeCommand, toImpl, TypeId } from "./internal/command.ts"
24
24
  import { parseConfig } from "./internal/config.ts"
25
- import { getHelpForCommandPath } from "./internal/help.ts"
25
+ import { getGlobalFlagsForCommandPath, getGlobalFlagsForCommandTree, getHelpForCommandPath } from "./internal/help.ts"
26
26
  import * as Lexer from "./internal/lexer.ts"
27
27
  import * as Parser from "./internal/parser.ts"
28
28
  import * as Param from "./Param.ts"
@@ -588,7 +588,7 @@ export const make: {
588
588
  name: Name,
589
589
  config: Config,
590
590
  handler: (config: Command.Config.Infer<Config>) => Effect.Effect<void, E, R>
591
- ): Command<Name, Command.Config.Infer<Config>, E, R>
591
+ ): Command<Name, Command.Config.Infer<Config>, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
592
592
  } = ((
593
593
  name: string,
594
594
  config?: Command.Config,
@@ -661,7 +661,7 @@ export const withHandler: {
661
661
  */
662
662
  <A, R, E>(handler: (value: A) => Effect.Effect<void, E, R>): <Name extends string, XR, XE>(
663
663
  self: Command<Name, A, XE, XR>
664
- ) => Command<Name, A, E, R>
664
+ ) => Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
665
665
  /* ========================================================================== */
666
666
  /* Combinators */
667
667
  /* ========================================================================== */
@@ -693,11 +693,12 @@ export const withHandler: {
693
693
  <Name extends string, A, XR, XE, R, E>(
694
694
  self: Command<Name, A, XE, XR>,
695
695
  handler: (value: A) => Effect.Effect<void, E, R>
696
- ): Command<Name, A, E, R>
696
+ ): Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>>
697
697
  } = dual(2, <Name extends string, A, XR, XE, R, E>(
698
698
  self: Command<Name, A, XE, XR>,
699
699
  handler: (value: A) => Effect.Effect<void, E, R>
700
- ): Command<Name, A, E, R> => makeCommand({ ...toImpl(self), handle: handler }))
700
+ ): Command<Name, A, E, Exclude<R, GlobalFlag.BuiltInSettingContext>> =>
701
+ makeCommand({ ...toImpl(self), handle: handler }))
701
702
 
702
703
  interface SubcommandGroupInternal {
703
704
  readonly group: string | undefined
@@ -942,6 +943,7 @@ export const withSubcommands: {
942
943
  shortDescription: impl.shortDescription,
943
944
  alias: impl.alias,
944
945
  annotations: impl.annotations,
946
+ globalFlags: impl.globalFlags,
945
947
  examples: impl.examples,
946
948
  service: impl.service,
947
949
  subcommands: normalized.groups,
@@ -950,7 +952,50 @@ export const withSubcommands: {
950
952
  })
951
953
  })
952
954
 
955
+ /**
956
+ * Declares global flags for a command scope.
957
+ *
958
+ * Declared global flags apply to the command and all of its descendants.
959
+ *
960
+ * @since 4.0.0
961
+ * @category combinators
962
+ */
963
+ export const withGlobalFlags: {
964
+ /**
965
+ * Declares global flags for a command scope.
966
+ *
967
+ * Declared global flags apply to the command and all of its descendants.
968
+ *
969
+ * @since 4.0.0
970
+ * @category combinators
971
+ */
972
+ <const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(globalFlags: GlobalFlags): <Name extends string, Input, E, R>(
973
+ self: Command<Name, Input, E, R>
974
+ ) => Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>>
975
+ /**
976
+ * Declares global flags for a command scope.
977
+ *
978
+ * Declared global flags apply to the command and all of its descendants.
979
+ *
980
+ * @since 4.0.0
981
+ * @category combinators
982
+ */
983
+ <Name extends string, Input, E, R, const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(self: Command<Name, Input, E, R>, globalFlags: GlobalFlags): Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>>
984
+ } = dual(
985
+ 2,
986
+ <Name extends string, Input, E, R, const GlobalFlags extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>>(
987
+ self: Command<Name, Input, E, R>,
988
+ globalFlags: GlobalFlags
989
+ ): Command<Name, Input, E, Exclude<R, ExtractGlobalFlagContext<GlobalFlags>>> => {
990
+ const impl = toImpl(self)
991
+ const next = Array.from(new Set([...impl.globalFlags, ...globalFlags]))
992
+ return makeCommand({ ...impl, globalFlags: next })
993
+ }
994
+ )
995
+
953
996
  // Type extractors for subcommand arrays - T[number] gives union of all elements
997
+ type ExtractGlobalFlagContext<T extends ReadonlyArray<GlobalFlag.GlobalFlag<any>>> = T[number] extends
998
+ GlobalFlag.Setting<infer Id, any> ? GlobalFlag.Setting.Identifier<Id> : never
954
999
  type ExtractSubcommand<T> = T extends Command<any, any, any, any> ? T
955
1000
  : T extends Command.SubcommandGroup<infer Commands> ? Commands[number]
956
1001
  : never
@@ -1534,6 +1579,45 @@ export const provideEffectDiscard: {
1534
1579
  /* Execution */
1535
1580
  /* ========================================================================== */
1536
1581
 
1582
+ const getOutOfScopeGlobalFlagErrors = (
1583
+ allFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>,
1584
+ activeFlags: ReadonlyArray<GlobalFlag.GlobalFlag<any>>,
1585
+ flagMap: Record<string, ReadonlyArray<string>>,
1586
+ commandPath: ReadonlyArray<string>
1587
+ ): ReadonlyArray<CliError.CliError> => {
1588
+ const activeSet = new Set(activeFlags)
1589
+ const errors: Array<CliError.CliError> = []
1590
+ const seen = new Set<string>()
1591
+
1592
+ for (const flag of allFlags) {
1593
+ if (activeSet.has(flag)) {
1594
+ continue
1595
+ }
1596
+
1597
+ const singles = Param.extractSingleParams(flag.flag)
1598
+ for (const single of singles) {
1599
+ const entries = flagMap[single.name]
1600
+ if (!entries || entries.length === 0) {
1601
+ continue
1602
+ }
1603
+ const option = `--${single.name}`
1604
+ if (seen.has(option)) {
1605
+ continue
1606
+ }
1607
+ seen.add(option)
1608
+ errors.push(
1609
+ new CliError.UnrecognizedOption({
1610
+ option,
1611
+ suggestions: [],
1612
+ command: commandPath
1613
+ })
1614
+ )
1615
+ }
1616
+ }
1617
+
1618
+ return errors
1619
+ }
1620
+
1537
1621
  const showHelp = <Name extends string, Input, E, R>(
1538
1622
  command: Command<Name, Input, E, R>,
1539
1623
  commandPath: ReadonlyArray<string>,
@@ -1541,7 +1625,7 @@ const showHelp = <Name extends string, Input, E, R>(
1541
1625
  ): Effect.Effect<void, never, Environment> =>
1542
1626
  Effect.gen(function*() {
1543
1627
  const formatter = yield* CliOutput.Formatter
1544
- const helpDoc = yield* getHelpForCommandPath(command, commandPath, GlobalFlag.Registry)
1628
+ const helpDoc = yield* getHelpForCommandPath(command, commandPath, GlobalFlag.BuiltIns)
1545
1629
  yield* Console.log(formatter.formatHelpDoc(helpDoc))
1546
1630
  if (errors && errors.length > 0) {
1547
1631
  yield* Console.error(formatter.formatErrors(errors))
@@ -1698,11 +1782,11 @@ export const runWith = <const Name extends string, Input, E, R>(
1698
1782
  function*(args: ReadonlyArray<string>) {
1699
1783
  const { tokens, trailingOperands } = Lexer.lex(args)
1700
1784
 
1701
- // 1. Read global flags from registry
1702
- const flags = Array.from(yield* GlobalFlag.Registry)
1785
+ // 1. Collect known global flags from the command tree
1786
+ const allFlags = getGlobalFlagsForCommandTree(command, GlobalFlag.BuiltIns)
1703
1787
 
1704
1788
  // 2. Extract global flag tokens
1705
- const allFlagParams = flags.flatMap((f) => Param.extractSingleParams(f.flag))
1789
+ const allFlagParams = allFlags.flatMap((f) => Param.extractSingleParams(f.flag))
1706
1790
  const globalRegistry = Parser.createFlagRegistry(allFlagParams.filter(Param.isFlagParam))
1707
1791
  const { flagMap, remainder } = Parser.consumeKnownFlags(tokens, globalRegistry)
1708
1792
  const emptyArgs: Param.ParsedArgs = { flags: flagMap, arguments: [] }
@@ -1711,9 +1795,17 @@ export const runWith = <const Name extends string, Input, E, R>(
1711
1795
  const parsedArgs = yield* Parser.parseArgs({ tokens: remainder, trailingOperands }, command)
1712
1796
  const commandPath = [command.name, ...Parser.getCommandPath(parsedArgs)] as const
1713
1797
  const handlerCtx: GlobalFlag.HandlerContext = { command, commandPath, version: config.version }
1798
+ const activeFlags = getGlobalFlagsForCommandPath(command, commandPath, GlobalFlag.BuiltIns)
1714
1799
 
1715
- // 4. Process action flags first present action wins, then exit
1716
- for (const flag of flags) {
1800
+ // 4. Reject globals that were passed outside the active command scope
1801
+ const outOfScopeErrors = getOutOfScopeGlobalFlagErrors(allFlags, activeFlags, flagMap, commandPath)
1802
+ if (outOfScopeErrors.length > 0) {
1803
+ const parseErrors = parsedArgs.errors ?? []
1804
+ return yield* showHelp(command, commandPath, [...outOfScopeErrors, ...parseErrors])
1805
+ }
1806
+
1807
+ // 5. Process action flags — first present action wins, then exit
1808
+ for (const flag of activeFlags) {
1717
1809
  if (flag._tag !== "Action") continue
1718
1810
  const singles = Param.extractSingleParams(flag.flag)
1719
1811
  const hasEntry = singles.some((s) => {
@@ -1726,7 +1818,7 @@ export const runWith = <const Name extends string, Input, E, R>(
1726
1818
  return
1727
1819
  }
1728
1820
 
1729
- // 5. Handle parsing errors
1821
+ // 6. Handle parsing errors
1730
1822
  if (parsedArgs.errors && parsedArgs.errors.length > 0) {
1731
1823
  return yield* showHelp(command, commandPath, parsedArgs.errors)
1732
1824
  }
@@ -1735,29 +1827,25 @@ export const runWith = <const Name extends string, Input, E, R>(
1735
1827
  return yield* showHelp(command, commandPath, [parseResult.failure])
1736
1828
  }
1737
1829
 
1738
- // 6. Provide setting values
1739
- let contextLayer: Layer.Layer<never> = Layer.empty
1740
- for (const flag of flags) {
1830
+ // 7. Provide setting values
1831
+ let program = commandImpl.handle(parseResult.success, [command.name])
1832
+ for (const flag of activeFlags) {
1741
1833
  if (flag._tag !== "Setting") continue
1742
1834
  const [, value] = yield* flag.flag.parse(emptyArgs)
1743
- contextLayer = Layer.merge(contextLayer, Layer.succeed(flag, value))
1835
+ program = Effect.provideService(program, flag, value)
1744
1836
  }
1745
1837
 
1746
- // 7. Apply built-in setting behavior
1747
- if (flags.includes(GlobalFlag.LogLevel)) {
1748
- const [, logLevel] = yield* GlobalFlag.LogLevel.flag.parse(emptyArgs)
1749
- contextLayer = Layer.merge(
1750
- contextLayer,
1751
- Option.match(logLevel, {
1752
- onNone: () => Layer.empty,
1753
- onSome: (level) => Layer.succeed(References.MinimumLogLevel, level)
1754
- })
1755
- )
1756
- }
1838
+ const [, logLevel] = yield* GlobalFlag.LogLevel.flag.parse(emptyArgs)
1839
+ program = Effect.provideService(program, GlobalFlag.LogLevel, logLevel)
1840
+
1841
+ // 8. Apply built-in setting behavior
1842
+ const services = Option.match(logLevel, {
1843
+ onNone: () => ServiceMap.empty(),
1844
+ onSome: (level) => ServiceMap.make(References.MinimumLogLevel, level)
1845
+ })
1757
1846
 
1758
- // 8. Run command handler with composed context
1759
- const program = commandImpl.handle(parseResult.success, [command.name])
1760
- yield* Effect.provide(program, contextLayer)
1847
+ // 9. Run command handler with composed context
1848
+ yield* Effect.provideServices(program, services)
1761
1849
  },
1762
1850
  Effect.catchIf(
1763
1851
  ((error: any) =>