ts-procedures 6.2.0 → 7.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +2 -1
- package/agent_config/claude-code/skills/ts-procedures/anti-patterns.md +38 -1
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +215 -3
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +60 -2
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +1 -1
- package/agent_config/copilot/copilot-instructions.md +3 -0
- package/agent_config/cursor/cursorrules +3 -0
- package/build/client/augment-error-map.test-d.d.ts +10 -0
- package/build/client/augment-error-map.test-d.js +14 -0
- package/build/client/augment-error-map.test-d.js.map +1 -0
- package/build/client/call.d.ts +14 -2
- package/build/client/call.js +96 -9
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +50 -1
- package/build/client/call.test.js.map +1 -1
- package/build/client/classify-error.d.ts +11 -0
- package/build/client/classify-error.js +49 -0
- package/build/client/classify-error.js.map +1 -0
- package/build/client/classify-error.test.d.ts +1 -0
- package/build/client/classify-error.test.js +55 -0
- package/build/client/classify-error.test.js.map +1 -0
- package/build/client/error-dispatch.d.ts +1 -1
- package/build/client/error-dispatch.js +1 -1
- package/build/client/errors.d.ts +55 -4
- package/build/client/errors.js +54 -7
- package/build/client/errors.js.map +1 -1
- package/build/client/errors.test.js +89 -4
- package/build/client/errors.test.js.map +1 -1
- package/build/client/fetch-adapter.d.ts +2 -1
- package/build/client/fetch-adapter.js +2 -1
- package/build/client/fetch-adapter.js.map +1 -1
- package/build/client/fetch-adapter.test.js +12 -0
- package/build/client/fetch-adapter.test.js.map +1 -1
- package/build/client/index.d.ts +5 -3
- package/build/client/index.js +15 -3
- package/build/client/index.js.map +1 -1
- package/build/client/resolve-options.d.ts +32 -1
- package/build/client/resolve-options.js +32 -16
- package/build/client/resolve-options.js.map +1 -1
- package/build/client/resolve-options.test.js +67 -6
- package/build/client/resolve-options.test.js.map +1 -1
- package/build/client/result-type.test-d.d.ts +1 -0
- package/build/client/result-type.test-d.js +28 -0
- package/build/client/result-type.test-d.js.map +1 -0
- package/build/client/safe-call.test.d.ts +1 -0
- package/build/client/safe-call.test.js +137 -0
- package/build/client/safe-call.test.js.map +1 -0
- package/build/client/stream.d.ts +1 -1
- package/build/client/stream.js +22 -8
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +11 -1
- package/build/client/stream.test.js.map +1 -1
- package/build/client/types.d.ts +96 -3
- package/build/codegen/bundle-size.test.d.ts +1 -0
- package/build/codegen/bundle-size.test.js +68 -0
- package/build/codegen/bundle-size.test.js.map +1 -0
- package/build/codegen/e2e.test.js +103 -1
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.js +7 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/build/codegen/emit-client-runtime.test.js +6 -2
- package/build/codegen/emit-client-runtime.test.js.map +1 -1
- package/build/codegen/emit-client-types.d.ts +7 -2
- package/build/codegen/emit-client-types.js +29 -8
- package/build/codegen/emit-client-types.js.map +1 -1
- package/build/codegen/emit-client-types.test.js +20 -8
- package/build/codegen/emit-client-types.test.js.map +1 -1
- package/build/codegen/emit-errors.d.ts +1 -1
- package/build/codegen/emit-errors.js +1 -1
- package/build/codegen/emit-index.js +1 -1
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-scope.js +94 -26
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +297 -2
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/docs/client-and-codegen.md +77 -7
- package/docs/client-error-handling.md +357 -0
- package/docs/superpowers/plans/2026-04-29-safe-result-api.md +2293 -0
- package/docs/superpowers/specs/2026-04-29-safe-result-api-design.md +324 -0
- package/package.json +1 -1
- package/src/client/augment-error-map.test-d.ts +22 -0
- package/src/client/call.test.ts +65 -1
- package/src/client/call.ts +111 -9
- package/src/client/classify-error.test.ts +65 -0
- package/src/client/classify-error.ts +59 -0
- package/src/client/error-dispatch.ts +1 -1
- package/src/client/errors.test.ts +108 -4
- package/src/client/errors.ts +70 -7
- package/src/client/fetch-adapter.test.ts +15 -0
- package/src/client/fetch-adapter.ts +5 -2
- package/src/client/index.ts +39 -3
- package/src/client/resolve-options.test.ts +83 -5
- package/src/client/resolve-options.ts +61 -16
- package/src/client/result-type.test-d.ts +51 -0
- package/src/client/safe-call.test.ts +157 -0
- package/src/client/stream.test.ts +13 -1
- package/src/client/stream.ts +25 -8
- package/src/client/types.ts +112 -3
- package/src/codegen/bundle-size.test.ts +74 -0
- package/src/codegen/e2e.test.ts +108 -1
- package/src/codegen/emit-client-runtime.test.ts +7 -2
- package/src/codegen/emit-client-runtime.ts +7 -0
- package/src/codegen/emit-client-types.test.ts +22 -7
- package/src/codegen/emit-client-types.ts +35 -10
- package/src/codegen/emit-errors.ts +1 -1
- package/src/codegen/emit-index.ts +1 -1
- package/src/codegen/emit-scope.test.ts +324 -2
- package/src/codegen/emit-scope.ts +98 -36
|
@@ -246,28 +246,57 @@ async function emitRpcRoute(route: RPCHttpRouteDoc, ctx: EmitRouteContext): Prom
|
|
|
246
246
|
const responseTypeName = refs['Response'] ?? 'unknown'
|
|
247
247
|
const scopeStr = Array.isArray(route.scope) ? route.scope.join('-') : route.scope
|
|
248
248
|
|
|
249
|
+
// Compute the error union once — used for both the .safe return type and
|
|
250
|
+
// injectRouteErrors. Use errorUnion !== null (not route.errors?.length) so
|
|
251
|
+
// we only emit Result<…, Errors> when keys were actually emitted in _errors.ts.
|
|
252
|
+
const errorUnion = buildErrorUnion(route.errors, ctx)
|
|
253
|
+
const hasErrors = errorUnion !== null
|
|
254
|
+
const errorsRef = ctx.namespaceTypes
|
|
255
|
+
? `${ctx.scopePascal}.${pascal}.Errors`
|
|
256
|
+
: `${pascal}Errors`
|
|
257
|
+
const resultType = hasErrors
|
|
258
|
+
? `Result<${responseTypeName}, ${errorsRef}>`
|
|
259
|
+
: `ResultNoTyped<${responseTypeName}>`
|
|
260
|
+
|
|
261
|
+
// When no typed errors, cast Result<T, never> → ResultNoTyped<T> so consumers
|
|
262
|
+
// see the simpler type in their IDE. The cast is safe: Result<T, never> has an
|
|
263
|
+
// unreachable `kind: 'typed'` arm that ResultNoTyped intentionally omits.
|
|
264
|
+
const safeReturn = hasErrors
|
|
265
|
+
? `return client.safeCall<${responseTypeName}, ${errorsRef}>({`
|
|
266
|
+
: `return client.safeCall<${responseTypeName}>({`
|
|
267
|
+
const safeResultCast = hasErrors ? '' : ` as Promise<${resultType}>`
|
|
268
|
+
|
|
249
269
|
const callable = [
|
|
250
270
|
` /** ${route.method.toUpperCase()} ${route.path} */`,
|
|
251
|
-
` ${pascal}
|
|
252
|
-
`
|
|
253
|
-
`
|
|
254
|
-
`
|
|
255
|
-
`
|
|
256
|
-
`
|
|
257
|
-
`
|
|
258
|
-
`
|
|
259
|
-
`
|
|
260
|
-
`
|
|
271
|
+
` ${pascal}: Object.assign(`,
|
|
272
|
+
` function ${pascal}(params: ${paramsTypeName}, options?: ProcedureCallOptions): Promise<${responseTypeName}> {`,
|
|
273
|
+
` return client.call<${responseTypeName}>({`,
|
|
274
|
+
` name: '${pascal}',`,
|
|
275
|
+
` scope: '${scopeStr}',`,
|
|
276
|
+
` path: '${route.path}',`,
|
|
277
|
+
` method: '${route.method}',`,
|
|
278
|
+
` kind: 'rpc',`,
|
|
279
|
+
` params,`,
|
|
280
|
+
` }, options)`,
|
|
281
|
+
` },`,
|
|
282
|
+
` {`,
|
|
283
|
+
` safe(params: ${paramsTypeName}, options?: ProcedureCallOptions): Promise<${resultType}> {`,
|
|
284
|
+
` ${safeReturn}`,
|
|
285
|
+
` name: '${pascal}',`,
|
|
286
|
+
` scope: '${scopeStr}',`,
|
|
287
|
+
` path: '${route.path}',`,
|
|
288
|
+
` method: '${route.method}',`,
|
|
289
|
+
` kind: 'rpc',`,
|
|
290
|
+
` params,`,
|
|
291
|
+
` }, options)${safeResultCast}`,
|
|
292
|
+
` },`,
|
|
293
|
+
` },`,
|
|
294
|
+
` ),`,
|
|
261
295
|
].join('\n')
|
|
262
296
|
|
|
263
|
-
const
|
|
264
|
-
declarations,
|
|
265
|
-
pascal,
|
|
266
|
-
buildErrorUnion(route.errors, ctx),
|
|
267
|
-
ctx.namespaceTypes
|
|
268
|
-
)
|
|
297
|
+
const hasErrorsInjected = injectRouteErrors(declarations, pascal, errorUnion, ctx.namespaceTypes)
|
|
269
298
|
|
|
270
|
-
return { typeDeclarations: declarations, callable, hasStream: false, hasErrors }
|
|
299
|
+
return { typeDeclarations: declarations, callable, hasStream: false, hasErrors: hasErrorsInjected }
|
|
271
300
|
}
|
|
272
301
|
|
|
273
302
|
async function emitApiRoute(route: APIHttpRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
|
|
@@ -323,28 +352,61 @@ async function emitApiRoute(route: APIHttpRouteDoc, ctx: EmitRouteContext): Prom
|
|
|
323
352
|
const responseTypeName = refs['Response'] ?? 'unknown'
|
|
324
353
|
const scopeStr = route.scope ?? 'default'
|
|
325
354
|
|
|
355
|
+
// Compute the error union once — used for both the .safe return type and
|
|
356
|
+
// injectRouteErrors. Use errorUnion !== null (not route.errors?.length) so
|
|
357
|
+
// we only emit Result<…, Errors> when keys were actually emitted in _errors.ts.
|
|
358
|
+
const errorUnion = buildErrorUnion(route.errors, ctx)
|
|
359
|
+
const hasErrors = errorUnion !== null
|
|
360
|
+
const errorsRef = ctx.namespaceTypes
|
|
361
|
+
? `${ctx.scopePascal}.${pascal}.Errors`
|
|
362
|
+
: `${pascal}Errors`
|
|
363
|
+
const resultType = hasErrors
|
|
364
|
+
? `Result<${responseTypeName}, ${errorsRef}>`
|
|
365
|
+
: `ResultNoTyped<${responseTypeName}>`
|
|
366
|
+
|
|
367
|
+
// When no typed errors, cast Result<T, never> → ResultNoTyped<T> so consumers
|
|
368
|
+
// see the simpler type in their IDE. The cast is safe: Result<T, never> has an
|
|
369
|
+
// unreachable `kind: 'typed'` arm that ResultNoTyped intentionally omits.
|
|
370
|
+
const safeReturn = hasErrors
|
|
371
|
+
? `return client.safeCall<${responseTypeName}, ${errorsRef}>({`
|
|
372
|
+
: `return client.safeCall<${responseTypeName}>({`
|
|
373
|
+
const safeResultCast = hasErrors ? '' : ` as Promise<${resultType}>`
|
|
374
|
+
|
|
375
|
+
// Property key uses route.name verbatim (preserving the prior API
|
|
376
|
+
// emission contract). Function name uses route.name as well — JS function
|
|
377
|
+
// names accept any identifier, and matching the property key avoids any
|
|
378
|
+
// surprise asymmetry in stack traces.
|
|
326
379
|
const callable = [
|
|
327
380
|
` /** ${route.method.toUpperCase()} ${route.fullPath} */`,
|
|
328
|
-
` ${route.name}
|
|
329
|
-
`
|
|
330
|
-
`
|
|
331
|
-
`
|
|
332
|
-
`
|
|
333
|
-
`
|
|
334
|
-
`
|
|
335
|
-
`
|
|
336
|
-
`
|
|
337
|
-
`
|
|
381
|
+
` ${route.name}: Object.assign(`,
|
|
382
|
+
` function ${route.name}(params: ${paramsTypeName}, options?: ProcedureCallOptions): Promise<${responseTypeName}> {`,
|
|
383
|
+
` return client.call<${responseTypeName}>({`,
|
|
384
|
+
` name: '${route.name}',`,
|
|
385
|
+
` scope: '${scopeStr}',`,
|
|
386
|
+
` path: '${route.fullPath}',`,
|
|
387
|
+
` method: '${route.method}',`,
|
|
388
|
+
` kind: 'api',`,
|
|
389
|
+
` params,`,
|
|
390
|
+
` }, options)`,
|
|
391
|
+
` },`,
|
|
392
|
+
` {`,
|
|
393
|
+
` safe(params: ${paramsTypeName}, options?: ProcedureCallOptions): Promise<${resultType}> {`,
|
|
394
|
+
` ${safeReturn}`,
|
|
395
|
+
` name: '${route.name}',`,
|
|
396
|
+
` scope: '${scopeStr}',`,
|
|
397
|
+
` path: '${route.fullPath}',`,
|
|
398
|
+
` method: '${route.method}',`,
|
|
399
|
+
` kind: 'api',`,
|
|
400
|
+
` params,`,
|
|
401
|
+
` }, options)${safeResultCast}`,
|
|
402
|
+
` },`,
|
|
403
|
+
` },`,
|
|
404
|
+
` ),`,
|
|
338
405
|
].join('\n')
|
|
339
406
|
|
|
340
|
-
const
|
|
341
|
-
declarations,
|
|
342
|
-
pascal,
|
|
343
|
-
buildErrorUnion(route.errors, ctx),
|
|
344
|
-
ctx.namespaceTypes
|
|
345
|
-
)
|
|
407
|
+
const hasErrorsInjected = injectRouteErrors(declarations, pascal, errorUnion, ctx.namespaceTypes)
|
|
346
408
|
|
|
347
|
-
return { typeDeclarations: declarations, callable, hasStream: false, hasErrors }
|
|
409
|
+
return { typeDeclarations: declarations, callable, hasStream: false, hasErrors: hasErrorsInjected }
|
|
348
410
|
}
|
|
349
411
|
|
|
350
412
|
async function emitStreamRoute(route: StreamHttpRouteDoc, ctx: EmitRouteContext): Promise<RouteChunks> {
|
|
@@ -458,8 +520,8 @@ export async function emitScopeFile(
|
|
|
458
520
|
|
|
459
521
|
// Build client import line
|
|
460
522
|
const clientImports = hasStream
|
|
461
|
-
? `import type { ClientInstance, ProcedureCallOptions, TypedStream } from '${clientImportPath}'`
|
|
462
|
-
: `import type { ClientInstance, ProcedureCallOptions } from '${clientImportPath}'`
|
|
523
|
+
? `import type { ClientInstance, ProcedureCallOptions, TypedStream, Result, ResultNoTyped } from '${clientImportPath}'`
|
|
524
|
+
: `import type { ClientInstance, ProcedureCallOptions, Result, ResultNoTyped } from '${clientImportPath}'`
|
|
463
525
|
|
|
464
526
|
// Build _errors import line when at least one route emits an Errors union.
|
|
465
527
|
// Namespace mode uses the qualified `${Service}Errors` namespace; flat mode
|