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.
- package/agent_config/claude-code/skills/ts-procedures/SKILL.md +1 -1
- package/agent_config/claude-code/skills/ts-procedures/api-reference.md +57 -4
- package/agent_config/claude-code/skills/ts-procedures/patterns.md +102 -3
- package/agent_config/claude-code/skills/ts-procedures-scaffold/templates/client.md +33 -5
- package/agent_config/copilot/copilot-instructions.md +55 -7
- package/agent_config/cursor/cursorrules +55 -7
- package/build/client/call.d.ts +18 -9
- package/build/client/call.js +25 -19
- package/build/client/call.js.map +1 -1
- package/build/client/call.test.js +167 -17
- package/build/client/call.test.js.map +1 -1
- package/build/client/index.d.ts +1 -1
- package/build/client/index.js +18 -3
- package/build/client/index.js.map +1 -1
- package/build/client/index.test.js +104 -0
- package/build/client/index.test.js.map +1 -1
- package/build/client/resolve-options.d.ts +45 -0
- package/build/client/resolve-options.js +82 -0
- package/build/client/resolve-options.js.map +1 -0
- package/build/client/resolve-options.test.d.ts +1 -0
- package/build/client/resolve-options.test.js +158 -0
- package/build/client/resolve-options.test.js.map +1 -0
- package/build/client/stream.d.ts +18 -9
- package/build/client/stream.js +24 -19
- package/build/client/stream.js.map +1 -1
- package/build/client/stream.test.js +102 -46
- package/build/client/stream.test.js.map +1 -1
- package/build/client/types.d.ts +68 -1
- package/build/client/types.js +1 -1
- package/build/codegen/e2e.test.js +141 -0
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.js +3 -0
- package/build/codegen/emit-client-runtime.js.map +1 -1
- package/docs/client-and-codegen.md +123 -2
- package/package.json +1 -1
- package/src/client/call.test.ts +202 -29
- package/src/client/call.ts +41 -28
- package/src/client/index.test.ts +117 -0
- package/src/client/index.ts +25 -8
- package/src/client/resolve-options.test.ts +205 -0
- package/src/client/resolve-options.ts +113 -0
- package/src/client/stream.test.ts +132 -107
- package/src/client/stream.ts +40 -25
- package/src/client/types.ts +74 -2
- package/src/codegen/e2e.test.ts +151 -0
- package/src/codegen/emit-client-runtime.ts +3 -0
- package/src/implementations/http/README.md +9 -1
package/src/codegen/e2e.test.ts
CHANGED
|
@@ -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 {
|
|
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'
|