effect 4.0.0-beta.37 → 4.0.0-beta.39

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 (104) hide show
  1. package/dist/ErrorReporter.d.ts.map +1 -1
  2. package/dist/ErrorReporter.js +3 -2
  3. package/dist/ErrorReporter.js.map +1 -1
  4. package/dist/Queue.d.ts +5 -2
  5. package/dist/Queue.d.ts.map +1 -1
  6. package/dist/Queue.js +5 -2
  7. package/dist/Queue.js.map +1 -1
  8. package/dist/References.d.ts +235 -224
  9. package/dist/References.d.ts.map +1 -1
  10. package/dist/References.js +234 -246
  11. package/dist/References.js.map +1 -1
  12. package/dist/Schedule.d.ts +6 -202
  13. package/dist/Schedule.d.ts.map +1 -1
  14. package/dist/Schedule.js +6 -71
  15. package/dist/Schedule.js.map +1 -1
  16. package/dist/Schema.d.ts +6 -6
  17. package/dist/Schema.d.ts.map +1 -1
  18. package/dist/Schema.js +9 -13
  19. package/dist/Schema.js.map +1 -1
  20. package/dist/SchemaAST.d.ts +5 -0
  21. package/dist/SchemaAST.d.ts.map +1 -1
  22. package/dist/SchemaAST.js.map +1 -1
  23. package/dist/SchemaParser.d.ts.map +1 -1
  24. package/dist/SchemaParser.js +7 -2
  25. package/dist/SchemaParser.js.map +1 -1
  26. package/dist/Stream.d.ts +1 -1
  27. package/dist/Stream.js +1 -1
  28. package/dist/Struct.d.ts +7 -7
  29. package/dist/Struct.d.ts.map +1 -1
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.js +1 -1
  32. package/dist/internal/effect.js +1 -5
  33. package/dist/internal/effect.js.map +1 -1
  34. package/dist/internal/references.d.ts +2 -0
  35. package/dist/internal/references.d.ts.map +1 -0
  36. package/dist/internal/references.js +51 -0
  37. package/dist/internal/references.js.map +1 -0
  38. package/dist/unstable/cluster/EntityAddress.d.ts.map +1 -1
  39. package/dist/unstable/cluster/EntityAddress.js +1 -1
  40. package/dist/unstable/cluster/EntityAddress.js.map +1 -1
  41. package/dist/unstable/cluster/Runner.d.ts.map +1 -1
  42. package/dist/unstable/cluster/Runner.js +1 -1
  43. package/dist/unstable/cluster/Runner.js.map +1 -1
  44. package/dist/unstable/cluster/RunnerAddress.d.ts.map +1 -1
  45. package/dist/unstable/cluster/RunnerAddress.js +1 -1
  46. package/dist/unstable/cluster/RunnerAddress.js.map +1 -1
  47. package/dist/unstable/cluster/ShardId.js +3 -3
  48. package/dist/unstable/cluster/ShardId.js.map +1 -1
  49. package/dist/unstable/eventlog/EventJournal.js +2 -2
  50. package/dist/unstable/eventlog/EventJournal.js.map +1 -1
  51. package/dist/unstable/eventlog/EventLog.js +1 -1
  52. package/dist/unstable/eventlog/EventLog.js.map +1 -1
  53. package/dist/unstable/eventlog/SqlEventLogJournal.js +2 -2
  54. package/dist/unstable/eventlog/SqlEventLogJournal.js.map +1 -1
  55. package/dist/unstable/httpapi/HttpApiBuilder.js +9 -8
  56. package/dist/unstable/httpapi/HttpApiBuilder.js.map +1 -1
  57. package/dist/unstable/httpapi/HttpApiClient.d.ts +36 -20
  58. package/dist/unstable/httpapi/HttpApiClient.d.ts.map +1 -1
  59. package/dist/unstable/httpapi/HttpApiClient.js +49 -18
  60. package/dist/unstable/httpapi/HttpApiClient.js.map +1 -1
  61. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts +186 -67
  62. package/dist/unstable/httpapi/HttpApiEndpoint.d.ts.map +1 -1
  63. package/dist/unstable/httpapi/HttpApiEndpoint.js +44 -29
  64. package/dist/unstable/httpapi/HttpApiEndpoint.js.map +1 -1
  65. package/dist/unstable/httpapi/HttpApiSchema.d.ts +5 -0
  66. package/dist/unstable/httpapi/HttpApiSchema.d.ts.map +1 -1
  67. package/dist/unstable/httpapi/HttpApiSchema.js +20 -2
  68. package/dist/unstable/httpapi/HttpApiSchema.js.map +1 -1
  69. package/dist/unstable/httpapi/OpenApi.d.ts.map +1 -1
  70. package/dist/unstable/httpapi/OpenApi.js +2 -5
  71. package/dist/unstable/httpapi/OpenApi.js.map +1 -1
  72. package/dist/unstable/reactivity/AtomHttpApi.d.ts +11 -7
  73. package/dist/unstable/reactivity/AtomHttpApi.d.ts.map +1 -1
  74. package/dist/unstable/reactivity/AtomHttpApi.js +6 -6
  75. package/dist/unstable/reactivity/AtomHttpApi.js.map +1 -1
  76. package/dist/unstable/schema/VariantSchema.d.ts +1 -1
  77. package/dist/unstable/schema/VariantSchema.d.ts.map +1 -1
  78. package/package.json +1 -1
  79. package/src/ErrorReporter.ts +3 -2
  80. package/src/Queue.ts +5 -2
  81. package/src/References.ts +276 -285
  82. package/src/Schedule.ts +7 -212
  83. package/src/Schema.ts +8 -12
  84. package/src/SchemaAST.ts +6 -0
  85. package/src/SchemaParser.ts +7 -2
  86. package/src/Stream.ts +1 -1
  87. package/src/Struct.ts +7 -7
  88. package/src/index.ts +1 -1
  89. package/src/internal/effect.ts +14 -21
  90. package/src/internal/references.ts +72 -0
  91. package/src/unstable/cluster/EntityAddress.ts +1 -1
  92. package/src/unstable/cluster/Runner.ts +1 -1
  93. package/src/unstable/cluster/RunnerAddress.ts +1 -1
  94. package/src/unstable/cluster/ShardId.ts +2 -2
  95. package/src/unstable/eventlog/EventJournal.ts +2 -2
  96. package/src/unstable/eventlog/EventLog.ts +1 -1
  97. package/src/unstable/eventlog/SqlEventLogJournal.ts +2 -2
  98. package/src/unstable/httpapi/HttpApiBuilder.ts +15 -9
  99. package/src/unstable/httpapi/HttpApiClient.ts +118 -55
  100. package/src/unstable/httpapi/HttpApiEndpoint.ts +164 -36
  101. package/src/unstable/httpapi/HttpApiSchema.ts +20 -2
  102. package/src/unstable/httpapi/OpenApi.ts +2 -6
  103. package/src/unstable/reactivity/AtomHttpApi.ts +22 -17
  104. package/src/unstable/schema/VariantSchema.ts +1 -1
