@shivaedev/effect-trpc 0.4.0 → 0.4.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1 - 2026-09-26
4
+
5
+ ### Added
6
+
7
+ - Map the Platform `TooManyRequests` rejection to `TOO_MANY_REQUESTS`, which
8
+ tRPC sends as HTTP 429.
9
+ - Mark the `BadRequest` that `rejectionFormatter` sends for an input the
10
+ procedure's schema rejects with `invalidInput: true`, so a client can tell
11
+ input that did not parse from a declared `BadRequest` with a `field`. The
12
+ rest of the rejection is unchanged and still decodes as `BadRequest`.
13
+ `EncodedRejection`, and so `rejectionOf` and the router-typed
14
+ `TRPCClientError` data, type the mark as `invalidInput?: true`.
15
+
16
+ ### Changed
17
+
18
+ - Reserve `invalidInput` for that mark. `rejectWith` no longer accepts a schema
19
+ whose encoded value has an `invalidInput` field, and `rejectionFormatter`
20
+ strips the key from every declared rejection, which 0.4.0 sent as it was, so
21
+ a handler can neither fake the mark nor turn its rejection into a 500.
22
+ `rejectWith` also requires the encoded value to have a string `_tag`, which
23
+ sending the rejection already required at run time. Both checks happen at
24
+ compile time, so a schema with an encoded `invalidInput` field or without an
25
+ encoded string `_tag` no longer compiles where it used to fail at run time
26
+ or, for `invalidInput`, send a spoofable mark. This stays a patch release
27
+ because such a schema could not be sent correctly before.
28
+
3
29
  ## 0.4.0 - 2026-09-26
4
30
 
5
31
  ### Added
package/README.md CHANGED
@@ -159,18 +159,31 @@ adds it to the error data, next to tRPC's own fields:
159
159
  | `Forbidden` | `FORBIDDEN` | 403 |
160
160
  | `Conflict` | `CONFLICT` | 409 |
161
161
  | `PreconditionFailed` | `PRECONDITION_FAILED` | 412 |
162
+ | `TooManyRequests` | `TOO_MANY_REQUESTS` | 429 |
162
163
  | `AuthUnavailable` | `SERVICE_UNAVAILABLE` | 503 |
163
164
  | any other, including `BadRequest` | `BAD_REQUEST` | 400 |
164
165
  - When a procedure's input schema rejects the input, the formatter sends a
165
166
  `BadRequest` rejection with the first issue's message and, unless the issue
166
167
  concerns the whole input, its path as `field` (`"address.city"`,
167
168
  `"tags.1"`). The code is `BAD_REQUEST`, as for a `BadRequest` the procedure
168
- raises, so a form shows the error on its field before the handler runs.
169
+ raises, so a form shows the error on its field before the handler runs. It
170
+ also carries `invalidInput: true`, which a declared rejection never gets, so
171
+ a client can show its own copy for input that did not parse and the
172
+ declared message otherwise:
173
+
174
+ ```jsonc
175
+ { "code": "BAD_REQUEST", "httpStatus": 400, "path": "rename",
176
+ "rejection": { "_tag": "BadRequest", "message": "Expected a value with a length of at least 1", "field": "name", "invalidInput": true } }
177
+ ```
178
+
179
+ `invalidInput` is reserved: `rejectWith` does not accept a schema that
180
+ encodes a field by that name, and the formatter strips it from any declared
181
+ rejection, so only input that did not parse carries it.
169
182
  - An application with its own `errorFormatter` calls
170
183
  `withRejection(shape, error)` inside it.
171
184
  - The error channel loses the declared types and gains `RejectionError`. An
172
185
  effect-contract operation's `error` schema works directly:
173
- `rejectWith(SaveMeal.error)`.
186
+ `rejectWith(SaveOrder.error)`.
174
187
 
175
188
  The browser-safe `@shivaedev/effect-trpc/client` entry reads the rejection from
176
189
  a `TRPCClientError`:
@@ -179,14 +192,18 @@ a `TRPCClientError`:
179
192
  import { decodeRejection, rejectionOf } from "@shivaedev/effect-trpc/client"
180
193
  import { rejectedField } from "@shivaedev/platform/errors"
181
194
 
182
- rejectionOf(error) // Option<{ _tag: string, ... }>, still encoded
195
+ rejectionOf(error) // Option<{ _tag: string, invalidInput?: true, ... }>, still encoded
183
196
  decodeRejection(Schema.Union([BadRequest, Conflict]))(error) // Option<BadRequest | Conflict>
