@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 +26 -0
- package/README.md +22 -5
- package/dist/client/rejection.d.ts +1 -0
- package/dist/client/rejection.d.ts.map +1 -1
- package/dist/client/rejection.js +1 -1
- package/dist/client/rejection.js.map +1 -1
- package/dist/rejection-formatter.d.ts.map +1 -1
- package/dist/rejection-formatter.js +5 -2
- package/dist/rejection-formatter.js.map +1 -1
- package/dist/rejection.d.ts +16 -5
- package/dist/rejection.d.ts.map +1 -1
- package/dist/rejection.js +3 -2
- package/dist/rejection.js.map +1 -1
- package/package.json +3 -3
- package/src/client/rejection.ts +4 -1
- package/src/rejection-formatter.ts +7 -3
- package/src/rejection.ts +18 -5
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(
|
|
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.
|
|
189
|
-
|
|
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
|
|
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"}
|
package/dist/client/rejection.js
CHANGED
|
@@ -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,
|
|
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;
|
|
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 === ""
|
|
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,
|
|
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"}
|
package/dist/rejection.d.ts
CHANGED
|
@@ -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:
|
|
7
|
-
constructor(rejection:
|
|
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.
|
|
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
|
package/dist/rejection.d.ts.map
CHANGED
|
@@ -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;
|
|
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(
|
|
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) {
|
package/dist/rejection.js.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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",
|
package/src/client/rejection.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { Option, Schema } from "effect";
|
|
2
2
|
|
|
3
|
-
export const EncodedRejection = Schema.StructWithRest(
|
|
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 === ""
|
|
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:
|
|
30
|
+
readonly rejection: DeclaredRejection;
|
|
18
31
|
|
|
19
|
-
constructor(rejection:
|
|
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(
|
|
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.
|
|
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"]>;
|