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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-options.js","sourceRoot":"","sources":["../../src/client/resolve-options.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,QAA2C,EAC3C,OAAyC,EACzC,QAAgB;IAEhB,OAAO,OAAO,EAAE,QAAQ,IAAI,QAAQ,EAAE,QAAQ,IAAI,QAAQ,CAAA;AAC5D,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA2C,EAC3C,OAAyC;IAEzC,MAAM,OAAO,GAAkB,EAAE,CAAA;IAEjC,IAAI,QAAQ,EAAE,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IACnD,IAAI,OAAO,EAAE,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAEjD,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,CAAA;IACrD,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;IAC5C,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAC1C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAA;IAC3C,OAAO,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA2C,EAC3C,OAAyC;IAEzC,MAAM,cAAc,GAAG,QAAQ,EAAE,OAAO,CAAA;IACxC,MAAM,WAAW,GAAG,OAAO,EAAE,OAAO,CAAA;IAEpC,IAAI,CAAC,cAAc,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAA;IAErD,OAAO,EAAE,GAAG,cAAc,EAAE,GAAG,WAAW,EAAE,CAAA;AAC9C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,QAA2C,EAC3C,OAAyC;IAEzC,MAAM,WAAW,GAAG,QAAQ,EAAE,IAAI,CAAA;IAClC,MAAM,QAAQ,GAAG,OAAO,EAAE,IAAI,CAAA;IAE9B,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAA;IAE/C,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,QAAQ,EAAiB,CAAA;AACvD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CACjC,OAAuB,EACvB,QAA2C,EAC3C,OAAyC;IAEzC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAC/C,MAAM,eAAe,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IACzD,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;IAE3C,MAAM,OAAO,GACX,eAAe,IAAI,OAAO,CAAC,OAAO;QAChC,CAAC,CAAC,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;QAC5C,CAAC,CAAC,SAAS,CAAA;IAEf,OAAO,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;AAC9C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { applyRequestOptions, resolveBasePath, resolveHeaders, resolveMeta, resolveSignal, } from './resolve-options.js';
|
|
3
|
+
// ── resolveBasePath ───────────────────────────────────────
|
|
4
|
+
describe('resolveBasePath', () => {
|
|
5
|
+
it('uses fallback when nothing is set', () => {
|
|
6
|
+
expect(resolveBasePath(undefined, undefined, 'https://api.example.com')).toBe('https://api.example.com');
|
|
7
|
+
});
|
|
8
|
+
it('uses default when only defaults.basePath is set', () => {
|
|
9
|
+
expect(resolveBasePath({ basePath: 'https://default.example.com' }, undefined, 'https://fallback')).toBe('https://default.example.com');
|
|
10
|
+
});
|
|
11
|
+
it('per-call basePath overrides default', () => {
|
|
12
|
+
expect(resolveBasePath({ basePath: 'https://default.example.com' }, { basePath: 'https://percall.example.com' }, 'https://fallback')).toBe('https://percall.example.com');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
// ── resolveHeaders ────────────────────────────────────────
|
|
16
|
+
describe('resolveHeaders', () => {
|
|
17
|
+
it('returns undefined when neither side sets headers', () => {
|
|
18
|
+
expect(resolveHeaders(undefined, undefined)).toBeUndefined();
|
|
19
|
+
});
|
|
20
|
+
it('returns default headers when only defaults set', () => {
|
|
21
|
+
expect(resolveHeaders({ headers: { 'x-a': '1' } }, undefined)).toEqual({ 'x-a': '1' });
|
|
22
|
+
});
|
|
23
|
+
it('per-call keys override default keys', () => {
|
|
24
|
+
const defaults = { headers: { 'x-a': 'default', 'x-b': 'keep' } };
|
|
25
|
+
const options = { headers: { 'x-a': 'override' } };
|
|
26
|
+
expect(resolveHeaders(defaults, options)).toEqual({
|
|
27
|
+
'x-a': 'override',
|
|
28
|
+
'x-b': 'keep',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// ── resolveMeta ───────────────────────────────────────────
|
|
33
|
+
describe('resolveMeta', () => {
|
|
34
|
+
it('returns undefined when neither side sets meta', () => {
|
|
35
|
+
expect(resolveMeta(undefined, undefined)).toBeUndefined();
|
|
36
|
+
});
|
|
37
|
+
it('merges default + per-call meta (shallow), per-call keys win', () => {
|
|
38
|
+
const defaults = { meta: { a: 1, b: 2 } };
|
|
39
|
+
const options = { meta: { b: 99, c: 3 } };
|
|
40
|
+
expect(resolveMeta(defaults, options)).toEqual({ a: 1, b: 99, c: 3 });
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ── resolveSignal ─────────────────────────────────────────
|
|
44
|
+
describe('resolveSignal', () => {
|
|
45
|
+
it('returns undefined when nothing is set', () => {
|
|
46
|
+
expect(resolveSignal(undefined, undefined)).toBeUndefined();
|
|
47
|
+
});
|
|
48
|
+
it('returns the default signal when no timeout and no per-call signal', () => {
|
|
49
|
+
const controller = new AbortController();
|
|
50
|
+
expect(resolveSignal({ signal: controller.signal }, undefined)).toBe(controller.signal);
|
|
51
|
+
});
|
|
52
|
+
it('returns the per-call signal when no default and no timeout', () => {
|
|
53
|
+
const controller = new AbortController();
|
|
54
|
+
expect(resolveSignal(undefined, { signal: controller.signal })).toBe(controller.signal);
|
|
55
|
+
});
|
|
56
|
+
it('combines default + per-call signals — default aborts combined signal', () => {
|
|
57
|
+
const defaultCtrl = new AbortController();
|
|
58
|
+
const callCtrl = new AbortController();
|
|
59
|
+
const signal = resolveSignal({ signal: defaultCtrl.signal }, { signal: callCtrl.signal });
|
|
60
|
+
expect(signal.aborted).toBe(false);
|
|
61
|
+
defaultCtrl.abort(new Error('default-abort'));
|
|
62
|
+
expect(signal.aborted).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it('combines default + per-call signals — per-call aborts combined signal', () => {
|
|
65
|
+
const defaultCtrl = new AbortController();
|
|
66
|
+
const callCtrl = new AbortController();
|
|
67
|
+
const signal = resolveSignal({ signal: defaultCtrl.signal }, { signal: callCtrl.signal });
|
|
68
|
+
callCtrl.abort(new Error('call-abort'));
|
|
69
|
+
expect(signal.aborted).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('applies timeout via AbortSignal.timeout', () => {
|
|
72
|
+
const spy = vi.spyOn(AbortSignal, 'timeout');
|
|
73
|
+
try {
|
|
74
|
+
const signal = resolveSignal(undefined, { timeout: 100 });
|
|
75
|
+
expect(spy).toHaveBeenCalledWith(100);
|
|
76
|
+
expect(signal).toBeDefined();
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
spy.mockRestore();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('per-call timeout overrides default timeout', () => {
|
|
83
|
+
const spy = vi.spyOn(AbortSignal, 'timeout');
|
|
84
|
+
try {
|
|
85
|
+
resolveSignal({ timeout: 10_000 }, { timeout: 100 });
|
|
86
|
+
expect(spy).toHaveBeenCalledWith(100);
|
|
87
|
+
expect(spy).not.toHaveBeenCalledWith(10_000);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
spy.mockRestore();
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
it('combines signal + timeout — signal aborts first', () => {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const signal = resolveSignal(undefined, { signal: controller.signal, timeout: 10_000 });
|
|
96
|
+
expect(signal.aborted).toBe(false);
|
|
97
|
+
controller.abort();
|
|
98
|
+
expect(signal.aborted).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
it('per-call timeout: 0 disables default timeout', () => {
|
|
101
|
+
const signal = resolveSignal({ timeout: 1000 }, { timeout: 0 });
|
|
102
|
+
expect(signal).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
// ── applyRequestOptions ───────────────────────────────────
|
|
106
|
+
describe('applyRequestOptions', () => {
|
|
107
|
+
const baseRequest = {
|
|
108
|
+
url: 'https://api.example.com/foo',
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: { hello: 'world' },
|
|
111
|
+
};
|
|
112
|
+
it('returns the request unchanged when nothing is provided', () => {
|
|
113
|
+
const result = applyRequestOptions(baseRequest, undefined, undefined);
|
|
114
|
+
expect(result.url).toBe(baseRequest.url);
|
|
115
|
+
expect(result.body).toEqual({ hello: 'world' });
|
|
116
|
+
expect(result.headers).toBeUndefined();
|
|
117
|
+
expect(result.signal).toBeUndefined();
|
|
118
|
+
expect(result.meta).toBeUndefined();
|
|
119
|
+
});
|
|
120
|
+
it('merges default + per-call headers, preserving route-declared headers', () => {
|
|
121
|
+
const reqWithHeaders = {
|
|
122
|
+
...baseRequest,
|
|
123
|
+
headers: { 'content-type': 'application/json', 'x-route': 'declared' },
|
|
124
|
+
};
|
|
125
|
+
const result = applyRequestOptions(reqWithHeaders, { headers: { 'x-default': 'd', 'x-route': 'from-default' } }, { headers: { 'x-call': 'c', 'x-route': 'from-call' } });
|
|
126
|
+
expect(result.headers).toEqual({
|
|
127
|
+
'x-default': 'd',
|
|
128
|
+
'x-call': 'c',
|
|
129
|
+
// Route-declared headers WIN over resolved options (typed contract)
|
|
130
|
+
'content-type': 'application/json',
|
|
131
|
+
'x-route': 'declared',
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
it('attaches meta to the request when provided', () => {
|
|
135
|
+
const result = applyRequestOptions(baseRequest, undefined, {
|
|
136
|
+
meta: { traceId: 'abc' },
|
|
137
|
+
});
|
|
138
|
+
expect(result.meta).toEqual({ traceId: 'abc' });
|
|
139
|
+
});
|
|
140
|
+
it('passes per-call signal through', () => {
|
|
141
|
+
const controller = new AbortController();
|
|
142
|
+
const result = applyRequestOptions(baseRequest, undefined, { signal: controller.signal });
|
|
143
|
+
expect(result.signal).toBe(controller.signal);
|
|
144
|
+
});
|
|
145
|
+
it('attaches a signal when per-call timeout is set', () => {
|
|
146
|
+
const spy = vi.spyOn(AbortSignal, 'timeout');
|
|
147
|
+
try {
|
|
148
|
+
const result = applyRequestOptions(baseRequest, undefined, { timeout: 100 });
|
|
149
|
+
expect(spy).toHaveBeenCalledWith(100);
|
|
150
|
+
expect(result.signal).toBeDefined();
|
|
151
|
+
expect(result.signal?.aborted).toBe(false);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
spy.mockRestore();
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
//# sourceMappingURL=resolve-options.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-options.test.js","sourceRoot":"","sources":["../../src/client/resolve-options.test.ts"],"names":[],"mappings":"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"}
|
package/build/client/stream.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientAdapter, ClientHooks, StreamDescriptor, TypedStream } from './types.js';
|
|
1
|
+
import type { ClientAdapter, ClientHooks, StreamDescriptor, TypedStream, ProcedureCallDefaults, ProcedureCallOptions } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Wraps an AsyncIterable into a TypedStream.
|
|
4
4
|
*
|
|
@@ -12,16 +12,25 @@ import type { ClientAdapter, ClientHooks, StreamDescriptor, TypedStream } from '
|
|
|
12
12
|
* On error: `.result` rejects and the error is re-thrown from the async iterator.
|
|
13
13
|
*/
|
|
14
14
|
export declare function createTypedStream<TYield, TReturn = void>(source: AsyncIterable<unknown>, streamMode: 'sse' | 'text'): TypedStream<TYield, TReturn>;
|
|
15
|
+
export interface ExecuteStreamConfig {
|
|
16
|
+
descriptor: StreamDescriptor;
|
|
17
|
+
basePath: string;
|
|
18
|
+
adapter: ClientAdapter;
|
|
19
|
+
hooks: ClientHooks;
|
|
20
|
+
defaults?: ProcedureCallDefaults;
|
|
21
|
+
options?: ProcedureCallOptions;
|
|
22
|
+
}
|
|
15
23
|
/**
|
|
16
24
|
* Executes a streaming procedure call through the adapter.
|
|
17
25
|
*
|
|
18
26
|
* Flow:
|
|
19
|
-
* 1.
|
|
20
|
-
* 2.
|
|
21
|
-
* 3.
|
|
22
|
-
* 4.
|
|
23
|
-
* 5.
|
|
24
|
-
* 6.
|
|
25
|
-
* 7.
|
|
27
|
+
* 1. Resolve base path and build AdapterRequest
|
|
28
|
+
* 2. Apply request options (headers, signal, timeout, meta) from defaults + per-call
|
|
29
|
+
* 3. Run onBeforeRequest hooks
|
|
30
|
+
* 4. Call adapter.stream()
|
|
31
|
+
* 5. On adapter error: run onError hooks, re-throw
|
|
32
|
+
* 6. Run onAfterResponse immediately (before iteration), body is null
|
|
33
|
+
* 7. If non-2xx: throw ClientRequestError
|
|
34
|
+
* 8. Return createTypedStream(streamResponse.body, descriptor.streamMode)
|
|
26
35
|
*/
|
|
27
|
-
export declare function executeStream<TYield, TReturn = void>(
|
|
36
|
+
export declare function executeStream<TYield, TReturn = void>(config: ExecuteStreamConfig): Promise<TypedStream<TYield, TReturn>>;
|
package/build/client/stream.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildAdapterRequest } from './request-builder.js';
|
|
2
2
|
import { runBeforeRequest, runAfterResponse, runOnError } from './hooks.js';
|
|
3
|
+
import { applyRequestOptions, resolveBasePath } from './resolve-options.js';
|
|
3
4
|
import { ClientRequestError } from './errors.js';
|
|
4
5
|
// ── createTypedStream ─────────────────────────────────────
|
|
5
6
|
/**
|
|
@@ -65,33 +66,37 @@ export function createTypedStream(source, streamMode) {
|
|
|
65
66
|
result: resultPromise,
|
|
66
67
|
};
|
|
67
68
|
}
|
|
68
|
-
// ── executeStream ─────────────────────────────────────────
|
|
69
69
|
/**
|
|
70
70
|
* Executes a streaming procedure call through the adapter.
|
|
71
71
|
*
|
|
72
72
|
* Flow:
|
|
73
|
-
* 1.
|
|
74
|
-
* 2.
|
|
75
|
-
* 3.
|
|
76
|
-
* 4.
|
|
77
|
-
* 5.
|
|
78
|
-
* 6.
|
|
79
|
-
* 7.
|
|
73
|
+
* 1. Resolve base path and build AdapterRequest
|
|
74
|
+
* 2. Apply request options (headers, signal, timeout, meta) from defaults + per-call
|
|
75
|
+
* 3. Run onBeforeRequest hooks
|
|
76
|
+
* 4. Call adapter.stream()
|
|
77
|
+
* 5. On adapter error: run onError hooks, re-throw
|
|
78
|
+
* 6. Run onAfterResponse immediately (before iteration), body is null
|
|
79
|
+
* 7. If non-2xx: throw ClientRequestError
|
|
80
|
+
* 8. Return createTypedStream(streamResponse.body, descriptor.streamMode)
|
|
80
81
|
*/
|
|
81
|
-
export async function executeStream(
|
|
82
|
+
export async function executeStream(config) {
|
|
83
|
+
const { descriptor, basePath, adapter, hooks, defaults, options } = config;
|
|
82
84
|
// 1. Build the initial request
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
const resolvedBasePath = resolveBasePath(defaults, options, basePath);
|
|
86
|
+
let request = buildAdapterRequest(descriptor, resolvedBasePath);
|
|
87
|
+
// 2. Apply request-level options (headers, signal, timeout, meta)
|
|
88
|
+
request = applyRequestOptions(request, defaults, options);
|
|
89
|
+
// 3. Run before-request hooks
|
|
90
|
+
const beforeCtx = await runBeforeRequest({ procedureName: descriptor.name, scope: descriptor.scope, request }, hooks, options);
|
|
86
91
|
request = beforeCtx.request;
|
|
87
|
-
//
|
|
92
|
+
// 4. Call the adapter
|
|
88
93
|
let streamResponse;
|
|
89
94
|
try {
|
|
90
95
|
streamResponse = await adapter.stream(request);
|
|
91
96
|
}
|
|
92
97
|
catch (err) {
|
|
93
|
-
//
|
|
94
|
-
await runOnError({ procedureName: descriptor.name, scope: descriptor.scope, request, error: err },
|
|
98
|
+
// 5. On adapter error: run error hooks, re-throw
|
|
99
|
+
await runOnError({ procedureName: descriptor.name, scope: descriptor.scope, request, error: err }, hooks, options);
|
|
95
100
|
throw err;
|
|
96
101
|
}
|
|
97
102
|
// Build an AdapterResponse shape for the hooks (body is null for streams at this point)
|
|
@@ -100,9 +105,9 @@ export async function executeStream(descriptor, basePath, adapter, globalHooks,
|
|
|
100
105
|
headers: streamResponse.headers,
|
|
101
106
|
body: null,
|
|
102
107
|
};
|
|
103
|
-
//
|
|
104
|
-
await runAfterResponse({ procedureName: descriptor.name, scope: descriptor.scope, request, response: responseForHooks },
|
|
105
|
-
//
|
|
108
|
+
// 6. Run after-response hooks immediately (before iteration)
|
|
109
|
+
await runAfterResponse({ procedureName: descriptor.name, scope: descriptor.scope, request, response: responseForHooks }, hooks, options);
|
|
110
|
+
// 7. Check status after hooks (hooks may mutate responseForHooks.status)
|
|
106
111
|
if (responseForHooks.status < 200 || responseForHooks.status >= 300) {
|
|
107
112
|
throw new ClientRequestError({
|
|
108
113
|
status: responseForHooks.status,
|
|
@@ -112,7 +117,7 @@ export async function executeStream(descriptor, basePath, adapter, globalHooks,
|
|
|
112
117
|
scope: descriptor.scope,
|
|
113
118
|
});
|
|
114
119
|
}
|
|
115
|
-
//
|
|
120
|
+
// 8. Return the typed stream
|
|
116
121
|
return createTypedStream(streamResponse.body, descriptor.streamMode);
|
|
117
122
|
}
|
|
118
123
|
//# sourceMappingURL=stream.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/client/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/client/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,6 +32,9 @@ function makeStreamAdapter(response, items) {
|
|
|
32
32
|
})),
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
+
function run({ adapter, hooks = {}, defaults, options, descriptor = makeDescriptor(), basePath = 'https://api.example.com', }) {
|
|
36
|
+
return executeStream({ descriptor, basePath, adapter, hooks, defaults, options });
|
|
37
|
+
}
|
|
35
38
|
// ── createTypedStream — SSE mode ──────────────────────────
|
|
36
39
|
describe('createTypedStream — SSE mode', () => {
|
|
37
40
|
it('yields data payloads for normal events', async () => {
|
|
@@ -41,9 +44,8 @@ describe('createTypedStream — SSE mode', () => {
|
|
|
41
44
|
];
|
|
42
45
|
const stream = createTypedStream(makeAsyncIterable(sseItems), 'sse');
|
|
43
46
|
const received = [];
|
|
44
|
-
for await (const item of stream)
|
|
47
|
+
for await (const item of stream)
|
|
45
48
|
received.push(item);
|
|
46
|
-
}
|
|
47
49
|
expect(received).toEqual([{ count: 1 }, { count: 2 }]);
|
|
48
50
|
});
|
|
49
51
|
it('captures return event data in .result instead of yielding', async () => {
|
|
@@ -53,23 +55,15 @@ describe('createTypedStream — SSE mode', () => {
|
|
|
53
55
|
];
|
|
54
56
|
const stream = createTypedStream(makeAsyncIterable(sseItems), 'sse');
|
|
55
57
|
const yielded = [];
|
|
56
|
-
for await (const item of stream)
|
|
58
|
+
for await (const item of stream)
|
|
57
59
|
yielded.push(item);
|
|
58
|
-
}
|
|
59
|
-
// Only the 'update' event is yielded
|
|
60
60
|
expect(yielded).toEqual([{ count: 1 }]);
|
|
61
|
-
|
|
62
|
-
const result = await stream.result;
|
|
63
|
-
expect(result).toEqual({ total: 99 });
|
|
61
|
+
await expect(stream.result).resolves.toEqual({ total: 99 });
|
|
64
62
|
});
|
|
65
63
|
it('resolves .result with undefined when no return event', async () => {
|
|
66
|
-
const
|
|
67
|
-
{ data: 'hello', event: 'message' },
|
|
68
|
-
];
|
|
69
|
-
const stream = createTypedStream(makeAsyncIterable(sseItems), 'sse');
|
|
64
|
+
const stream = createTypedStream(makeAsyncIterable([{ data: 'hello', event: 'message' }]), 'sse');
|
|
70
65
|
for await (const _ of stream) { /* drain */ }
|
|
71
|
-
|
|
72
|
-
expect(result).toBeUndefined();
|
|
66
|
+
await expect(stream.result).resolves.toBeUndefined();
|
|
73
67
|
});
|
|
74
68
|
it('rejects .result and re-throws on error', async () => {
|
|
75
69
|
async function* errorIterable() {
|
|
@@ -77,23 +71,16 @@ describe('createTypedStream — SSE mode', () => {
|
|
|
77
71
|
throw new Error('stream broke');
|
|
78
72
|
}
|
|
79
73
|
const stream = createTypedStream(errorIterable(), 'sse');
|
|
80
|
-
// Consuming the stream should throw
|
|
81
74
|
await expect(async () => {
|
|
82
75
|
for await (const _ of stream) { /* drain */ }
|
|
83
76
|
}).rejects.toThrow('stream broke');
|
|
84
|
-
// .result should also reject
|
|
85
77
|
await expect(stream.result).rejects.toThrow('stream broke');
|
|
86
78
|
});
|
|
87
79
|
it('handles SSE items without event field (defaults to yielding data)', async () => {
|
|
88
|
-
const
|
|
89
|
-
{ data: 'a' },
|
|
90
|
-
{ data: 'b' },
|
|
91
|
-
];
|
|
92
|
-
const stream = createTypedStream(makeAsyncIterable(sseItems), 'sse');
|
|
80
|
+
const stream = createTypedStream(makeAsyncIterable([{ data: 'a' }, { data: 'b' }]), 'sse');
|
|
93
81
|
const yielded = [];
|
|
94
|
-
for await (const item of stream)
|
|
82
|
+
for await (const item of stream)
|
|
95
83
|
yielded.push(item);
|
|
96
|
-
}
|
|
97
84
|
expect(yielded).toEqual(['a', 'b']);
|
|
98
85
|
});
|
|
99
86
|
});
|
|
@@ -103,16 +90,14 @@ describe('createTypedStream — text mode', () => {
|
|
|
103
90
|
const chunks = ['chunk1', 'chunk2', 'chunk3'];
|
|
104
91
|
const stream = createTypedStream(makeAsyncIterable(chunks), 'text');
|
|
105
92
|
const received = [];
|
|
106
|
-
for await (const chunk of stream)
|
|
93
|
+
for await (const chunk of stream)
|
|
107
94
|
received.push(chunk);
|
|
108
|
-
}
|
|
109
95
|
expect(received).toEqual(chunks);
|
|
110
96
|
});
|
|
111
97
|
it('.result resolves to void on normal completion', async () => {
|
|
112
98
|
const stream = createTypedStream(makeAsyncIterable(['a', 'b']), 'text');
|
|
113
99
|
for await (const _ of stream) { /* drain */ }
|
|
114
|
-
|
|
115
|
-
expect(result).toBeUndefined();
|
|
100
|
+
await expect(stream.result).resolves.toBeUndefined();
|
|
116
101
|
});
|
|
117
102
|
it('rejects .result and re-throws on error', async () => {
|
|
118
103
|
async function* errorIterable() {
|
|
@@ -131,12 +116,11 @@ describe('executeStream', () => {
|
|
|
131
116
|
it('calls adapter.stream and returns a TypedStream', async () => {
|
|
132
117
|
const items = [{ data: 'hello', event: 'msg' }];
|
|
133
118
|
const adapter = makeStreamAdapter({}, items);
|
|
134
|
-
const stream = await
|
|
119
|
+
const stream = await run({ adapter });
|
|
135
120
|
expect(adapter.stream).toHaveBeenCalledOnce();
|
|
136
121
|
const received = [];
|
|
137
|
-
for await (const item of stream)
|
|
122
|
+
for await (const item of stream)
|
|
138
123
|
received.push(item);
|
|
139
|
-
}
|
|
140
124
|
expect(received).toEqual(['hello']);
|
|
141
125
|
});
|
|
142
126
|
it('runs onBeforeRequest before calling adapter', async () => {
|
|
@@ -148,15 +132,14 @@ describe('executeStream', () => {
|
|
|
148
132
|
return { status: 200, headers: {}, body: makeAsyncIterable([]) };
|
|
149
133
|
}),
|
|
150
134
|
};
|
|
151
|
-
const
|
|
135
|
+
const hooks = {
|
|
152
136
|
onBeforeRequest: (ctx) => ({
|
|
153
137
|
...ctx,
|
|
154
138
|
request: { ...ctx.request, headers: { 'x-stream-auth': 'stream-token' } },
|
|
155
139
|
}),
|
|
156
140
|
};
|
|
157
|
-
const stream = await
|
|
158
|
-
|
|
159
|
-
for await (const _ of stream) { /* noop */ }
|
|
141
|
+
const stream = await run({ adapter, hooks });
|
|
142
|
+
for await (const _ of stream) { /* drain */ }
|
|
160
143
|
expect(capturedHeaders[0]?.['x-stream-auth']).toBe('stream-token');
|
|
161
144
|
});
|
|
162
145
|
it('runs onAfterResponse immediately (before iteration)', async () => {
|
|
@@ -168,11 +151,10 @@ describe('executeStream', () => {
|
|
|
168
151
|
return { status: 200, headers: {}, body: makeAsyncIterable([]) };
|
|
169
152
|
}),
|
|
170
153
|
};
|
|
171
|
-
const
|
|
154
|
+
const hooks = {
|
|
172
155
|
onAfterResponse: () => { order.push('afterResponse'); },
|
|
173
156
|
};
|
|
174
|
-
await
|
|
175
|
-
// After executeStream returns (before iteration), afterResponse should have fired
|
|
157
|
+
await run({ adapter, hooks });
|
|
176
158
|
expect(order).toEqual(['adapter', 'afterResponse']);
|
|
177
159
|
});
|
|
178
160
|
it('throws ClientRequestError on non-2xx status', async () => {
|
|
@@ -184,7 +166,7 @@ describe('executeStream', () => {
|
|
|
184
166
|
body: makeAsyncIterable([]),
|
|
185
167
|
})),
|
|
186
168
|
};
|
|
187
|
-
await expect(
|
|
169
|
+
await expect(run({ adapter })).rejects.toThrow(ClientRequestError);
|
|
188
170
|
});
|
|
189
171
|
it('runs onError on adapter failure and re-throws', async () => {
|
|
190
172
|
const adapterError = new Error('stream connection failed');
|
|
@@ -193,10 +175,10 @@ describe('executeStream', () => {
|
|
|
193
175
|
stream: vi.fn(async () => { throw adapterError; }),
|
|
194
176
|
};
|
|
195
177
|
const receivedErrors = [];
|
|
196
|
-
const
|
|
178
|
+
const hooks = {
|
|
197
179
|
onError: (ctx) => { receivedErrors.push(ctx.error); },
|
|
198
180
|
};
|
|
199
|
-
await expect(
|
|
181
|
+
await expect(run({ adapter, hooks })).rejects.toThrow('stream connection failed');
|
|
200
182
|
expect(receivedErrors[0]).toBe(adapterError);
|
|
201
183
|
});
|
|
202
184
|
it('returns TypedStream with working .result for SSE return event', async () => {
|
|
@@ -205,24 +187,98 @@ describe('executeStream', () => {
|
|
|
205
187
|
{ data: { final: true }, event: 'return' },
|
|
206
188
|
];
|
|
207
189
|
const adapter = makeStreamAdapter({}, sseItems);
|
|
208
|
-
const stream = await
|
|
190
|
+
const stream = await run({
|
|
191
|
+
adapter,
|
|
192
|
+
descriptor: makeDescriptor({ streamMode: 'sse' }),
|
|
193
|
+
});
|
|
209
194
|
const yielded = [];
|
|
210
|
-
for await (const item of stream)
|
|
195
|
+
for await (const item of stream)
|
|
211
196
|
yielded.push(item);
|
|
212
|
-
}
|
|
213
197
|
expect(yielded).toEqual([{ n: 1 }]);
|
|
214
198
|
await expect(stream.result).resolves.toEqual({ final: true });
|
|
215
199
|
});
|
|
216
200
|
it('returns TypedStream for text mode', async () => {
|
|
217
201
|
const chunks = ['line1', 'line2'];
|
|
218
202
|
const adapter = makeStreamAdapter({}, chunks);
|
|
219
|
-
const stream = await
|
|
203
|
+
const stream = await run({
|
|
204
|
+
adapter,
|
|
205
|
+
descriptor: makeDescriptor({ streamMode: 'text' }),
|
|
206
|
+
});
|
|
220
207
|
const received = [];
|
|
221
|
-
for await (const chunk of stream)
|
|
208
|
+
for await (const chunk of stream)
|
|
222
209
|
received.push(chunk);
|
|
223
|
-
}
|
|
224
210
|
expect(received).toEqual(chunks);
|
|
225
211
|
await expect(stream.result).resolves.toBeUndefined();
|
|
226
212
|
});
|
|
213
|
+
// ── Per-call options ──
|
|
214
|
+
it('per-call timeout attaches a signal via AbortSignal.timeout', async () => {
|
|
215
|
+
const spy = vi.spyOn(AbortSignal, 'timeout');
|
|
216
|
+
try {
|
|
217
|
+
let observedSignal;
|
|
218
|
+
const adapter = {
|
|
219
|
+
request: vi.fn(async () => { throw new Error('not expected'); }),
|
|
220
|
+
stream: vi.fn(async (req) => {
|
|
221
|
+
observedSignal = req.signal;
|
|
222
|
+
return { status: 200, headers: {}, body: makeAsyncIterable([]) };
|
|
223
|
+
}),
|
|
224
|
+
};
|
|
225
|
+
await run({ adapter, options: { timeout: 5000 } });
|
|
226
|
+
expect(spy).toHaveBeenCalledWith(5000);
|
|
227
|
+
expect(observedSignal).toBeDefined();
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
spy.mockRestore();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
it('adapter receives a signal that reflects abort when the caller cancels', async () => {
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
const adapter = {
|
|
236
|
+
request: vi.fn(async () => { throw new Error('not expected'); }),
|
|
237
|
+
stream: vi.fn(async (req) => {
|
|
238
|
+
return new Promise((_resolve, reject) => {
|
|
239
|
+
const abort = () => reject(new Error('aborted'));
|
|
240
|
+
if (req.signal?.aborted)
|
|
241
|
+
abort();
|
|
242
|
+
else
|
|
243
|
+
req.signal?.addEventListener('abort', abort, { once: true });
|
|
244
|
+
});
|
|
245
|
+
}),
|
|
246
|
+
};
|
|
247
|
+
const promise = run({ adapter, options: { signal: controller.signal } });
|
|
248
|
+
// Let executeStream reach the adapter before aborting
|
|
249
|
+
await Promise.resolve();
|
|
250
|
+
controller.abort();
|
|
251
|
+
await expect(promise).rejects.toThrow('aborted');
|
|
252
|
+
});
|
|
253
|
+
it('per-call basePath overrides base path for streams', async () => {
|
|
254
|
+
const capturedUrls = [];
|
|
255
|
+
const adapter = {
|
|
256
|
+
request: vi.fn(async () => { throw new Error('not expected'); }),
|
|
257
|
+
stream: vi.fn(async (req) => {
|
|
258
|
+
capturedUrls.push(req.url);
|
|
259
|
+
return { status: 200, headers: {}, body: makeAsyncIterable([]) };
|
|
260
|
+
}),
|
|
261
|
+
};
|
|
262
|
+
const stream = await run({
|
|
263
|
+
adapter,
|
|
264
|
+
descriptor: makeDescriptor({ path: '/tail' }),
|
|
265
|
+
basePath: 'https://default.example.com',
|
|
266
|
+
options: { basePath: 'https://override.example.com' },
|
|
267
|
+
});
|
|
268
|
+
for await (const _ of stream) { /* drain */ }
|
|
269
|
+
expect(capturedUrls[0]).toBe('https://override.example.com/tail');
|
|
270
|
+
});
|
|
271
|
+
it('per-call meta is forwarded to the adapter', async () => {
|
|
272
|
+
let observedMeta;
|
|
273
|
+
const adapter = {
|
|
274
|
+
request: vi.fn(async () => { throw new Error('not expected'); }),
|
|
275
|
+
stream: vi.fn(async (req) => {
|
|
276
|
+
observedMeta = req.meta;
|
|
277
|
+
return { status: 200, headers: {}, body: makeAsyncIterable([]) };
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
280
|
+
await run({ adapter, options: { meta: { traceId: 'stream-trace' } } });
|
|
281
|
+
expect(observedMeta).toEqual({ traceId: 'stream-trace' });
|
|
282
|
+
});
|
|
227
283
|
});
|
|
228
284
|
//# sourceMappingURL=stream.test.js.map
|