ts-procedures 10.1.1 → 10.2.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/CHANGELOG.md +26 -65
- package/README.md +2 -0
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +8 -0
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +25 -22
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +24 -13
- package/agent_config/copilot/copilot-instructions.md +1 -1
- package/agent_config/cursor/cursorrules +1 -1
- package/build/adapters/hono/envelope-parity.test.js +6 -17
- package/build/adapters/hono/envelope-parity.test.js.map +1 -1
- package/build/adapters/hono/handlers/http-stream.d.ts +2 -1
- package/build/adapters/hono/handlers/http-stream.js +9 -2
- package/build/adapters/hono/handlers/http-stream.js.map +1 -1
- package/build/adapters/hono/handlers/http.d.ts +2 -1
- package/build/adapters/hono/handlers/http.js +9 -2
- package/build/adapters/hono/handlers/http.js.map +1 -1
- package/build/adapters/hono/handlers/rpc.d.ts +2 -1
- package/build/adapters/hono/handlers/rpc.js +9 -2
- package/build/adapters/hono/handlers/rpc.js.map +1 -1
- package/build/adapters/hono/handlers/stream.d.ts +2 -1
- package/build/adapters/hono/handlers/stream.js +9 -2
- package/build/adapters/hono/handlers/stream.js.map +1 -1
- package/build/adapters/hono/index.d.ts +14 -7
- package/build/adapters/hono/index.js +45 -5
- package/build/adapters/hono/index.js.map +1 -1
- package/build/adapters/hono/index.test.js +55 -0
- package/build/adapters/hono/index.test.js.map +1 -1
- package/build/adapters/hono/request.d.ts +7 -0
- package/build/adapters/hono/request.js +15 -0
- package/build/adapters/hono/request.js.map +1 -1
- package/build/adapters/hono/types.d.ts +31 -0
- package/build/adapters/hono/types.js.map +1 -1
- package/build/codegen/bin/cli.js +1 -1
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/constants.js +2 -2
- package/build/codegen/constants.js.map +1 -1
- package/build/codegen/emit/api-route.js +1 -1
- package/build/codegen/emit/api-route.js.map +1 -1
- package/build/codegen/emit/scope-file.js +1 -1
- package/build/codegen/emit/scope-file.js.map +1 -1
- package/build/codegen/emit-errors.integration.test.js +2 -2
- package/build/codegen/emit-errors.integration.test.js.map +1 -1
- package/build/codegen/resolve-envelope.js +1 -1
- package/build/codegen/resolve-envelope.js.map +1 -1
- package/build/codegen/test-helpers/run-tsc.js +1 -1
- package/build/codegen/test-helpers/run-tsc.js.map +1 -1
- package/build/core/create-http-stream.d.ts +3 -14
- package/build/core/create-http-stream.js +4 -2
- package/build/core/create-http-stream.js.map +1 -1
- package/build/core/create-http.d.ts +2 -9
- package/build/core/create-http.js +3 -2
- package/build/core/create-http.js.map +1 -1
- package/build/core/create-http.test-d.d.ts +1 -0
- package/build/core/create-http.test-d.js +104 -0
- package/build/core/create-http.test-d.js.map +1 -0
- package/build/core/create-stream.js +6 -2
- package/build/core/create-stream.js.map +1 -1
- package/build/core/create.js +5 -4
- package/build/core/create.js.map +1 -1
- package/build/core/create.test.js +63 -0
- package/build/core/create.test.js.map +1 -1
- package/build/core/internal.d.ts +16 -0
- package/build/core/internal.js +22 -0
- package/build/core/internal.js.map +1 -1
- package/build/core/procedures.d.ts +9 -16
- package/build/core/procedures.js +4 -0
- package/build/core/procedures.js.map +1 -1
- package/build/core/types.d.ts +85 -4
- package/build/errors.d.ts +33 -0
- package/build/errors.js +91 -0
- package/build/errors.js.map +1 -0
- package/build/errors.test.d.ts +1 -0
- package/build/errors.test.js +122 -0
- package/build/errors.test.js.map +1 -0
- package/build/exports.d.ts +1 -2
- package/build/exports.js +0 -4
- package/build/exports.js.map +1 -1
- package/build/implementations/http/doc-registry.d.ts +28 -0
- package/build/implementations/http/doc-registry.js +96 -0
- package/build/implementations/http/doc-registry.js.map +1 -0
- package/build/implementations/http/doc-registry.test.d.ts +1 -0
- package/build/implementations/http/doc-registry.test.js +516 -0
- package/build/implementations/http/doc-registry.test.js.map +1 -0
- package/build/implementations/http/error-taxonomy.d.ts +249 -0
- package/build/implementations/http/error-taxonomy.js +252 -0
- package/build/implementations/http/error-taxonomy.js.map +1 -0
- package/build/implementations/http/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/error-taxonomy.test.js +399 -0
- package/build/implementations/http/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.js +83 -0
- package/build/implementations/http/express-rpc/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/express-rpc/index.d.ts +125 -0
- package/build/implementations/http/express-rpc/index.js +216 -0
- package/build/implementations/http/express-rpc/index.js.map +1 -0
- package/build/implementations/http/express-rpc/index.test.d.ts +1 -0
- package/build/implementations/http/express-rpc/index.test.js +684 -0
- package/build/implementations/http/express-rpc/index.test.js.map +1 -0
- package/build/implementations/http/express-rpc/types.d.ts +11 -0
- package/build/implementations/http/express-rpc/types.js +2 -0
- package/build/implementations/http/express-rpc/types.js.map +1 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.js +137 -0
- package/build/implementations/http/hono-api/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-api/index.d.ts +151 -0
- package/build/implementations/http/hono-api/index.js +344 -0
- package/build/implementations/http/hono-api/index.js.map +1 -0
- package/build/implementations/http/hono-api/index.test.d.ts +1 -0
- package/build/implementations/http/hono-api/index.test.js +992 -0
- package/build/implementations/http/hono-api/index.test.js.map +1 -0
- package/build/implementations/http/hono-api/types.d.ts +13 -0
- package/build/implementations/http/hono-api/types.js +2 -0
- package/build/implementations/http/hono-api/types.js.map +1 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js +64 -0
- package/build/implementations/http/hono-rpc/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-rpc/index.d.ts +130 -0
- package/build/implementations/http/hono-rpc/index.js +209 -0
- package/build/implementations/http/hono-rpc/index.js.map +1 -0
- package/build/implementations/http/hono-rpc/index.test.d.ts +1 -0
- package/build/implementations/http/hono-rpc/index.test.js +828 -0
- package/build/implementations/http/hono-rpc/index.test.js.map +1 -0
- package/build/implementations/http/hono-rpc/types.d.ts +11 -0
- package/build/implementations/http/hono-rpc/types.js +2 -0
- package/build/implementations/http/hono-rpc/types.js.map +1 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js +159 -0
- package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -0
- package/build/implementations/http/hono-stream/index.d.ts +171 -0
- package/build/implementations/http/hono-stream/index.js +415 -0
- package/build/implementations/http/hono-stream/index.js.map +1 -0
- package/build/implementations/http/hono-stream/index.test.d.ts +1 -0
- package/build/implementations/http/hono-stream/index.test.js +1383 -0
- package/build/implementations/http/hono-stream/index.test.js.map +1 -0
- package/build/implementations/http/hono-stream/types.d.ts +15 -0
- package/build/implementations/http/hono-stream/types.js +2 -0
- package/build/implementations/http/hono-stream/types.js.map +1 -0
- package/build/implementations/http/on-request-error.test.d.ts +1 -0
- package/build/implementations/http/on-request-error.test.js +173 -0
- package/build/implementations/http/on-request-error.test.js.map +1 -0
- package/build/implementations/http/route-errors.test.d.ts +1 -0
- package/build/implementations/http/route-errors.test.js +139 -0
- package/build/implementations/http/route-errors.test.js.map +1 -0
- package/build/implementations/types.d.ts +192 -0
- package/build/implementations/types.js +2 -0
- package/build/implementations/types.js.map +1 -0
- package/build/index.d.ts +165 -0
- package/build/index.js +258 -0
- package/build/index.js.map +1 -0
- package/build/index.test.d.ts +1 -0
- package/build/index.test.js +920 -0
- package/build/index.test.js.map +1 -0
- package/build/schema/extract-json-schema.d.ts +2 -0
- package/build/schema/extract-json-schema.js +12 -0
- package/build/schema/extract-json-schema.js.map +1 -0
- package/build/schema/extract-json-schema.test.d.ts +1 -0
- package/build/schema/extract-json-schema.test.js +23 -0
- package/build/schema/extract-json-schema.test.js.map +1 -0
- package/build/schema/parser.d.ts +28 -0
- package/build/schema/parser.js +170 -0
- package/build/schema/parser.js.map +1 -0
- package/build/schema/parser.test.d.ts +1 -0
- package/build/schema/parser.test.js +120 -0
- package/build/schema/parser.test.js.map +1 -0
- package/build/schema/resolve-schema-lib.d.ts +12 -0
- package/build/schema/resolve-schema-lib.js +11 -0
- package/build/schema/resolve-schema-lib.js.map +1 -0
- package/build/schema/resolve-schema-lib.test.d.ts +1 -0
- package/build/schema/resolve-schema-lib.test.js +17 -0
- package/build/schema/resolve-schema-lib.test.js.map +1 -0
- package/build/schema/typebox.d.ts +1 -1
- package/build/schema/typebox.js +2 -20
- package/build/schema/typebox.js.map +1 -1
- package/build/schema/typebox.test.js +0 -10
- package/build/schema/typebox.test.js.map +1 -1
- package/build/schema/types.d.ts +8 -0
- package/build/schema/types.js +2 -0
- package/build/schema/types.js.map +1 -0
- package/build/server/context.d.ts +12 -2
- package/build/server/context.js +49 -4
- package/build/server/context.js.map +1 -1
- package/build/server/context.test.js +32 -1
- package/build/server/context.test.js.map +1 -1
- package/build/server/doc-registry.js +0 -3
- package/build/server/doc-registry.js.map +1 -1
- package/build/server/doc-registry.test.js +0 -31
- package/build/server/doc-registry.test.js.map +1 -1
- package/build/server/index.d.ts +0 -1
- package/build/server/index.js +0 -1
- package/build/server/index.js.map +1 -1
- package/build/server/types.d.ts +6 -27
- package/build/stack-utils.d.ts +25 -0
- package/build/stack-utils.js +95 -0
- package/build/stack-utils.js.map +1 -0
- package/build/stack-utils.test.d.ts +1 -0
- package/build/stack-utils.test.js +80 -0
- package/build/stack-utils.test.js.map +1 -0
- package/docs/core.md +8 -0
- package/docs/http-integrations.md +41 -27
- package/package.json +8 -8
- package/src/adapters/astro/README.md +2 -0
- package/src/adapters/hono/envelope-parity.test.ts +6 -18
- package/src/adapters/hono/handlers/http-stream.ts +12 -3
- package/src/adapters/hono/handlers/http.ts +12 -3
- package/src/adapters/hono/handlers/rpc.ts +12 -3
- package/src/adapters/hono/handlers/stream.ts +12 -3
- package/src/adapters/hono/index.test.ts +70 -0
- package/src/adapters/hono/index.ts +53 -11
- package/src/adapters/hono/request.ts +18 -0
- package/src/adapters/hono/types.ts +37 -0
- package/src/codegen/bin/cli.ts +1 -1
- package/src/codegen/constants.ts +2 -2
- package/src/codegen/emit/api-route.ts +1 -1
- package/src/codegen/emit/scope-file.ts +1 -2
- package/src/codegen/emit-errors.integration.test.ts +2 -2
- package/src/codegen/resolve-envelope.ts +1 -1
- package/src/codegen/test-helpers/run-tsc.ts +0 -1
- package/src/core/create-http-stream.ts +20 -13
- package/src/core/create-http.test-d.ts +154 -0
- package/src/core/create-http.ts +19 -8
- package/src/core/create-stream.ts +12 -4
- package/src/core/create.test.ts +74 -0
- package/src/core/create.ts +10 -5
- package/src/core/internal.ts +35 -0
- package/src/core/procedures.ts +4 -0
- package/src/core/types.ts +99 -4
- package/src/exports.ts +6 -4
- package/src/schema/typebox.test.ts +0 -12
- package/src/schema/typebox.ts +2 -21
- package/src/server/context.test.ts +39 -1
- package/src/server/context.ts +54 -4
- package/src/server/doc-registry.test.ts +0 -34
- package/src/server/doc-registry.ts +0 -3
- package/src/server/index.ts +0 -1
- package/src/server/types.ts +8 -27
- package/build/server/spec-version.d.ts +0 -24
- package/build/server/spec-version.js +0 -26
- package/build/server/spec-version.js.map +0 -1
- package/build/version.d.ts +0 -9
- package/build/version.js +0 -11
- package/build/version.js.map +0 -1
- package/docs/doc-envelope-spec-v1.md +0 -296
- package/docs/doc-envelope-v1.schema.json +0 -253
- package/src/server/spec-version.ts +0 -27
- package/src/version.ts +0 -11
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { captureDefinitionInfo, formatDefinitionInfo } from './stack-utils.js';
|
|
3
|
+
describe('Stack Utils', () => {
|
|
4
|
+
describe('captureDefinitionInfo', () => {
|
|
5
|
+
test('returns definition info with definedAt', () => {
|
|
6
|
+
const info = captureDefinitionInfo();
|
|
7
|
+
// Should capture the call site in this test file
|
|
8
|
+
expect(info).toBeDefined();
|
|
9
|
+
expect(info.definitionStack).toBeDefined();
|
|
10
|
+
expect(typeof info.definitionStack).toBe('string');
|
|
11
|
+
});
|
|
12
|
+
test('definedAt contains file, line, column when available', () => {
|
|
13
|
+
const info = captureDefinitionInfo();
|
|
14
|
+
// The definedAt should be present since we're calling from user code (test file)
|
|
15
|
+
if (info.definedAt) {
|
|
16
|
+
expect(info.definedAt.file).toBeDefined();
|
|
17
|
+
expect(typeof info.definedAt.file).toBe('string');
|
|
18
|
+
expect(info.definedAt.line).toBeDefined();
|
|
19
|
+
expect(typeof info.definedAt.line).toBe('number');
|
|
20
|
+
expect(info.definedAt.line).toBeGreaterThan(0);
|
|
21
|
+
expect(info.definedAt.column).toBeDefined();
|
|
22
|
+
expect(typeof info.definedAt.column).toBe('number');
|
|
23
|
+
expect(info.definedAt.column).toBeGreaterThan(0);
|
|
24
|
+
expect(info.definedAt.raw).toBeDefined();
|
|
25
|
+
expect(typeof info.definedAt.raw).toBe('string');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
test('definitionStack contains Error stack trace', () => {
|
|
29
|
+
const info = captureDefinitionInfo();
|
|
30
|
+
expect(info.definitionStack).toContain('Error');
|
|
31
|
+
expect(info.definitionStack).toContain('at ');
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('formatDefinitionInfo', () => {
|
|
35
|
+
test('returns undefined when definedAt is not present', () => {
|
|
36
|
+
const info = {};
|
|
37
|
+
const result = formatDefinitionInfo(info, 'TestProcedure');
|
|
38
|
+
expect(result).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
test('returns formatted string when definedAt is present', () => {
|
|
41
|
+
const info = {
|
|
42
|
+
definedAt: {
|
|
43
|
+
file: '/app/procedures/test.ts',
|
|
44
|
+
line: 42,
|
|
45
|
+
column: 5,
|
|
46
|
+
raw: 'at Object.<anonymous> (/app/procedures/test.ts:42:5)',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const result = formatDefinitionInfo(info, 'TestProcedure');
|
|
50
|
+
expect(result).toBeDefined();
|
|
51
|
+
expect(result).toContain('--- Procedure "TestProcedure" defined at ---');
|
|
52
|
+
expect(result).toContain('/app/procedures/test.ts:42:5');
|
|
53
|
+
});
|
|
54
|
+
test('includes procedure name in formatted output', () => {
|
|
55
|
+
const info = {
|
|
56
|
+
definedAt: {
|
|
57
|
+
file: '/path/to/file.ts',
|
|
58
|
+
line: 10,
|
|
59
|
+
column: 3,
|
|
60
|
+
raw: 'at /path/to/file.ts:10:3',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const result = formatDefinitionInfo(info, 'MyCustomProcedure');
|
|
64
|
+
expect(result).toContain('"MyCustomProcedure"');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('integration with procedure creation', () => {
|
|
68
|
+
test('captures location from calling code', () => {
|
|
69
|
+
// Helper to simulate what happens in Create()
|
|
70
|
+
function simulateCreate() {
|
|
71
|
+
return captureDefinitionInfo();
|
|
72
|
+
}
|
|
73
|
+
const info = simulateCreate();
|
|
74
|
+
// Should have captured the location of the simulateCreate() call
|
|
75
|
+
expect(info).toBeDefined();
|
|
76
|
+
expect(info.definitionStack).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
//# sourceMappingURL=stack-utils.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-utils.test.js","sourceRoot":"","sources":["../src/stack-utils.test.ts"],"names":[],"mappings":"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"}
|
package/docs/core.md
CHANGED
|
@@ -14,6 +14,7 @@ Procedures<TContext, TExtendedConfig>(options?: {
|
|
|
14
14
|
schema?: { adapters?: SchemaAdapter[] }
|
|
15
15
|
http?: { pathPrefix?: string; scope?: string }
|
|
16
16
|
onCreate?: (procedure: AnyProcedureRegistration<TContext, TExtendedConfig>) => void
|
|
17
|
+
middleware?: FactoryMiddleware<TContext>[] // runs with final ctx + input for all procedures
|
|
17
18
|
})
|
|
18
19
|
```
|
|
19
20
|
|
|
@@ -25,9 +26,12 @@ Procedures<TContext, TExtendedConfig>(options?: {
|
|
|
25
26
|
| `options.schema.adapters` | Plug in additional schema libraries via the `SchemaAdapter` interface. TypeBox is built in. See [Other Schema Libraries](#other-schema-libraries--schemaadapter). |
|
|
26
27
|
| `options.http` | Factory-level defaults for `CreateHttp` / `CreateHttpStream`: `pathPrefix` is prepended to every route's `path` at registration time (part of the route's identity, unlike a server builder's mount prefix), and `scope` is the default codegen scope for routes that don't set one. Per-route config always wins. |
|
|
27
28
|
| `options.onCreate` | Optional callback invoked when each procedure is registered (runtime) |
|
|
29
|
+
| `options.middleware` | Array of middleware functions that run for every procedure on this factory. They receive the final resolved `ctx` (your `TContext` + `error()` + `signal`) plus the validated input, and can inspect ("parse the incoming call"), augment via `next({ ctx?, input? })`, or short-circuit. See `FactoryMiddleware`. |
|
|
28
30
|
|
|
29
31
|
**Returns:** `Create`, `CreateStream`, `CreateHttp`, `CreateHttpStream`, plus introspection helpers `getProcedures()`, `getProcedure(name)`, `removeProcedure(name)`, and `clear()`.
|
|
30
32
|
|
|
33
|
+
Factory `middleware` runs with the resolved context + input for every procedure defined through the factory (transport-independent, useful for authorization that needs `ctx` + call data). Builder-level middleware lives on `HonoAppBuilder` (see [HTTP Integrations](./http-integrations.md)).
|
|
34
|
+
|
|
31
35
|
## Create Function
|
|
32
36
|
|
|
33
37
|
The `Create` function defines individual procedures:
|
|
@@ -74,6 +78,7 @@ const { UpdateUser } = CreateHttp(
|
|
|
74
78
|
**Rules:**
|
|
75
79
|
- `schema.req` and `schema.params` are **mutually exclusive** — `schema.params` belongs to RPC procedures (`Create`/`CreateStream`), `schema.req` to HTTP procedures. Using `schema.params` on `CreateHttp` throws `ProcedureRegistrationError`.
|
|
76
80
|
- Valid channels are `pathParams`, `query`, `body`, and `headers`; each is validated independently with per-channel error messages.
|
|
81
|
+
- The channel names are provided by `THttpReqInput` (and its alias `APIInput` from `ts-procedures/http`) so that IDEs offer autocomplete when writing `schema: { req: { ... } }`. Use `satisfies APIInput` for additional typo protection on the keys.
|
|
77
82
|
- `schema.res.body` documents the response body (drives codegen); omit `schema.res` entirely for 204-style no-content routes.
|
|
78
83
|
- Factory-level `http.pathPrefix` / `http.scope` defaults apply; per-route config wins.
|
|
79
84
|
- For HTTP streaming routes, use `CreateHttpStream` — same `schema.req` channels plus `schema.yield`.
|
|
@@ -500,6 +505,7 @@ Creates a procedure factory.
|
|
|
500
505
|
- `options.schema.adapters` - Additional schema-library adapters (see [Other Schema Libraries](#other-schema-libraries--schemaadapter))
|
|
501
506
|
- `options.http` - Factory-level `pathPrefix` / `scope` defaults for `CreateHttp` / `CreateHttpStream`
|
|
502
507
|
- `options.onCreate` - Callback invoked when each procedure is registered
|
|
508
|
+
- `options.middleware` - Factory-level middleware with access to final `ctx` + input (for authz, enrichment, call parsing, etc.)
|
|
503
509
|
|
|
504
510
|
**Returns:**
|
|
505
511
|
- `Create` / `CreateStream` / `CreateHttp` / `CreateHttpStream` - Functions to define procedures
|
|
@@ -560,6 +566,8 @@ import type {
|
|
|
560
566
|
TNoContextProvided,
|
|
561
567
|
HttpMethod,
|
|
562
568
|
HttpReturn,
|
|
569
|
+
FactoryMiddleware, // for Procedures({ middleware }) — receives final ctx + input
|
|
570
|
+
|
|
563
571
|
|
|
564
572
|
// Errors
|
|
565
573
|
ProcedureErrorKind, // 'procedure' | 'validation' | 'yield-validation' | 'registration'
|
|
@@ -219,6 +219,46 @@ new HonoAppBuilder({
|
|
|
219
219
|
|
|
220
220
|
The observer is awaited before the response is sent, and any error it throws is swallowed (logged to the console) so a broken instrumentation hook can't corrupt the primary flow.
|
|
221
221
|
|
|
222
|
+
### Middleware (auth, tenancy, guards)
|
|
223
|
+
|
|
224
|
+
Middleware can be declared at two levels that compose naturally.
|
|
225
|
+
|
|
226
|
+
**At the `HonoAppBuilder` (serving / transport level):**
|
|
227
|
+
|
|
228
|
+
- `new HonoAppBuilder({ middleware: [mw, ...] })`
|
|
229
|
+
- `builder.use(mw)` or `builder.use('/specific/*', mw)` (chainable)
|
|
230
|
+
- Per registration: `.register(factory, ctx, { middleware: [mw], context?: extraLayer })`
|
|
231
|
+
|
|
232
|
+
Signature: `(c: Context, next) => Response | Promise<Response|void> | void`.
|
|
233
|
+
|
|
234
|
+
**At the `Procedures()` factory (definition / business-logic level):**
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
const Protected = Procedures<MyCtx>({
|
|
238
|
+
middleware: [({ ctx, input, next }) => { ... }]
|
|
239
|
+
})
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
These run *after* context resolution but before your handler. They receive the final `ctx` (same one the handler sees) + the validated `input`, so you can "parse the incoming call", perform authz using `ctx.user` + `input`, or augment via `next({ ctx?, input? })`. Defined as `FactoryMiddleware<TContext>`.
|
|
243
|
+
|
|
244
|
+
See the `Procedures()` options in [Core](./core.md).
|
|
245
|
+
|
|
246
|
+
Use builder middleware for raw request concerns and factory middleware for ctx-aware logic. Errors from either flow through your taxonomy / `onError`.
|
|
247
|
+
|
|
248
|
+
The builder example below shows the two layers working together:
|
|
249
|
+
|
|
250
|
+
```ts
|
|
251
|
+
const app = new HonoAppBuilder({ pathPrefix: '/api', middleware: [authenticate] })
|
|
252
|
+
.register(Protected, (c) => ({
|
|
253
|
+
tenant: c.get('tenant'),
|
|
254
|
+
user: getUser(c),
|
|
255
|
+
}))
|
|
256
|
+
.register(Public, (c) => ({ requestId: c.req.header('x-rid') }))
|
|
257
|
+
.build()
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`context` layers (via `register(..., { context })` or arrays) are resolved left-to-right via `composeContextInits`. Middleware declared at either level flows errors through `onRequestError` → taxonomy → `onError`.
|
|
261
|
+
|
|
222
262
|
### Dispatch order inside each builder's catch block
|
|
223
263
|
|
|
224
264
|
1. **`onRequestError`** (observer) — awaited, can't alter dispatch or response
|
|
@@ -311,33 +351,7 @@ const docs = new DocRegistry({ errors: appErrors, basePath: '/api' })
|
|
|
311
351
|
|
|
312
352
|
`http-stream` route docs include the route's `yield` and `res.headers` schemas. (Through v8, live envelopes silently dropped these for `http-stream` routes, so generated clients lost those types unless the envelope was hand-authored; v9 fixed this.)
|
|
313
353
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
Every envelope carries two top-level metadata fields so downstream consumers (docs viewers, codegen, dashboards) can reason about format compatibility:
|
|
317
|
-
|
|
318
|
-
```jsonc
|
|
319
|
-
{
|
|
320
|
-
"specVersion": 1, // wire-format version of the envelope itself
|
|
321
|
-
"generatorVersion": "10.0.0", // ts-procedures package version that produced it
|
|
322
|
-
"basePath": "/api",
|
|
323
|
-
"headers": [],
|
|
324
|
-
"errors": [ /* ... */ ],
|
|
325
|
-
"routes": [ /* ... */ ]
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
- **`specVersion`** is a small monotonic integer, bumped **only** when the envelope shape changes in a way parsers must react to — never on ordinary package releases. Branch on it. An envelope with no `specVersion` predates the field and must be treated as `1`. The current value is exported as `DOC_ENVELOPE_SPEC_VERSION` from `ts-procedures` and `ts-procedures/server`.
|
|
330
|
-
- **`generatorVersion`** is the producing package version (`DOC_ENVELOPE_GENERATOR_VERSION`, exported from `ts-procedures/server`). It is informational only — for telemetry and "which release served this?" — and must **not** drive parse-compatibility decisions, since it churns on every release regardless of the envelope format.
|
|
331
|
-
|
|
332
|
-
If you'd rather not advertise the exact package version on a public docs endpoint, strip it with the `transform` hook:
|
|
333
|
-
|
|
334
|
-
```typescript
|
|
335
|
-
app.get('/docs', (c) => c.json(
|
|
336
|
-
builder.toDocEnvelope({ transform: ({ generatorVersion, ...env }) => env }),
|
|
337
|
-
))
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md). Building a third-party docs viewer or other tool that consumes the served envelope? The full wire contract — every field, per-kind shapes, and a validatable JSON Schema — is in [DocEnvelope Spec](./doc-envelope-spec-v1.md) (`doc-envelope-v1.schema.json`).
|
|
354
|
+
The `DocRegistry` output is the input for [Client Code Generation](./client-and-codegen.md).
|
|
341
355
|
|
|
342
356
|
## Type Exports
|
|
343
357
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.2.0",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -76,15 +76,15 @@
|
|
|
76
76
|
"typebox": "^1.0.30"
|
|
77
77
|
},
|
|
78
78
|
"devDependencies": {
|
|
79
|
-
"@eslint/js": "^
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"eslint": "^10.5.0",
|
|
84
|
-
"globals": "^17.7.0",
|
|
79
|
+
"@eslint/js": "^9.17.0",
|
|
80
|
+
"astro": "^6.3.1",
|
|
81
|
+
"eslint": "^9.17.0",
|
|
82
|
+
"globals": "^15.0.0",
|
|
85
83
|
"hono": "^4.7.4",
|
|
86
|
-
"typescript": "^
|
|
84
|
+
"typescript": "^5.7.0",
|
|
87
85
|
"typescript-eslint": "^8.53.0",
|
|
86
|
+
"@typescript-eslint/eslint-plugin": "^8.53.0",
|
|
87
|
+
"@typescript-eslint/parser": "^8.53.0",
|
|
88
88
|
"vitest": "^4.0.18"
|
|
89
89
|
}
|
|
90
90
|
}
|
|
@@ -86,3 +86,5 @@ Dispatch rules:
|
|
|
86
86
|
## What's NOT included
|
|
87
87
|
|
|
88
88
|
- DocRegistry coupling — wire `DocRegistry` against the same builders separately for client codegen.
|
|
89
|
+
|
|
90
|
+
Middleware, guards, and context layering are configured on the `HonoAppBuilder` instances you pass in (`.use()`, `middleware`, per-`register` options). The Astro adapter just dispatches the pre-built apps.
|
|
@@ -3,10 +3,8 @@ import { dirname, resolve } from 'node:path'
|
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
import { describe, expect, it } from 'vitest'
|
|
5
5
|
import { Type } from 'typebox'
|
|
6
|
-
import pkg from '../../../package.json' with { type: 'json' }
|
|
7
6
|
import { Procedures } from '../../core/procedures.js'
|
|
8
7
|
import { HonoAppBuilder, defineErrorTaxonomy } from './index.js'
|
|
9
|
-
import { DOC_ENVELOPE_SPEC_VERSION } from '../../server/spec-version.js'
|
|
10
8
|
import type { RPCConfig } from '../../server/types.js'
|
|
11
9
|
|
|
12
10
|
/**
|
|
@@ -18,15 +16,12 @@ import type { RPCConfig } from '../../server/types.js'
|
|
|
18
16
|
* The envelope is codegen's input; if this test fails, generated client output
|
|
19
17
|
* WILL drift.
|
|
20
18
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* never emitted. These are asserted explicitly below, then stripped before
|
|
28
|
-
* the deep-equal so the rest stays byte-for-byte with the v8 capture.
|
|
29
|
-
* Everything else is the untouched v8 capture.
|
|
19
|
+
* ONE documented deviation from the raw v8 capture: v8's CreateHttpStream
|
|
20
|
+
* dropped the `yield` schema (and `res.headers`) from http-stream route docs —
|
|
21
|
+
* the registration stored `yieldType` while the doc builder read `yield`. v9
|
|
22
|
+
* fixes that, so the fixture's TailUser route was patched to include the
|
|
23
|
+
* `yield` schema the builder now (correctly) emits. Everything else is the
|
|
24
|
+
* untouched v8 capture.
|
|
30
25
|
*/
|
|
31
26
|
class TeapotError extends Error {}
|
|
32
27
|
|
|
@@ -126,13 +121,6 @@ describe('DocEnvelope parity with v8.6.0', () => {
|
|
|
126
121
|
const expected = JSON.parse(await readFile(fixturePath, 'utf-8'))
|
|
127
122
|
const actual = JSON.parse(JSON.stringify(builder.toDocEnvelope()))
|
|
128
123
|
|
|
129
|
-
// Deviation #2: assert the new v9 envelope metadata, then strip it so the
|
|
130
|
-
// remaining shape matches the untouched v8 capture.
|
|
131
|
-
expect(actual.specVersion).toBe(DOC_ENVELOPE_SPEC_VERSION)
|
|
132
|
-
expect(actual.generatorVersion).toBe(pkg.version)
|
|
133
|
-
delete actual.specVersion
|
|
134
|
-
delete actual.generatorVersion
|
|
135
|
-
|
|
136
124
|
expect(actual).toEqual(expected)
|
|
137
125
|
})
|
|
138
126
|
})
|
|
@@ -7,9 +7,9 @@ import { resolveFactoryContext } from '../../../server/context.js'
|
|
|
7
7
|
import { extractReqChannels } from '../../../server/request/params.js'
|
|
8
8
|
import { parseQueryNative } from '../../../server/request/query.js'
|
|
9
9
|
import { SseEventSequencer } from '../../../server/sse.js'
|
|
10
|
-
import { honoRequestSource, respondToDispatch } from '../request.js'
|
|
10
|
+
import { honoRequestSource, respondToDispatch, runMiddlewareList } from '../request.js'
|
|
11
11
|
import { midStreamErrorConfig, preStreamErrorConfig } from '../types.js'
|
|
12
|
-
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
|
|
12
|
+
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
|
|
13
13
|
|
|
14
14
|
export function installHttpStreamRoute(params: {
|
|
15
15
|
app: Hono
|
|
@@ -17,8 +17,9 @@ export function installHttpStreamRoute(params: {
|
|
|
17
17
|
factoryItem: HonoFactoryItem
|
|
18
18
|
cfg: HonoAppBuilderConfig
|
|
19
19
|
docs: DocAccumulator
|
|
20
|
+
builderMiddleware?: HonoRequestMiddleware[]
|
|
20
21
|
}): void {
|
|
21
|
-
const { app, procedure, factoryItem, cfg, docs } = params
|
|
22
|
+
const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
|
|
22
23
|
const queryParser = cfg.api?.queryParser ?? parseQueryNative
|
|
23
24
|
|
|
24
25
|
const route = buildHttpStreamRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
|
|
@@ -26,8 +27,16 @@ export function installHttpStreamRoute(params: {
|
|
|
26
27
|
|
|
27
28
|
const reqSchema = procedure.config.schema?.req
|
|
28
29
|
|
|
30
|
+
const combinedMw = [
|
|
31
|
+
...(builderMiddleware ?? []),
|
|
32
|
+
...(factoryItem.middleware ?? []),
|
|
33
|
+
]
|
|
34
|
+
|
|
29
35
|
app.on(procedure.config.method.toUpperCase(), route.fullPath, async (c: Context) => {
|
|
30
36
|
try {
|
|
37
|
+
const early = await runMiddlewareList(c, combinedMw)
|
|
38
|
+
if (early) return early
|
|
39
|
+
|
|
31
40
|
const context = await resolveFactoryContext(factoryItem.factoryContext, c)
|
|
32
41
|
|
|
33
42
|
const reqParams = reqSchema
|
|
@@ -5,9 +5,9 @@ import { buildHttpRouteDoc } from '../../../server/docs/http-doc.js'
|
|
|
5
5
|
import { resolveFactoryContext } from '../../../server/context.js'
|
|
6
6
|
import { defaultSuccessStatus, extractReqChannels } from '../../../server/request/params.js'
|
|
7
7
|
import { parseQueryNative } from '../../../server/request/query.js'
|
|
8
|
-
import { honoRequestSource, respondToDispatch } from '../request.js'
|
|
8
|
+
import { honoRequestSource, respondToDispatch, runMiddlewareList } from '../request.js'
|
|
9
9
|
import { preStreamErrorConfig } from '../types.js'
|
|
10
|
-
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
|
|
10
|
+
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
|
|
11
11
|
|
|
12
12
|
export function installHttpRoute(params: {
|
|
13
13
|
app: Hono
|
|
@@ -15,8 +15,9 @@ export function installHttpRoute(params: {
|
|
|
15
15
|
factoryItem: HonoFactoryItem
|
|
16
16
|
cfg: HonoAppBuilderConfig
|
|
17
17
|
docs: DocAccumulator
|
|
18
|
+
builderMiddleware?: HonoRequestMiddleware[]
|
|
18
19
|
}): void {
|
|
19
|
-
const { app, procedure, factoryItem, cfg, docs } = params
|
|
20
|
+
const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
|
|
20
21
|
const queryParser = cfg.api?.queryParser ?? parseQueryNative
|
|
21
22
|
|
|
22
23
|
const route = buildHttpRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
|
|
@@ -25,8 +26,16 @@ export function installHttpRoute(params: {
|
|
|
25
26
|
const successStatus = procedure.config.successStatus ?? defaultSuccessStatus(procedure.config.method)
|
|
26
27
|
const reqSchema = procedure.config.schema?.req
|
|
27
28
|
|
|
29
|
+
const combinedMw = [
|
|
30
|
+
...(builderMiddleware ?? []),
|
|
31
|
+
...(factoryItem.middleware ?? []),
|
|
32
|
+
]
|
|
33
|
+
|
|
28
34
|
app.on(procedure.config.method.toUpperCase(), route.fullPath, async (c: Context) => {
|
|
29
35
|
try {
|
|
36
|
+
const early = await runMiddlewareList(c, combinedMw)
|
|
37
|
+
if (early) return early
|
|
38
|
+
|
|
30
39
|
const context = await resolveFactoryContext(factoryItem.factoryContext, c)
|
|
31
40
|
|
|
32
41
|
const reqParams = reqSchema
|
|
@@ -4,9 +4,9 @@ import type { RPCConfig } from '../../../server/types.js'
|
|
|
4
4
|
import { dispatchPreStreamError } from '../../../server/errors/dispatch.js'
|
|
5
5
|
import { buildRpcRouteDoc } from '../../../server/docs/rpc-doc.js'
|
|
6
6
|
import { resolveFactoryContext } from '../../../server/context.js'
|
|
7
|
-
import { respondToDispatch } from '../request.js'
|
|
7
|
+
import { respondToDispatch, runMiddlewareList } from '../request.js'
|
|
8
8
|
import { preStreamErrorConfig } from '../types.js'
|
|
9
|
-
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
|
|
9
|
+
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
|
|
10
10
|
|
|
11
11
|
export function installRpcRoute(params: {
|
|
12
12
|
app: Hono
|
|
@@ -14,14 +14,23 @@ export function installRpcRoute(params: {
|
|
|
14
14
|
factoryItem: HonoFactoryItem
|
|
15
15
|
cfg: HonoAppBuilderConfig
|
|
16
16
|
docs: DocAccumulator
|
|
17
|
+
builderMiddleware?: HonoRequestMiddleware[]
|
|
17
18
|
}): void {
|
|
18
|
-
const { app, procedure, factoryItem, cfg, docs } = params
|
|
19
|
+
const { app, procedure, factoryItem, cfg, docs, builderMiddleware } = params
|
|
19
20
|
|
|
20
21
|
const route = buildRpcRouteDoc(procedure, cfg.pathPrefix, factoryItem.extendProcedureDoc as any)
|
|
21
22
|
docs.push(route)
|
|
22
23
|
|
|
24
|
+
const combinedMw = [
|
|
25
|
+
...(builderMiddleware ?? []),
|
|
26
|
+
...(factoryItem.middleware ?? []),
|
|
27
|
+
]
|
|
28
|
+
|
|
23
29
|
app.post(route.path, async (c: Context) => {
|
|
24
30
|
try {
|
|
31
|
+
const early = await runMiddlewareList(c, combinedMw)
|
|
32
|
+
if (early) return early
|
|
33
|
+
|
|
25
34
|
const context = await resolveFactoryContext(factoryItem.factoryContext, c)
|
|
26
35
|
const body = await c.req.json().catch(() => ({}))
|
|
27
36
|
const result = await procedure.handler({ ...context, signal: c.req.raw.signal }, body)
|
|
@@ -7,9 +7,9 @@ import { dispatchMidStreamError, dispatchPreStreamError } from '../../../server/
|
|
|
7
7
|
import { buildStreamRouteDoc } from '../../../server/docs/stream-doc.js'
|
|
8
8
|
import { resolveFactoryContext } from '../../../server/context.js'
|
|
9
9
|
import { SseEventSequencer } from '../../../server/sse.js'
|
|
10
|
-
import { respondToDispatch } from '../request.js'
|
|
10
|
+
import { respondToDispatch, runMiddlewareList } from '../request.js'
|
|
11
11
|
import { midStreamErrorConfig, preStreamErrorConfig } from '../types.js'
|
|
12
|
-
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem } from '../types.js'
|
|
12
|
+
import type { DocAccumulator, HonoAppBuilderConfig, HonoFactoryItem, HonoRequestMiddleware } from '../types.js'
|
|
13
13
|
|
|
14
14
|
export function installRpcStreamRoute(params: {
|
|
15
15
|
app: Hono
|
|
@@ -18,8 +18,9 @@ export function installRpcStreamRoute(params: {
|
|
|
18
18
|
cfg: HonoAppBuilderConfig
|
|
19
19
|
docs: DocAccumulator
|
|
20
20
|
streamMode: StreamMode
|
|
21
|
+
builderMiddleware?: HonoRequestMiddleware[]
|
|
21
22
|
}): void {
|
|
22
|
-
const { app, procedure, factoryItem, cfg, docs, streamMode } = params
|
|
23
|
+
const { app, procedure, factoryItem, cfg, docs, streamMode, builderMiddleware } = params
|
|
23
24
|
|
|
24
25
|
const route = buildStreamRouteDoc(
|
|
25
26
|
procedure,
|
|
@@ -29,8 +30,16 @@ export function installRpcStreamRoute(params: {
|
|
|
29
30
|
)
|
|
30
31
|
docs.push(route)
|
|
31
32
|
|
|
33
|
+
const combinedMw = [
|
|
34
|
+
...(builderMiddleware ?? []),
|
|
35
|
+
...(factoryItem.middleware ?? []),
|
|
36
|
+
]
|
|
37
|
+
|
|
32
38
|
const handler = async (c: Context) => {
|
|
33
39
|
try {
|
|
40
|
+
const early = await runMiddlewareList(c, combinedMw)
|
|
41
|
+
if (early) return early
|
|
42
|
+
|
|
34
43
|
const context = await resolveFactoryContext(factoryItem.factoryContext, c)
|
|
35
44
|
|
|
36
45
|
const reqParams =
|
|
@@ -325,3 +325,73 @@ describe('HonoAppBuilder — per-factory streamMode override', () => {
|
|
|
325
325
|
await res.text()
|
|
326
326
|
})
|
|
327
327
|
})
|
|
328
|
+
|
|
329
|
+
describe('HonoAppBuilder — middleware (new)', () => {
|
|
330
|
+
test('builder config.middleware and per-register middleware both run; register ones after builder ones', async () => {
|
|
331
|
+
const order: string[] = []
|
|
332
|
+
const builderMw = vi.fn(async (_c: any, next: any) => { order.push('builder'); await next() })
|
|
333
|
+
const regMw = vi.fn(async (_c: any, next: any) => { order.push('register'); await next() })
|
|
334
|
+
|
|
335
|
+
const P = Procedures<object, RPCConfig>()
|
|
336
|
+
P.Create('Ping', { scope: 'p', version: 1 }, async () => ({ ok: true }))
|
|
337
|
+
|
|
338
|
+
const app = new HonoAppBuilder({ middleware: [builderMw] })
|
|
339
|
+
.register(P, () => ({}), { middleware: [regMw] })
|
|
340
|
+
.build()
|
|
341
|
+
|
|
342
|
+
const res = await app.request('/p/ping/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
|
|
343
|
+
expect(res.status).toBe(200)
|
|
344
|
+
expect(order).toEqual(['builder', 'register'])
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test('middleware can short-circuit by throwing (flows to dispatch + onRequestError)', async () => {
|
|
348
|
+
const onRequestError = vi.fn()
|
|
349
|
+
const P = Procedures<object, RPCConfig>()
|
|
350
|
+
P.Create('Secret', { scope: 's', version: 1 }, async () => ({ secret: true }))
|
|
351
|
+
|
|
352
|
+
const app = new HonoAppBuilder({ onRequestError })
|
|
353
|
+
.register(P, () => ({}), {
|
|
354
|
+
middleware: [async () => { throw Object.assign(new Error('nope'), { status: 401 }) }],
|
|
355
|
+
})
|
|
356
|
+
.build()
|
|
357
|
+
|
|
358
|
+
const res = await app.request('/s/secret/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
|
|
359
|
+
expect(res.status).toBe(500) // default path since no taxonomy matched the thrown Error
|
|
360
|
+
expect(onRequestError).toHaveBeenCalledTimes(1)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
test('register options.context layers are composed with main factoryContext (later wins)', async () => {
|
|
364
|
+
const P = Procedures<{ tenant?: string; uid: string }, RPCConfig>()
|
|
365
|
+
P.Create('WhoAmI', { scope: 'w', version: 1 }, async (ctx) => ({ tenant: ctx.tenant, uid: ctx.uid }))
|
|
366
|
+
|
|
367
|
+
const base = (c: any) => ({ uid: c.req.header('x-uid') || 'u0' })
|
|
368
|
+
const tenantLayer = { tenant: 't-from-layer' }
|
|
369
|
+
|
|
370
|
+
const app = new HonoAppBuilder()
|
|
371
|
+
.register(P, base, { context: tenantLayer as any })
|
|
372
|
+
.build()
|
|
373
|
+
|
|
374
|
+
const res = await app.request('/w/who-am-i/1', {
|
|
375
|
+
method: 'POST',
|
|
376
|
+
headers: { 'Content-Type': 'application/json', 'x-uid': 'user-xyz' },
|
|
377
|
+
body: '{}',
|
|
378
|
+
})
|
|
379
|
+
expect(await res.json()).toEqual({ tenant: 't-from-layer', uid: 'user-xyz' })
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test('chainable .use() installs and runs for procedures', async () => {
|
|
383
|
+
const calls: string[] = []
|
|
384
|
+
const mw = async (_c: any, next: any) => { calls.push('used'); await next() }
|
|
385
|
+
|
|
386
|
+
const P = Procedures<object, RPCConfig>()
|
|
387
|
+
P.Create('X', { scope: 'x', version: 1 }, async () => ({}))
|
|
388
|
+
|
|
389
|
+
const b = new HonoAppBuilder()
|
|
390
|
+
b.use(mw)
|
|
391
|
+
b.register(P, () => ({}))
|
|
392
|
+
const app = b.build()
|
|
393
|
+
|
|
394
|
+
await app.request('/x/x/1', { method: 'POST', body: '{}', headers: { 'Content-Type': 'application/json' } })
|
|
395
|
+
expect(calls).toContain('used')
|
|
396
|
+
})
|
|
397
|
+
})
|