ts-procedures 7.0.0 → 7.1.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.
Files changed (80) hide show
  1. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +4 -0
  2. package/agent_config/copilot/copilot-instructions.md +2 -0
  3. package/agent_config/cursor/cursorrules +2 -0
  4. package/build/client/index.js +5 -0
  5. package/build/client/index.js.map +1 -1
  6. package/build/client/stream.d.ts +25 -1
  7. package/build/client/stream.js +48 -5
  8. package/build/client/stream.js.map +1 -1
  9. package/build/client/stream.test.js +68 -1
  10. package/build/client/stream.test.js.map +1 -1
  11. package/build/codegen/bin/cli.js +91 -0
  12. package/build/codegen/bin/cli.js.map +1 -1
  13. package/build/codegen/bin/cli.test.js +15 -0
  14. package/build/codegen/bin/cli.test.js.map +1 -1
  15. package/build/codegen/e2e.test.js +97 -74
  16. package/build/codegen/e2e.test.js.map +1 -1
  17. package/build/codegen/emit-index.js +11 -1
  18. package/build/codegen/emit-index.js.map +1 -1
  19. package/build/codegen/emit-scope.js +58 -16
  20. package/build/codegen/emit-scope.js.map +1 -1
  21. package/build/codegen/emit-scope.test.js +164 -2
  22. package/build/codegen/emit-scope.test.js.map +1 -1
  23. package/build/codegen/emit-types.d.ts +28 -0
  24. package/build/codegen/emit-types.js +69 -5
  25. package/build/codegen/emit-types.js.map +1 -1
  26. package/build/codegen/emit-types.test.js +30 -0
  27. package/build/codegen/emit-types.test.js.map +1 -1
  28. package/build/codegen/resolve-envelope.js +4 -1
  29. package/build/codegen/resolve-envelope.js.map +1 -1
  30. package/build/codegen/resolve-envelope.test.js +10 -0
  31. package/build/codegen/resolve-envelope.test.js.map +1 -1
  32. package/build/codegen/test-helpers/run-tsc.d.ts +33 -0
  33. package/build/codegen/test-helpers/run-tsc.js +49 -0
  34. package/build/codegen/test-helpers/run-tsc.js.map +1 -0
  35. package/build/implementations/http/doc-registry.js +14 -0
  36. package/build/implementations/http/doc-registry.js.map +1 -1
  37. package/build/implementations/http/doc-registry.test.js +37 -1
  38. package/build/implementations/http/doc-registry.test.js.map +1 -1
  39. package/build/implementations/http/hono-rpc/index.d.ts +11 -0
  40. package/build/implementations/http/hono-rpc/index.js +22 -1
  41. package/build/implementations/http/hono-rpc/index.js.map +1 -1
  42. package/build/implementations/http/hono-rpc/index.test.js +25 -0
  43. package/build/implementations/http/hono-rpc/index.test.js.map +1 -1
  44. package/build/implementations/http/hono-stream/error-taxonomy.test.js +72 -0
  45. package/build/implementations/http/hono-stream/error-taxonomy.test.js.map +1 -1
  46. package/build/implementations/http/hono-stream/index.d.ts +18 -4
  47. package/build/implementations/http/hono-stream/index.js +97 -18
  48. package/build/implementations/http/hono-stream/index.js.map +1 -1
  49. package/build/implementations/http/hono-stream/index.test.js +3 -3
  50. package/build/implementations/http/hono-stream/index.test.js.map +1 -1
  51. package/build/implementations/types.d.ts +10 -0
  52. package/build/index.js +22 -17
  53. package/build/index.js.map +1 -1
  54. package/build/index.test.js +36 -6
  55. package/build/index.test.js.map +1 -1
  56. package/package.json +1 -1
  57. package/src/client/index.ts +6 -0
  58. package/src/client/stream.test.ts +82 -1
  59. package/src/client/stream.ts +67 -4
  60. package/src/codegen/bin/cli.test.ts +26 -0
  61. package/src/codegen/bin/cli.ts +91 -0
  62. package/src/codegen/e2e.test.ts +100 -78
  63. package/src/codegen/emit-index.ts +11 -1
  64. package/src/codegen/emit-scope.test.ts +172 -2
  65. package/src/codegen/emit-scope.ts +66 -13
  66. package/src/codegen/emit-types.test.ts +34 -0
  67. package/src/codegen/emit-types.ts +83 -5
  68. package/src/codegen/resolve-envelope.test.ts +11 -0
  69. package/src/codegen/resolve-envelope.ts +4 -1
  70. package/src/codegen/test-helpers/run-tsc.ts +56 -0
  71. package/src/implementations/http/doc-registry.test.ts +43 -1
  72. package/src/implementations/http/doc-registry.ts +19 -0
  73. package/src/implementations/http/hono-rpc/index.test.ts +32 -0
  74. package/src/implementations/http/hono-rpc/index.ts +27 -1
  75. package/src/implementations/http/hono-stream/error-taxonomy.test.ts +80 -0
  76. package/src/implementations/http/hono-stream/index.test.ts +3 -3
  77. package/src/implementations/http/hono-stream/index.ts +118 -22
  78. package/src/implementations/types.ts +7 -0
  79. package/src/index.test.ts +43 -6
  80. package/src/index.ts +23 -20