184
197
  Option.flatMap(rejectionOf(error), rejectedField) // Option<{ field, message }>
198
+ Option.exists(rejectionOf(error), (rejection) => rejection.invalidInput === true) // the input did not parse
185
199
  ```
186
200
 
187
201
  Both return `Option.none()` for any error without a tagged rejection, and
188
- `decodeRejection` also for a rejection its schema does not declare. The client
189
- entry imports only `effect`.
202
+ `decodeRejection` also for a rejection its schema does not declare.
203
+ `rejectionOf` also returns `Option.none()` when `invalidInput` is present but
204
+ not `true`. An input failure still decodes as the declared `BadRequest`; the
205
+ mark is read from `rejectionOf`, or from `error.data.rejection.invalidInput` on a
206
+ `TRPCClientError` typed by the router. The client entry imports only `effect`.
190
207
 
191
208
  ## Vitest
192
209
 
@@ -1,6 +1,7 @@
1
1
  import { Option, Schema } from "effect";
2
2
  export declare const EncodedRejection: Schema.StructWithRest<Schema.Struct<{
3
3
  readonly _tag: Schema.String;
4
+ readonly invalidInput: Schema.optionalKey<Schema.Literal<true>>;
4
5
  }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
5
6
  export type EncodedRejection = typeof EncodedRejection.Type;
6
7
  export declare const rejectionOf: (error: unknown) => Option.Option<EncodedRejection>;
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../../src/client/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,eAAO,MAAM,gBAAgB;;6DAAgH,CAAC;AAE9I,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAC;AAI5D,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAmE,CAAC;AAE/I,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,CAAC,iBAAiB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5F,QAAQ,CAAC,KACP,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAG/C,CAAC"}
1
+ {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../../src/client/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,eAAO,MAAM,gBAAgB;;;6DAG5B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,OAAO,gBAAgB,CAAC,IAAI,CAAC;AAI5D,eAAO,MAAM,WAAW,GAAI,OAAO,OAAO,KAAG,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAmE,CAAC;AAE/I,eAAO,MAAM,eAAe,GAAI,CAAC,SAAS,MAAM,CAAC,iBAAiB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,EAC5F,QAAQ,CAAC,KACP,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAG/C,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { Option, Schema } from "effect";
2
- export const EncodedRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
2
+ export const EncodedRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String, invalidInput: Schema.optionalKey(Schema.Literal(true)) }), [Schema.Record(Schema.String, Schema.Unknown)]);
3
3
  const decodeCarrier = Schema.decodeUnknownOption(Schema.Struct({ data: Schema.Struct({ rejection: EncodedRejection }) }));
4
4
  export const rejectionOf = (error) => Option.map(decodeCarrier(error), ({ data }) => data.rejection);
5
5
  export const decodeRejection = (schema) => {
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../../src/client/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAI9I,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1H,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAc,EAAmC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE/I,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,MAAS,EACwC,EAAE;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AACxF,CAAC,CAAC"}
1
+ {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../../src/client/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC,cAAc,CACpD,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,EAC9F,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAC9C,CAAC;AAIF,MAAM,aAAa,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAE1H,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAc,EAAmC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAE/I,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,MAAS,EACwC,EAAE;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;AACxF,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"rejection-formatter.d.ts","sourceRoot":"","sources":["../src/rejection-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACtC;AAED,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAc3F,eAAO,MAAM,aAAa,GAAI,KAAK,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,KAAK,EAAE,OAAO,SAAS,KAAG,KAAK,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAG7I,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,kBAAkB;IAAE,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAG,mBAChG,CAAC"}
1
+ {"version":3,"file":"rejection-formatter.d.ts","sourceRoot":"","sources":["../src/rejection-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,KAAK,qBAAqB,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AACjG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG9D,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CACtC;AAED,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,CAAC;AAkB3F,eAAO,MAAM,aAAa,GAAI,KAAK,SAAS;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,KAAK,EAAE,OAAO,SAAS,KAAG,KAAK,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAG7I,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,kBAAkB;IAAE,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;CAAE,KAAG,mBAChG,CAAC"}
@@ -4,11 +4,14 @@ const inputRejection = ({ issues: [issue] }) => {
4
4
  if (issue === undefined)
5
5
  return undefined;
6
6
  const field = (issue.path ?? []).map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
7
- return field === "" ? { _tag: "BadRequest", message: issue.message } : { _tag: "BadRequest", message: issue.message, field };
7
+ return field === ""
8
+ ? { _tag: "BadRequest", message: issue.message, invalidInput: true }
9
+ : { _tag: "BadRequest", message: issue.message, field, invalidInput: true };
8
10
  };
11
+ const declared = ({ invalidInput: _reserved, ...rejection }) => rejection;
9
12
  const rejectionFrom = (error) => {
10
13
  if (error instanceof RejectionError)
11
- return error.rejection;
14
+ return declared(error.rejection);
12
15
  if (error.code === "BAD_REQUEST" && error.cause instanceof StandardSchemaV1Error)
13
16
  return inputRejection(error.cause);
14
17
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"rejection-formatter.js","sourceRoot":"","sources":["../src/rejection-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAA8C,MAAM,cAAc,CAAC;AAEjG,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQhD,MAAM,cAAc,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAyB,EAAgC,EAAE;IACnG,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzH,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;AAC9H,CAAC,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,KAAgB,EAAgC,EAAE;IACxE,IAAI,KAAK,YAAY,cAAc;QAAE,OAAO,KAAK,CAAC,SAAS,CAAC;IAC5D,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,KAAK,YAAY,qBAAqB;QAAE,OAAO,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrH,OAAO,SAAS,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAA0C,KAAY,EAAE,KAAgB,EAA4C,EAAE;IAClJ,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACvC,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAwE,EAAuB,EAAE,CACjJ,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC"}
1
+ {"version":3,"file":"rejection-formatter.js","sourceRoot":"","sources":["../src/rejection-formatter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAA8C,MAAM,cAAc,CAAC;AAEjG,OAAO,EAA0B,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQxE,MAAM,cAAc,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,EAAyB,EAAgC,EAAE;IACnG,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACzH,OAAO,KAAK,KAAK,EAAE;QAClB,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE;QACpE,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAC9E,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,SAAS,EAAqB,EAAoB,EAAE,CAAC,SAAS,CAAC;AAE/G,MAAM,aAAa,GAAG,CAAC,KAAgB,EAAgC,EAAE;IACxE,IAAI,KAAK,YAAY,cAAc;QAAE,OAAO,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACtE,IAAI,KAAK,CAAC,IAAI,KAAK,aAAa,IAAI,KAAK,CAAC,KAAK,YAAY,qBAAqB;QAAE,OAAO,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACrH,OAAO,SAAS,CAAC;AAClB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAA0C,KAAY,EAAE,KAAgB,EAA4C,EAAE;IAClJ,MAAM,SAAS,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC;IACvC,OAAO,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;AAC3F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,EAAE,KAAK,EAAE,KAAK,EAAwE,EAAuB,EAAE,CACjJ,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC"}
@@ -1,15 +1,26 @@
1
1
  import { type TRPC_ERROR_CODE_KEY, TRPCError } from "@trpc/server";
2
2
  import { Effect, Schema } from "effect";
3
- import { EncodedRejection } from "./client/rejection.ts";
4
3
  export declare const rejectionCode: (tag: string) => TRPC_ERROR_CODE_KEY;
4
+ declare const DeclaredRejection: Schema.StructWithRest<Schema.Struct<{
5
+ readonly _tag: Schema.String;
6
+ }>, readonly [Schema.$Record<Schema.String, Schema.Unknown>]>;
7
+ export type DeclaredRejection = typeof DeclaredRejection.Type;
8
+ interface InvalidInputIsReserved {
9
+ readonly "invalidInput is reserved for the mark on input that did not parse": never;
10
+ }
11
+ interface DeclaredEncoding {
12
+ readonly _tag: string;
13
+ readonly invalidInput?: InvalidInputIsReserved;
14
+ }
5
15
  export declare class RejectionError extends TRPCError {
6
- readonly rejection: EncodedRejection;
7
- constructor(rejection: EncodedRejection, code?: TRPC_ERROR_CODE_KEY);
16
+ readonly rejection: DeclaredRejection;
17
+ constructor(rejection: DeclaredRejection, code?: TRPC_ERROR_CODE_KEY);
8
18
  }
9
19
  export interface RejectWithOptions<Tag extends string> {
10
20
  readonly code?: (tag: Tag) => TRPC_ERROR_CODE_KEY;
11
21
  }
12
- export declare function rejectWith<S extends Schema.ConstraintDecoder<{
22
+ export declare function rejectWith<S extends Schema.ConstraintCodec<{
13
23
  readonly _tag: string;
14
- }, unknown>>(schema: S, options?: RejectWithOptions<S["Type"]["_tag"]>): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Exclude<E, S["Type"]> | RejectionError, R | S["EncodingServices"]>;
24
+ }, DeclaredEncoding, unknown, unknown>>(schema: S, options?: RejectWithOptions<S["Type"]["_tag"]>): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Exclude<E, S["Type"]> | RejectionError, R | S["EncodingServices"]>;
25
+ export {};
15
26
  //# sourceMappingURL=rejection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAWzD,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,mBAA+D,CAAC;AAE5G,qBAAa,cAAe,SAAQ,SAAS;IAC5C,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;gBAEzB,SAAS,EAAE,gBAAgB,EAAE,IAAI,GAAE,mBAAmD;CAIlG;AAED,MAAM,WAAW,iBAAiB,CAAC,GAAG,SAAS,MAAM;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,mBAAmB,CAAC;CAClD;AAQD,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,iBAAiB,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,CAAC,EAChG,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAC5C,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAYxC,eAAO,MAAM,aAAa,GAAI,KAAK,MAAM,KAAG,mBAA+D,CAAC;AAE5G,QAAA,MAAM,iBAAiB;;6DAAgH,CAAC;AAExI,MAAM,MAAM,iBAAiB,GAAG,OAAO,iBAAiB,CAAC,IAAI,CAAC;AAE9D,UAAU,sBAAsB;IAC/B,QAAQ,CAAC,mEAAmE,EAAE,KAAK,CAAC;CACpF;AAED,UAAU,gBAAgB;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,sBAAsB,CAAC;CAC/C;AAED,qBAAa,cAAe,SAAQ,SAAS;IAC5C,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAC;gBAE1B,SAAS,EAAE,iBAAiB,EAAE,IAAI,GAAE,mBAAmD;CAInG;AAED,MAAM,WAAW,iBAAiB,CAAC,GAAG,SAAS,MAAM;IACpD,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,mBAAmB,CAAC;CAClD;AAQD,wBAAgB,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,eAAe,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,CAAC,EACzH,MAAM,EAAE,CAAC,EACT,OAAO,CAAC,EAAE,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,GAC5C,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,cAAc,EAAE,CAAC,GAAG,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC"}
package/dist/rejection.js CHANGED
@@ -1,15 +1,16 @@
1
1
  import { TRPCError } from "@trpc/server";
2
2
  import { Effect, Schema } from "effect";
3
- import { EncodedRejection } from "./client/rejection.js";
4
3
  const TAXONOMY_CODES = new Map([
5
4
  ["NotFound", "NOT_FOUND"],
6
5
  ["Unauthorized", "UNAUTHORIZED"],
7
6
  ["Forbidden", "FORBIDDEN"],
8
7
  ["Conflict", "CONFLICT"],
9
8
  ["PreconditionFailed", "PRECONDITION_FAILED"],
9
+ ["TooManyRequests", "TOO_MANY_REQUESTS"],
10
10
  ["AuthUnavailable", "SERVICE_UNAVAILABLE"],
11
11
  ]);
12
12
  export const rejectionCode = (tag) => TAXONOMY_CODES.get(tag) ?? "BAD_REQUEST";
13
+ const DeclaredRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
13
14
  export class RejectionError extends TRPCError {
14
15
  rejection;
15
16
  constructor(rejection, code = rejectionCode(rejection._tag)) {
@@ -19,7 +20,7 @@ export class RejectionError extends TRPCError {
19
20
  }
20
21
  const encodeRejection = (schema) => {
21
22
  const encode = Schema.encodeUnknownEffect(schema);
22
- const tagged = Schema.decodeUnknownEffect(EncodedRejection);
23
+ const tagged = Schema.decodeUnknownEffect(DeclaredRejection);
23
24
  return (rejection) => Effect.orDie(Effect.flatMap(encode(rejection), tagged));
24
25
  };
25
26
  export function rejectWith(schema, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,SAAS,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD,MAAM,cAAc,GAAG,IAAI,GAAG,CAA8B;IAC3D,CAAC,UAAU,EAAE,WAAW,CAAC;IACzB,CAAC,cAAc,EAAE,cAAc,CAAC;IAChC,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IAC7C,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAW,EAAuB,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC;AAE5G,MAAM,OAAO,cAAe,SAAQ,SAAS;IACnC,SAAS,CAAmB;IAErC,YAAY,SAA2B,EAAE,OAA4B,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC;QACjG,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QACrG,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,CAAC;CACD;AAMD,MAAM,eAAe,GAAG,CAAC,MAAyB,EAAE,EAAE;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;IAC5D,OAAO,CAAC,SAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACxF,CAAC,CAAC;AAMF,MAAM,UAAU,UAAU,CAAC,MAAyB,EAAE,OAAmC;IACxF,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,aAAa,CAAC;IAC5C,OAAO,CAAU,IAA4B,EAAE,EAAE,CAChD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CACtC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAC5G,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,SAAS,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,MAAM,cAAc,GAAG,IAAI,GAAG,CAA8B;IAC3D,CAAC,UAAU,EAAE,WAAW,CAAC;IACzB,CAAC,cAAc,EAAE,cAAc,CAAC;IAChC,CAAC,WAAW,EAAE,WAAW,CAAC;IAC1B,CAAC,UAAU,EAAE,UAAU,CAAC;IACxB,CAAC,oBAAoB,EAAE,qBAAqB,CAAC;IAC7C,CAAC,iBAAiB,EAAE,mBAAmB,CAAC;IACxC,CAAC,iBAAiB,EAAE,qBAAqB,CAAC;CAC1C,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAW,EAAuB,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC;AAE5G,MAAM,iBAAiB,GAAG,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAaxI,MAAM,OAAO,cAAe,SAAQ,SAAS;IACnC,SAAS,CAAoB;IAEtC,YAAY,SAA4B,EAAE,OAA4B,aAAa,CAAC,SAAS,CAAC,IAAI,CAAC;QAClG,KAAK,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,SAAS,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;QACrG,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC5B,CAAC;CACD;AAMD,MAAM,eAAe,GAAG,CAAC,MAAyB,EAAE,EAAE;IACrD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,MAAM,CAAC,mBAAmB,CAAC,iBAAiB,CAAC,CAAC;IAC7D,OAAO,CAAC,SAAkB,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AACxF,CAAC,CAAC;AAMF,MAAM,UAAU,UAAU,CAAC,MAAyB,EAAE,OAAmC;IACxF,MAAM,EAAE,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,aAAa,CAAC;IAC5C,OAAO,CAAU,IAA4B,EAAE,EAAE,CAChD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE,CACtC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAC5G,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shivaedev/effect-trpc",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Effect-native procedures and testing for tRPC",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -63,7 +63,6 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@effect/vitest": "4.0.0-rc.112",
66
- "@shivaedev/effect-contract": "0.1.0",
67
66
  "@trpc/client": "11.18.0",
68
67
  "@trpc/server": "11.18.0",
69
68
  "@types/node": "24.10.1",
@@ -71,7 +70,8 @@
71
70
  "effect": "4.0.0-rc.112",
72
71
  "superjson": "2.2.6",
73
72
  "typescript": "npm:@typescript/typescript6@6.0.2",
74
- "vitest": "4.1.9"
73
+ "vitest": "4.1.9",
74
+ "@shivaedev/effect-contract": "0.1.0"
75
75
  },
76
76
  "scripts": {
77
77
  "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
@@ -1,6 +1,9 @@
1
1
  import { Option, Schema } from "effect";
2
2
 
3
- export const EncodedRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
3
+ export const EncodedRejection = Schema.StructWithRest(
4
+ Schema.Struct({ _tag: Schema.String, invalidInput: Schema.optionalKey(Schema.Literal(true)) }),
5
+ [Schema.Record(Schema.String, Schema.Unknown)],
6
+ );
4
7
 
5
8
  export type EncodedRejection = typeof EncodedRejection.Type;
6
9
 
@@ -1,6 +1,6 @@
1
1
  import { StandardSchemaV1Error, type TRPCDefaultErrorShape, type TRPCError } from "@trpc/server";
2
2
  import type { EncodedRejection } from "./client/rejection.ts";
3
- import { RejectionError } from "./rejection.ts";
3
+ import { type DeclaredRejection, RejectionError } from "./rejection.ts";
4
4
 
5
5
  export interface RejectionData {
6
6
  readonly rejection?: EncodedRejection;
@@ -11,11 +11,15 @@ export type RejectionErrorShape = TRPCDefaultErrorShape & { readonly data: Rejec
11
11
  const inputRejection = ({ issues: [issue] }: StandardSchemaV1Error): EncodedRejection | undefined => {
12
12
  if (issue === undefined) return undefined;
13
13
  const field = (issue.path ?? []).map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
14
- return field === "" ? { _tag: "BadRequest", message: issue.message } : { _tag: "BadRequest", message: issue.message, field };
14
+ return field === ""
15
+ ? { _tag: "BadRequest", message: issue.message, invalidInput: true }
16
+ : { _tag: "BadRequest", message: issue.message, field, invalidInput: true };
15
17
  };
16
18
 
19
+ const declared = ({ invalidInput: _reserved, ...rejection }: DeclaredRejection): EncodedRejection => rejection;
20
+
17
21
  const rejectionFrom = (error: TRPCError): EncodedRejection | undefined => {
18
- if (error instanceof RejectionError) return error.rejection;
22
+ if (error instanceof RejectionError) return declared(error.rejection);
19
23
  if (error.code === "BAD_REQUEST" && error.cause instanceof StandardSchemaV1Error) return inputRejection(error.cause);
20
24
  return undefined;
21
25
  };
package/src/rejection.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { type TRPC_ERROR_CODE_KEY, TRPCError } from "@trpc/server";
2
2
  import { Effect, Schema } from "effect";
3
- import { EncodedRejection } from "./client/rejection.ts";
4
3
 
5
4
  const TAXONOMY_CODES = new Map<string, TRPC_ERROR_CODE_KEY>([
6
5
  ["NotFound", "NOT_FOUND"],
@@ -8,15 +7,29 @@ const TAXONOMY_CODES = new Map<string, TRPC_ERROR_CODE_KEY>([
8
7
  ["Forbidden", "FORBIDDEN"],
9
8
  ["Conflict", "CONFLICT"],
10
9
  ["PreconditionFailed", "PRECONDITION_FAILED"],
10
+ ["TooManyRequests", "TOO_MANY_REQUESTS"],
11
11
  ["AuthUnavailable", "SERVICE_UNAVAILABLE"],
12
12
  ]);
13
13
 
14
14
  export const rejectionCode = (tag: string): TRPC_ERROR_CODE_KEY => TAXONOMY_CODES.get(tag) ?? "BAD_REQUEST";
15
15
 
16
+ const DeclaredRejection = Schema.StructWithRest(Schema.Struct({ _tag: Schema.String }), [Schema.Record(Schema.String, Schema.Unknown)]);
17
+
18
+ export type DeclaredRejection = typeof DeclaredRejection.Type;
19
+
20
+ interface InvalidInputIsReserved {
21
+ readonly "invalidInput is reserved for the mark on input that did not parse": never;
22
+ }
23
+
24
+ interface DeclaredEncoding {
25
+ readonly _tag: string;
26
+ readonly invalidInput?: InvalidInputIsReserved;
27
+ }
28
+
16
29
  export class RejectionError extends TRPCError {
17
- readonly rejection: EncodedRejection;
30
+ readonly rejection: DeclaredRejection;
18
31
 
19
- constructor(rejection: EncodedRejection, code: TRPC_ERROR_CODE_KEY = rejectionCode(rejection._tag)) {
32
+ constructor(rejection: DeclaredRejection, code: TRPC_ERROR_CODE_KEY = rejectionCode(rejection._tag)) {
20
33
  super({ code, message: typeof rejection.message === "string" ? rejection.message : rejection._tag });
21
34
  this.rejection = rejection;
22
35
  }
@@ -28,11 +41,11 @@ export interface RejectWithOptions<Tag extends string> {
28
41
 
29
42
  const encodeRejection = (schema: Schema.Constraint) => {
30
43
  const encode = Schema.encodeUnknownEffect(schema);
31
- const tagged = Schema.decodeUnknownEffect(EncodedRejection);
44
+ const tagged = Schema.decodeUnknownEffect(DeclaredRejection);
32
45
  return (rejection: unknown) => Effect.orDie(Effect.flatMap(encode(rejection), tagged));
33
46
  };
34
47
 
35
- export function rejectWith<S extends Schema.ConstraintDecoder<{ readonly _tag: string }, unknown>>(
48
+ export function rejectWith<S extends Schema.ConstraintCodec<{ readonly _tag: string }, DeclaredEncoding, unknown, unknown>>(
36
49
  schema: S,
37
50
  options?: RejectWithOptions<S["Type"]["_tag"]>,
38
51
  ): <A, E, R>(self: Effect.Effect<A, E, R>) => Effect.Effect<A, Exclude<E, S["Type"]> | RejectionError, R | S["EncodingServices"]>;