@@ -199,7 +199,7 @@ export const make = (options?: {
199
199
  event,
200
200
  primaryKey,
201
201
  payload
202
- }, { disableValidation: true })
202
+ }, { disableChecks: true })
203
203
  yield* insertEntry(toEntryRow(entry))
204
204
  const value = yield* effect(entry)
205
205
  yield* PubSub.publish(pubsub, entry)
@@ -280,7 +280,7 @@ const toEntry = (row: EntryRow): EventJournal.Entry =>
280
280
  event: row.event,
281
281
  primaryKey: row.primary_key,
282
282
  payload: row.payload
283
- }, { disableValidation: true })
283
+ }, { disableChecks: true })
284
284
 
285
285
  const toEntryRow = (entry: EventJournal.Entry): EntryRow => ({
286
286
  id: entry.id,
@@ -652,7 +652,7 @@ const applyMiddleware = <A extends Effect.Effect<any, any, any>>(
652
652
  }
653
653
 
654
654
  const securityMiddlewareCache = new WeakMap<
655
- any,
655
+ object,
656
656
  (effect: Effect.Effect<any, any, any>, options: any) => Effect.Effect<any, any, any>
657
657
  >()
658
658
 
@@ -660,13 +660,14 @@ const makeSecurityMiddleware = (
660
660
  key: HttpApiMiddleware.AnyServiceSecurity,
661
661
  service: HttpApiMiddleware.HttpApiMiddlewareSecurity<any, any, any, any>
662
662
  ): (effect: Effect.Effect<any, any, any>, options: any) => Effect.Effect<any, any, any> => {
663
- if (securityMiddlewareCache.has(key)) {
664
- return securityMiddlewareCache.get(key)!
663
+ const cached = securityMiddlewareCache.get(service)
664
+ if (cached !== undefined) {
665
+ return cached
665
666
  }
666
667
 
667
- const entries = Object.entries(key.security).map(([key, security]) => ({
668
+ const entries = Object.entries(key.security).map(([securityKey, security]) => ({
668
669
  decode: securityDecode(security),
669
- middleware: service[key]
670
+ middleware: service[securityKey]
670
671
  }))
671
672
  if (entries.length === 0) {
672
673
  return identity
@@ -694,7 +695,7 @@ const makeSecurityMiddleware = (
694
695
  return yield* lastResult!.asEffect()
695
696
  })
696
697
 
697
- securityMiddlewareCache.set(key, middleware)
698
+ securityMiddlewareCache.set(service, middleware)
698
699
  return middleware
699
700
  }
700
701
 
@@ -734,7 +735,11 @@ function getResponseTransformation(
734
735
  schema: Schema.Top
735
736
  ): Transformation.Transformation<unknown, Response.HttpServerResponse> {
736
737
  const ast = schema.ast
737
- const encode = getResponseEncode(getStatus(ast), HttpApiSchema.getResponseEncoding(ast))
738
+ const encode = getResponseEncode(
739
+ getStatus(ast),
740
+ HttpApiSchema.getResponseEncoding(ast),
741
+ HttpApiSchema.isNoContent(ast)
742
+ )
738
743
 
739
744
  return Transformation.transformOrFail({
740
745
  decode: (res) => Effect.fail(new Issue.Forbidden(Option.some(res), { message: "Encode only schema" })),
@@ -744,12 +749,13 @@ function getResponseTransformation(
744
749
 
745
750
  function getResponseEncode<E>(
746
751
  status: number,
747
- encoding: HttpApiSchema.ResponseEncoding
752
+ encoding: HttpApiSchema.ResponseEncoding,
753
+ isNoContent: boolean
748
754
  ): (e: E) => Effect.Effect<Response.HttpServerResponse, Issue.InvalidValue, never> {
749
755
  switch (encoding._tag) {
750
756
  case "Json": {
751
757
  return ((e) => {
752
- if (e === undefined) {
758
+ if (e === undefined || isNoContent) {
753
759
  return Effect.succeed(Response.empty({ status }))
754
760
  }
755
761
  try {
@@ -8,7 +8,7 @@ import { identity } from "../../Function.ts"
8
8
  import * as Option from "../../Option.ts"
9
9
  import * as Predicate from "../../Predicate.ts"
10
10
  import * as Schema from "../../Schema.ts"
11
- import type * as AST from "../../SchemaAST.ts"
11
+ import * as AST from "../../SchemaAST.ts"
12
12
  import * as Issue from "../../SchemaIssue.ts"
13
13
  import * as Transformation from "../../SchemaTransformation.ts"
14
14
  import type * as ServiceMap from "../../ServiceMap.ts"
@@ -59,6 +59,21 @@ export type ForApi<Api extends HttpApi.Any, E = BadRequest, R = never> = Api ext
59
59
  * @category models
60
60
  */
61
61
  export declare namespace Client {
62
+ /**
63
+ * @since 4.0.0
64
+ * @category models
65
+ */
66
+ export type ResponseMode = HttpApiEndpoint.ClientResponseMode
67
+
68
+ /**
69
+ * @since 4.0.0
70
+ * @category models
71
+ */
72
+ export type Response<Success, Mode extends ResponseMode> = [Mode] extends ["decoded-and-response"]
73
+ ? [Success, HttpClientResponse.HttpClientResponse]
74
+ : [Mode] extends ["response-only"] ? HttpClientResponse.HttpClientResponse
75
+ : Success
76
+
62
77
  /**
63
78
  * @since 4.0.0
64
79
  * @category models
@@ -88,23 +103,21 @@ export declare namespace Client {
88
103
  infer _Middleware,
89
104
  infer _MR
90
105
  >
91
- ] ? <WithResponse extends boolean = false>(
92
- request: Simplify<HttpApiEndpoint.ClientRequest<_Params, _Query, _Payload, _Headers, WithResponse>>
106
+ ] ? <Mode extends ResponseMode = ResponseMode>(
107
+ request: Simplify<HttpApiEndpoint.ClientRequest<_Params, _Query, _Payload, _Headers, Mode>>
93
108
  ) => Effect.Effect<
94
- WithResponse extends true ? [_Success["Type"], HttpClientResponse.HttpClientResponse] : _Success["Type"],
95
- | _Error["Type"]
109
+ Response<_Success["Type"], Mode>,
96
110
  | HttpApiMiddleware.Error<_Middleware>
97
111
  | HttpApiMiddleware.ClientError<_Middleware>
98
112
  | E
99
113
  | HttpClientError.HttpClientError
100
- | Schema.SchemaError,
114
+ | ([Mode] extends ["response-only"] ? never : _Error["Type"] | Schema.SchemaError),
101
115
  | R
102
116
  | _Params["EncodingServices"]
103
117
  | _Query["EncodingServices"]
104
118
  | _Payload["EncodingServices"]
105
119
  | _Headers["EncodingServices"]
106
- | _Success["DecodingServices"]
107
- | _Error["DecodingServices"]
120
+ | ([Mode] extends ["response-only"] ? never : _Success["DecodingServices"] | _Error["DecodingServices"])
108
121
  > :
109
122
  never
110
123
 
@@ -120,24 +133,11 @@ export declare namespace Client {
120
133
  never
121
134
  }
122
135
 
123
- type ApiGroups<Api extends HttpApi.Any> = Api extends HttpApi.HttpApi<infer _ApiId, infer Groups> ? Groups : never
124
-
125
- type EndpointId<Endpoint extends HttpApiEndpoint.Any> = Endpoint extends {
126
- readonly method: infer Method extends HttpMethod.HttpMethod
127
- readonly path: infer Path extends string
128
- } ? `${Method} ${Path}`
129
- : never
130
-
131
- type EndpointWithId<Endpoints extends HttpApiEndpoint.Any, Id extends string> = Id extends
132
- `${infer Method extends HttpMethod.HttpMethod} ${infer Path extends string}` ?
133
- Extract<Endpoints, { readonly method: Method; readonly path: Path }> :
134
- never
135
-
136
136
  type UrlBuilderRequest<Endpoint extends HttpApiEndpoint.Any> = (
137
- & ([HttpApiEndpoint.Params<Endpoint>["Encoded"]] extends [never] ? {}
138
- : { readonly params: HttpApiEndpoint.Params<Endpoint>["Encoded"] })
139
- & ([HttpApiEndpoint.Query<Endpoint>["Encoded"]] extends [never] ? {}
140
- : { readonly query: HttpApiEndpoint.Query<Endpoint>["Encoded"] })
137
+ & ([HttpApiEndpoint.Params<Endpoint>["Type"]] extends [never] ? {}
138
+ : { readonly params: HttpApiEndpoint.Params<Endpoint>["Type"] })
139
+ & ([HttpApiEndpoint.Query<Endpoint>["Type"]] extends [never] ? {}
140
+ : { readonly query: HttpApiEndpoint.Query<Endpoint>["Type"] })
141
141
  ) extends infer Request ? keyof Request extends never ? void | undefined : Request
142
142
  : never
143
143
 
@@ -149,15 +149,33 @@ type UrlBuilderArgs<Endpoint extends HttpApiEndpoint.Any> = [UrlBuilderRequest<E
149
149
  * @since 4.0.0
150
150
  * @category models
151
151
  */
152
- export type UrlBuilder<Api extends HttpApi.Any> = <
153
- const GroupName extends HttpApiGroup.Name<ApiGroups<Api>>,
154
- const Id extends EndpointId<HttpApiGroup.EndpointsWithName<ApiGroups<Api>, GroupName>>
155
- >(
156
- group: GroupName,
157
- endpoint: Id,
158
- ...args: UrlBuilderArgs<EndpointWithId<HttpApiGroup.EndpointsWithName<ApiGroups<Api>, GroupName>, Id>>
152
+ export type UrlBuilder<Api extends HttpApi.Any> = Api extends HttpApi.HttpApi<infer _ApiId, infer Groups> ? Simplify<
153
+ & {
154
+ readonly [Group in Extract<Groups, { readonly topLevel: false }> as HttpApiGroup.Name<Group>]: UrlBuilderGroup<
155
+ HttpApiGroup.Endpoints<Group>
156
+ >
157
+ }
158
+ & {
159
+ readonly [Method in UrlBuilderTopLevelMethods<Groups> as Method[0]]: Method[1]
160
+ }
161
+ >
162
+ : never
163
+
164
+ type UrlBuilderGroup<Endpoints extends HttpApiEndpoint.Any> = {
165
+ readonly [Endpoint in Endpoints as HttpApiEndpoint.Name<Endpoint>]: UrlBuilderMethod<Endpoint>
166
+ }
167
+
168
+ type UrlBuilderMethod<Endpoint extends HttpApiEndpoint.Any> = (
169
+ ...args: UrlBuilderArgs<Endpoint>
159
170
  ) => string
160
171
 
172
+ type UrlBuilderTopLevelMethods<Groups extends HttpApiGroup.Any> = Extract<Groups, { readonly topLevel: true }> extends
173
+ HttpApiGroup.HttpApiGroup<infer _Id, infer _Endpoints, infer _TopLevel> ?
174
+ _Endpoints extends infer Endpoint extends HttpApiEndpoint.Any ?
175
+ [HttpApiEndpoint.Name<Endpoint>, UrlBuilderMethod<Endpoint>]
176
+ : never :
177
+ never
178
+
161
179
  const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>(
162
180
  api: HttpApi.HttpApi<ApiId, Groups>,
163
181
  options: {
@@ -268,7 +286,7 @@ const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>
268
286
  const payloadSchemas = HttpApiEndpoint.getPayloadSchemas(endpoint)
269
287
  const encodePayload = Arr.isArrayNonEmpty(payloadSchemas) ?
270
288
  HttpMethod.hasBody(endpoint.method)
271
- ? Schema.encodeUnknownEffect(getEncodePayloadSchema(payloadSchemas))
289
+ ? Schema.encodeUnknownEffect(getEncodePayloadSchema(payloadSchemas, endpoint.method))
272
290
  : Schema.encodeUnknownEffect(Schema.Union(payloadSchemas)) :
273
291
  undefined
274
292
 
@@ -283,7 +301,7 @@ const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>
283
301
  readonly query: unknown
284
302
  readonly payload: unknown
285
303
  readonly headers: Record<string, string> | undefined
286
- readonly withResponse?: boolean
304
+ readonly responseMode?: HttpApiEndpoint.ClientResponseMode
287
305
  } | undefined
288
306
  ) {
289
307
  let httpRequest = HttpClientRequest.make(endpoint.method)(endpoint.path)
@@ -331,11 +349,15 @@ const makeClient = <ApiId extends string, Groups extends HttpApiGroup.Any, E, R>
331
349
  middlewareKeys.length - 1
332
350
  )
333
351
 
352
+ if (request?.responseMode === "response-only") {
353
+ return response
354
+ }
355
+
334
356
  const value = yield* (options.transformResponse === undefined
335
357
  ? decodeResponse(response)
336
358
  : options.transformResponse(decodeResponse(response)))
337
359
 
338
- return request?.withResponse === true ? [value, response] : value
360
+ return request?.responseMode === "decoded-and-response" ? [value, response] : value
339
361
  })
340
362
 
341
363
  options.onEndpoint({
@@ -477,7 +499,7 @@ export const endpoint = <
477
499
  }
478
500
 
479
501
  /**
480
- * Creates a type-safe URL builder keyed by `${method} ${path}`.
502
+ * Creates a type-safe URL builder that mirrors `HttpApiClient.make`.
481
503
  *
482
504
  * @example
483
505
  * ```ts
@@ -492,11 +514,11 @@ export const endpoint = <
492
514
  * )
493
515
  * )
494
516
  *
495
- * const buildUrl = HttpApiClient.urlBuilder<typeof Api>({
517
+ * const buildUrl = HttpApiClient.urlBuilder(Api, {
496
518
  * baseUrl: "https://api.example.com"
497
519
  * })
498
520
  *
499
- * buildUrl("users", "GET /users/:id", {
521
+ * buildUrl.users.getUser({
500
522
  * params: { id: "123" }
501
523
  * })
502
524
  * //=> "https://api.example.com/users/123"
@@ -504,19 +526,45 @@ export const endpoint = <
504
526
  * @since 4.0.0
505
527
  * @category constructors
506
528
  */
507
- export const urlBuilder = <Api extends HttpApi.Any>(options?: {
529
+ export const urlBuilder = <Api extends HttpApi.Any>(api: Api, options?: {
508
530
  readonly baseUrl?: URL | string | undefined
509
531
  }): UrlBuilder<Api> => {
510
- return ((_: string, endpoint: string, request?: {
511
- readonly params?: Record<string, string | undefined> | undefined
512
- readonly query?: UrlParams.Input | undefined
513
- }) => {
514
- const path = endpoint.slice(endpoint.indexOf(" ") + 1)
515
- const withParams = request?.params === undefined ? path : compilePath(path)(request.params)
516
- const query = request?.query === undefined ? "" : UrlParams.toString(UrlParams.fromInput(request.query))
517
- const url = query === "" ? withParams : `${withParams}?${query}`
518
- return options?.baseUrl === undefined ? url : new URL(url, options.baseUrl.toString()).toString()
519
- }) as UrlBuilder<Api>
532
+ const builder: Record<string, any> = {}
533
+
534
+ HttpApi.reflect(api as unknown as HttpApi.AnyWithProps, {
535
+ onGroup({ group }) {
536
+ if (group.topLevel) return
537
+ builder[group.identifier] = {}
538
+ },
539
+ onEndpoint({ group, endpoint }) {
540
+ const makeUrl = compilePath(endpoint.path)
541
+ const encodeParams = endpoint.params === undefined
542
+ ? undefined
543
+ : Schema.encodeSync(endpoint.params as Schema.Encoder<unknown>)
544
+ const encodeQuery = endpoint.query === undefined
545
+ ? undefined
546
+ : Schema.encodeSync(endpoint.query as Schema.Encoder<unknown>)
547
+
548
+ const endpointBuilder = (request?: {
549
+ readonly params?: unknown
550
+ readonly query?: unknown
551
+ }) => {
552
+ const params = request?.params
553
+ const path = params === undefined
554
+ ? endpoint.path
555
+ : makeUrl((encodeParams === undefined ? params : encodeParams(params)) as Record<string, string | undefined>)
556
+ const queryInput = request?.query === undefined
557
+ ? undefined
558
+ : (encodeQuery === undefined ? request.query : encodeQuery(request.query)) as UrlParams.Input
559
+ const query = queryInput === undefined ? "" : UrlParams.toString(UrlParams.fromInput(queryInput))
560
+ const url = query === "" ? path : `${path}?${query}`
561
+ return options?.baseUrl === undefined ? url : new URL(url, options.baseUrl.toString()).toString()
562
+ }
563
+ ;(group.topLevel ? builder : builder[group.identifier])[endpoint.name] = endpointBuilder
564
+ }
565
+ })
566
+
567
+ return builder as UrlBuilder<Api>
520
568
  }
521
569
 
522
570
  // ----------------------------------------------------------------------------
@@ -608,8 +656,19 @@ function toCodecArrayBuffer(schemas: readonly [Schema.Top, ...Array<Schema.Top>]
608
656
  function onSchema(schema: Schema.Top) {
609
657
  const encoding = HttpApiSchema.getResponseEncoding(schema.ast)
610
658
  switch (encoding._tag) {
611
- case "Json":
612
- return UnknownFromArrayBuffer.pipe(Schema.decodeTo(schema))
659
+ case "Json": {
660
+ // handle json codecs that transform void schemas to null
661
+ const encodedIsNull = AST.isNull(AST.toEncoded(schema.ast))
662
+ return UnknownFromArrayBuffer.pipe(Schema.decodeTo(
663
+ schema,
664
+ encodedIsNull ?
665
+ Transformation.transform({
666
+ decode: (a) => a === undefined ? null : a,
667
+ encode: (a) => a === null ? undefined : a
668
+ }) as any :
669
+ undefined
670
+ ))
671
+ }
613
672
  case "FormUrlEncoded":
614
673
  return StringFromArrayBuffer.pipe(
615
674
  Schema.decodeTo(UrlParams.schemaRecord),
@@ -635,21 +694,25 @@ const statusOrElse = (response: HttpClientResponse.HttpClientResponse) =>
635
694
 
636
695
  const $HttpBody = Schema.declare(HttpBody.isHttpBody)
637
696
 
638
- function getEncodePayloadSchema(schemas: readonly [Schema.Top, ...Array<Schema.Top>]): Schema.Top {
639
- return Schema.Union(schemas.map(getEncodePayloadSchemaFromBody))
697
+ function getEncodePayloadSchema(
698
+ schemas: readonly [Schema.Top, ...Array<Schema.Top>],
699
+ method: HttpMethod.HttpMethod
700
+ ): Schema.Top {
701
+ return Schema.Union(schemas.map((s) => getEncodePayloadSchemaFromBody(s, method)))
640
702
  }
641
703
 
642
704
  const bodyFromPayloadCache = new WeakMap<AST.AST, Schema.Top>()
643
705
 
644
706
  function getEncodePayloadSchemaFromBody(
645
- schema: Schema.Top
707
+ schema: Schema.Top,
708
+ method: HttpMethod.HttpMethod
646
709
  ): Schema.Top {
647
710
  const ast = schema.ast
648
711
  const cached = bodyFromPayloadCache.get(ast)
649
712
  if (cached !== undefined) {
650
713
  return cached
651
714
  }
652
- const encoding = HttpApiSchema.getPayloadEncoding(ast)
715
+ const encoding = HttpApiSchema.getPayloadEncoding(ast, method)
653
716
  const out = $HttpBody.pipe(Schema.decodeTo(
654
717
  schema,
655
718
  Transformation.transformOrFail({
@@ -4,6 +4,7 @@
4
4
  import * as Arr from "../../Array.ts"
5
5
  import type { Brand } from "../../Brand.ts"
6
6
  import type { Effect } from "../../Effect.ts"
7
+ import { identity } from "../../Function.ts"
7
8
  import { type Pipeable, pipeArguments } from "../../Pipeable.ts"
8
9
  import * as Predicate from "../../Predicate.ts"
9
10
  import * as Schema from "../../Schema.ts"
@@ -476,7 +477,7 @@ export type ClientRequest<
476
477
  Query extends Schema.Top,
477
478
  Payload extends Schema.Top,
478
479
  Headers extends Schema.Top,
479
- WithResponse extends boolean
480
+ ResponseMode extends ClientResponseMode
480
481
  > = (
481
482
  & ([Params["Type"]] extends [never] ? {} : { readonly params: Params["Type"] })
482
483
  & ([Query["Type"]] extends [never] ? {} : { readonly query: Query["Type"] })
@@ -487,10 +488,16 @@ export type ClientRequest<
487
488
  ? { readonly payload: FormData }
488
489
  : { readonly payload: Payload["Type"] }
489
490
  : { readonly payload: Payload["Type"] })
490
- ) extends infer Req ? keyof Req extends never ? (void | { readonly withResponse?: WithResponse }) :
491
- Req & { readonly withResponse?: WithResponse } :
491
+ ) extends infer Req ? keyof Req extends never ? (void | { readonly responseMode?: ResponseMode }) :
492
+ Req & { readonly responseMode?: ResponseMode } :
492
493
  void
493
494
 
495
+ /**
496
+ * @since 4.0.0
497
+ * @category models
498
+ */
499
+ export type ClientResponseMode = "decoded-only" | "decoded-and-response" | "response-only"
500
+
494
501
  /**
495
502
  * @since 4.0.0
496
503
  * @category models
@@ -888,6 +895,14 @@ export type PayloadConstraint<Method extends HttpMethod> = Method extends HttpMe
888
895
  > :
889
896
  SuccessConstraint
890
897
 
898
+ /**
899
+ * @since 4.0.0
900
+ * @category constraints
901
+ */
902
+ export type PayloadConstraintCodecs<Method extends HttpMethod> = Method extends HttpMethod.NoBody ?
903
+ Record<string, Schema.Top> :
904
+ Schema.Top | ReadonlyArray<Schema.Top>
905
+
891
906
  /**
892
907
  * @since 4.0.0
893
908
  * @category constraints
@@ -904,7 +919,73 @@ export type ErrorConstraint = Schema.Top | ReadonlyArray<Schema.Top>
904
919
  * @since 4.0.0
905
920
  * @category constructors
906
921
  */
907
- export const make = <Method extends HttpMethod>(method: Method) =>
922
+ export const make = <Method extends HttpMethod>(method: Method): {
923
+ <
924
+ const Name extends string,
925
+ const Path extends HttpRouter.PathInput,
926
+ Params extends Schema.Top | Schema.Struct.Fields = never,
927
+ Query extends Schema.Top | Schema.Struct.Fields = never,
928
+ Payload extends PayloadConstraintCodecs<Method> = never,
929
+ Headers extends Schema.Top | Schema.Struct.Fields = never,
930
+ const Success extends Schema.Top | ReadonlyArray<Schema.Top> = HttpApiSchema.NoContent,
931
+ const Error extends Schema.Top | ReadonlyArray<Schema.Top> = never
932
+ >(
933
+ name: Name,
934
+ path: Path,
935
+ options?: {
936
+ readonly disableCodecs?: false | undefined
937
+ readonly params?: Params | undefined
938
+ readonly query?: Query | undefined
939
+ readonly headers?: Headers | undefined
940
+ readonly payload?: Payload | undefined
941
+ readonly success?: Success | undefined
942
+ readonly error?: Error | undefined
943
+ }
944
+ ): HttpApiEndpoint<
945
+ Name,
946
+ Method,
947
+ Path,
948
+ StringTree<Params extends Schema.Struct.Fields ? Schema.Struct<Params> : Params>,
949
+ StringTree<Query extends Schema.Struct.Fields ? Schema.Struct<Query> : Query>,
950
+ Method extends HttpMethod.WithBody ? Json<ExtractSchemaOrArray<Payload>>
951
+ : StringTree<ExtractSchemaOrArray<Payload>>,
952
+ StringTree<Headers extends Schema.Struct.Fields ? Schema.Struct<Headers> : Headers>,
953
+ Json<Success extends ReadonlyArray<Schema.Top> ? Success[number] : Success>,
954
+ Json<(Error extends ReadonlyArray<Schema.Top> ? Error[number] : Error) | typeof BadRequestNoContent>
955
+ >
956
+ <
957
+ const Name extends string,
958
+ const Path extends HttpRouter.PathInput,
959
+ Params extends ParamsConstraint = never,
960
+ Query extends QueryConstraint = never,
961
+ Payload extends PayloadConstraint<Method> = never,
962
+ Headers extends HeadersConstraint = never,
963
+ const Success extends SuccessConstraint = HttpApiSchema.NoContent,
964
+ const Error extends ErrorConstraint = never
965
+ >(
966
+ name: Name,
967
+ path: Path,
968
+ options?: {
969
+ readonly disableCodecs: true
970
+ readonly params?: Params | undefined
971
+ readonly query?: Query | undefined
972
+ readonly headers?: Headers | undefined
973
+ readonly payload?: Payload | undefined
974
+ readonly success?: Success | undefined
975
+ readonly error?: Error | undefined
976
+ }
977
+ ): HttpApiEndpoint<
978
+ Name,
979
+ Method,
980
+ Path,
981
+ Params extends Schema.Struct.Fields ? Schema.Struct<Params> : Params,
982
+ Query extends Schema.Struct.Fields ? Schema.Struct<Query> : Query,
983
+ ExtractSchemaOrArray<Payload>,
984
+ ExtractSchemaOrArray<Headers>,
985
+ Success extends ReadonlyArray<Schema.Top> ? Success[number] : Success,
986
+ (Error extends ReadonlyArray<Schema.Top> ? Error[number] : Error) | typeof BadRequestNoContent
987
+ >
988
+ } =>
908
989
  <
909
990
  const Name extends string,
910
991
  const Path extends HttpRouter.PathInput,
@@ -918,6 +999,7 @@ export const make = <Method extends HttpMethod>(method: Method) =>
918
999
  name: Name,
919
1000
  path: Path,
920
1001
  options?: {
1002
+ readonly disableCodecs?: boolean | undefined
921
1003
  readonly params?: Params | undefined
922
1004
  readonly query?: Query | undefined
923
1005
  readonly headers?: Headers | undefined
@@ -938,42 +1020,62 @@ export const make = <Method extends HttpMethod>(method: Method) =>
938
1020
  Success extends ReadonlyArray<Schema.Top> ? Success[number] : Success,
939
1021
  (Error extends ReadonlyArray<Schema.Top> ? Error[number] : Error) | typeof BadRequestNoContent
940
1022
  > => {
1023
+ const disableCodecs = options?.disableCodecs ?? false
1024
+ const transformStringTree = disableCodecs ? identity : Schema.toCodecStringTree
941
1025
  return makeProto({
942
1026
  name,
943
1027
  path,
944
1028
  method,
945
- params: getParams(options?.params),
946
- query: getQuery(options?.query),
947
- headers: getHeaders(options?.headers),
948
- payload: getPayload(options?.payload),
949
- success: getSuccess(options?.success),
950
- error: getError(options?.error),
1029
+ params: ensureStruct(options?.params, transformStringTree),
1030
+ query: ensureStruct(options?.query, transformStringTree),
1031
+ headers: ensureStruct(options?.headers, transformStringTree),
1032
+ payload: getPayload(options?.payload, method, disableCodecs),
1033
+ success: getResponse(options?.success, disableCodecs),
1034
+ error: getResponse(options?.error, disableCodecs),
951
1035
  annotations: ServiceMap.empty(),
952
1036
  middlewares: new Set()
953
1037
  })
954
1038
  }
955
1039
 
956
- function getParams(params: ParamsConstraint | undefined): Schema.Top | undefined {
957
- if (params === undefined) return undefined
958
- if (Schema.isSchema(params)) return params
959
- return Schema.Struct(params)
960
- }
1040
+ type ExtractSchemaOrArray<S extends Schema.Struct.Fields | Schema.Top | ReadonlyArray<Schema.Top>> = S extends
1041
+ Schema.Struct.Fields ? Schema.Struct<S>
1042
+ : S extends ReadonlyArray<Schema.Top> ? S[number]
1043
+ : S
961
1044
 
962
- function getQuery(query: QueryConstraint | undefined): Schema.Top | undefined {
963
- if (query === undefined) return undefined
964
- if (Schema.isSchema(query)) return query
965
- return Schema.Struct(query)
966
- }
1045
+ /**
1046
+ * @since 4.0.0
1047
+ * @category Codecs
1048
+ */
1049
+ export interface Json<S extends Schema.Top>
1050
+ extends Schema.Codec<S["Type"], Schema.Json, S["DecodingServices"], S["EncodingServices"]>
1051
+ {}
1052
+
1053
+ /**
1054
+ * @since 4.0.0
1055
+ * @category Codecs
1056
+ */
1057
+ export interface StringTree<S extends Schema.Top> extends
1058
+ Schema.Codec<
1059
+ S["Type"],
1060
+ Schema.StringTree,
1061
+ S["DecodingServices"],
1062
+ S["EncodingServices"]
1063
+ >
1064
+ {}
967
1065
 
968
- // all keys should be lowercase
969
- function getHeaders(headers: HeadersConstraint | undefined): Schema.Top | undefined {
970
- if (headers === undefined) return undefined
971
- if (Schema.isSchema(headers)) return headers
972
- return Schema.Struct(headers)
1066
+ function ensureStruct(
1067
+ params: Schema.Struct.Fields | Schema.Top | undefined,
1068
+ transform: typeof Schema.toCodecJson | typeof Schema.toCodecStringTree
1069
+ ): Schema.Top | undefined {
1070
+ if (params === undefined) return undefined
1071
+ if (Schema.isSchema(params)) return transform(params)
1072
+ return transform(Schema.Struct(params))
973
1073
  }
974
1074
 
975
1075
  function getPayload(
976
- payload: Schema.Top | ReadonlyArray<Schema.Top> | Schema.Struct.Fields | undefined
1076
+ payload: Schema.Top | ReadonlyArray<Schema.Top> | Schema.Struct.Fields | undefined,
1077
+ method: HttpMethod,
1078
+ disableCodecs: boolean
977
1079
  ): PayloadMap {
978
1080
  const result: Map<string, { encoding: HttpApiSchema.PayloadEncoding; schemas: [Schema.Top, ...Array<Schema.Top>] }> =
979
1081
  new Map()
@@ -982,9 +1084,11 @@ function getPayload(
982
1084
  ? payload
983
1085
  : Schema.isSchema(payload)
984
1086
  ? [payload]
985
- : [Schema.Struct(payload as any).pipe(HttpApiSchema.asFormUrlEncoded())]
1087
+ : [(Schema.Struct(payload as any)).pipe(HttpApiSchema.asFormUrlEncoded())]
1088
+ const transform = disableCodecs ? identity : transformPayload
1089
+
986
1090
  for (const schema of schemas) {
987
- const encoding = HttpApiSchema.getPayloadEncoding(schema.ast)
1091
+ const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method)
988
1092
  const existing = result.get(encoding.contentType)
989
1093
  if (existing) {
990
1094
  if (existing.encoding._tag !== encoding._tag) {
@@ -993,24 +1097,48 @@ function getPayload(
993
1097
  if (existing.encoding._tag === "Multipart") {
994
1098
  throw new Error(`Multiple multipart payloads for content-type: ${encoding.contentType}`)
995
1099
  }
996
- existing.schemas.push(schema)
1100
+ existing.schemas.push(transform(schema, method))
997
1101
  } else {
998
- result.set(encoding.contentType, { encoding, schemas: [schema] })
1102
+ result.set(encoding.contentType, { encoding, schemas: [transform(schema, method)] })
999
1103
  }
1000
1104
  }
1001
1105
  return result
1002
1106
  }
1003
1107
 
1004
- function getSuccess(
1005
- success: Schema.Top | ReadonlyArray<Schema.Top> | undefined
1108
+ function getResponse(
1109
+ success: Schema.Top | ReadonlyArray<Schema.Top> | undefined,
1110
+ disableCodecs: boolean
1006
1111
  ): Set<Schema.Top> {
1007
1112
  if (success === undefined) return new Set()
1008
- return new Set(Array.isArray(success) ? success : [success])
1113
+ const arr = Arr.ensure(success)
1114
+ return new Set(disableCodecs ? arr : arr.map(transformResponse))
1009
1115
  }
1010
1116
 
1011
- function getError(error: Schema.Top | ReadonlyArray<Schema.Top> | undefined): Set<Schema.Top> {
1012
- if (error === undefined) return new Set()
1013
- return new Set(Array.isArray(error) ? error : [error])
1117
+ function transformResponse(schema: Schema.Top): Schema.Top {
1118
+ const encoding = HttpApiSchema.getResponseEncoding(schema.ast)
1119
+ switch (encoding._tag) {
1120
+ case "Json":
1121
+ return Schema.toCodecJson(schema)
1122
+ case "FormUrlEncoded":
1123
+ return Schema.toCodecStringTree(schema)
1124
+ case "Text":
1125
+ case "Uint8Array":
1126
+ return schema
1127
+ }
1128
+ }
1129
+
1130
+ function transformPayload(schema: Schema.Top, method: HttpMethod): Schema.Top {
1131
+ const encoding = HttpApiSchema.getPayloadEncoding(schema.ast, method)
1132
+ switch (encoding._tag) {
1133
+ case "Json":
1134
+ return Schema.toCodecJson(schema)
1135
+ case "FormUrlEncoded":
1136
+ return Schema.toCodecStringTree(schema)
1137
+ case "Text":
1138
+ case "Uint8Array":
1139
+ case "Multipart":
1140
+ return schema
1141
+ }
1014
1142
  }
1015
1143
 
1016
1144
  /**