litclaude-ai 0.2.2
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 +155 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/README_ko-KR.md +374 -0
- package/RELEASE_CHECKLIST.md +165 -0
- package/bin/litclaude-ai.js +643 -0
- package/cover.png +0 -0
- package/docs/agents.md +67 -0
- package/docs/hooks.md +134 -0
- package/docs/lsp.md +40 -0
- package/docs/migration.md +209 -0
- package/docs/workflow-compatibility-audit.md +119 -0
- package/generate_cover.py +123 -0
- package/package.json +48 -0
- package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
- package/plugins/litclaude/.lsp.json +13 -0
- package/plugins/litclaude/.mcp.json +9 -0
- package/plugins/litclaude/agents/boulder-executor.md +12 -0
- package/plugins/litclaude/agents/librarian-researcher.md +15 -0
- package/plugins/litclaude/agents/oracle-verifier.md +16 -0
- package/plugins/litclaude/agents/prometheus-planner.md +13 -0
- package/plugins/litclaude/agents/qa-runner.md +16 -0
- package/plugins/litclaude/agents/quality-reviewer.md +17 -0
- package/plugins/litclaude/bin/litclaude-hook.js +110 -0
- package/plugins/litclaude/bin/litclaude-hud.js +271 -0
- package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
- package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
- package/plugins/litclaude/commands/deep-interview.md +21 -0
- package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
- package/plugins/litclaude/commands/lit-loop.md +40 -0
- package/plugins/litclaude/commands/lit-plan.md +35 -0
- package/plugins/litclaude/commands/litgoal.md +30 -0
- package/plugins/litclaude/commands/review-work.md +35 -0
- package/plugins/litclaude/commands/start-work.md +36 -0
- package/plugins/litclaude/hooks/hooks.json +54 -0
- package/plugins/litclaude/lib/context-pressure.mjs +25 -0
- package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
- package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
- package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
- package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
- package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
- package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
- package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
- package/plugins/litclaude/lib/workflow-check.mjs +83 -0
- package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
- package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
- package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
- package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
- package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
- package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
- package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
- package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
- package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
- package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
- package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
- package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
- package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
- package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
- package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
- package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
- package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
- package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
- package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
- package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
- package/plugins/litclaude/skills/programming/SKILL.md +106 -0
- package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
- package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
- package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
- package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
- package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
- package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
- package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
- package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
- package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
- package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
- package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
- package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
- package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
- package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
- package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
- package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
- package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
- package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
- package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
- package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
- package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
- package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
- package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
- package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
- package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
- package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
- package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
- package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
- package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
- package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
- package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
- package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
- package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
- package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
- package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
- package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
- package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
- package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
- package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
- package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
- package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
- package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
- package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
- package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
- package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
- package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
- package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
- package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
- package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
- package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
- package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
- package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
- package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
- package/plugins/litclaude/skills/rules/SKILL.md +66 -0
- package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
- package/scripts/audit-plan-checkboxes.mjs +37 -0
- package/scripts/doctor.mjs +41 -0
- package/scripts/inspect-agent-tools.mjs +27 -0
- package/scripts/postinstall.mjs +50 -0
- package/scripts/qa-claude-plugin-smoke.sh +60 -0
- package/scripts/qa-portable-install.sh +136 -0
- package/scripts/validate-plugin.mjs +72 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
# RPC — Connect-Go (default) + grpc-go (fallback) + protovalidate
|
|
2
|
+
|
|
3
|
+
`connectrpc/connect-go` is the default. It is wire-compatible with gRPC, also speaks Connect protocol + gRPC-Web from browsers, and uses ordinary `net/http` so middleware (logging, auth, tracing) composes the same way as REST. Reach for raw `grpc-go` only when you need a gRPC-specific feature Connect lacks.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When Connect vs grpc-go
|
|
8
|
+
|
|
9
|
+
| Need | Use |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Standard unary + server-streaming + client-streaming | **Connect** |
|
|
12
|
+
| Browser client without `grpc-web` proxy | **Connect** (native gRPC-Web support) |
|
|
13
|
+
| HTTP/1.1 fallback for hostile networks | **Connect** (gRPC requires HTTP/2 end-to-end) |
|
|
14
|
+
| Server reflection for `grpcurl` | grpc-go (Connect has reflection too, but ecosystem smaller) |
|
|
15
|
+
| Bidirectional streaming with frame-level control | grpc-go |
|
|
16
|
+
| Strict gRPC environment (Envoy with gRPC filters, Istio strict mode) | grpc-go |
|
|
17
|
+
|
|
18
|
+
**Default**: Connect. The default has been correct since 2024.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Toolchain — Buf, not protoc
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
go install github.com/bufbuild/buf/cmd/buf@latest
|
|
26
|
+
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
|
|
27
|
+
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
|
|
28
|
+
go install github.com/bufbuild/protovalidate/cmd/protoc-gen-go-vtproto@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Buf replaces `protoc` for everything: linting, breaking-change detection, codegen, formatting. The `protoc` toolchain is dead-letter walking — every modern proto project uses Buf.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Project layout
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
proto/
|
|
39
|
+
buf.yaml
|
|
40
|
+
buf.gen.yaml
|
|
41
|
+
buf.lock
|
|
42
|
+
myservice/v1/
|
|
43
|
+
user.proto
|
|
44
|
+
auth.proto
|
|
45
|
+
|
|
46
|
+
gen/
|
|
47
|
+
myservice/v1/
|
|
48
|
+
user.pb.go # protoc-gen-go output
|
|
49
|
+
auth.pb.go
|
|
50
|
+
myservicev1connect/ # protoc-gen-connect-go output
|
|
51
|
+
user.connect.go
|
|
52
|
+
auth.connect.go
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**`gen/` is committed.** Generated code is part of the API contract; CI proves it is up-to-date.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## `buf.yaml`
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
version: v2
|
|
63
|
+
modules:
|
|
64
|
+
- path: proto
|
|
65
|
+
lint:
|
|
66
|
+
use:
|
|
67
|
+
- STANDARD
|
|
68
|
+
breaking:
|
|
69
|
+
use:
|
|
70
|
+
- FILE
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## `buf.gen.yaml`
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
version: v2
|
|
77
|
+
managed:
|
|
78
|
+
enabled: true
|
|
79
|
+
override:
|
|
80
|
+
- file_option: go_package_prefix
|
|
81
|
+
value: github.com/your-org/myservice/gen
|
|
82
|
+
plugins:
|
|
83
|
+
- remote: buf.build/protocolbuffers/go
|
|
84
|
+
out: gen
|
|
85
|
+
opt:
|
|
86
|
+
- paths=source_relative
|
|
87
|
+
- remote: buf.build/connectrpc/go
|
|
88
|
+
out: gen
|
|
89
|
+
opt:
|
|
90
|
+
- paths=source_relative
|
|
91
|
+
- remote: buf.build/bufbuild/validate-go
|
|
92
|
+
out: gen
|
|
93
|
+
opt:
|
|
94
|
+
- paths=source_relative
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The `buf.build/...` plugin URIs use Buf's hosted remote registry — no local plugin installation needed.
|
|
98
|
+
|
|
99
|
+
## Taskfile target
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
gen:proto:
|
|
103
|
+
cmds:
|
|
104
|
+
- buf lint
|
|
105
|
+
- buf format -w
|
|
106
|
+
- buf generate
|
|
107
|
+
sources:
|
|
108
|
+
- proto/**/*.proto
|
|
109
|
+
- buf.yaml
|
|
110
|
+
- buf.gen.yaml
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Run `task gen:proto` after editing any `.proto`. CI runs `buf generate` then `git diff --exit-code` to catch stale generated code.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## A `.proto` with validation
|
|
118
|
+
|
|
119
|
+
```proto
|
|
120
|
+
syntax = "proto3";
|
|
121
|
+
|
|
122
|
+
package myservice.v1;
|
|
123
|
+
|
|
124
|
+
import "buf/validate/validate.proto";
|
|
125
|
+
|
|
126
|
+
option go_package = "github.com/your-org/myservice/gen/myservice/v1;myservicev1";
|
|
127
|
+
|
|
128
|
+
service UserService {
|
|
129
|
+
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
|
|
130
|
+
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
|
131
|
+
rpc StreamEvents(StreamEventsRequest) returns (stream Event);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
message CreateUserRequest {
|
|
135
|
+
string email = 1 [(buf.validate.field).string.email = true];
|
|
136
|
+
string username = 2 [
|
|
137
|
+
(buf.validate.field).string.min_len = 3,
|
|
138
|
+
(buf.validate.field).string.max_len = 32,
|
|
139
|
+
(buf.validate.field).string.pattern = "^[a-zA-Z0-9_]+$"
|
|
140
|
+
];
|
|
141
|
+
int32 age = 3 [
|
|
142
|
+
(buf.validate.field).int32.gte = 13,
|
|
143
|
+
(buf.validate.field).int32.lte = 130
|
|
144
|
+
];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
message CreateUserResponse {
|
|
148
|
+
User user = 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
message User {
|
|
152
|
+
string id = 1;
|
|
153
|
+
string email = 2;
|
|
154
|
+
string username = 3;
|
|
155
|
+
google.protobuf.Timestamp created_at = 4;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`protovalidate` replaces the abandoned `protoc-gen-validate` — it is the official Buf-backed successor as of 2024, supported by Connect's interceptor pipeline.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Server — Connect
|
|
164
|
+
|
|
165
|
+
```go
|
|
166
|
+
package main
|
|
167
|
+
|
|
168
|
+
import (
|
|
169
|
+
"context"
|
|
170
|
+
"log/slog"
|
|
171
|
+
"net/http"
|
|
172
|
+
|
|
173
|
+
"connectrpc.com/connect"
|
|
174
|
+
"buf.build/go/protovalidate"
|
|
175
|
+
validateinterceptor "connectrpc.com/validate"
|
|
176
|
+
"golang.org/x/net/http2"
|
|
177
|
+
"golang.org/x/net/http2/h2c"
|
|
178
|
+
|
|
179
|
+
myservicev1 "github.com/your-org/myservice/gen/myservice/v1"
|
|
180
|
+
"github.com/your-org/myservice/gen/myservice/v1/myservicev1connect"
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
type UserServer struct {
|
|
184
|
+
svc *UserService
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
func (s *UserServer) CreateUser(
|
|
188
|
+
ctx context.Context,
|
|
189
|
+
req *connect.Request[myservicev1.CreateUserRequest],
|
|
190
|
+
) (*connect.Response[myservicev1.CreateUserResponse], error) {
|
|
191
|
+
|
|
192
|
+
// protovalidate already ran via the interceptor below.
|
|
193
|
+
// req.Msg is guaranteed to satisfy the .proto constraints.
|
|
194
|
+
|
|
195
|
+
user, err := s.svc.Create(ctx, req.Msg.Email, req.Msg.Username, req.Msg.Age)
|
|
196
|
+
if err != nil {
|
|
197
|
+
return nil, mapError(err)
|
|
198
|
+
}
|
|
199
|
+
return connect.NewResponse(&myservicev1.CreateUserResponse{
|
|
200
|
+
User: userToProto(user),
|
|
201
|
+
}), nil
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func main() {
|
|
205
|
+
validator, _ := protovalidate.New()
|
|
206
|
+
interceptors := connect.WithInterceptors(
|
|
207
|
+
loggingInterceptor(),
|
|
208
|
+
validateinterceptor.NewInterceptor(validator),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
mux := http.NewServeMux()
|
|
212
|
+
mux.Handle(myservicev1connect.NewUserServiceHandler(
|
|
213
|
+
&UserServer{svc: newUserService()},
|
|
214
|
+
interceptors,
|
|
215
|
+
))
|
|
216
|
+
|
|
217
|
+
// h2c lets the server speak HTTP/2 cleartext for gRPC clients.
|
|
218
|
+
srv := &http.Server{
|
|
219
|
+
Addr: ":8080",
|
|
220
|
+
Handler: h2c.NewHandler(mux, &http2.Server{}),
|
|
221
|
+
}
|
|
222
|
+
slog.Info("rpc server listening", slog.String("addr", srv.Addr))
|
|
223
|
+
if err := srv.ListenAndServe(); err != nil { slog.Error("rpc", slog.Any("err", err)) }
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The handler is **just an `http.Handler`** — mount it in the same `http.ServeMux` as your REST routes if you want one binary serving both.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Error mapping — Connect codes
|
|
232
|
+
|
|
233
|
+
```go
|
|
234
|
+
func mapError(err error) error {
|
|
235
|
+
if err == nil { return nil }
|
|
236
|
+
|
|
237
|
+
switch {
|
|
238
|
+
case errors.Is(err, domain.ErrInvalidEmail),
|
|
239
|
+
errors.Is(err, domain.ErrInvalidUsername):
|
|
240
|
+
return connect.NewError(connect.CodeInvalidArgument, err)
|
|
241
|
+
case errors.Is(err, ErrNotFound):
|
|
242
|
+
return connect.NewError(connect.CodeNotFound, err)
|
|
243
|
+
case errors.Is(err, ErrUnauthorized):
|
|
244
|
+
return connect.NewError(connect.CodeUnauthenticated, err)
|
|
245
|
+
case errors.Is(err, ErrConflict):
|
|
246
|
+
return connect.NewError(connect.CodeAlreadyExists, err)
|
|
247
|
+
default:
|
|
248
|
+
slog.Error("unmapped rpc error", slog.Any("err", err))
|
|
249
|
+
return connect.NewError(connect.CodeInternal, errors.New("internal"))
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Connect codes map 1:1 to gRPC codes. Clients see canonical error semantics.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Logging interceptor
|
|
259
|
+
|
|
260
|
+
```go
|
|
261
|
+
func loggingInterceptor() connect.UnaryInterceptorFunc {
|
|
262
|
+
return func(next connect.UnaryFunc) connect.UnaryFunc {
|
|
263
|
+
return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) {
|
|
264
|
+
start := time.Now()
|
|
265
|
+
res, err := next(ctx, req)
|
|
266
|
+
attrs := []slog.Attr{
|
|
267
|
+
slog.String("proc", req.Spec().Procedure),
|
|
268
|
+
slog.Duration("elapsed", time.Since(start)),
|
|
269
|
+
}
|
|
270
|
+
if err != nil {
|
|
271
|
+
attrs = append(attrs, slog.Any("err", err))
|
|
272
|
+
slog.LogAttrs(ctx, slog.LevelWarn, "rpc failed", attrs...)
|
|
273
|
+
} else {
|
|
274
|
+
slog.LogAttrs(ctx, slog.LevelInfo, "rpc ok", attrs...)
|
|
275
|
+
}
|
|
276
|
+
return res, err
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
For streaming, implement the full `connect.Interceptor` (`WrapStreamingClient`, `WrapStreamingHandler`). Pattern is identical.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Server streaming
|
|
287
|
+
|
|
288
|
+
```go
|
|
289
|
+
func (s *UserServer) StreamEvents(
|
|
290
|
+
ctx context.Context,
|
|
291
|
+
req *connect.Request[myservicev1.StreamEventsRequest],
|
|
292
|
+
stream *connect.ServerStream[myservicev1.Event],
|
|
293
|
+
) error {
|
|
294
|
+
events, errs := s.svc.Subscribe(ctx, req.Msg.UserId)
|
|
295
|
+
for {
|
|
296
|
+
select {
|
|
297
|
+
case <-ctx.Done():
|
|
298
|
+
return ctx.Err()
|
|
299
|
+
case e, ok := <-events:
|
|
300
|
+
if !ok { return nil }
|
|
301
|
+
if err := stream.Send(eventToProto(e)); err != nil {
|
|
302
|
+
return err
|
|
303
|
+
}
|
|
304
|
+
case err := <-errs:
|
|
305
|
+
return connect.NewError(connect.CodeInternal, err)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Same shape as SSE in `backend-stack.md`. Connect handles HTTP/2 framing.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Client
|
|
316
|
+
|
|
317
|
+
```go
|
|
318
|
+
client := myservicev1connect.NewUserServiceClient(
|
|
319
|
+
http.DefaultClient,
|
|
320
|
+
"https://api.example.com",
|
|
321
|
+
// Use connect.WithGRPC() if the server is grpc-go and you want strict gRPC framing.
|
|
322
|
+
// Default is Connect protocol — works with Connect or gRPC servers transparently.
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
res, err := client.CreateUser(ctx, connect.NewRequest(&myservicev1.CreateUserRequest{
|
|
326
|
+
Email: "a@b.com",
|
|
327
|
+
Username: "alice",
|
|
328
|
+
Age: 30,
|
|
329
|
+
}))
|
|
330
|
+
if err != nil {
|
|
331
|
+
var connectErr *connect.Error
|
|
332
|
+
if errors.As(err, &connectErr) {
|
|
333
|
+
slog.Error("rpc failed",
|
|
334
|
+
slog.String("code", connectErr.Code().String()),
|
|
335
|
+
slog.String("msg", connectErr.Message()))
|
|
336
|
+
}
|
|
337
|
+
return err
|
|
338
|
+
}
|
|
339
|
+
slog.Info("created", slog.String("id", res.Msg.User.Id))
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## When you genuinely need raw grpc-go
|
|
345
|
+
|
|
346
|
+
```go
|
|
347
|
+
import "google.golang.org/grpc"
|
|
348
|
+
|
|
349
|
+
lis, _ := net.Listen("tcp", ":8080")
|
|
350
|
+
srv := grpc.NewServer(
|
|
351
|
+
grpc.UnaryInterceptor(loggingUnaryInterceptor),
|
|
352
|
+
)
|
|
353
|
+
myservicev1.RegisterUserServiceServer(srv, &userServer{})
|
|
354
|
+
_ = srv.Serve(lis)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The codegen is from `protoc-gen-go-grpc` (different binary from `protoc-gen-connect-go`). You can codegen **both** in the same `buf.gen.yaml` and switch by importing the right package. Most teams pick one.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## When NOT to use RPC at all
|
|
362
|
+
|
|
363
|
+
If your callers are all browsers, mobile apps, third-party developers, or the long tail of "things humans curl": **stay with REST + OpenAPI**. RPC's overhead is justified for service-to-service inside a single org. Outside that boundary, JSON over HTTP wins on debuggability.
|
|
364
|
+
|
|
365
|
+
`oapi-codegen/oapi-codegen/v2` generates Go server stubs and clients from OpenAPI 3 — the REST equivalent of what Connect does for proto. Same parse-don't-validate boundary discipline, different wire format.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Sources
|
|
370
|
+
|
|
371
|
+
- Connect docs: https://connectrpc.com/docs/go/getting-started
|
|
372
|
+
- Buf: https://buf.build/docs
|
|
373
|
+
- protovalidate: https://github.com/bufbuild/protovalidate
|
|
374
|
+
- "Why we replaced protoc with buf" (Buf blog): https://buf.build/blog
|
|
375
|
+
- gRPC vs Connect comparison: https://connectrpc.com/docs/introduction
|