@vibesdotdev/client 0.1.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/SPEC.md +107 -0
- package/dist/factory.d.ts +34 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/factory.js +80 -0
- package/dist/factory.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/client/core.d.ts +8 -0
- package/dist/lib/client/core.d.ts.map +1 -0
- package/dist/lib/client/core.js +5 -0
- package/dist/lib/client/core.js.map +1 -0
- package/dist/lib/client/internal/base-client.d.ts +19 -0
- package/dist/lib/client/internal/base-client.d.ts.map +1 -0
- package/dist/lib/client/internal/base-client.js +92 -0
- package/dist/lib/client/internal/base-client.js.map +1 -0
- package/dist/lib/client/internal/base-helpers.d.ts +9 -0
- package/dist/lib/client/internal/base-helpers.d.ts.map +1 -0
- package/dist/lib/client/internal/base-helpers.js +79 -0
- package/dist/lib/client/internal/base-helpers.js.map +1 -0
- package/dist/lib/client/internal/base-types.d.ts +35 -0
- package/dist/lib/client/internal/base-types.d.ts.map +1 -0
- package/dist/lib/client/internal/base-types.js +15 -0
- package/dist/lib/client/internal/base-types.js.map +1 -0
- package/dist/lib/client/internal/endpoint.d.ts +22 -0
- package/dist/lib/client/internal/endpoint.d.ts.map +1 -0
- package/dist/lib/client/internal/endpoint.js +35 -0
- package/dist/lib/client/internal/endpoint.js.map +1 -0
- package/dist/lib/client/internal/generator.d.ts +20 -0
- package/dist/lib/client/internal/generator.d.ts.map +1 -0
- package/dist/lib/client/internal/generator.js +173 -0
- package/dist/lib/client/internal/generator.js.map +1 -0
- package/dist/lib/client/internal/index.d.ts +5 -0
- package/dist/lib/client/internal/index.d.ts.map +1 -0
- package/dist/lib/client/internal/index.js +4 -0
- package/dist/lib/client/internal/index.js.map +1 -0
- package/dist/lib/client/internal/node/http2-fetch.node.d.ts +2 -0
- package/dist/lib/client/internal/node/http2-fetch.node.d.ts.map +1 -0
- package/dist/lib/client/internal/node/http2-fetch.node.js +131 -0
- package/dist/lib/client/internal/node/http2-fetch.node.js.map +1 -0
- package/dist/lib/client/internal/request-builder.d.ts +15 -0
- package/dist/lib/client/internal/request-builder.d.ts.map +1 -0
- package/dist/lib/client/internal/request-builder.js +158 -0
- package/dist/lib/client/internal/request-builder.js.map +1 -0
- package/dist/lib/client/internal/sse-stream.d.ts +23 -0
- package/dist/lib/client/internal/sse-stream.d.ts.map +1 -0
- package/dist/lib/client/internal/sse-stream.js +110 -0
- package/dist/lib/client/internal/sse-stream.js.map +1 -0
- package/dist/lib/client/internal/vibes-client.d.ts +32 -0
- package/dist/lib/client/internal/vibes-client.d.ts.map +1 -0
- package/dist/lib/client/internal/vibes-client.js +120 -0
- package/dist/lib/client/internal/vibes-client.js.map +1 -0
- package/dist/lib/client/internal/wrap-fetch.d.ts +6 -0
- package/dist/lib/client/internal/wrap-fetch.d.ts.map +1 -0
- package/dist/lib/client/internal/wrap-fetch.js +46 -0
- package/dist/lib/client/internal/wrap-fetch.js.map +1 -0
- package/dist/lib/client/node.d.ts +5 -0
- package/dist/lib/client/node.d.ts.map +1 -0
- package/dist/lib/client/node.js +33 -0
- package/dist/lib/client/node.js.map +1 -0
- package/dist/lib/client/types.d.ts +145 -0
- package/dist/lib/client/types.d.ts.map +1 -0
- package/dist/lib/client/types.js +21 -0
- package/dist/lib/client/types.js.map +1 -0
- package/dist/plugin.d.ts +19 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +80 -0
- package/dist/plugin.js.map +1 -0
- package/dist/schemas.d.ts +90 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +9 -0
- package/dist/schemas.js.map +1 -0
- package/dist/sse-client.d.ts +39 -0
- package/dist/sse-client.d.ts.map +1 -0
- package/dist/sse-client.js +124 -0
- package/dist/sse-client.js.map +1 -0
- package/dist/tools/api-request/api-request.descriptor.d.ts +48 -0
- package/dist/tools/api-request/api-request.descriptor.d.ts.map +1 -0
- package/dist/tools/api-request/api-request.descriptor.js +27 -0
- package/dist/tools/api-request/api-request.descriptor.js.map +1 -0
- package/dist/tools/api-request/api-request.impl.consumer.d.ts +13 -0
- package/dist/tools/api-request/api-request.impl.consumer.d.ts.map +1 -0
- package/dist/tools/api-request/api-request.impl.consumer.js +51 -0
- package/dist/tools/api-request/api-request.impl.consumer.js.map +1 -0
- package/dist/tools/api-request/index.d.ts +5 -0
- package/dist/tools/api-request/index.d.ts.map +1 -0
- package/dist/tools/api-request/index.js +4 -0
- package/dist/tools/api-request/index.js.map +1 -0
- package/dist/tools/api-request/schemas/index.d.ts +33 -0
- package/dist/tools/api-request/schemas/index.d.ts.map +1 -0
- package/dist/tools/api-request/schemas/index.js +24 -0
- package/dist/tools/api-request/schemas/index.js.map +1 -0
- package/package.json +99 -0
- package/src/factory.ts +114 -0
- package/src/index.ts +15 -0
- package/src/lib/client/core.ts +13 -0
- package/src/lib/client/internal/base-client.ts +107 -0
- package/src/lib/client/internal/base-helpers.ts +74 -0
- package/src/lib/client/internal/base-types.ts +42 -0
- package/src/lib/client/internal/endpoint.ts +51 -0
- package/src/lib/client/internal/generator.ts +181 -0
- package/src/lib/client/internal/index.ts +4 -0
- package/src/lib/client/internal/node/http2-fetch.node.ts +138 -0
- package/src/lib/client/internal/request-builder.ts +147 -0
- package/src/lib/client/internal/sse-stream.ts +130 -0
- package/src/lib/client/internal/vibes-client.ts +167 -0
- package/src/lib/client/internal/wrap-fetch.ts +59 -0
- package/src/lib/client/node.ts +36 -0
- package/src/lib/client/types.ts +156 -0
- package/src/plugin.ts +104 -0
- package/src/schemas.ts +91 -0
- package/src/sse-client.ts +155 -0
- package/src/tools/api-request/api-request.descriptor.ts +28 -0
- package/src/tools/api-request/api-request.impl.consumer.ts +66 -0
- package/src/tools/api-request/index.ts +4 -0
- package/src/tools/api-request/schemas/index.ts +29 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { AuthConfig, ClientConfig, ClientHooks, RequestOptions } from '../types.ts';
|
|
2
|
+
import { APIGenerator, type OpenAPISpec } from './generator.ts';
|
|
3
|
+
import { getRuntimeEnv } from '@vibesdotdev/runtime';
|
|
4
|
+
import { RequestBuilder } from './request-builder.ts';
|
|
5
|
+
import { streamSSE, type SSEEvent, type SSEStreamOptions } from './sse-stream.ts';
|
|
6
|
+
import { createHookedFetch } from './wrap-fetch.ts';
|
|
7
|
+
|
|
8
|
+
interface ResolvedClientConfig {
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
auth: AuthConfig;
|
|
11
|
+
fetch: typeof fetch;
|
|
12
|
+
hooks: ClientHooks;
|
|
13
|
+
timeout: number;
|
|
14
|
+
debug: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class VibesClient {
|
|
18
|
+
private config: ResolvedClientConfig;
|
|
19
|
+
private requestBuilder: RequestBuilder;
|
|
20
|
+
private apiGenerator: APIGenerator;
|
|
21
|
+
private apis: Record<string, unknown> = {};
|
|
22
|
+
private specPromise: Promise<OpenAPISpec> | null = null;
|
|
23
|
+
|
|
24
|
+
constructor(config: ClientConfig = {}) {
|
|
25
|
+
const baseUrl =
|
|
26
|
+
config.baseUrl ||
|
|
27
|
+
getRuntimeEnv('VIBES_API_URL') ||
|
|
28
|
+
'http://localhost:5173';
|
|
29
|
+
const fetchImpl = config.fetch || globalThis.fetch?.bind(globalThis);
|
|
30
|
+
if (!fetchImpl)
|
|
31
|
+
throw new Error('Fetch is not available. Please provide a fetch implementation.');
|
|
32
|
+
const hooks = config.hooks || {};
|
|
33
|
+
this.config = {
|
|
34
|
+
baseUrl,
|
|
35
|
+
auth: config.auth || { type: 'none' },
|
|
36
|
+
fetch: fetchImpl as typeof fetch,
|
|
37
|
+
hooks,
|
|
38
|
+
timeout: config.timeout || 30000,
|
|
39
|
+
debug: config.debug || false
|
|
40
|
+
};
|
|
41
|
+
const hookedFetch = createHookedFetch({
|
|
42
|
+
fetchImpl: this.config.fetch,
|
|
43
|
+
hooks: this.config.hooks
|
|
44
|
+
});
|
|
45
|
+
this.requestBuilder = new RequestBuilder(
|
|
46
|
+
this.config.baseUrl,
|
|
47
|
+
this.config.auth,
|
|
48
|
+
hookedFetch as typeof fetch,
|
|
49
|
+
this.config.timeout,
|
|
50
|
+
this.config.debug
|
|
51
|
+
);
|
|
52
|
+
this.apiGenerator = new APIGenerator(this.requestBuilder);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async initialize(): Promise<void> {
|
|
56
|
+
try {
|
|
57
|
+
const spec = await this.getOpenAPISpec();
|
|
58
|
+
this.buildAPIs(spec);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
const shouldWarn =
|
|
61
|
+
this.config.debug ||
|
|
62
|
+
(typeof process !== 'undefined' && process.env?.VIBES_CLIENT_WARN_OAS === 'true');
|
|
63
|
+
if (shouldWarn) {
|
|
64
|
+
console.warn('Failed to load OpenAPI spec, client will have limited functionality:', error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getOpenAPISpec(): Promise<OpenAPISpec> {
|
|
70
|
+
if (!this.specPromise) this.specPromise = this.fetchOpenAPISpec();
|
|
71
|
+
return this.specPromise;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private async fetchOpenAPISpec(): Promise<OpenAPISpec> {
|
|
75
|
+
const response = await this.config.fetch(`${this.config.baseUrl}/api/docs/openapi.json`);
|
|
76
|
+
if (!response.ok) throw new Error(`Failed to fetch OpenAPI spec: ${response.statusText}`);
|
|
77
|
+
return response.json() as Promise<OpenAPISpec>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private buildAPIs(spec: OpenAPISpec): void {
|
|
81
|
+
const apis = this.apiGenerator.generateAPIs(spec);
|
|
82
|
+
for (const [name, api] of Object.entries(apis)) {
|
|
83
|
+
this.apis[name] = api;
|
|
84
|
+
Object.defineProperty(this, name, {
|
|
85
|
+
get: () => this.apis[name],
|
|
86
|
+
enumerable: true,
|
|
87
|
+
configurable: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
request<T = unknown>(
|
|
93
|
+
path: string,
|
|
94
|
+
options: {
|
|
95
|
+
method?: string;
|
|
96
|
+
params?: Record<string, string | number | boolean>;
|
|
97
|
+
query?: Record<string, string | number | boolean | string[] | undefined>;
|
|
98
|
+
body?: unknown;
|
|
99
|
+
headers?: Record<string, string>;
|
|
100
|
+
timeout?: number;
|
|
101
|
+
} = {}
|
|
102
|
+
): Promise<T> {
|
|
103
|
+
return this.requestBuilder.request({
|
|
104
|
+
path,
|
|
105
|
+
method: options.method || 'GET',
|
|
106
|
+
params: options.params,
|
|
107
|
+
query: options.query,
|
|
108
|
+
body: options.body,
|
|
109
|
+
headers: options.headers,
|
|
110
|
+
timeout: options.timeout
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setAuth(auth: AuthConfig): void {
|
|
115
|
+
this.config.auth = auth;
|
|
116
|
+
const hookedFetch = createHookedFetch({
|
|
117
|
+
fetchImpl: this.config.fetch,
|
|
118
|
+
hooks: this.config.hooks
|
|
119
|
+
});
|
|
120
|
+
this.requestBuilder = new RequestBuilder(
|
|
121
|
+
this.config.baseUrl,
|
|
122
|
+
auth,
|
|
123
|
+
hookedFetch as typeof fetch,
|
|
124
|
+
this.config.timeout,
|
|
125
|
+
this.config.debug
|
|
126
|
+
);
|
|
127
|
+
this.apiGenerator = new APIGenerator(this.requestBuilder);
|
|
128
|
+
if (this.specPromise) this.specPromise.then((spec) => this.buildAPIs(spec));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getResources(): string[] {
|
|
132
|
+
return Object.keys(this.apis);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
hasResource(name: string): boolean {
|
|
136
|
+
return name in this.apis;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
get<T = unknown>(endpoint: string, options?: RequestOptions): Promise<T> {
|
|
140
|
+
return this.request<T>(endpoint, { ...options, method: 'GET' });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
post<T = unknown>(endpoint: string, body?: unknown, options?: RequestOptions): Promise<T> {
|
|
144
|
+
return this.request<T>(endpoint, { ...options, method: 'POST', body });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
put<T = unknown>(endpoint: string, body?: unknown, options?: RequestOptions): Promise<T> {
|
|
148
|
+
return this.request<T>(endpoint, { ...options, method: 'PUT', body });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
delete<T = unknown>(endpoint: string, options?: RequestOptions): Promise<T> {
|
|
152
|
+
return this.request<T>(endpoint, { ...options, method: 'DELETE' });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async *stream<T = unknown>(
|
|
156
|
+
path: string,
|
|
157
|
+
options: SSEStreamOptions = {}
|
|
158
|
+
): AsyncIterableIterator<SSEEvent<T>> {
|
|
159
|
+
yield* streamSSE<T>({
|
|
160
|
+
baseUrl: this.config.baseUrl,
|
|
161
|
+
auth: this.config.auth,
|
|
162
|
+
fetchImpl: this.config.fetch,
|
|
163
|
+
path,
|
|
164
|
+
options
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ClientError, type ClientHooks } from '../types.ts';
|
|
2
|
+
|
|
3
|
+
export function createHookedFetch(args: {
|
|
4
|
+
fetchImpl: typeof fetch;
|
|
5
|
+
hooks: ClientHooks;
|
|
6
|
+
}): (input: Parameters<typeof fetch>[0], init?: RequestInit) => Promise<Response> {
|
|
7
|
+
return async (input: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
|
8
|
+
if (!args.hooks.beforeRequest) {
|
|
9
|
+
let response: Response;
|
|
10
|
+
try {
|
|
11
|
+
response = await args.fetchImpl(input, init);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
if (args.hooks.onError) {
|
|
14
|
+
await args.hooks.onError(
|
|
15
|
+
new ClientError(
|
|
16
|
+
error instanceof Error ? error.message : 'Network error',
|
|
17
|
+
0,
|
|
18
|
+
undefined,
|
|
19
|
+
undefined
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
if (args.hooks.afterResponse) response = await args.hooks.afterResponse(response);
|
|
26
|
+
return response;
|
|
27
|
+
}
|
|
28
|
+
const requestInput = input instanceof URL
|
|
29
|
+
? new Request(input.href, init)
|
|
30
|
+
: typeof input === 'string'
|
|
31
|
+
? new Request(input, init)
|
|
32
|
+
: new Request(input, init);
|
|
33
|
+
const modifiedRequest = await args.hooks.beforeRequest(requestInput);
|
|
34
|
+
const modifiedInit: RequestInit = {
|
|
35
|
+
...init,
|
|
36
|
+
method: modifiedRequest.method,
|
|
37
|
+
headers: Object.fromEntries(modifiedRequest.headers.entries()),
|
|
38
|
+
...(modifiedRequest.body !== requestInput.body && { body: modifiedRequest.body })
|
|
39
|
+
};
|
|
40
|
+
let response: Response;
|
|
41
|
+
try {
|
|
42
|
+
response = await args.fetchImpl(modifiedRequest.url, modifiedInit);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (args.hooks.onError) {
|
|
45
|
+
await args.hooks.onError(
|
|
46
|
+
new ClientError(
|
|
47
|
+
error instanceof Error ? error.message : 'Network error',
|
|
48
|
+
0,
|
|
49
|
+
undefined,
|
|
50
|
+
undefined
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
if (args.hooks.afterResponse) response = await args.hooks.afterResponse(response);
|
|
57
|
+
return response;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { getRuntimeEnv } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { ClientConfig } from './types.ts';
|
|
3
|
+
import { VibesClient } from './internal/vibes-client.ts';
|
|
4
|
+
|
|
5
|
+
export { createHttp2Fetch } from './internal/node/http2-fetch.node.ts';
|
|
6
|
+
|
|
7
|
+
async function resolveNodeFetch(baseUrl: string): Promise<typeof globalThis.fetch> {
|
|
8
|
+
const isHttps = baseUrl.startsWith('https://');
|
|
9
|
+
const isLocal =
|
|
10
|
+
baseUrl.includes('.local') || baseUrl.includes('localhost') || baseUrl.includes('127.0.0.1');
|
|
11
|
+
if (isHttps && isLocal) {
|
|
12
|
+
try {
|
|
13
|
+
if (!getRuntimeEnv('REJECT_UNAUTHORIZED')) {
|
|
14
|
+
globalThis.process?.['env'] && (globalThis.process.env.REJECT_UNAUTHORIZED = 'false');
|
|
15
|
+
}
|
|
16
|
+
const { createHttp2Fetch } = await import('./internal/node/http2-fetch.node.js');
|
|
17
|
+
return createHttp2Fetch(baseUrl) as unknown as typeof globalThis.fetch;
|
|
18
|
+
} catch {
|
|
19
|
+
// Fallback to regular fetch if h2 module unavailable
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return globalThis.fetch;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function createCLIClient(config?: Partial<ClientConfig>): Promise<VibesClient> {
|
|
26
|
+
const baseUrl = config?.baseUrl || getRuntimeEnv('VIBES_API_URL') || 'http://localhost:5173';
|
|
27
|
+
const fetchImpl = config?.fetch || (await resolveNodeFetch(baseUrl));
|
|
28
|
+
const clientConfig: ClientConfig = {
|
|
29
|
+
baseUrl,
|
|
30
|
+
auth: config?.auth || { type: 'none' },
|
|
31
|
+
fetch: fetchImpl,
|
|
32
|
+
debug: getRuntimeEnv('VIBES_DEBUG') === 'true',
|
|
33
|
+
...config
|
|
34
|
+
};
|
|
35
|
+
return new VibesClient(clientConfig);
|
|
36
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vibes Client Types
|
|
3
|
+
*
|
|
4
|
+
* Configuration, errors, and type contracts for the VibesClient HTTP API client.
|
|
5
|
+
* Extracted from libraries/vibeskit (PR #447 migration) to canonicalize as a
|
|
6
|
+
* pure-isomorphic @vibesdotdev/client surface.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** Authentication configuration */
|
|
10
|
+
export interface AuthConfig {
|
|
11
|
+
/** Authentication type */
|
|
12
|
+
type: 'apiKey' | 'session' | 'bearer' | 'none';
|
|
13
|
+
/** Static credentials */
|
|
14
|
+
credentials?: string;
|
|
15
|
+
/** Dynamic token provider */
|
|
16
|
+
tokenProvider?: () => Promise<string | null>;
|
|
17
|
+
/** Session refresh handler for expired sessions */
|
|
18
|
+
refreshHandler?: () => Promise<AuthConfig | null>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Client lifecycle hooks */
|
|
22
|
+
export interface ClientHooks {
|
|
23
|
+
/** Called before each request */
|
|
24
|
+
beforeRequest?: (request: Request) => Promise<Request> | Request;
|
|
25
|
+
/** Called after each response */
|
|
26
|
+
afterResponse?: (response: Response) => Promise<Response> | Response;
|
|
27
|
+
/** Called on request error */
|
|
28
|
+
onError?: (error: ClientError) => Promise<void> | void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Request options */
|
|
32
|
+
export interface RequestOptions {
|
|
33
|
+
/** HTTP method */
|
|
34
|
+
method: string;
|
|
35
|
+
/** Request path (will be appended to baseUrl) */
|
|
36
|
+
path: string;
|
|
37
|
+
/** URL parameters */
|
|
38
|
+
params?: Record<string, string | number | boolean>;
|
|
39
|
+
/** Query parameters */
|
|
40
|
+
query?: Record<string, string | number | boolean | string[] | undefined>;
|
|
41
|
+
/** Request body */
|
|
42
|
+
body?: unknown;
|
|
43
|
+
/** Additional headers */
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
/** Override timeout for this request */
|
|
46
|
+
timeout?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Client configuration options */
|
|
50
|
+
export interface ClientConfig {
|
|
51
|
+
/** Base URL for API requests */
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
/** Authentication configuration */
|
|
54
|
+
auth?: AuthConfig;
|
|
55
|
+
/** Custom fetch implementation */
|
|
56
|
+
fetch?: typeof fetch;
|
|
57
|
+
/** Request/response hooks */
|
|
58
|
+
hooks?: ClientHooks;
|
|
59
|
+
/** Request timeout in milliseconds */
|
|
60
|
+
timeout?: number;
|
|
61
|
+
/** Enable debug logging */
|
|
62
|
+
debug?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Client error with additional context */
|
|
66
|
+
export class ClientError extends Error {
|
|
67
|
+
constructor(
|
|
68
|
+
message: string,
|
|
69
|
+
public status?: number,
|
|
70
|
+
public response?: Response,
|
|
71
|
+
public request?: RequestOptions
|
|
72
|
+
) {
|
|
73
|
+
super(message);
|
|
74
|
+
this.name = 'ClientError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Paginated response wrapper */
|
|
79
|
+
export interface PaginatedResponse<T> {
|
|
80
|
+
data: T[];
|
|
81
|
+
total: number;
|
|
82
|
+
page: number;
|
|
83
|
+
pageSize: number;
|
|
84
|
+
hasMore: boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Standard list parameters */
|
|
88
|
+
export interface ListParams {
|
|
89
|
+
/** Page number (1-based) */
|
|
90
|
+
page?: number;
|
|
91
|
+
/** Items per page */
|
|
92
|
+
limit?: number;
|
|
93
|
+
/** Sort field */
|
|
94
|
+
sortBy?: string;
|
|
95
|
+
/** Sort direction */
|
|
96
|
+
sortOrder?: 'asc' | 'desc';
|
|
97
|
+
/** Search query */
|
|
98
|
+
search?: string;
|
|
99
|
+
/** Additional filters */
|
|
100
|
+
filters?: Record<string, unknown>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** OpenAPI path info (generic -- no openapi-types dependency) */
|
|
104
|
+
export interface PathInfo {
|
|
105
|
+
path: string;
|
|
106
|
+
method: string;
|
|
107
|
+
operationId?: string;
|
|
108
|
+
parameters?: Record<string, unknown>[];
|
|
109
|
+
requestBody?: Record<string, unknown>;
|
|
110
|
+
responses?: Record<string, unknown>;
|
|
111
|
+
tags?: string[];
|
|
112
|
+
summary?: string;
|
|
113
|
+
description?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Grouped API resources */
|
|
117
|
+
export interface APIResource {
|
|
118
|
+
name: string;
|
|
119
|
+
basePath: string;
|
|
120
|
+
operations: Map<string, PathInfo>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Migration debt: ClientUser is an auth-domain type owned by @vibesdotdev/auth.
|
|
125
|
+
* Defined here temporarily so @vibesdotdev/kit/client consumers can migrate.
|
|
126
|
+
* Target: move to packages/auth/src/shared/client-user.ts and re-export from client/core.
|
|
127
|
+
*/
|
|
128
|
+
export interface ClientUser {
|
|
129
|
+
id: string;
|
|
130
|
+
email: string;
|
|
131
|
+
displayName?: string | null;
|
|
132
|
+
avatarUrl?: string | null;
|
|
133
|
+
isSuperuser?: boolean;
|
|
134
|
+
roles?: Array<{
|
|
135
|
+
id: string;
|
|
136
|
+
name: string;
|
|
137
|
+
description: string | null;
|
|
138
|
+
isSystemRole: boolean;
|
|
139
|
+
}>;
|
|
140
|
+
permissions?: string[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Type helpers for schema conversion
|
|
145
|
+
*/
|
|
146
|
+
export type SchemaToType<T> = T extends { type: 'string' }
|
|
147
|
+
? string
|
|
148
|
+
: T extends { type: 'number' }
|
|
149
|
+
? number
|
|
150
|
+
: T extends { type: 'boolean' }
|
|
151
|
+
? boolean
|
|
152
|
+
: T extends { type: 'array'; items: infer I }
|
|
153
|
+
? Array<SchemaToType<I>>
|
|
154
|
+
: T extends { type: 'object'; properties: infer P }
|
|
155
|
+
? { [K in keyof P]: SchemaToType<P[K]> }
|
|
156
|
+
: unknown;
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { createRuntimePlugin, loader } from '@vibesdotdev/runtime';
|
|
2
|
+
import type { RuntimeDescriptor } from '@vibesdotdev/runtime/schemas/descriptor';
|
|
3
|
+
import {
|
|
4
|
+
ApiClientDescriptorSchema,
|
|
5
|
+
type ApiClientDescriptor,
|
|
6
|
+
type ApiClientImplementation,
|
|
7
|
+
type ApiStreamEvent
|
|
8
|
+
} from './schemas.ts';
|
|
9
|
+
import { vibesApiClient } from './factory.ts';
|
|
10
|
+
import type { KindContext, RuntimeKindDescriptorRecord } from '@vibesdotdev/runtime/schemas/kind';
|
|
11
|
+
import apiRequestToolDescriptor from './tools/api-request/api-request.descriptor.ts';
|
|
12
|
+
|
|
13
|
+
function isApiClientDescriptor(descriptor: RuntimeDescriptor): descriptor is ApiClientDescriptor {
|
|
14
|
+
return descriptor.kind === 'api/client' && typeof descriptor.id === 'string';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default API client implementation.
|
|
19
|
+
*
|
|
20
|
+
* When no specific client implementation is registered for a descriptor,
|
|
21
|
+
* this default constructs a client using the descriptor's baseUrl.
|
|
22
|
+
*/
|
|
23
|
+
class DefaultApiClient implements ApiClientImplementation {
|
|
24
|
+
private client: ApiClientImplementation;
|
|
25
|
+
|
|
26
|
+
constructor(descriptor: RuntimeDescriptor, _context: KindContext) {
|
|
27
|
+
if (!isApiClientDescriptor(descriptor)) {
|
|
28
|
+
throw new Error(`Invalid api/client descriptor '${descriptor.id ?? 'unknown'}'`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.client = vibesApiClient({
|
|
32
|
+
id: descriptor.id,
|
|
33
|
+
baseUrl: descriptor.baseUrl,
|
|
34
|
+
timeout: descriptor.timeout,
|
|
35
|
+
debug: descriptor.debug
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
get<T = unknown>(endpoint: string, options?: Parameters<ApiClientImplementation['get']>[1]): Promise<T> {
|
|
40
|
+
return this.client.get<T>(endpoint, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
post<T = unknown>(endpoint: string, body?: unknown, options?: Parameters<ApiClientImplementation['post']>[2]): Promise<T> {
|
|
44
|
+
return this.client.post<T>(endpoint, body, options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
put<T = unknown>(endpoint: string, body?: unknown, options?: Parameters<ApiClientImplementation['put']>[2]): Promise<T> {
|
|
48
|
+
return this.client.put<T>(endpoint, body, options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
patch<T = unknown>(endpoint: string, body?: unknown, options?: Parameters<ApiClientImplementation['patch']>[2]): Promise<T> {
|
|
52
|
+
return this.client.patch<T>(endpoint, body, options);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
delete<T = unknown>(endpoint: string, options?: Parameters<ApiClientImplementation['delete']>[1]): Promise<T> {
|
|
56
|
+
return this.client.delete<T>(endpoint, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
request<T = unknown>(endpoint: string, options?: Parameters<ApiClientImplementation['request']>[1]): Promise<T> {
|
|
60
|
+
return this.client.request<T>(endpoint, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
stream<T = unknown>(
|
|
64
|
+
endpoint: string,
|
|
65
|
+
options?: Parameters<ApiClientImplementation['stream']>[1]
|
|
66
|
+
): AsyncIterableIterator<ApiStreamEvent<T>> {
|
|
67
|
+
return this.client.stream<T>(endpoint, options) as AsyncIterableIterator<ApiStreamEvent<T>>;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const apiClientKind = {
|
|
72
|
+
id: 'api/client',
|
|
73
|
+
descriptorSchema: ApiClientDescriptorSchema,
|
|
74
|
+
defaultImplementation: DefaultApiClient
|
|
75
|
+
} satisfies RuntimeKindDescriptorRecord<ApiClientImplementation>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Client plugin — registers the `api/client` kind with the runtime.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* import { getVibesRuntime } from '@vibesdotdev/runtime';
|
|
83
|
+
* import clientPlugin from '@vibesdotdev/client/plugin';
|
|
84
|
+
*
|
|
85
|
+
* const runtime = getVibesRuntime();
|
|
86
|
+
* await runtime.registerPlugin(clientPlugin);
|
|
87
|
+
*
|
|
88
|
+
* // Query a registered API client
|
|
89
|
+
* const authClient = await runtime.query('api/client').withId('auth').resolve();
|
|
90
|
+
* await authClient.get('/api/health');
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export default createRuntimePlugin({
|
|
94
|
+
id: 'client',
|
|
95
|
+
name: 'API Client Module',
|
|
96
|
+
description: 'Runtime API client kind with environment-aware factories',
|
|
97
|
+
kinds: [apiClientKind],
|
|
98
|
+
descriptors: [apiRequestToolDescriptor],
|
|
99
|
+
loaders: [
|
|
100
|
+
loader('api-request', { kind: 'tools/tool', scope: 'consumer' }, () =>
|
|
101
|
+
import('./tools/api-request/api-request.impl.consumer.js')
|
|
102
|
+
)
|
|
103
|
+
]
|
|
104
|
+
});
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as z from 'zod/v4';
|
|
2
|
+
import {
|
|
3
|
+
RuntimeDescriptorSchema,
|
|
4
|
+
type RuntimeDescriptor
|
|
5
|
+
} from '@vibesdotdev/runtime/schemas/descriptor';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* API Client descriptor — metadata for a registered API client asset.
|
|
9
|
+
*/
|
|
10
|
+
export interface ApiClientDescriptor extends RuntimeDescriptor {
|
|
11
|
+
kind: 'api/client';
|
|
12
|
+
/** Base URL for this client's API */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** Default timeout in milliseconds */
|
|
15
|
+
timeout?: number;
|
|
16
|
+
/** Whether to enable debug logging */
|
|
17
|
+
debug?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const ApiClientDescriptorSchema = RuntimeDescriptorSchema.extend({
|
|
21
|
+
kind: z.literal('api/client'),
|
|
22
|
+
baseUrl: z.string().optional(),
|
|
23
|
+
timeout: z.number().int().positive().optional(),
|
|
24
|
+
debug: z.boolean().optional()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* API Client implementation interface.
|
|
29
|
+
*
|
|
30
|
+
* Any runtime API client implementation must satisfy this contract.
|
|
31
|
+
*/
|
|
32
|
+
export interface ApiClientImplementation {
|
|
33
|
+
/** Make a GET request */
|
|
34
|
+
get<T = unknown>(endpoint: string, options?: ApiRequestOptions): Promise<T>;
|
|
35
|
+
/** Make a POST request */
|
|
36
|
+
post<T = unknown>(endpoint: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
37
|
+
/** Make a PUT request */
|
|
38
|
+
put<T = unknown>(endpoint: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
39
|
+
/** Make a PATCH request */
|
|
40
|
+
patch<T = unknown>(endpoint: string, body?: unknown, options?: ApiRequestOptions): Promise<T>;
|
|
41
|
+
/** Make a DELETE request */
|
|
42
|
+
delete<T = unknown>(endpoint: string, options?: ApiRequestOptions): Promise<T>;
|
|
43
|
+
/** Make a generic request */
|
|
44
|
+
request<T = unknown>(
|
|
45
|
+
endpoint: string,
|
|
46
|
+
options?: ApiRequestOptions & { method?: string; body?: unknown }
|
|
47
|
+
): Promise<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Stream Server-Sent Events from an endpoint.
|
|
50
|
+
*
|
|
51
|
+
* Yields parsed SSE frames as they arrive. Each frame has already been
|
|
52
|
+
* split into event/data/id/retry fields and the data payload is
|
|
53
|
+
* JSON-parsed when possible. Returning from the generator — or the
|
|
54
|
+
* server closing the stream — ends iteration; an explicit `[DONE]`
|
|
55
|
+
* sentinel in the data stream also terminates.
|
|
56
|
+
*
|
|
57
|
+
* Use this instead of manually parsing SSE-wrapped POST bodies — the
|
|
58
|
+
* raw-fetch workaround (line-split + regex for `data: ...` lines) is
|
|
59
|
+
* brittle and fails on multi-line data, retry hints, and reconnects.
|
|
60
|
+
*/
|
|
61
|
+
stream<T = unknown>(
|
|
62
|
+
endpoint: string,
|
|
63
|
+
options?: ApiStreamOptions
|
|
64
|
+
): AsyncIterableIterator<ApiStreamEvent<T>>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ApiRequestOptions {
|
|
68
|
+
query?: Record<string, string | number | boolean | string[] | undefined>;
|
|
69
|
+
headers?: Record<string, string>;
|
|
70
|
+
timeout?: number;
|
|
71
|
+
params?: Record<string, string | number | boolean>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ApiStreamOptions extends ApiRequestOptions {
|
|
75
|
+
method?: string;
|
|
76
|
+
body?: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* A single Server-Sent Event frame.
|
|
81
|
+
*
|
|
82
|
+
* `data` is JSON-parsed when the frame payload is valid JSON, otherwise
|
|
83
|
+
* the raw string is returned. `event`, `id`, and `retry` map to the
|
|
84
|
+
* corresponding SSE fields when present.
|
|
85
|
+
*/
|
|
86
|
+
export interface ApiStreamEvent<T = unknown> {
|
|
87
|
+
event?: string;
|
|
88
|
+
data: T;
|
|
89
|
+
id?: string;
|
|
90
|
+
retry?: number;
|
|
91
|
+
}
|