@trevrpc/protovalidate 0.0.0-stage → 1.8.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 trev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,54 @@
1
- # Temporary Holding Version
1
+ # @trevrpc/protovalidate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [protovalidate](https://github.com/bufbuild/protovalidate) request validation for the TrevRPC browser client, [`@trevrpc/trevrpc-js`](https://www.npmjs.com/package/@trevrpc/trevrpc-js): requests that break their [`buf.validate`](https://buf.build/bufbuild/protovalidate) rules fail before they are sent. It is a separate package so that `@trevrpc/trevrpc-js` itself does not depend on protovalidate or CEL.
4
+
5
+ > **Compatibility:** every TrevRPC package shares one version, and packages of the same version are compatible
6
+ > with each other: `trevrpc-rust` 1.8.0 works with `trevrpc-go` 1.8.0, `trevrpc-js` 1.8.0, and `trevrpc-kotlin`
7
+ > 1.8.0. Compatibility across different versions isn't guaranteed yet; that's a milestone for 2.0.
8
+
9
+ ```sh
10
+ npm install @trevrpc/protovalidate @trevrpc/trevrpc-js @bufbuild/protobuf @bufbuild/protovalidate
11
+ ```
12
+
13
+ Generate the messages with `protoc-gen-es` as usual, with `buf/validate/validate.proto` on the include path (or a `buf.build/bufbuild/protovalidate` dependency in `buf.yaml`), and pass a request validator to `createClient`:
14
+
15
+ ```ts
16
+ import { createClient } from "@trevrpc/trevrpc-js";
17
+ import { createRequestValidator } from "@trevrpc/protovalidate";
18
+ import { ViolationsSchema } from "@bufbuild/protovalidate/gen/buf/validate/validate_pb.js";
19
+
20
+ import { Users } from "./gen/users/v1/users_pb.js";
21
+
22
+ const users = createClient(Users, channel, { validator: createRequestValidator() });
23
+
24
+ try {
25
+ await users.create({ name: "", email: "ada@example.com" });
26
+ } catch (error) {
27
+ // Code.InvalidArgument, whether the client or a validating server rejected it.
28
+ for (const { violations } of error.findDetails(ViolationsSchema)) {
29
+ for (const violation of violations) console.log(violation.field, violation.message);
30
+ }
31
+ }
32
+ ```
33
+
34
+ - A request message that breaks its rules is not sent, and fails with `InvalidArgument` and a `buf.validate.Violations` status detail, as the Go and Rust servers report it, so one error handler serves both. The message names the first violation and counts the rest. The request of a unary or server-streaming call fails the call, after its interceptors ran; a message passed to `send()` of a client-streaming or bidirectional call rejects that send, and the call goes on.
35
+ - A rule that cannot be evaluated for a message fails it with `Internal`.
36
+ - `createClient` compiles the rules of each method's request type, and throws a `TrevRpcError` with `Code.Internal` when one cannot compile.
37
+ - Methods whose request type has no rules, directly or in a message it holds, are not checked and cost nothing.
38
+ - `createRequestValidator(validator?)` takes a protovalidate validator, by default one from `createValidator()`. Pass `createValidator({ registry })`, with a registry that holds their extensions, to enforce predefined rules, or `createValidator({ failFast: true })` to stop at the first violation.
39
+
40
+ Checking requests on the client saves a round trip and shows the violations at once. It does not replace checking them on the server, with [`protovalidatetrevrpc`](https://trev.zip/llc/TrevRPC/src/branch/main/trevrpc-go/protovalidatetrevrpc) or [`trevrpc-protovalidate`](https://trev.zip/llc/TrevRPC/src/branch/main/trevrpc-rust/crates/trevrpc-protovalidate), since clients can skip it.
41
+
42
+ See [Request Validation](https://trev.zip/llc/TrevRPC/wiki/JavaScript-Guide#request-validation).
43
+
44
+ ## Development
45
+
46
+ The package is developed against the trevrpc-js of this checkout, which it links as `file:..`, so install that first:
47
+
48
+ ```sh
49
+ npm ci --prefix ..
50
+ npm ci
51
+ npm run check # format check, lint, typecheck, tests
52
+ ```
53
+
54
+ The tests run the real client against the in-memory server of trevrpc-js's own tests. `nix build .#trevrpc-js-protovalidate` runs them against the built trevrpc-js, and checks the published files.
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "@trevrpc/protovalidate",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.8.0",
4
+ "description": "protovalidate request validation for TrevRPC browser clients",
5
+ "homepage": "https://trev.zip/llc/TrevRPC",
6
+ "bugs": {
7
+ "url": "https://trev.zip/llc/TrevRPC/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://trev.zip/llc/TrevRPC.git",
13
+ "directory": "trevrpc-js/protovalidate"
14
+ },
15
+ "files": [
16
+ "src/**",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "type": "module",
21
+ "sideEffects": false,
22
+ "types": "./src/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./src/index.d.ts",
26
+ "import": "./src/index.js"
27
+ }
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "scripts": {
33
+ "build": "node test/generate.js",
34
+ "format": "oxfmt --write",
35
+ "format:check": "oxfmt --check",
36
+ "lint": "oxlint --deny-warnings src test",
37
+ "typecheck": "npm run build && tsc -p tsconfig.json",
38
+ "test": "npm run build && node --test test/*.test.js",
39
+ "check": "npm run format:check && npm run lint && npm run typecheck && npm test"
40
+ },
41
+ "devDependencies": {
42
+ "@bufbuild/protobuf": "^2.17.0",
43
+ "@bufbuild/protoc-gen-es": "^2.17.0",
44
+ "@bufbuild/protovalidate": "^1.3.0",
45
+ "@trevrpc/trevrpc-js": "file:..",
46
+ "typescript": "^7.0.2"
47
+ },
48
+ "peerDependencies": {
49
+ "@bufbuild/protobuf": "^2.17.0",
50
+ "@bufbuild/protovalidate": "^1.3.0",
51
+ "@trevrpc/trevrpc-js": "^1.8.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=24"
55
+ },
56
+ "packageManager": "npm@12.2.2"
57
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import type { Validator } from "@bufbuild/protovalidate";
2
+ import type { RequestValidator } from "@trevrpc/trevrpc-js";
3
+
4
+ /**
5
+ * Returns a request validator for the `validator` option of `createClient` in
6
+ * `@trevrpc/trevrpc-js`, which enforces the `buf.validate` rules of request messages with
7
+ * `validator`, or with one that protovalidate's `createValidator()` makes when it is undefined.
8
+ * Pass a validator made with `createValidator({ registry })` to enforce predefined rules.
9
+ *
10
+ * A request message that breaks its rules is not sent: it fails with `Code.InvalidArgument` and a
11
+ * `buf.validate.Violations` detail, which `error.findDetails(ViolationsSchema)` decodes, as TrevRPC
12
+ * servers report requests that break them. One whose rules cannot be evaluated fails with
13
+ * `Code.Internal`. Methods whose request type has no rules, directly or in a message it holds,
14
+ * are not checked and cost nothing.
15
+ *
16
+ * `createClient` compiles the rules of each request type that has some, and throws a TrevRpcError
17
+ * with `Code.Internal` when they cannot compile.
18
+ */
19
+ export function createRequestValidator(validator?: Validator): RequestValidator;
package/src/index.js ADDED
@@ -0,0 +1,97 @@
1
+ import { create, hasOption, toBinary } from "@bufbuild/protobuf";
2
+ import { createValidator, violationsToProto } from "@bufbuild/protovalidate";
3
+ import {
4
+ ViolationsSchema,
5
+ field as fieldRules,
6
+ message as messageRules,
7
+ oneof as oneofRules,
8
+ } from "@bufbuild/protovalidate/gen/buf/validate/validate_pb.js";
9
+ import { Code, TrevRpcError } from "@trevrpc/trevrpc-js";
10
+
11
+ /**
12
+ * Returns a request validator for the `validator` option of `createClient`, which enforces the
13
+ * `buf.validate` rules of request messages with `validator`, a protovalidate validator, or with
14
+ * one that `createValidator()` makes when it is undefined.
15
+ */
16
+ export function createRequestValidator(validator = createValidator()) {
17
+ return {
18
+ validateFunc(schema) {
19
+ if (!hasRules(schema, new Set())) {
20
+ return undefined;
21
+ }
22
+ // Compiles the rules now, through the validator's cache, so a rule that cannot compile fails
23
+ // createClient instead of the first call.
24
+ const compiled = validator.validate(schema, create(schema));
25
+ if (compiled.kind === "error") {
26
+ throw new TrevRpcError(
27
+ Code.Internal,
28
+ `cannot validate ${schema.typeName}: ${compiled.error.message}`,
29
+ {},
30
+ { cause: compiled.error },
31
+ );
32
+ }
33
+ return (message) => {
34
+ const result = validator.validate(schema, message);
35
+ if (result.kind !== "valid") {
36
+ throw statusFromResult(result);
37
+ }
38
+ };
39
+ },
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Converts a failed protovalidate result into the error a request fails with: InvalidArgument
45
+ * with a `buf.validate.Violations` detail for broken rules, as TrevRPC servers report them, and
46
+ * Internal for rules that could not be evaluated.
47
+ */
48
+ function statusFromResult(result) {
49
+ if (result.kind === "error") {
50
+ return new TrevRpcError(
51
+ Code.Internal,
52
+ `request validation failed: ${result.error.message}`,
53
+ {},
54
+ { cause: result.error },
55
+ );
56
+ }
57
+ const { violations } = result;
58
+ if (violations.length === 0) {
59
+ return new TrevRpcError(Code.InvalidArgument, "validation error", {}, { cause: result.error });
60
+ }
61
+ let message = `validation error: ${violations[0]}`;
62
+ if (violations.length > 1) {
63
+ message += ` (and ${violations.length - 1} more)`;
64
+ }
65
+ const [listed] = violationsToProto(violations);
66
+ return new TrevRpcError(
67
+ Code.InvalidArgument,
68
+ message,
69
+ {},
70
+ {
71
+ cause: result.error,
72
+ details: [{ type: ViolationsSchema.typeName, value: toBinary(ViolationsSchema, listed) }],
73
+ },
74
+ );
75
+ }
76
+
77
+ /**
78
+ * Reports whether `schema`, or a message type it holds however deep, carries `buf.validate`
79
+ * rules. `seen` breaks recursive types.
80
+ */
81
+ function hasRules(schema, seen) {
82
+ if (seen.has(schema.typeName)) {
83
+ return false;
84
+ }
85
+ seen.add(schema.typeName);
86
+ if (hasOption(schema, messageRules)) {
87
+ return true;
88
+ }
89
+ if (schema.oneofs.some((oneof) => hasOption(oneof, oneofRules))) {
90
+ return true;
91
+ }
92
+ return schema.fields.some(
93
+ (field) =>
94
+ hasOption(field, fieldRules) ||
95
+ (field.message !== undefined && hasRules(field.message, seen)),
96
+ );
97
+ }