ts-procedures 10.2.0 → 10.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +9 -6
  3. package/agent_config/bin/postinstall.mjs +20 -89
  4. package/agent_config/bin/setup.mjs +51 -264
  5. package/agent_config/lib/install-skills.mjs +108 -0
  6. package/build/codegen/bin/cli.js +1 -1
  7. package/build/codegen/bin/cli.js.map +1 -1
  8. package/build/codegen/emit/api-route.js +1 -1
  9. package/build/codegen/emit/api-route.js.map +1 -1
  10. package/build/codegen/emit/scope-file.js +1 -1
  11. package/build/codegen/emit/scope-file.js.map +1 -1
  12. package/build/codegen/emit-errors.integration.test.js +2 -2
  13. package/build/codegen/emit-errors.integration.test.js.map +1 -1
  14. package/build/codegen/resolve-envelope.js +1 -1
  15. package/build/codegen/resolve-envelope.js.map +1 -1
  16. package/build/codegen/test-helpers/run-tsc.js +1 -1
  17. package/build/codegen/test-helpers/run-tsc.js.map +1 -1
  18. package/build/exports.d.ts +1 -1
  19. package/build/exports.js.map +1 -1
  20. package/build/exports.surface.test.d.ts +1 -0
  21. package/build/exports.surface.test.js +132 -0
  22. package/build/exports.surface.test.js.map +1 -0
  23. package/docs/ai-agent-setup.md +19 -27
  24. package/docs/client-error-handling.md +1 -1
  25. package/docs/http-integrations.md +1 -1
  26. package/package.json +9 -8
  27. package/src/codegen/bin/cli.ts +1 -1
  28. package/src/codegen/emit/api-route.ts +1 -1
  29. package/src/codegen/emit/scope-file.ts +2 -1
  30. package/src/codegen/emit-errors.integration.test.ts +2 -2
  31. package/src/codegen/resolve-envelope.ts +1 -1
  32. package/src/codegen/test-helpers/run-tsc.ts +1 -0
  33. package/src/exports.surface.test.ts +134 -0
  34. package/src/exports.ts +1 -0
  35. package/agent_config/claude-code/.claude-plugin/plugin.json +0 -5
  36. package/agent_config/claude-code/agents/ts-procedures-architect.md +0 -138
  37. package/agent_config/copilot/copilot-instructions.md +0 -521
  38. package/agent_config/cursor/cursorrules +0 -521
  39. package/agent_config/lib/install-claude.mjs +0 -57
  40. /package/agent_config/{claude-code/skills → skills}/ts-procedures/SKILL.md +0 -0
  41. /package/agent_config/{claude-code/skills → skills}/ts-procedures/anti-patterns.md +0 -0
  42. /package/agent_config/{claude-code/skills → skills}/ts-procedures/api-reference.md +0 -0
  43. /package/agent_config/{claude-code/skills → skills}/ts-procedures/checklist.md +0 -0
  44. /package/agent_config/{claude-code/skills → skills}/ts-procedures/patterns.md +0 -0
  45. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/astro-catchall.md +0 -0
  46. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/client.md +0 -0
  47. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/hono.md +0 -0
  48. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/procedure.md +0 -0
  49. /package/agent_config/{claude-code/skills → skills}/ts-procedures/templates/stream-procedure.md +0 -0
