ts-procedures 5.6.0-beta.2 → 5.7.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.
- package/README.md +37 -2
- package/agent_config/claude-code/skills/guide/api-reference.md +29 -6
- package/agent_config/claude-code/skills/guide/patterns.md +1 -0
- package/agent_config/copilot/copilot-instructions.md +7 -0
- package/agent_config/cursor/cursorrules +7 -0
- package/build/codegen/bin/cli.d.ts +27 -1
- package/build/codegen/bin/cli.js +90 -11
- package/build/codegen/bin/cli.js.map +1 -1
- package/build/codegen/bin/cli.test.js +96 -1
- package/build/codegen/bin/cli.test.js.map +1 -1
- package/build/codegen/constants.d.ts +1 -0
- package/build/codegen/constants.js +2 -0
- package/build/codegen/constants.js.map +1 -0
- package/build/codegen/e2e.test.js +143 -0
- package/build/codegen/e2e.test.js.map +1 -1
- package/build/codegen/emit-client-runtime.d.ts +9 -0
- package/build/codegen/emit-client-runtime.js +99 -0
- package/build/codegen/emit-client-runtime.js.map +1 -0
- package/build/codegen/emit-client-runtime.test.d.ts +1 -0
- package/build/codegen/emit-client-runtime.test.js +78 -0
- package/build/codegen/emit-client-runtime.test.js.map +1 -0
- package/build/codegen/emit-client-types.d.ts +8 -0
- package/build/codegen/emit-client-types.js +25 -0
- package/build/codegen/emit-client-types.js.map +1 -0
- package/build/codegen/emit-client-types.test.d.ts +1 -0
- package/build/codegen/emit-client-types.test.js +33 -0
- package/build/codegen/emit-client-types.test.js.map +1 -0
- package/build/codegen/emit-errors.d.ts +8 -1
- package/build/codegen/emit-errors.js +36 -13
- package/build/codegen/emit-errors.js.map +1 -1
- package/build/codegen/emit-errors.test.js +29 -1
- package/build/codegen/emit-errors.test.js.map +1 -1
- package/build/codegen/emit-index.d.ts +5 -1
- package/build/codegen/emit-index.js +4 -5
- package/build/codegen/emit-index.js.map +1 -1
- package/build/codegen/emit-index.test.js +1 -1
- package/build/codegen/emit-index.test.js.map +1 -1
- package/build/codegen/emit-scope.d.ts +10 -1
- package/build/codegen/emit-scope.js +133 -54
- package/build/codegen/emit-scope.js.map +1 -1
- package/build/codegen/emit-scope.test.js +126 -9
- package/build/codegen/emit-scope.test.js.map +1 -1
- package/build/codegen/emit-types.d.ts +29 -0
- package/build/codegen/emit-types.js +79 -8
- package/build/codegen/emit-types.js.map +1 -1
- package/build/codegen/emit-types.test.js +103 -1
- package/build/codegen/emit-types.test.js.map +1 -1
- package/build/codegen/index.d.ts +2 -0
- package/build/codegen/index.js +2 -0
- package/build/codegen/index.js.map +1 -1
- package/build/codegen/pipeline.d.ts +2 -0
- package/build/codegen/pipeline.js +29 -4
- package/build/codegen/pipeline.js.map +1 -1
- package/build/codegen/pipeline.test.js +59 -1
- package/build/codegen/pipeline.test.js.map +1 -1
- package/package.json +10 -2
- package/src/client/call.ts +74 -0
- package/src/client/errors.ts +43 -0
- package/src/client/fetch-adapter.ts +191 -0
- package/src/client/hooks.ts +65 -0
- package/src/client/index.ts +121 -0
- package/src/client/request-builder.ts +73 -0
- package/src/client/stream.ts +164 -0
- package/src/client/types.ts +103 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/codegen/pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/codegen/pipeline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
2
2
|
import { generateClient } from './index.js';
|
|
3
3
|
import { runPipeline } from './pipeline.js';
|
|
4
4
|
import { mkdirSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
@@ -147,5 +147,63 @@ describe('generateClient pipeline', () => {
|
|
|
147
147
|
expect(file.code).toContain('// Auto-generated by ts-procedures-codegen — do not edit');
|
|
148
148
|
}
|
|
149
149
|
});
|
|
150
|
+
it('selfContained: true produces _types.ts and _client.ts', async () => {
|
|
151
|
+
tmpDir = makeTmpDir();
|
|
152
|
+
const files = await runPipeline({ envelope, outDir: tmpDir, selfContained: true });
|
|
153
|
+
const paths = files.map((f) => f.path);
|
|
154
|
+
expect(paths).toContain(join(tmpDir, '_types.ts'));
|
|
155
|
+
expect(paths).toContain(join(tmpDir, '_client.ts'));
|
|
156
|
+
// Verify both files have auto-generated header and source hash
|
|
157
|
+
const typesFile = files.find((f) => f.path.endsWith('_types.ts'));
|
|
158
|
+
const clientFile = files.find((f) => f.path.endsWith('_client.ts'));
|
|
159
|
+
expect(typesFile.code).toContain('// Auto-generated by ts-procedures-codegen — do not edit');
|
|
160
|
+
expect(clientFile.code).toContain('// Auto-generated by ts-procedures-codegen — do not edit');
|
|
161
|
+
const typesLines = typesFile.code.split('\n');
|
|
162
|
+
expect(typesLines[1]).toMatch(/^\/\/ Source hash: [a-f0-9]{32}$/);
|
|
163
|
+
const clientLines = clientFile.code.split('\n');
|
|
164
|
+
expect(clientLines[1]).toMatch(/^\/\/ Source hash: [a-f0-9]{32}$/);
|
|
165
|
+
});
|
|
166
|
+
it('selfContained: true makes scope files import from ./_types', async () => {
|
|
167
|
+
tmpDir = makeTmpDir();
|
|
168
|
+
const files = await runPipeline({ envelope, outDir: tmpDir, selfContained: true });
|
|
169
|
+
const scopeFiles = files.filter((f) => !f.path.endsWith('index.ts') && !f.path.startsWith(join(tmpDir, '_')));
|
|
170
|
+
expect(scopeFiles.length).toBeGreaterThan(0);
|
|
171
|
+
for (const file of scopeFiles) {
|
|
172
|
+
expect(file.code).toContain("from './_types'");
|
|
173
|
+
expect(file.code).not.toContain("from 'ts-procedures/client'");
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
it('selfContained + clientImportPath emits a console.warn', async () => {
|
|
177
|
+
tmpDir = makeTmpDir();
|
|
178
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => { });
|
|
179
|
+
try {
|
|
180
|
+
await runPipeline({ envelope, outDir: tmpDir, selfContained: true, clientImportPath: './my-client' });
|
|
181
|
+
expect(warnSpy).toHaveBeenCalledWith('[ts-procedures-codegen] --self-contained overrides --client-import-path; using ./_types');
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
warnSpy.mockRestore();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
it('throws when a scope key collides with _types in selfContained mode', async () => {
|
|
188
|
+
tmpDir = makeTmpDir();
|
|
189
|
+
const collisionEnvelope = {
|
|
190
|
+
...envelope,
|
|
191
|
+
routes: [
|
|
192
|
+
{
|
|
193
|
+
kind: 'rpc',
|
|
194
|
+
name: 'GetUser',
|
|
195
|
+
path: '/users/1',
|
|
196
|
+
method: 'post',
|
|
197
|
+
scope: '_types',
|
|
198
|
+
version: 1,
|
|
199
|
+
jsonSchema: {
|
|
200
|
+
body: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
|
|
201
|
+
response: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] },
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
],
|
|
205
|
+
};
|
|
206
|
+
await expect(runPipeline({ envelope: collisionEnvelope, outDir: tmpDir, selfContained: true })).rejects.toThrow('[ts-procedures-codegen] Scope "_types" conflicts with self-contained mode reserved filename "_types.ts". Rename the scope to avoid collision.');
|
|
207
|
+
});
|
|
150
208
|
});
|
|
151
209
|
//# sourceMappingURL=pipeline.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.test.js","sourceRoot":"","sources":["../../src/codegen/pipeline.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"pipeline.test.js","sourceRoot":"","sources":["../../src/codegen/pipeline.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-procedures",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.1",
|
|
4
4
|
"description": "A TypeScript RPC framework that creates type-safe, schema-validated procedure calls with a single function definition. Define your procedures once and get full type inference, runtime validation, and framework integration hooks.",
|
|
5
5
|
"main": "build/exports.js",
|
|
6
6
|
"types": "build/exports.d.ts",
|
|
@@ -59,7 +59,15 @@
|
|
|
59
59
|
"files": [
|
|
60
60
|
"assets",
|
|
61
61
|
"build",
|
|
62
|
-
"agent_config"
|
|
62
|
+
"agent_config",
|
|
63
|
+
"src/client/types.ts",
|
|
64
|
+
"src/client/errors.ts",
|
|
65
|
+
"src/client/request-builder.ts",
|
|
66
|
+
"src/client/hooks.ts",
|
|
67
|
+
"src/client/call.ts",
|
|
68
|
+
"src/client/stream.ts",
|
|
69
|
+
"src/client/fetch-adapter.ts",
|
|
70
|
+
"src/client/index.ts"
|
|
63
71
|
],
|
|
64
72
|
"keywords": [
|
|
65
73
|
"typescript",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { buildAdapterRequest } from './request-builder.js'
|
|
2
|
+
import { runBeforeRequest, runAfterResponse, runOnError } from './hooks.js'
|
|
3
|
+
import { ClientRequestError } from './errors.js'
|
|
4
|
+
import type {
|
|
5
|
+
ClientAdapter,
|
|
6
|
+
ClientHooks,
|
|
7
|
+
CallDescriptor,
|
|
8
|
+
} from './types.js'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Executes a single procedure call through the adapter.
|
|
12
|
+
*
|
|
13
|
+
* Flow:
|
|
14
|
+
* 1. Build AdapterRequest from descriptor
|
|
15
|
+
* 2. Run onBeforeRequest hooks (global then local)
|
|
16
|
+
* 3. Call adapter.request()
|
|
17
|
+
* 4. On adapter error: run onError hooks, re-throw
|
|
18
|
+
* 5. Run onAfterResponse hooks (hooks may mutate response.status)
|
|
19
|
+
* 6. If response status is non-2xx: throw ClientRequestError
|
|
20
|
+
* 7. Return response.body as TResponse
|
|
21
|
+
*/
|
|
22
|
+
export async function executeCall<TResponse>(
|
|
23
|
+
descriptor: CallDescriptor,
|
|
24
|
+
basePath: string,
|
|
25
|
+
adapter: ClientAdapter,
|
|
26
|
+
globalHooks: ClientHooks,
|
|
27
|
+
localHooks: ClientHooks | undefined
|
|
28
|
+
): Promise<TResponse> {
|
|
29
|
+
// 1. Build the initial request
|
|
30
|
+
let request = buildAdapterRequest(descriptor, basePath)
|
|
31
|
+
|
|
32
|
+
// 2. Run before-request hooks — they may mutate the request
|
|
33
|
+
const beforeCtx = await runBeforeRequest(
|
|
34
|
+
{ procedureName: descriptor.name, scope: descriptor.scope, request },
|
|
35
|
+
globalHooks,
|
|
36
|
+
localHooks
|
|
37
|
+
)
|
|
38
|
+
request = beforeCtx.request
|
|
39
|
+
|
|
40
|
+
// 3. Call the adapter
|
|
41
|
+
let response
|
|
42
|
+
try {
|
|
43
|
+
response = await adapter.request(request)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
// 4. On adapter error: run error hooks, re-throw
|
|
46
|
+
await runOnError(
|
|
47
|
+
{ procedureName: descriptor.name, scope: descriptor.scope, request, error: err },
|
|
48
|
+
globalHooks,
|
|
49
|
+
localHooks
|
|
50
|
+
)
|
|
51
|
+
throw err
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 5. Run after-response hooks — they may mutate response.status to swallow errors
|
|
55
|
+
await runAfterResponse(
|
|
56
|
+
{ procedureName: descriptor.name, scope: descriptor.scope, request, response },
|
|
57
|
+
globalHooks,
|
|
58
|
+
localHooks
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
// 6. Check status AFTER hooks (hooks may have swallowed the error by mutating status)
|
|
62
|
+
if (response.status < 200 || response.status >= 300) {
|
|
63
|
+
throw new ClientRequestError({
|
|
64
|
+
status: response.status,
|
|
65
|
+
headers: response.headers,
|
|
66
|
+
body: response.body,
|
|
67
|
+
procedureName: descriptor.name,
|
|
68
|
+
scope: descriptor.scope,
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 7. Return the body
|
|
73
|
+
return response.body as TResponse
|
|
74
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export class ClientRequestError extends Error {
|
|
2
|
+
readonly name = 'ClientRequestError'
|
|
3
|
+
readonly status: number
|
|
4
|
+
readonly headers: Record<string, string>
|
|
5
|
+
readonly body: unknown
|
|
6
|
+
readonly procedureName: string
|
|
7
|
+
readonly scope: string
|
|
8
|
+
|
|
9
|
+
constructor(opts: {
|
|
10
|
+
status: number
|
|
11
|
+
headers: Record<string, string>
|
|
12
|
+
body: unknown
|
|
13
|
+
procedureName: string
|
|
14
|
+
scope: string
|
|
15
|
+
}) {
|
|
16
|
+
super(`${opts.procedureName} (${opts.scope}) failed with status ${opts.status}`)
|
|
17
|
+
this.status = opts.status
|
|
18
|
+
this.headers = opts.headers
|
|
19
|
+
this.body = opts.body
|
|
20
|
+
this.procedureName = opts.procedureName
|
|
21
|
+
this.scope = opts.scope
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ClientPathParamError extends Error {
|
|
26
|
+
readonly name = 'ClientPathParamError'
|
|
27
|
+
|
|
28
|
+
constructor(param: string, path: string, procedureName: string) {
|
|
29
|
+
super(`Missing path parameter "${param}" in "${path}" for procedure ${procedureName}`)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ClientStreamError extends Error {
|
|
34
|
+
readonly name = 'ClientStreamError'
|
|
35
|
+
readonly procedureName: string
|
|
36
|
+
readonly scope: string
|
|
37
|
+
|
|
38
|
+
constructor(message: string, procedureName: string, scope: string) {
|
|
39
|
+
super(message)
|
|
40
|
+
this.procedureName = procedureName
|
|
41
|
+
this.scope = scope
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import type { ClientAdapter, AdapterRequest, AdapterResponse, AdapterStreamResponse } from './types.js'
|
|
2
|
+
|
|
3
|
+
// ── Config ────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface FetchAdapterConfig {
|
|
6
|
+
headers?: Record<string, string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// ── SSE parser ────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
interface SSEEvent {
|
|
12
|
+
data: unknown
|
|
13
|
+
event?: string
|
|
14
|
+
id?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Parses an SSE message block (the text between double-newlines).
|
|
19
|
+
* Returns null if there is no data field (e.g., comment-only blocks).
|
|
20
|
+
*/
|
|
21
|
+
function parseSSEBlock(block: string): SSEEvent | null {
|
|
22
|
+
const lines = block.split('\n')
|
|
23
|
+
let event: string | undefined
|
|
24
|
+
let id: string | undefined
|
|
25
|
+
const dataParts: string[] = []
|
|
26
|
+
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
if (line.startsWith('event:')) {
|
|
29
|
+
event = line.slice('event:'.length).trim()
|
|
30
|
+
} else if (line.startsWith('data:')) {
|
|
31
|
+
dataParts.push(line.slice('data:'.length).trimStart())
|
|
32
|
+
} else if (line.startsWith('id:')) {
|
|
33
|
+
id = line.slice('id:'.length).trim()
|
|
34
|
+
}
|
|
35
|
+
// Lines starting with ':' are comments — skip them
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (dataParts.length === 0) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const dataStr = dataParts.join('\n')
|
|
43
|
+
let data: unknown
|
|
44
|
+
try {
|
|
45
|
+
data = JSON.parse(dataStr)
|
|
46
|
+
} catch {
|
|
47
|
+
data = dataStr
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { data, event, id }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Async generator that reads a ReadableStream<Uint8Array>, buffers text,
|
|
55
|
+
* splits on double-newline SSE boundaries, and yields parsed SSE events.
|
|
56
|
+
*/
|
|
57
|
+
async function* parseSseStream(
|
|
58
|
+
readableStream: ReadableStream<Uint8Array>
|
|
59
|
+
): AsyncGenerator<SSEEvent> {
|
|
60
|
+
const reader = readableStream.getReader()
|
|
61
|
+
const decoder = new TextDecoder()
|
|
62
|
+
let buffer = ''
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
while (true) {
|
|
66
|
+
const { done, value } = await reader.read()
|
|
67
|
+
|
|
68
|
+
if (value) {
|
|
69
|
+
buffer += decoder.decode(value, { stream: !done })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Process all complete SSE message blocks (split on \n\n)
|
|
73
|
+
let boundary: number
|
|
74
|
+
while ((boundary = buffer.indexOf('\n\n')) !== -1) {
|
|
75
|
+
const block = buffer.slice(0, boundary).trim()
|
|
76
|
+
buffer = buffer.slice(boundary + 2)
|
|
77
|
+
|
|
78
|
+
if (block.length > 0) {
|
|
79
|
+
const event = parseSSEBlock(block)
|
|
80
|
+
if (event !== null) {
|
|
81
|
+
yield event
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (done) break
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle any remaining buffer content (no trailing \n\n)
|
|
90
|
+
const remaining = buffer.trim()
|
|
91
|
+
if (remaining.length > 0) {
|
|
92
|
+
const event = parseSSEBlock(remaining)
|
|
93
|
+
if (event !== null) {
|
|
94
|
+
yield event
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
reader.releaseLock()
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Adapter ───────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extracts response headers as a plain Record<string, string>.
|
|
106
|
+
*/
|
|
107
|
+
function extractHeaders(response: Response): Record<string, string> {
|
|
108
|
+
const headers: Record<string, string> = {}
|
|
109
|
+
response.headers.forEach((value, key) => {
|
|
110
|
+
headers[key] = value
|
|
111
|
+
})
|
|
112
|
+
return headers
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Attempts to parse the response body as JSON, then as text, then returns null.
|
|
117
|
+
*/
|
|
118
|
+
async function parseResponseBody(response: Response): Promise<unknown> {
|
|
119
|
+
// Clone so we can attempt multiple reads
|
|
120
|
+
const clone = response.clone()
|
|
121
|
+
try {
|
|
122
|
+
return await clone.json()
|
|
123
|
+
} catch {
|
|
124
|
+
try {
|
|
125
|
+
const text = await response.text()
|
|
126
|
+
return text || null
|
|
127
|
+
} catch {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Creates a fetch-based ClientAdapter.
|
|
135
|
+
*
|
|
136
|
+
* - `config.headers` are default headers applied to every request.
|
|
137
|
+
* - Per-request headers override config headers (spread order).
|
|
138
|
+
* - Works in Node.js 18+ and browsers (uses standard fetch + ReadableStream).
|
|
139
|
+
*/
|
|
140
|
+
export function createFetchAdapter(config?: FetchAdapterConfig): ClientAdapter {
|
|
141
|
+
const configHeaders = config?.headers ?? {}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
async request(req: AdapterRequest): Promise<AdapterResponse> {
|
|
145
|
+
const mergedHeaders: Record<string, string> = {
|
|
146
|
+
...configHeaders,
|
|
147
|
+
...(req.headers ?? {}),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const response = await fetch(req.url, {
|
|
151
|
+
method: req.method,
|
|
152
|
+
headers: mergedHeaders,
|
|
153
|
+
body: req.body !== undefined ? JSON.stringify(req.body) : undefined,
|
|
154
|
+
signal: req.signal,
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const headers = extractHeaders(response)
|
|
158
|
+
const body = await parseResponseBody(response)
|
|
159
|
+
|
|
160
|
+
return { status: response.status, headers, body }
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
async stream(req: AdapterRequest): Promise<AdapterStreamResponse> {
|
|
164
|
+
const mergedHeaders: Record<string, string> = {
|
|
165
|
+
...configHeaders,
|
|
166
|
+
...(req.headers ?? {}),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const response = await fetch(req.url, {
|
|
170
|
+
method: req.method,
|
|
171
|
+
headers: mergedHeaders,
|
|
172
|
+
body: req.body !== undefined ? JSON.stringify(req.body) : undefined,
|
|
173
|
+
signal: req.signal,
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const headers = extractHeaders(response)
|
|
177
|
+
|
|
178
|
+
if (!response.body) {
|
|
179
|
+
// No body — return an empty async iterable
|
|
180
|
+
const emptyBody: AsyncIterable<unknown> = {
|
|
181
|
+
[Symbol.asyncIterator]: async function* () {},
|
|
182
|
+
}
|
|
183
|
+
return { status: response.status, headers, body: emptyBody }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const body = parseSseStream(response.body as ReadableStream<Uint8Array>)
|
|
187
|
+
|
|
188
|
+
return { status: response.status, headers, body }
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
BeforeRequestContext,
|
|
3
|
+
AfterResponseContext,
|
|
4
|
+
ErrorContext,
|
|
5
|
+
ClientHooks,
|
|
6
|
+
} from './types.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runs `onBeforeRequest` hooks: global first, then per-procedure.
|
|
10
|
+
* Each hook receives the (possibly mutated) context from the previous hook.
|
|
11
|
+
* Returns the final context.
|
|
12
|
+
*/
|
|
13
|
+
export async function runBeforeRequest(
|
|
14
|
+
ctx: BeforeRequestContext,
|
|
15
|
+
globalHooks: ClientHooks,
|
|
16
|
+
localHooks: ClientHooks | undefined
|
|
17
|
+
): Promise<BeforeRequestContext> {
|
|
18
|
+
let current = ctx
|
|
19
|
+
|
|
20
|
+
if (globalHooks.onBeforeRequest) {
|
|
21
|
+
current = await globalHooks.onBeforeRequest(current)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (localHooks?.onBeforeRequest) {
|
|
25
|
+
current = await localHooks.onBeforeRequest(current)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return current
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Runs `onAfterResponse` hooks: global first, then per-procedure.
|
|
33
|
+
* Returns void.
|
|
34
|
+
*/
|
|
35
|
+
export async function runAfterResponse(
|
|
36
|
+
ctx: AfterResponseContext,
|
|
37
|
+
globalHooks: ClientHooks,
|
|
38
|
+
localHooks: ClientHooks | undefined
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
if (globalHooks.onAfterResponse) {
|
|
41
|
+
await globalHooks.onAfterResponse(ctx)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (localHooks?.onAfterResponse) {
|
|
45
|
+
await localHooks.onAfterResponse(ctx)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Runs `onError` hooks: global first, then per-procedure.
|
|
51
|
+
* Returns void.
|
|
52
|
+
*/
|
|
53
|
+
export async function runOnError(
|
|
54
|
+
ctx: ErrorContext,
|
|
55
|
+
globalHooks: ClientHooks,
|
|
56
|
+
localHooks: ClientHooks | undefined
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
if (globalHooks.onError) {
|
|
59
|
+
await globalHooks.onError(ctx)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (localHooks?.onError) {
|
|
63
|
+
await localHooks.onError(ctx)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { executeCall } from './call.js'
|
|
2
|
+
import { executeStream, createTypedStream } from './stream.js'
|
|
3
|
+
import type {
|
|
4
|
+
CreateClientConfig,
|
|
5
|
+
ClientInstance,
|
|
6
|
+
CallDescriptor,
|
|
7
|
+
StreamDescriptor,
|
|
8
|
+
ProcedureCallOptions,
|
|
9
|
+
TypedStream,
|
|
10
|
+
} from './types.js'
|
|
11
|
+
|
|
12
|
+
// ── createClient ──────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Creates a typed client from a config object.
|
|
16
|
+
*
|
|
17
|
+
* The `scopes` callback receives a `ClientInstance` and returns the typed
|
|
18
|
+
* scope bindings (e.g., `{ users: { getUser, createUser }, posts: { ... } }`).
|
|
19
|
+
* The return value of `createClient` is the scopes object.
|
|
20
|
+
*
|
|
21
|
+
* `client.stream()` must return `TypedStream` synchronously even though
|
|
22
|
+
* `executeStream` is async. We achieve this by creating a deferred TypedStream:
|
|
23
|
+
* - A deferred async generator awaits `executeStream` internally, then forwards
|
|
24
|
+
* yields from the inner stream.
|
|
25
|
+
* - The outer `.result` is wired up to the inner stream's `.result`.
|
|
26
|
+
*/
|
|
27
|
+
export function createClient<TScopes>(config: CreateClientConfig<TScopes>): TScopes {
|
|
28
|
+
const { adapter, basePath, hooks: globalHooks = {}, scopes } = config
|
|
29
|
+
|
|
30
|
+
const instance: ClientInstance = {
|
|
31
|
+
basePath,
|
|
32
|
+
adapter,
|
|
33
|
+
hooks: globalHooks,
|
|
34
|
+
|
|
35
|
+
call<TResponse>(
|
|
36
|
+
descriptor: CallDescriptor,
|
|
37
|
+
options?: ProcedureCallOptions
|
|
38
|
+
): Promise<TResponse> {
|
|
39
|
+
return executeCall<TResponse>(descriptor, basePath, adapter, globalHooks, options)
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
stream<TYield, TReturn>(
|
|
43
|
+
descriptor: StreamDescriptor,
|
|
44
|
+
options?: ProcedureCallOptions
|
|
45
|
+
): TypedStream<TYield, TReturn> {
|
|
46
|
+
// executeStream is async but stream() must be synchronous.
|
|
47
|
+
// Create a deferred TypedStream that wraps the async executeStream call.
|
|
48
|
+
|
|
49
|
+
let resolveResult: (value: TReturn) => void
|
|
50
|
+
let rejectResult: (reason: unknown) => void
|
|
51
|
+
|
|
52
|
+
const resultPromise = new Promise<TReturn>((resolve, reject) => {
|
|
53
|
+
resolveResult = resolve
|
|
54
|
+
rejectResult = reject
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// The deferred async generator: awaits executeStream, then forwards
|
|
58
|
+
async function* deferredGenerator(): AsyncGenerator<TYield> {
|
|
59
|
+
let innerStream: TypedStream<TYield, TReturn>
|
|
60
|
+
try {
|
|
61
|
+
innerStream = await executeStream<TYield, TReturn>(
|
|
62
|
+
descriptor,
|
|
63
|
+
basePath,
|
|
64
|
+
adapter,
|
|
65
|
+
globalHooks,
|
|
66
|
+
options
|
|
67
|
+
)
|
|
68
|
+
} catch (err) {
|
|
69
|
+
rejectResult(err)
|
|
70
|
+
throw err
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Wire up .result from the inner stream
|
|
74
|
+
innerStream.result.then(resolveResult, rejectResult)
|
|
75
|
+
|
|
76
|
+
for await (const item of innerStream) {
|
|
77
|
+
yield item
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const iterator = deferredGenerator()
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
[Symbol.asyncIterator]() {
|
|
85
|
+
return iterator
|
|
86
|
+
},
|
|
87
|
+
result: resultPromise,
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return scopes(instance)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Barrel exports ────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
export type {
|
|
98
|
+
ClientAdapter,
|
|
99
|
+
AdapterRequest,
|
|
100
|
+
AdapterResponse,
|
|
101
|
+
AdapterStreamResponse,
|
|
102
|
+
ClientHooks,
|
|
103
|
+
BeforeRequestContext,
|
|
104
|
+
AfterResponseContext,
|
|
105
|
+
ErrorContext,
|
|
106
|
+
CallDescriptor,
|
|
107
|
+
StreamDescriptor,
|
|
108
|
+
TypedStream,
|
|
109
|
+
ClientInstance,
|
|
110
|
+
ProcedureCallOptions,
|
|
111
|
+
CreateClientConfig,
|
|
112
|
+
} from './types.js'
|
|
113
|
+
|
|
114
|
+
export { ClientRequestError, ClientPathParamError, ClientStreamError } from './errors.js'
|
|
115
|
+
|
|
116
|
+
export { createTypedStream } from './stream.js'
|
|
117
|
+
export { executeCall } from './call.js'
|
|
118
|
+
export { executeStream } from './stream.js'
|
|
119
|
+
|
|
120
|
+
export { createFetchAdapter } from './fetch-adapter.js'
|
|
121
|
+
export type { FetchAdapterConfig } from './fetch-adapter.js'
|