ts-procedures 5.15.0 → 5.16.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.
Files changed (47) hide show
  1. package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -1
  2. package/agent_config/claude-code/skills/ts-procedures/api-reference.md +57 -4
  3. package/agent_config/claude-code/skills/ts-procedures/patterns.md +102 -3
  4. package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +33 -5
  5. package/agent_config/copilot/copilot-instructions.md +55 -7
  6. package/agent_config/cursor/cursorrules +55 -7
  7. package/build/client/call.d.ts +18 -9
  8. package/build/client/call.js +25 -19
  9. package/build/client/call.js.map +1 -1
  10. package/build/client/call.test.js +167 -17
  11. package/build/client/call.test.js.map +1 -1
  12. package/build/client/index.d.ts +1 -1
  13. package/build/client/index.js +18 -3
  14. package/build/client/index.js.map +1 -1
  15. package/build/client/index.test.js +104 -0
  16. package/build/client/index.test.js.map +1 -1
  17. package/build/client/resolve-options.d.ts +45 -0
  18. package/build/client/resolve-options.js +82 -0
  19. package/build/client/resolve-options.js.map +1 -0
  20. package/build/client/resolve-options.test.d.ts +1 -0
  21. package/build/client/resolve-options.test.js +158 -0
  22. package/build/client/resolve-options.test.js.map +1 -0
  23. package/build/client/stream.d.ts +18 -9
  24. package/build/client/stream.js +24 -19
  25. package/build/client/stream.js.map +1 -1
  26. package/build/client/stream.test.js +102 -46
  27. package/build/client/stream.test.js.map +1 -1
  28. package/build/client/types.d.ts +68 -1
  29. package/build/client/types.js +1 -1
  30. package/build/codegen/e2e.test.js +141 -0
  31. package/build/codegen/e2e.test.js.map +1 -1
  32. package/build/codegen/emit-client-runtime.js +3 -0
  33. package/build/codegen/emit-client-runtime.js.map +1 -1
  34. package/docs/client-and-codegen.md +123 -2
  35. package/package.json +1 -1
  36. package/src/client/call.test.ts +202 -29
  37. package/src/client/call.ts +41 -28
  38. package/src/client/index.test.ts +117 -0
  39. package/src/client/index.ts +25 -8
  40. package/src/client/resolve-options.test.ts +205 -0
  41. package/src/client/resolve-options.ts +113 -0
  42. package/src/client/stream.test.ts +132 -107
  43. package/src/client/stream.ts +40 -25
  44. package/src/client/types.ts +74 -2
  45. package/src/codegen/e2e.test.ts +151 -0
  46. package/src/codegen/emit-client-runtime.ts +3 -0
  47. package/src/implementations/http/README.md +9 -1
@@ -481,6 +481,157 @@ describe('E2E: generateClient full pipeline', () => {
481
481
  execSync(`${tscPath} --noEmit --project ${join(tmpDir, 'tsconfig.json')}`, { stdio: 'pipe' })
482
482
  }).not.toThrow()
483
483
  })
