@rotorsoft/act-http 1.2.1 → 1.3.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/README.md +44 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/trpc/index.d.ts +168 -0
- package/dist/@types/trpc/index.d.ts.map +1 -0
- package/dist/api/index.js +4 -55
- package/dist/api/index.js.map +1 -1
- package/dist/chunk-Z6FVXDDU.js +63 -0
- package/dist/chunk-Z6FVXDDU.js.map +1 -0
- package/dist/trpc/index.cjs +169 -0
- package/dist/trpc/index.cjs.map +1 -0
- package/dist/trpc/index.js +95 -0
- package/dist/trpc/index.js.map +1 -0
- package/package.json +27 -22
package/README.md
CHANGED
|
@@ -135,6 +135,42 @@ On failure: the adapter responds with the framework's idiomatic 400 (`missing-ke
|
|
|
135
135
|
|
|
136
136
|
The framework-agnostic core (`checkWebhook`) and the underlying primitives (`extractIdempotencyKey`, `verifyWebhook`) are exported from `@rotorsoft/act-http/receiver` for receivers whose framework isn't in the adapter list (Koa, raw Node `http`, gRPC-over-HTTP, …) or for receivers with custom policy (e.g. "missing key falls back to body-derived dedup"). Use the `receiver` builder when you can; fall back to the framework `webhookMiddleware`, then the primitives.
|
|
137
137
|
|
|
138
|
+
### `trpc` — auto-generated tRPC router
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { trpc } from "@rotorsoft/act-http/trpc";
|
|
142
|
+
import { initTRPC } from "@trpc/server";
|
|
143
|
+
import { createHTTPServer } from "@trpc/server/adapters/standalone";
|
|
144
|
+
|
|
145
|
+
const router = trpc(app, {
|
|
146
|
+
// ActorExtractor from `@rotorsoft/act-http/api` — host's auth seam.
|
|
147
|
+
actor: (ctx) => ({ id: ctx.user.id, name: ctx.user.name }),
|
|
148
|
+
// Resolve the target stream per call; singleton aggregates return a constant.
|
|
149
|
+
stream: (action, input, ctx) => `tenant-${ctx.tenant}`,
|
|
150
|
+
// Optional optimistic concurrency — return `undefined` to skip the check.
|
|
151
|
+
expectedVersion: (action, input, ctx) => readIfMatchHeader(ctx),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
createHTTPServer({ router }).listen(4000);
|
|
155
|
+
|
|
156
|
+
// Client (same types, no codegen):
|
|
157
|
+
// await client.OpenTicket.mutate({ title: "support" });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The generator walks `app.registry.actions` once and emits a flat top-level mutation per action. The internal `authenticated(extractor)` middleware runs `options.actor` once per call and injects the resolved `Actor` onto `ctx.actor`, so any procedure logic downstream of the chain sees the same actor. `actor` and `stream` are required; `idempotency` is optional — when configured, the mutation reads `Idempotency-Key` via `keyFrom(ctx)` and throws `CONFLICT` on duplicate claims (the contract intentionally doesn't cache original results, matching the receiver-side "ack the duplicate" semantics).
|
|
161
|
+
|
|
162
|
+
Errors map through the shared `toApiError(...)` table at `@rotorsoft/act-http/api`: `ConcurrencyError` → `CONFLICT`, `InvariantError` → `CONFLICT`, `ValidationError` → `UNPROCESSABLE_CONTENT`, `StreamClosedError` → `PRECONDITION_FAILED`, `NonRetryableError` → `BAD_REQUEST`, anything else → `INTERNAL_SERVER_ERROR`. The same envelope flows from every transport sibling so a client speaking two of them never sees two shapes for the same framework error.
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// Compose the auth middleware into your own procedure chain instead:
|
|
166
|
+
import { authenticated } from "@rotorsoft/act-http/trpc";
|
|
167
|
+
|
|
168
|
+
const t = initTRPC.context<MyCtx>().create();
|
|
169
|
+
const authed = t.procedure.use(authenticated(myExtractor));
|
|
170
|
+
// `authed` carries `ctx.actor: Actor` downstream — use it for hand-written
|
|
171
|
+
// procedures alongside the generated ones.
|
|
172
|
+
```
|
|
173
|
+
|
|
138
174
|
### `sse` — live state broadcast
|
|
139
175
|
|
|
140
176
|
```ts
|
|
@@ -200,6 +236,14 @@ Shared utilities consumed by every transport in the auto-generated API umbrella
|
|
|
200
236
|
- **`withIdempotency(store, key, handler)`** — wraps an action handler in an `Idempotency-Key` claim. Reuses `@rotorsoft/act-ops/idempotency` — same contract `@rotorsoft/act-http/receiver` already speaks, so one `IdempotencyStore` covers both halves of the "Act over the wire" surface. Returns `{ deduped: false, result }` on fresh claim, `{ deduped: true }` on duplicate (handler is not called).
|
|
201
237
|
- **Types**: `IdempotencyResult<T>`, `ErrorMapEntry`.
|
|
202
238
|
|
|
239
|
+
### `/trpc` subpath
|
|
240
|
+
|
|
241
|
+
Auto-generated tRPC router for the act-http-api epic (#835).
|
|
242
|
+
|
|
243
|
+
- **`trpc(app, options) → Router`** — generator function. Walks `app.registry.actions` once and emits a flat top-level mutation per action. Each procedure runs the internal `authenticated(options.actor)` middleware (so `ctx.actor: Actor` is set on the downstream context), resolves the target stream via `options.stream(action, input, ctx)`, and calls `app.do(action, { stream, actor }, input)`. Known framework errors map through `toApiError` to the conventional tRPC codes; unknown throws surface as `INTERNAL_SERVER_ERROR`.
|
|
244
|
+
- **`authenticated(extractor) → middleware`** — standalone export of the auth middleware the generator uses internally. Hosts composing their own procedure chain (logging + tracing + custom auth flavors) use `t.procedure.use(authenticated(extractor))` to inject `ctx.actor` without going through the generator. The middleware is structurally-typed so any host `t` instance accepts it.
|
|
245
|
+
- **`TrpcOptions<Ctx>`** — `{ actor, stream, expectedVersion?, idempotency? }`. `actor` is the `ActorExtractor` from `@rotorsoft/act-http/api`. `stream` returns the target stream per call. `expectedVersion` is optional — `(action, input, ctx) => number | undefined` — when set, the procedure threads the resolved value through `Target.expectedVersion` so `app.do` enforces optimistic concurrency; hosts typically read it from an `If-Match` header or the client's last-known snapshot, and returning `undefined` skips the check for that call. `idempotency` is optional — `{ store: IdempotencyStore; keyFrom: (ctx) => string | undefined }` — when set, the procedure honors `Idempotency-Key` via `withIdempotency`. Duplicate claims throw `CONFLICT` (the contract intentionally doesn't cache the original handler's result).
|
|
246
|
+
|
|
203
247
|
### `/sse` subpath
|
|
204
248
|
|
|
205
249
|
- **`BroadcastChannel<S>`** — server-side broadcast manager with per-stream subscriber sets and an LRU state cache.
|