@@ -91,7 +91,7 @@ describe('generated _errors.ts — runtime behavior', () => {
91
91
  const err = e as { stdout?: Buffer; stderr?: Buffer }
92
92
  const stdout = err.stdout?.toString() ?? ''
93
93
  const stderr = err.stderr?.toString() ?? ''
94
- throw new Error(`tsc failed in ${outDir}:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`)
94
+ throw new Error(`tsc failed in ${outDir}:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`, { cause: e })
95
95
  }
96
96
 
97
97
  // Dynamic import of the compiled output.
@@ -164,7 +164,7 @@ describe('generated _errors.ts — runtime behavior', () => {
164
164
  const err = e as { stdout?: Buffer; stderr?: Buffer }
165
165
  const stdout = err.stdout?.toString() ?? ''
166
166
  const stderr = err.stderr?.toString() ?? ''
167
- throw new Error(`tsc failed in ${outDir}:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`)
167
+ throw new Error(`tsc failed in ${outDir}:\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`, { cause: e })
168
168
  }
169
169
 
170
170
  const errorsUrl = `file://${join(outDir, 'out', '_errors.js')}`
@@ -21,7 +21,7 @@ export async function resolveEnvelope(input: ResolveInput): Promise<DocEnvelope>
21
21
  response = await fetch(input.url)
22
22
  } catch (err) {
23
23
  const msg = err instanceof Error ? err.message : String(err)
24
- throw new Error(`[ts-procedures-codegen] Failed to connect to ${input.url}: ${msg}`)
24
+ throw new Error(`[ts-procedures-codegen] Failed to connect to ${input.url}: ${msg}`, { cause: err })
25
25
  }
26
26
  if (!response.ok) {
27
27
  throw new Error(`[ts-procedures-codegen] ${input.url} returned HTTP ${response.status} ${response.statusText}`)
@@ -51,6 +51,7 @@ export function runTsc(args: {
51
51
  const combined = [stdout, stderr].filter(Boolean).join('\n').trim()
52
52
  throw new Error(
53
53
  `[runTsc] tsc failed for ${tsconfigPath}:\n${combined || (e.message ?? '(no output)')}`,
54
+ { cause: err },
54
55
  )
55
56
  }
56
57
  }
@@ -0,0 +1,134 @@
1
+ import { dirname, relative, resolve } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import ts from 'typescript'
4
+ import { describe, expect, it } from 'vitest'
5
+
6
+ /**
7
+ * PUBLIC-SURFACE PORTABILITY GUARD.
8
+ *
9
+ * Every type that appears in the *inferred* return/value type of a public API
10
+ * (e.g. `Procedures(...)`, the four creators, the Hono builder) must be
11
+ * re-exported from one of the package's public entry points. If it is only
12
+ * exported from an internal module, a downstream app that writes
13
+ * `export const x = Procedures<Ctx>(...)` cannot name the inferred type and
14
+ * gets TS2742/TS2883 ("The inferred type of 'x' cannot be named without a
15
+ * reference to '<Internal>' from '.../node_modules/ts-procedures/build/...'.
16
+ * This is likely not portable. A type annotation is necessary.").
17
+ *
18
+ * That's a packaging bug on our side, not the consumer's. This test emits the
19
+ * declarations for every public entry and fails if any public-surface `.d.ts`
20
+ * references — via an `import("<relative>").Symbol` — a symbol that no public
21
+ * entry re-exports. The fix is always: add the symbol to the relevant
22
+ * `export type { ... }` block (see `src/exports.ts`).
23
+ *
24
+ * Regression: `ProcedureResult` (the shape every creator returns) was referenced
25
+ * by `procedures.d.ts` but missing from the root export block.
26
+ */
27
+
28
+ const here = dirname(fileURLToPath(import.meta.url))
29
+ const srcRoot = here
30
+
31
+ // Public entry source files — mirror of package.json "exports".*.types (build/ -> src/).
32
+ const PUBLIC_ENTRY_FILES = [
33
+ 'exports.ts',
34
+ 'adapters/astro/index.ts',
35
+ 'server/types.ts',
36
+ 'adapters/hono/index.ts',
37
+ 'server/index.ts',
38
+ 'server/doc-registry.ts',
39
+ 'server/errors/taxonomy.ts',
40
+ 'client/index.ts',
41
+ 'codegen/index.ts',
42
+ ].map((p) => resolve(srcRoot, p))
43
+
44
+ /** Emit declarations for the whole `src` program into an in-memory map keyed by abs path. */
45
+ function emitDeclarations(): Map<string, string> {
46
+ const program = ts.createProgram(PUBLIC_ENTRY_FILES, {
47
+ target: ts.ScriptTarget.ES2024,
48
+ module: ts.ModuleKind.NodeNext,
49
+ moduleResolution: ts.ModuleResolutionKind.NodeNext,
50
+ strict: true,
51
+ declaration: true,
52
+ emitDeclarationOnly: true,
53
+ skipLibCheck: true,
54
+ noEmitOnError: false,
55
+ })
56
+ const out = new Map<string, string>()
57
+ program.emit(undefined, (fileName, text) => {
58
+ if (fileName.endsWith('.d.ts')) out.set(resolve(fileName), text)
59
+ })
60
+ return out
61
+ }
62
+
63
+ const reExportNamed = /export\s+(?:type\s+)?\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/g
64
+ const reExportStar = /export\s+\*\s+from\s*['"]([^'"]+)['"]/g
65
+ const localExport =
66
+ /export\s+(?:declare\s+)?(?:abstract\s+)?(?:function|class|const|let|var|type|interface|enum)\s+([A-Za-z0-9_]+)/g
67
+ const importRef = /import\(["']([^"']+)["'](?:,\s*\{[^}]*\})?\)\.([A-Za-z0-9_]+)/g
68
+
69
+ function resolveSpec(fromFile: string, spec: string): string | undefined {
70
+ if (!spec.startsWith('.')) return undefined
71
+ return resolve(dirname(fromFile), spec.replace(/\.js$/, '.d.ts'))
72
+ }
73
+
74
+ describe('public API surface — portability', () => {
75
+ it('every type in a public-surface declaration is re-exported from a public entry', () => {
76
+ const decls = emitDeclarations()
77
+ const declOf = (absTsPath: string) => decls.get(absTsPath.replace(/\.ts$/, '.d.ts'))
78
+
79
+ const publicSymbols = new Set<string>()
80
+ const surfaceFiles = new Set<string>() // .d.ts paths whose bodies form the public surface
81
+ const seen = new Set<string>()
82
+
83
+ const walk = (declPath: string, harvestLocal: boolean) => {
84
+ const key = `${declPath}|${harvestLocal}`
85
+ if (seen.has(key)) return
86
+ seen.add(key)
87
+ const src = decls.get(declPath)
88
+ if (src === undefined) return
89
+ if (harvestLocal) surfaceFiles.add(declPath)
90
+
91
+ for (const m of src.matchAll(reExportNamed)) {
92
+ for (const raw of (m[1] ?? '').split(',')) {
93
+ const name = raw.trim().split(/\s+as\s+/).pop()?.trim()
94
+ if (name) publicSymbols.add(name)
95
+ }
96
+ const tgt = m[2] && resolveSpec(declPath, m[2])
97
+ if (tgt) surfaceFiles.add(tgt) // body may leak internal refs through the named symbol
98
+ }
99
+ for (const m of src.matchAll(reExportStar)) {
100
+ const tgt = m[1] && resolveSpec(declPath, m[1])
101
+ if (tgt) walk(tgt, true)
102
+ }
103
+ if (harvestLocal) for (const m of src.matchAll(localExport)) if (m[1]) publicSymbols.add(m[1])
104
+ }
105
+
106
+ for (const entry of PUBLIC_ENTRY_FILES) {
107
+ const d = declOf(entry)
108
+ expect(d, `expected emitted declaration for public entry ${relative(srcRoot, entry)}`).toBeDefined()
109
+ walk(entry.replace(/\.ts$/, '.d.ts'), true)
110
+ }
111
+
112
+ const leaks: string[] = []
113
+ for (const file of surfaceFiles) {
114
+ const src = decls.get(file)
115
+ if (src === undefined) continue
116
+ for (const m of src.matchAll(importRef)) {
117
+ const spec = m[1]
118
+ const sym = m[2]
119
+ if (!spec || !sym) continue
120
+ if (!spec.startsWith('.')) continue // bare specifiers (e.g. hono) are portable for consumers
121
+ if (!publicSymbols.has(sym)) {
122
+ leaks.push(`${sym} (referenced by ${relative(srcRoot, file)} via import("${spec}"))`)
123
+ }
124
+ }
125
+ }
126
+
127
+ expect(
128
+ [...new Set(leaks)].sort(),
129
+ 'Internal type(s) leak into the public declaration surface but are not re-exported from any public entry. ' +
130
+ 'Downstream apps will hit TS2742/TS2883 ("inferred type cannot be named ... not portable"). ' +
131
+ 'Re-export each listed symbol from the appropriate public entry (e.g. src/exports.ts).',
132
+ ).toEqual([])
133
+ })
134
+ })
package/src/exports.ts CHANGED
@@ -17,6 +17,7 @@ export { Procedures } from './core/procedures.js'
17
17
  export type {
18
18
  ProceduresOptions,
19
19
  ProcedureKind,
20
+ ProcedureResult,
20
21
  AnyProcedureRegistration,
21
22
  TProcedureRegistration,
22
23
  TStreamProcedureRegistration,
@@ -1,5 +0,0 @@
1
- {
2
- "name": "ts-procedures",
3
- "version": "9.0.0",
4
- "description": "AI coding assistant plugin for ts-procedures — a TypeScript RPC framework for type-safe, schema-validated procedure calls with automatic validation, streaming support, and HTTP framework integrations."
5
- }
@@ -1,138 +0,0 @@
1
- ---
2
- name: ts-procedures-architect
3
- description: "Architecture planning agent for ts-procedures RPC applications. Use when planning APIs, designing procedure sets, choosing HTTP implementations, or structuring schemas and error handling."
4
- model: sonnet
5
- disallowedTools: Write, Edit
6
- skills:
7
- - ts-procedures
8
- color: blue
9
- effort: high
10
- ---
11
-
12
- You are an architecture planning agent for applications built with **ts-procedures**, a TypeScript RPC framework that creates type-safe, schema-validated procedure calls. You help developers plan APIs by deciding procedure structure, schema design, context shape, and HTTP integration strategy.
13
-
14
- The full ts-procedures framework reference is preloaded via the `ts-procedures` skill — use it for API details, schema rules, error classes, and HTTP builder specifics.
15
-
16
- ## Your Process
17
-
18
- When asked to plan an API or procedure set:
19
-
20
- 1. **Understand the requirement** — ask clarifying questions if ambiguous
21
- 2. **Identify procedure groups** — which factories, what context/config types
22
- 3. **List procedures** — name, type (Create vs CreateStream), description
23
- 4. **Design schemas** — params (validated), returnType/yieldType (documented)
24
- 5. **Design context** — what each handler needs from the request
25
- 6. **Choose HTTP implementation** — `HonoAppBuilder` covers all four procedure kinds (rpc, rpc-stream, http, http-stream); pick which kinds the app needs
26
- 7. **Plan error handling** — which layer for each error type
27
- 8. **Map route structure** — scope + version for RPC routes, path + method for API routes
28
-
29
- ## Architecture Rules
30
-
31
- - `schema.params` is validated at runtime; `schema.returnType` is documentation only.
32
- - Handlers receive `(ctx, params)` where ctx includes base context + `error()` function + optional `signal`.
33
- - Stream handlers always get `ctx.signal` (guaranteed `AbortSignal`). Standard handlers get it when provided by the HTTP implementation.
34
- - Pass `signal` to all downstream async calls (fetch, database queries) for cancellation support.
35
- - `onCreate` callback on the factory enables framework integration — use it for route registration, middleware setup, or documentation generation. Every registration carries a `kind` discriminant (`'rpc' | 'rpc-stream' | 'http' | 'http-stream'`). For a full custom server adapter, build on the transport-agnostic `ts-procedures/server` toolkit (route-doc builders, error dispatch, request channel extraction, SSE sequencing).
36
- - `getProcedures()` returns all registered procedures for introspection (OpenAPI generation, testing, etc.).
37
- - AJV is configured with `allErrors: true`, `coerceTypes: true`, `removeAdditional: true` — customizable per factory via `Procedures({ validation: { ajv } })`; `validation: false` skips per-call validation for trusted internal factories only.
38
- - TypeBox is the built-in schema library; other libraries plug in via `Procedures({ schema: { adapters: [SchemaAdapter] } })` (runtime validation + docs only — `Infer<T>` works only for TypeBox).
39
- - `Procedures({ http: { pathPrefix, scope } })` sets factory-level defaults for `CreateHttp` / `CreateHttpStream` routes (per-route values win).
40
- - `schema.params` and `schema.req` are mutually exclusive — defining both throws `ProcedureRegistrationError`.
41
- - Path param names in route template (`:id`) must match `schema.req.pathParams` property names.
42
- - Use `DocRegistry` to compose route docs from multiple builders — never manually wire `/docs` endpoints. Pass your taxonomy directly: `new DocRegistry({ errors: appErrors })` — framework defaults are auto-merged and deduped. For errors outside your taxonomy (middleware, infrastructure, doc-only), chain `.documentError(...docs)`.
43
- - Two first-class peer error-handling modes: **declarative taxonomy** (`defineErrorTaxonomy` + `errors` config) OR **imperative callback** (`onError`). Neither is deprecated. Pick the taxonomy for structured apps with typed client dispatch; pick `onError` for simple apps or full response control. Mixing both is allowed — the taxonomy handles what it covers, `onError` handles the tail. The anti-pattern is `instanceof` ladders inside `onError` (see anti-pattern #20 in the skill reference) — that's exactly what the taxonomy expresses declaratively.
44
- - Per-route `errors: ['UseCaseError', ...]` narrows typed errors on the generated client. For compile-time typo protection, narrow `RPCConfig<keyof typeof appErrors & string>` on RPC factories; on `CreateHttp` routes attach `satisfies (keyof typeof appErrors & string)[]` to the `errors` array (an explicit generic would bind `TName` and break `schema.req`/`schema.res` inference).
45
- - Generated `_errors.ts` emits real runtime classes extending `${Service}ProcedureError` — consumers catch with `instanceof ${Service}Errors.${Name}` and access `err.body`, `err.status`, `err.procedureName`, `err.scope`. Use the generated `create${Service}Client(config)` factory to wire the error registry automatically.
46
-
47
- ## Context Design Patterns
48
-
49
- ```typescript
50
- // Minimal — just auth
51
- type AppContext = { userId: string }
52
-
53
- // With services — inject dependencies
54
- type AppContext = { userId: string; db: Database; logger: Logger }
55
-
56
- // With request metadata
57
- type AppContext = { userId: string; requestId: string; signal?: AbortSignal }
58
- ```
59
-
60
- Context is resolved per-request by the HTTP builder's `factoryContext` function.
61
-
62
- ## Extended Config Patterns
63
-
64
- ```typescript
65
- // RPC-style: scope + version (required for RPC builders)
66
- interface AppConfig extends RPCConfig {
67
- scope: string | string[]
68
- version: number
69
- }
70
-
71
- // REST-style: no extended config needed — path, method, scope, errors are
72
- // first-class fields on the CreateHttp / CreateHttpStream config itself.
73
- const API = Procedures<AppContext>()
74
- API.CreateHttp('GetUser', { path: '/users/:id', method: 'get', schema: { /* req/res */ } }, handler)
75
- ```
76
-
77
- ## Output Format
78
-
79
- ```
80
- ## API: [name]
81
-
82
- ### Procedure Groups
83
- - `PublicRPC` — context: { requestId }, config: RPCConfig
84
- - `AuthRPC` — context: { userId, requestId }, config: RPCConfig
85
-
86
- ### Procedures
87
-
88
- #### PublicRPC
89
- - `HealthCheck` (Create) — Returns service health status
90
- - `GetPublicConfig` (Create) — Returns public configuration
91
-
92
- #### AuthRPC
93
- - `GetUser` (Create) — Fetch user by ID
94
- - `UpdateUser` (Create) — Update user fields
95
- - `StreamActivity` (CreateStream, SSE) — Real-time activity feed
96
-
97
- ### Schema Design
98
- ```typescript
99
- // GetUser params
100
- Type.Object({ userId: Type.String() })
101
-
102
- // GetUser returnType
103
- Type.Object({ id: Type.String(), name: Type.String(), email: Type.String() })
104
- ```
105
-
106
- ### Context Design
107
- ```typescript
108
- type PublicContext = { requestId: string }
109
- type AuthContext = { userId: string; requestId: string; db: Database }
110
- ```
111
-
112
- ### HTTP Setup
113
- - HonoAppBuilder for RPC, streams (SSE/text), and REST endpoints
114
- - DocRegistry to compose docs across multiple builders (or `builder.toDocEnvelope()` for single-app)
115
- - Path prefix: /api
116
-
117
- ### Route Map
118
- - POST /api/health/health-check/1
119
- - POST /api/users/get-user/1
120
- - GET|POST /api/activity/stream-activity/1
121
- - GET /api/users/:id (HonoAppBuilder)
122
- - POST /api/users (HonoAppBuilder, 201)
123
- - DELETE /api/users/:id (HonoAppBuilder, 204)
124
-
125
- ### Error Handling (pick a mode, optionally combine)
126
- - **Declarative mode (recommended for structured apps)**: `defineErrorTaxonomy({ AuthError: {class, 401}, NotFoundError: {class, 404}, ... })` wired via `errors` config. Drives typed client dispatch + DocEnvelope.
127
- - **Imperative mode (simple apps or full response control)**: `onError: (procedure, c|req/res, err) => Response|void` — first-class peer.
128
- - `unknownError` — fallback serializer for errors the taxonomy doesn't cover (pairs with declarative mode).
129
- - `onRequestError` — cross-cutting observer for logging/tracing/metrics. Fires for every error, before dispatch.
130
- - Input validation is automatic (default taxonomy: `ProcedureValidationError` → 400).
131
- - Business errors: throw typed class instances — registered taxonomy classes auto-serialize, or handle in `onError`.
132
- - Per-route: declare `errors: ['AuthError', ...]` on each route config so the generated client narrows `catch` types.
133
- - Stream pre-errors: both peer modes apply. Mid-stream errors: `onMidStreamError` → yield error event, close stream.
134
- ```
135
-
136
- ## Next Steps
137
-
138
- After planning, use `/ts-procedures scaffold <type> <Name>` to generate each procedure with correct patterns and tests.