484
+
485
+ it('_types.ts exports RequestMeta (empty interface ready for augmentation)', async () => {
486
+ tmpDir = makeTmpDir()
487
+ await generateClient({ envelope, outDir: tmpDir, selfContained: true })
488
+
489
+ const content = readFileSync(join(tmpDir, '_types.ts'), 'utf-8')
490
+ expect(content).toContain('export interface RequestMeta')
491
+ // meta on AdapterRequest and ProcedureCallDefaults should be typed as RequestMeta
492
+ expect(content).toContain('meta?: RequestMeta')
493
+ })
494
+
495
+ it('developers can augment RequestMeta for typed per-call meta + typed hook/adapter access', async () => {
496
+ tmpDir = makeTmpDir()
497
+ await generateClient({ envelope, outDir: tmpDir, selfContained: true })
498
+
499
+ // Write a consumer file that augments RequestMeta, then uses typed meta
500
+ // end-to-end: per-call options, createClient defaults, onBeforeRequest, and adapter.
501
+ const consumer = `
502
+ import { createClient } from './_client'
503
+ import type { ClientAdapter } from './_types'
504
+ import { createApiBindings } from './index'
505
+
506
+ declare module './_types' {
507
+ interface RequestMeta {
508
+ traceId: string
509
+ priority?: 'high' | 'low'
510
+ }
511
+ }
512
+
513
+ const typedAdapter: ClientAdapter = {
514
+ async request(req) {
515
+ // req.meta is now typed
516
+ const trace: string | undefined = req.meta?.traceId
517
+ const pri: 'high' | 'low' | undefined = req.meta?.priority
518
+ void trace; void pri
519
+ return { status: 200, headers: {}, body: {} }
520
+ },
521
+ async stream(req) {
522
+ const trace: string | undefined = req.meta?.traceId
523
+ void trace
524
+ return { status: 200, headers: {}, body: (async function*() {})() }
525
+ },
526
+ }
527
+
528
+ const client = createClient({
529
+ adapter: typedAdapter,
530
+ basePath: 'https://api.example.com',
531
+ scopes: createApiBindings,
532
+ defaults: {
533
+ meta: { traceId: 'default-trace' }, // typed
534
+ },
535
+ hooks: {
536
+ onBeforeRequest(ctx) {
537
+ // ctx.request.meta is typed via declaration merging
538
+ const trace: string | undefined = ctx.request.meta?.traceId
539
+ void trace
540
+ return ctx
541
+ },
542
+ },
543
+ })
544
+
545
+ async function run(): Promise<void> {
546
+ // Per-call meta is typed — traceId is required, priority is optional
547
+ await client.users.GetUser(
548
+ { id: '1' },
549
+ { meta: { traceId: 'per-call-trace', priority: 'high' } },
550
+ )
551
+ // Timeout + signal + headers typecheck
552
+ await client.users.GetUser(
553
+ { id: '2' },
554
+ { timeout: 5000, headers: { 'X-Request-Id': 'abc' }, basePath: 'https://other' },
555
+ )
556
+ }
557
+ void run
558
+ `
559
+ const { writeFileSync } = await import('node:fs')
560
+ writeFileSync(join(tmpDir, 'consumer.ts'), consumer)
561
+
562
+ const tsconfig = {
563
+ compilerOptions: {
564
+ strict: true,
565
+ target: 'ES2022',
566
+ module: 'ES2022',
567
+ moduleResolution: 'bundler',
568
+ noEmit: true,
569
+ skipLibCheck: true,
570
+ },
571
+ include: ['_types.ts', '_client.ts', 'index.ts', 'users.ts', 'events.ts', '_errors.ts', 'consumer.ts'],
572
+ }
573
+ writeFileSync(join(tmpDir, 'tsconfig.json'), JSON.stringify(tsconfig))
574
+
575
+ const { execSync } = await import('node:child_process')
576
+ const tscPath = join(process.cwd(), 'node_modules', '.bin', 'tsc')
577
+ expect(() => {
578
+ execSync(`${tscPath} --noEmit --project ${join(tmpDir, 'tsconfig.json')}`, { stdio: 'pipe' })
579
+ }).not.toThrow()
580
+ })
581
+
582
+ it('augmented RequestMeta rejects wrong types (compile error)', async () => {
583
+ tmpDir = makeTmpDir()
584
+ await generateClient({ envelope, outDir: tmpDir, selfContained: true })
585
+
586
+ // Passing a number for `traceId` (declared as string) should fail tsc
587
+ const consumer = `
588
+ import { createClient, createFetchAdapter } from './_client'
589
+ import { createApiBindings } from './index'
590
+ // RequestMeta is imported only for augmentation below
591
+ import type {} from './_types'
592
+
593
+ declare module './_types' {
594
+ interface RequestMeta {
595
+ traceId: string
596
+ }
597
+ }
598
+
599
+ const client = createClient({
600
+ adapter: createFetchAdapter(),
601
+ basePath: 'https://api.example.com',
602
+ scopes: createApiBindings,
603
+ })
604
+
605
+ async function run(): Promise<void> {
606
+ // @ts-expect-error traceId must be string, not number
607
+ await client.users.GetUser({ id: '1' }, { meta: { traceId: 42 } })
608
+ }
609
+ void run
610
+ `
611
+ const { writeFileSync } = await import('node:fs')
612
+ writeFileSync(join(tmpDir, 'consumer.ts'), consumer)
613
+
614
+ const tsconfig = {
615
+ compilerOptions: {
616
+ strict: true,
617
+ target: 'ES2022',
618
+ module: 'ES2022',
619
+ moduleResolution: 'bundler',
620
+ noEmit: true,
621
+ skipLibCheck: true,
622
+ },
623
+ include: ['_types.ts', '_client.ts', 'index.ts', 'users.ts', 'events.ts', '_errors.ts', 'consumer.ts'],
624
+ }
625
+ writeFileSync(join(tmpDir, 'tsconfig.json'), JSON.stringify(tsconfig))
626
+
627
+ const { execSync } = await import('node:child_process')
628
+ const tscPath = join(process.cwd(), 'node_modules', '.bin', 'tsc')
629
+ // With @ts-expect-error in place, tsc should pass; if RequestMeta wasn't
630
+ // enforcing the type, @ts-expect-error would fail because there'd be no error.
631
+ expect(() => {
632
+ execSync(`${tscPath} --noEmit --project ${join(tmpDir, 'tsconfig.json')}`, { stdio: 'pipe' })
633
+ }).not.toThrow()
634
+ })
484
635
  })
485
636
 
486
637
  // ── namespaceTypes mode ───────────────────────────────────────────────────
@@ -16,8 +16,10 @@ const TYPES_IMPORT = `import type {
16
16
  StreamDescriptor,
17
17
  TypedStream,
18
18
  ClientInstance,
19
+ ProcedureCallDefaults,
19
20
  ProcedureCallOptions,
20
21
  CreateClientConfig,
22
+ RequestMeta,
21
23
  } from './_types'`
22
24
 
23
25
  /**
@@ -27,6 +29,7 @@ const TYPES_IMPORT = `import type {
27
29
  const SOURCE_FILES = [
28
30
  'errors.ts',
29
31
  'request-builder.ts',
32
+ 'resolve-options.ts',
30
33
  'hooks.ts',
31
34
  'call.ts',
32
35
  'stream.ts',
@@ -319,7 +319,15 @@ import type { APIConfig, APIHttpRouteDoc, APIInput, HttpMethod } from 'ts-proced
319
319
 
320
320
  // Client Runtime
321
321
  import { createClient, createFetchAdapter } from 'ts-procedures/client'
322
- import type { ClientAdapter, ClientHooks, TypedStream, ClientInstance } from 'ts-procedures/client'
322
+ import type {
323
+ ClientAdapter,
324
+ ClientHooks,
325
+ TypedStream,
326
+ ClientInstance,
327
+ ProcedureCallDefaults,
328
+ ProcedureCallOptions,
329
+ RequestMeta,
330
+ } from 'ts-procedures/client'
323
331
 
324
332
  // Code Generation (build-time only)
325
333
  import { generateClient } from 'ts-procedures/codegen'