@@ -168,6 +168,13 @@ export type AnyHttpRouteDoc = RPCHttpRouteDoc | APIHttpRouteDoc | StreamHttpRout
168
168
 
169
169
  export interface DocSource<T = AnyHttpRouteDoc> {
170
170
  readonly docs: T[]
171
+ /**
172
+ * Optional list of procedures that were registered with this builder but
173
+ * couldn't be served by it (e.g. a streaming procedure registered with an
174
+ * RPC builder). DocRegistry aggregates these across sources and warns at
175
+ * `toJSON()` time so silently-dropped procedures don't slip through.
176
+ */
177
+ readonly skippedProcedures?: { name: string; reason: string }[]
171
178
  }
172
179
 
173
180
  export interface HeaderDoc {
package/src/index.test.ts CHANGED
@@ -915,10 +915,19 @@ describe('Streaming Procedures - CreateStream', () => {
915
915
  expect(values).toEqual(['user-123'])
916
916
  })
917
917
 
918
- test('CreateStream wrapped errors preserve cause', async () => {
918
+ test('CreateStream rethrows the original error preserving class identity', async () => {
919
+ // The streaming wrapper must NOT box user errors inside ProcedureError —
920
+ // doing so would defeat route-declared typed-error dispatch on the client
921
+ // (the HTTP builder's taxonomy would see `ProcedureError` instead of the
922
+ // user's class). Stack annotation is added in place; class identity and
923
+ // custom properties are preserved.
924
+ class MyDomainError extends Error {
925
+ readonly name = 'MyDomainError'
926
+ readonly code = 'STREAM_FAIL'
927
+ }
928
+
919
929
  const { CreateStream } = Procedures()
920
- const originalError = new Error('Stream underlying error')
921
- ;(originalError as any).code = 'STREAM_FAIL'
930
+ const originalError = new MyDomainError('Stream underlying error')
922
931
 
923
932
  const { StreamCause } = CreateStream(
924
933
  'StreamCause',
@@ -935,12 +944,40 @@ describe('Streaming Procedures - CreateStream', () => {
935
944
  }
936
945
  expect.fail('Should have thrown')
937
946
  } catch (e: any) {
938
- expect(e).toBeInstanceOf(ProcedureError)
939
- expect(e.cause).toBe(originalError)
940
- expect(e.cause.code).toBe('STREAM_FAIL')
947
+ expect(e).toBe(originalError)
948
+ expect(e).toBeInstanceOf(MyDomainError)
949
+ expect(e.code).toBe('STREAM_FAIL')
941
950
  }
942
951
  })
943
952
 
953
+ test('CreateStream propagates .return() to the user generator', async () => {
954
+ // Consumers that close a stream early (via `iterator.return()` or breaking
955
+ // out of for-await) must trigger the user generator's `finally` block so
956
+ // cleanup (db handles, subscriptions, signal-driven teardown) runs.
957
+ const { CreateStream } = Procedures()
958
+ let finallyRan = false
959
+
960
+ const { EarlyClose } = CreateStream(
961
+ 'EarlyClose',
962
+ {},
963
+ async function* () {
964
+ try {
965
+ yield 1
966
+ yield 2
967
+ yield 3
968
+ } finally {
969
+ finallyRan = true
970
+ }
971
+ }
972
+ )
973
+
974
+ const iter = EarlyClose({}, {})
975
+ const first = await iter.next()
976
+ expect(first.value).toBe(1)
977
+ await iter.return!(undefined)
978
+ expect(finallyRan).toBe(true)
979
+ })
980
+
944
981
  test('CreateStream with extended config', () => {
945
982
  interface ExtConfig {
946
983
  scope: string
package/src/index.ts CHANGED
@@ -383,8 +383,8 @@ export function Procedures<TContext = TNoContextProvided, TExtendedConfig = unkn
383
383
  params as any
384
384
  )
385
385
 
386
+ const userIterator = userGenerator[Symbol.asyncIterator]()
386
387
  try {
387
- const userIterator = userGenerator[Symbol.asyncIterator]()
388
388
  let userIterResult = await userIterator.next()
389
389
 
390
390
  while (!userIterResult.done) {
@@ -411,27 +411,30 @@ export function Procedures<TContext = TNoContextProvided, TExtendedConfig = unkn
411
411
  // can send it as a special 'return' SSE event
412
412
  return userIterResult.value
413
413
  } catch (error: any) {
414
- if (error instanceof ProcedureError) {
415
- throw error
416
- } else {
417
- const err = new ProcedureError(
418
- name,
419
- `Error in streaming handler for ${name} - ${error?.message}`,
420
- undefined,
421
- definitionInfo
422
- )
423
- err.cause = error
424
- if (error.stack && definitionInfo.definedAt) {
425
- const { file, line, column } = definitionInfo.definedAt
426
- err.stack =
427
- error.stack +
428
- `\n--- Procedure "${name}" defined at ---\n at ${file}:${line}:${column}`
429
- } else if (error.stack) {
430
- err.stack = error.stack
431
- }
432
- throw err
414
+ // Preserve the original error class so HTTP builders' taxonomies and
415
+ // `onMidStreamError` callbacks see the actual thrown type — boxing
416
+ // user-defined errors inside ProcedureError defeats route-declared
417
+ // typed-error dispatch on the client. Augment the stack trace in
418
+ // place with the procedure's definition site when available.
419
+ if (
420
+ definitionInfo.definedAt &&
421
+ error &&
422
+ typeof error.stack === 'string'
423
+ ) {
424
+ const { file, line, column } = definitionInfo.definedAt
425
+ error.stack =
426
+ `${error.stack}\n--- Procedure "${name}" defined at ---\n at ${file}:${line}:${column}`
433
427
  }
428
+ throw error
434
429
  } finally {
430
+ // Propagate `.return()` to the user generator so its `finally`
431
+ // blocks (and any `signal`-driven cleanup) run when the consumer
432
+ // closes the stream early. No-op when iteration already completed.
433
+ try {
434
+ await userIterator.return?.(undefined)
435
+ } catch {
436
+ // Swallow — cleanup must not mask the primary error path
437
+ }
435
438
  abortController.abort('stream-completed')
436
439
  }
437
440
  } as (ctx: TContext, params?: any) => AsyncGenerator<any, any, unknown>,