ts-procedures 10.1.2 → 10.2.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 +33 -86
- package/README.md +11 -6
- package/agent_config/bin/postinstall.mjs +20 -89
- package/agent_config/bin/setup.mjs +51 -264
- package/agent_config/lib/install-skills.mjs +108 -0
- package/agent_config/{claude-code/skills → skills}/ts-procedures/anti-patterns.md +8 -0
- package/agent_config/{claude-code/skills → skills}/ts-procedures/api-reference.md +25 -22
- package/agent_config/{claude-code/skills → skills}/ts-procedures/patterns.md +24 -13
- 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.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/compute-schema.js +1 -5
- package/build/schema/compute-schema.js.map +1 -1
- package/build/schema/compute-schema.test.js +0 -13
- package/build/schema/compute-schema.test.js.map +1 -1
- 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/ai-agent-setup.md +19 -27
- package/docs/client-error-handling.md +1 -1
- package/docs/core.md +8 -0
- package/docs/http-integrations.md +42 -28
- 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/compute-schema.test.ts +0 -16
- package/src/schema/compute-schema.ts +1 -5
- 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/agent_config/claude-code/.claude-plugin/plugin.json +0 -5
- package/agent_config/claude-code/agents/ts-procedures-architect.md +0 -138
- package/agent_config/copilot/copilot-instructions.md +0 -521
- package/agent_config/cursor/cursorrules +0 -521
- package/agent_config/lib/install-claude.mjs +0 -57
- package/build/schema/strip-internal-keywords.d.ts +0 -18
- package/build/schema/strip-internal-keywords.js +0 -33
- package/build/schema/strip-internal-keywords.js.map +0 -1
- package/build/schema/strip-internal-keywords.test.js +0 -38
- package/build/schema/strip-internal-keywords.test.js.map +0 -1
- 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/schema/strip-internal-keywords.test.ts +0 -43
- package/src/schema/strip-internal-keywords.ts +0 -36
- package/src/server/spec-version.ts +0 -27
- package/src/version.ts +0 -11
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/SKILL.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/checklist.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/astro-catchall.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/client.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/hono.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/procedure.md +0 -0
- /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/stream-procedure.md +0 -0
- /package/build/{schema/strip-internal-keywords.test.d.ts → core/create-http.test-d.d.ts} +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal ts-procedures files that should be skipped when finding user code.
|
|
3
|
+
* Only skip the core library files, not test files or user code.
|
|
4
|
+
*/
|
|
5
|
+
const INTERNAL_FILES = [
|
|
6
|
+
'/index.ts',
|
|
7
|
+
'/index.js',
|
|
8
|
+
'/errors.ts',
|
|
9
|
+
'/errors.js',
|
|
10
|
+
'/stack-utils.ts',
|
|
11
|
+
'/stack-utils.js',
|
|
12
|
+
'/compute-schema.ts',
|
|
13
|
+
'/compute-schema.js',
|
|
14
|
+
'/parser.ts',
|
|
15
|
+
'/parser.js',
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Captures the stack trace at the call site and extracts the definition location.
|
|
19
|
+
* Finds the first stack frame outside of ts-procedures internal files.
|
|
20
|
+
*/
|
|
21
|
+
export function captureDefinitionInfo() {
|
|
22
|
+
const err = new Error();
|
|
23
|
+
const stack = err.stack;
|
|
24
|
+
if (!stack) {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
const lines = stack.split('\n');
|
|
28
|
+
// Find the first frame that's not from ts-procedures internals
|
|
29
|
+
// Skip the first line (Error message) and frames from this module
|
|
30
|
+
let userFrame;
|
|
31
|
+
for (let i = 1; i < lines.length; i++) {
|
|
32
|
+
const rawLine = lines[i];
|
|
33
|
+
if (!rawLine)
|
|
34
|
+
continue;
|
|
35
|
+
const line = rawLine.trim();
|
|
36
|
+
// Skip empty or invalid frames
|
|
37
|
+
if (!line.startsWith('at ')) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
// Skip frames from ts-procedures internal source files
|
|
41
|
+
const isInternalFile = INTERNAL_FILES.some(file => line.includes(file));
|
|
42
|
+
if (isInternalFile) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Skip frames from ts-procedures in node_modules (when used as a dependency)
|
|
46
|
+
if (line.includes('/node_modules/ts-procedures/') || line.includes('\\node_modules\\ts-procedures\\')) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Skip internal node frames
|
|
50
|
+
if (line.includes('node:') || line.startsWith('at Module.') || line.startsWith('at Object.<anonymous> (node:')) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
userFrame = line;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
if (!userFrame) {
|
|
57
|
+
return { definitionStack: stack };
|
|
58
|
+
}
|
|
59
|
+
const definedAt = parseStackFrame(userFrame);
|
|
60
|
+
return {
|
|
61
|
+
definedAt,
|
|
62
|
+
definitionStack: stack,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parses a V8 stack frame line to extract file, line, and column info.
|
|
67
|
+
* Handles formats like:
|
|
68
|
+
* - "at Object.<anonymous> (/path/to/file.ts:10:5)"
|
|
69
|
+
* - "at functionName (/path/to/file.ts:10:5)"
|
|
70
|
+
* - "at /path/to/file.ts:10:5"
|
|
71
|
+
*/
|
|
72
|
+
function parseStackFrame(frame) {
|
|
73
|
+
// Match patterns like "(path:line:column)" or just "path:line:column"
|
|
74
|
+
const match = frame.match(/\(([^)]+):(\d+):(\d+)\)$/) || frame.match(/at ([^:]+):(\d+):(\d+)$/);
|
|
75
|
+
if (match && match[1] && match[2] && match[3]) {
|
|
76
|
+
return {
|
|
77
|
+
file: match[1],
|
|
78
|
+
line: parseInt(match[2], 10),
|
|
79
|
+
column: parseInt(match[3], 10),
|
|
80
|
+
raw: frame,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Formats definition info for appending to error stacks.
|
|
87
|
+
*/
|
|
88
|
+
export function formatDefinitionInfo(info, procedureName) {
|
|
89
|
+
if (!info.definedAt) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const { file, line, column } = info.definedAt;
|
|
93
|
+
return `\n--- Procedure "${procedureName}" defined at ---\n at ${file}:${line}:${column}`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=stack-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack-utils.js","sourceRoot":"","sources":["../src/stack-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC/C,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAkB,MAAM,kBAAkB,CAAA;AAE9F,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;QACrC,IAAI,CAAC,wCAAwC,EAAE,GAAG,EAAE;YAClD,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAA;YAEpC,iDAAiD;YACjD,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;YAC1B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,WAAW,EAAE,CAAA;YAC1C,MAAM,CAAC,OAAO,IAAI,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QACpD,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;YAChE,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAA;YAEpC,iFAAiF;YACjF,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;gBACzC,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACjD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;gBACzC,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACjD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;gBAC9C,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAA;gBAC3C,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACnD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAA;gBAChD,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;gBACxC,MAAM,CAAC,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YAClD,CAAC;QACH,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;YACtD,MAAM,IAAI,GAAG,qBAAqB,EAAE,CAAA;YAEpC,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;YAC/C,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;QAC/C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;YAC3D,MAAM,IAAI,GAAmB,EAAE,CAAA;YAC/B,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;YAE1D,MAAM,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAA;QAChC,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;YAC9D,MAAM,IAAI,GAAmB;gBAC3B,SAAS,EAAE;oBACT,IAAI,EAAE,yBAAyB;oBAC/B,IAAI,EAAE,EAAE;oBACR,MAAM,EAAE,CAAC;oBACT,GAAG,EAAE,sDAAsD;iBAC5D;aACF,CAAA;YACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;YAE1D,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAA;YAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,8CAA8C,CAAC,CAAA;YACxE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAA;QAC1D,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;YACvD,MAAM,IAAI,GAAmB;gBAC3B,SAAS,EAAE;oBACT,IAAI,EAAE,kBAAkB;oBACxB,IAAI,EAAE,EAAE;oBACR,MAAM,EAAE,CAAC;oBACT,GAAG,EAAE,0BAA0B;iBAChC;aACF,CAAA;YACD,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,EAAE,mBAAmB,CAAC,CAAA;YAE9D,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;QACjD,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;QACnD,IAAI,CAAC,qCAAqC,EAAE,GAAG,EAAE;YAC/C,8CAA8C;YAC9C,SAAS,cAAc;gBACrB,OAAO,qBAAqB,EAAE,CAAA;YAChC,CAAC;YAED,MAAM,IAAI,GAAG,cAAc,EAAE,CAAA;YAE7B,iEAAiE;YACjE,MAAM,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAA;YAC1B,MAAM,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,WAAW,EAAE,CAAA;QAC5C,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
package/docs/ai-agent-setup.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# AI Agent Setup
|
|
4
4
|
|
|
5
|
-
ts-procedures ships
|
|
5
|
+
ts-procedures ships a single, spec-compliant **Agent Skill** that gives AI coding tools framework-aware context when writing ts-procedures code in your project. It follows the open [Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) / `SKILL.md` standard, so a wide range of tools — Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, and others — read the same files.
|
|
6
6
|
|
|
7
7
|
## Quick Setup
|
|
8
8
|
|
|
@@ -10,52 +10,44 @@ ts-procedures ships with built-in AI assistant configuration for **Claude Code**
|
|
|
10
10
|
npx ts-procedures-setup
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
This installs
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
npx ts-procedures-setup claude # Claude Code only
|
|
17
|
-
npx ts-procedures-setup cursor # Cursor only
|
|
18
|
-
npx ts-procedures-setup copilot # GitHub Copilot only
|
|
19
|
-
```
|
|
13
|
+
This installs the skill into the standard agent directories. There are no per-tool targets — one skill works everywhere.
|
|
20
14
|
|
|
21
15
|
## What Gets Installed
|
|
22
16
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
|
26
|
-
|
|
27
|
-
|
|
|
17
|
+
The same skill is copied into two standard locations:
|
|
18
|
+
|
|
19
|
+
| Path | Read by |
|
|
20
|
+
|------|---------|
|
|
21
|
+
| `.agents/skills/ts-procedures/` | The cross-tool standard — 30+ agents (Codex, Cursor, GitHub Copilot, Gemini CLI, …) scan `.agents/skills` from the working directory up to the repo root |
|
|
22
|
+
| `.claude/skills/ts-procedures/` | Claude Code's native project skills location |
|
|
23
|
+
|
|
24
|
+
The skill bundles the framework reference plus `review` / `scaffold` modes (checklist + templates) and Kotlin/Swift client-codegen reference, using progressive disclosure so tools load detail only as needed.
|
|
28
25
|
|
|
29
26
|
## Auto-Updates
|
|
30
27
|
|
|
31
|
-
After initial setup,
|
|
28
|
+
After initial setup, the skill is automatically refreshed on every `npm install` or `npm update` via the package's postinstall hook. When ts-procedures publishes a new version, your AI tools get the latest framework guidance with no manual steps. Auto-update is opt-in: it only runs once the skill has been installed at least once.
|
|
32
29
|
|
|
33
|
-
##
|
|
30
|
+
## Skill Modes
|
|
34
31
|
|
|
35
|
-
Once installed,
|
|
32
|
+
Once installed, the `ts-procedures` skill exposes three modes selected by the first word of its arguments:
|
|
36
33
|
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- *Review* — `/ts-procedures review <path>` checks code against a 60+ item checklist
|
|
41
|
-
- **Architecture agent** — `ts-procedures-architect` helps plan procedure structure, schema design, and HTTP implementation choices
|
|
34
|
+
- *Reference* (no argument) — core API, schema system, error handling, decision framework, plus Kotlin/Swift client-codegen reference (auto-discovered by the agent)
|
|
35
|
+
- *Scaffold* — `scaffold <type> <Name>` generates procedures, streams, and HTTP setups with correct patterns
|
|
36
|
+
- *Review* — `review <path>` checks code against a 60+ item checklist
|
|
42
37
|
|
|
43
38
|
## CLI Options
|
|
44
39
|
|
|
45
40
|
```bash
|
|
46
|
-
npx ts-procedures-setup --force # Overwrite without prompting
|
|
47
41
|
npx ts-procedures-setup --dry-run # Preview what would be created/updated
|
|
48
42
|
npx ts-procedures-setup --check # Exit with code 1 if files are outdated (for CI)
|
|
49
43
|
```
|
|
50
44
|
|
|
51
45
|
## Gitignore
|
|
52
46
|
|
|
53
|
-
The
|
|
47
|
+
The installed skill is generated and regenerated on `npm install`. You can add the install roots to `.gitignore`:
|
|
54
48
|
|
|
55
49
|
```gitignore
|
|
56
|
-
# Auto-generated AI agent
|
|
50
|
+
# Auto-generated AI agent skill (regenerated on npm install)
|
|
51
|
+
.agents/skills/ts-procedures/
|
|
57
52
|
.claude/skills/ts-procedures/
|
|
58
|
-
.claude/agents/ts-procedures-*.md
|
|
59
53
|
```
|
|
60
|
-
|
|
61
|
-
Cursor and Copilot files use marker-based sections that coexist with your own rules, so they should typically be committed.
|
|
@@ -414,7 +414,7 @@ A framework-level "skip `onError` for `.safe()`" toggle was deliberately deferre
|
|
|
414
414
|
|
|
415
415
|
- Codegen setup, per-call options, hooks, and typed-error wiring: [`docs/client-and-codegen.md`](./client-and-codegen.md)
|
|
416
416
|
- Client types and interfaces: package source under `src/client/` (`types.ts`, `errors.ts`, `error-dispatch.ts`)
|
|
417
|
-
- Agent config patterns and anti-patterns: [`agent_config/
|
|
417
|
+
- Agent config patterns and anti-patterns: [`agent_config/skills/ts-procedures/patterns.md`](../agent_config/skills/ts-procedures/patterns.md) and [`anti-patterns.md`](../agent_config/skills/ts-procedures/anti-patterns.md)
|
|
418
418
|
|
|
419
419
|
---
|
|
420
420
|
|
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'
|
|
@@ -178,7 +178,7 @@ new HonoAppBuilder({
|
|
|
178
178
|
}).register(procs, /* ... */).build()
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
-
(Need to route different error classes to different status codes? Use the taxonomy — that's exactly what it's designed for. Hand-writing `instanceof` ladders inside `onError` is [anti-pattern #20](../agent_config/
|
|
181
|
+
(Need to route different error classes to different status codes? Use the taxonomy — that's exactly what it's designed for. Hand-writing `instanceof` ladders inside `onError` is [anti-pattern #20](../agent_config/skills/ts-procedures/anti-patterns.md).)
|
|
182
182
|
|
|
183
183
|
`HonoAppBuilder`'s `onError` signature: `(procedure, c: Context, error) => Response | Promise<Response>` — for streaming procedures, pre-stream only.
|
|
184
184
|
|
|
@@ -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.1
|
|
3
|
+
"version": "10.2.1",
|
|
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)
|