@cadenya/widgets 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +133 -0
  2. package/api.md +66 -0
  3. package/dist/client.d.ts +33 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +49 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/core/error.d.ts +39 -0
  8. package/dist/core/error.d.ts.map +1 -0
  9. package/dist/core/error.js +56 -0
  10. package/dist/core/error.js.map +1 -0
  11. package/dist/core/http.d.ts +140 -0
  12. package/dist/core/http.d.ts.map +1 -0
  13. package/dist/core/http.js +525 -0
  14. package/dist/core/http.js.map +1 -0
  15. package/dist/core/pagination.d.ts +12 -0
  16. package/dist/core/pagination.d.ts.map +1 -0
  17. package/dist/core/pagination.js +29 -0
  18. package/dist/core/pagination.js.map +1 -0
  19. package/dist/core/sse.d.ts +44 -0
  20. package/dist/core/sse.d.ts.map +1 -0
  21. package/dist/core/sse.js +350 -0
  22. package/dist/core/sse.js.map +1 -0
  23. package/dist/index.d.ts +12 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/resources/config.d.ts +16 -0
  28. package/dist/resources/config.d.ts.map +1 -0
  29. package/dist/resources/config.js +21 -0
  30. package/dist/resources/config.js.map +1 -0
  31. package/dist/resources/conversations.d.ts +173 -0
  32. package/dist/resources/conversations.d.ts.map +1 -0
  33. package/dist/resources/conversations.js +150 -0
  34. package/dist/resources/conversations.js.map +1 -0
  35. package/dist/types.d.ts +461 -0
  36. package/dist/types.d.ts.map +1 -0
  37. package/dist/types.js +3 -0
  38. package/dist/types.js.map +1 -0
  39. package/package.json +29 -0
  40. package/src/client.ts +87 -0
  41. package/src/core/error.ts +69 -0
  42. package/src/core/http.ts +639 -0
  43. package/src/core/pagination.ts +27 -0
  44. package/src/core/sse.ts +338 -0
  45. package/src/index.ts +13 -0
  46. package/src/resources/config.ts +22 -0
  47. package/src/resources/conversations.ts +232 -0
  48. package/src/types.ts +512 -0
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # CadenyaWidgets TypeScript SDK
2
+
3
+ The official TypeScript client for the CadenyaWidgets API. Generated by redwood.
4
+ Dependency-free ESM built on native `fetch`.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @cadenya/widgets
10
+ ```
11
+
12
+ ## Getting started
13
+
14
+ ```ts
15
+ import CadenyaWidgets from '@cadenya/widgets';
16
+
17
+ // Reads CADENYAWIDGETS_API_KEY from the environment when
18
+ // options are omitted. Explicit blank values are configuration errors.
19
+ const client = new CadenyaWidgets();
20
+
21
+ const result = await client.config.retrieveWidget();
22
+ ```
23
+
24
+ ## Errors
25
+
26
+ Non-2xx responses throw `APIError` (google.rpc Status: `status`, `code`,
27
+ `message`, `details`). Connection failures throw `APIConnectionError`;
28
+ user aborts throw `APIUserAbortError`; a request that cannot be
29
+ constructed locally (unserializable body) throws `APIRequestError` with
30
+ no network attempt and no retry.
31
+
32
+ ## Timeouts
33
+
34
+ Ordinary requests have a 60s deadline (through body decode) that throws
35
+ `APITimeoutError`; configure client-wide with `timeout` or per request
36
+ with `options.timeout` (<= 0 disables). Streams bound only response-header
37
+ acquisition — body lifetime stays under your `AbortSignal`.
38
+
39
+ ## Retries
40
+
41
+ Automatic retries apply only to idempotent methods (GET/HEAD/PUT/DELETE)
42
+ and default to 0. Enable client-wide with `maxRetries`; opt a single
43
+ mutation in per request with `options.maxRetries`. Retry counts normalize
44
+ to a bounded 0–10 integer; `Retry-After` (seconds or HTTP-date) is honored
45
+ and backoff sleeps wake on abort.
46
+
47
+ ## Pagination
48
+
49
+ ```ts
50
+ const page = await client.conversations.list();
51
+ for await (const item of page) {
52
+ // auto-fetches every page
53
+ }
54
+ ```
55
+
56
+ ## Raw response access
57
+
58
+ Plain methods return an `APIPromise`: awaiting yields the decoded value;
59
+ `.withResponse()` pairs it with the `Response` (status, headers);
60
+ `.asResponse()` — called synchronously, before any `await` — yields the raw
61
+ `Response` with the body UNCONSUMED, so you own reading it:
62
+
63
+ ```ts
64
+ const { data, response } = await client.config.retrieveWidget().withResponse();
65
+ console.log(response.status, response.headers.get('x-request-id'));
66
+
67
+ const raw = await client.config.retrieveWidget().asResponse();
68
+ const body = await raw.text();
69
+ ```
70
+
71
+ A dropped (never-awaited) call still cleans up after itself: the response
72
+ is consumed and the request deadline is released automatically.
73
+
74
+ ## Logging
75
+
76
+ ```ts
77
+ const client = new CadenyaWidgets({ logLevel: 'debug' }); // or logger: myLogger
78
+ ```
79
+
80
+ `'warn'` (default) logs only retries; `'debug'` adds one line per request
81
+ and response (method, path, status, duration); `'off'` silences the SDK.
82
+ Headers and bodies are NEVER logged.
83
+
84
+ ## Streaming (SSE)
85
+
86
+ A stream wraps one HTTP response body, so it can be consumed **once**, with
87
+ one of two views — pick whichever fits and iterate that one:
88
+
89
+ ```ts
90
+ // View 1 (normal case): iterate decoded event payloads directly.
91
+ const stream = await client.conversations.streamEvents(id, params);
92
+ for await (const event of stream) {
93
+ // event is the typed payload
94
+ }
95
+ ```
96
+
97
+ ```ts
98
+ // View 2 (alternative): stream.events() keeps the SSE metadata envelope,
99
+ // so the payload sits one level deeper: { event, data, id }.
100
+ const envelopes = await client.conversations.streamEvents(id, params);
101
+ for await (const { data, id } of envelopes.events()) {
102
+ // ...
103
+ }
104
+ ```
105
+
106
+ Streams RECONNECT AUTOMATICALLY on mid-stream transport drops (like
107
+ EventSource): they resume from the last received event id, retry at most 5
108
+ times per outage with backoff (honoring the server's `retry:` hint), and
109
+ reset the budget once events flow again. A clean stream end, `close()`, and
110
+ a caller abort never reconnect; HTTP-level reconnect failures (e.g. expired
111
+ credentials) surface immediately. Opt out with `{ reconnect: false }` in
112
+ request options — then drops raise `APIConnectionError` and you can resume
113
+ manually:
114
+
115
+ ```ts
116
+ const resumed = await client.conversations.streamEvents(id, params, {
117
+ lastEventId: stream.lastEventId,
118
+ });
119
+ try {
120
+ for await (const event of resumed) {
121
+ // ...
122
+ }
123
+ } finally {
124
+ await resumed.close();
125
+ }
126
+ ```
127
+
128
+ Consume (or close) every opened stream — an unconsumed stream holds its
129
+ connection until `close()`.
130
+
131
+ ## Reference
132
+
133
+ See [api.md](api.md) for every method signature.
package/api.md ADDED
@@ -0,0 +1,66 @@
1
+ # CadenyaWidgets TypeScript SDK reference
2
+
3
+ Plain methods return an awaitable APIPromise (with `.withResponse()` and
4
+ `.asResponse()` for raw Response access); pagination and streaming methods
5
+ return a Promise of a Page or Stream. See README.md for usage patterns.
6
+
7
+ ## config
8
+
9
+ Get widget config
10
+
11
+ ```ts
12
+ client.config.retrieveWidget(options?: RequestOptions): APIPromise<WidgetConfig>
13
+ ```
14
+
15
+ ## conversations
16
+
17
+ List conversations
18
+
19
+ ```ts
20
+ client.conversations.list(params?: ConversationListParams, options?: RequestOptions): Promise<Page<WidgetConversation>>
21
+ ```
22
+ Start a conversation
23
+
24
+ ```ts
25
+ client.conversations.create(params: ConversationCreateParams, options?: RequestOptions): APIPromise<WidgetConversation>
26
+ ```
27
+ Get a conversation
28
+
29
+ ```ts
30
+ client.conversations.retrieve(id: string, options?: RequestOptions): APIPromise<WidgetConversation>
31
+ ```
32
+ List conversation events
33
+
34
+ ```ts
35
+ client.conversations.listEvents(id: string, params?: ConversationListEventsParams, options?: RequestOptions): Promise<Page<WidgetEvent>>
36
+ ```
37
+ Stream conversation events
38
+
39
+ ```ts
40
+ client.conversations.streamEvents(id: string, options?: RequestOptions): Promise<Stream<WidgetEvent>>
41
+ ```
42
+ Submit conversation feedback
43
+
44
+ ```ts
45
+ client.conversations.submitFeedback(id: string, params: ConversationSubmitFeedbackParams, options?: RequestOptions): APIPromise<void>
46
+ ```
47
+ Approve a pending tool call
48
+
49
+ ```ts
50
+ client.conversations.approveToolCall(id: string, params: ConversationApproveToolCallParams, options?: RequestOptions): APIPromise<void>
51
+ ```
52
+ Deny a pending tool call
53
+
54
+ ```ts
55
+ client.conversations.denyToolCall(id: string, params: ConversationDenyToolCallParams, options?: RequestOptions): APIPromise<void>
56
+ ```
57
+ Supply a bare tool call's result
58
+
59
+ ```ts
60
+ client.conversations.setToolCallContent(id: string, params: ConversationSetToolCallContentParams, options?: RequestOptions): APIPromise<void>
61
+ ```
62
+ Send the next message
63
+
64
+ ```ts
65
+ client.conversations.continue(id: string, params: ConversationContinueParams, options?: RequestOptions): APIPromise<WidgetConversation>
66
+ ```
@@ -0,0 +1,33 @@
1
+ import type { Logger, LogLevel } from './core/http.js';
2
+ import { Config } from './resources/config.js';
3
+ import { Conversations } from './resources/conversations.js';
4
+ export interface ClientOptions {
5
+ /** API key. Defaults to the CADENYAWIDGETS_API_KEY environment variable. */
6
+ apiKey?: string;
7
+ /** Override the API base URL. Defaults to . */
8
+ baseURL?: string;
9
+ /** Max automatic retries for retryable failures. Defaults to 0. */
10
+ maxRetries?: number;
11
+ /**
12
+ * Deadline for ordinary (non-streaming) requests in milliseconds; override
13
+ * per request with `options.timeout`. Streams bound only response-header
14
+ * acquisition — body lifetime stays under the caller's AbortSignal.
15
+ * Defaults to 60000; a non-finite or <= 0 value disables the deadline.
16
+ */
17
+ timeout?: number;
18
+ /** Headers sent with every request. */
19
+ defaultHeaders?: Record<string, string>;
20
+ /** Custom fetch implementation. */
21
+ fetch?: typeof fetch;
22
+ /** Destination for SDK logs. Defaults to `console`. */
23
+ logger?: Logger;
24
+ /** 'debug' | 'warn' (default) | 'off'. Never logs headers or bodies. */
25
+ logLevel?: LogLevel;
26
+ }
27
+ export declare class CadenyaWidgets {
28
+ readonly config: Config;
29
+ readonly conversations: Conversations;
30
+ private readonly _client;
31
+ constructor(options?: ClientOptions);
32
+ }
33
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,mCAAmC;IACnC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED,qBAAa,cAAc;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IAEtC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;gBAEzB,OAAO,GAAE,aAAkB;CAuBxC"}
package/dist/client.js ADDED
@@ -0,0 +1,49 @@
1
+ // Generated by redwood. Do not edit by hand.
2
+ import { HttpClient } from './core/http.js';
3
+ import { Config } from './resources/config.js';
4
+ import { Conversations } from './resources/conversations.js';
5
+ export class CadenyaWidgets {
6
+ config;
7
+ conversations;
8
+ _client;
9
+ constructor(options = {}) {
10
+ // Presence is not validity: empty strings from options or environment
11
+ // are configuration mistakes, not credentials.
12
+ const apiKey = resolveOption('apiKey', options.apiKey, readEnv('CADENYAWIDGETS_API_KEY'));
13
+ if (!apiKey) {
14
+ throw new Error("Missing API key. Pass it with `new CadenyaWidgets({ apiKey })` or set the CADENYAWIDGETS_API_KEY environment variable.");
15
+ }
16
+ this._client = new HttpClient({
17
+ baseURL: resolveOption('baseURL', options.baseURL, readEnv('CADENYAWIDGETS_BASE_URL')) ?? '',
18
+ authHeader: () => ({ Authorization: `Bearer ${apiKey}` }),
19
+ maxRetries: options.maxRetries ?? 0,
20
+ timeout: options.timeout,
21
+ defaultHeaders: { 'User-Agent': 'cadenyawidgets-typescript/1.0.0 (api 1.0)', ...options.defaultHeaders },
22
+ fetch: options.fetch,
23
+ logger: options.logger,
24
+ logLevel: options.logLevel,
25
+ defaults: {},
26
+ });
27
+ this.config = new Config(this._client);
28
+ this.conversations = new Conversations(this._client);
29
+ }
30
+ }
31
+ function readEnv(name) {
32
+ const env = globalThis
33
+ .process?.env;
34
+ return env?.[name];
35
+ }
36
+ // Presence is not validity: an explicitly provided option must be usable —
37
+ // a blank value is a configuration mistake and never silently falls back to
38
+ // the environment or a default. Only an OMITTED option reads the env.
39
+ function resolveOption(label, explicit, env) {
40
+ if (explicit !== undefined) {
41
+ const value = explicit.trim();
42
+ if (!value) {
43
+ throw new Error(`${label} must not be blank when provided explicitly; omit it to use the environment instead.`);
44
+ }
45
+ return value;
46
+ }
47
+ return env?.trim() || undefined;
48
+ }
49
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAE7C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AA0B7D,MAAM,OAAO,cAAc;IAChB,MAAM,CAAS;IACf,aAAa,CAAgB;IAErB,OAAO,CAAa;IAErC,YAAY,UAAyB,EAAE;QACrC,sEAAsE;QACtE,+CAA+C;QAC/C,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,wBAAwB,CAAC,CAAC,CAAC;QAC1F,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CACb,wHAAwH,CACzH,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,UAAU,CAAC;YAC5B,OAAO,EAAE,aAAa,CAAC,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,yBAAyB,CAAC,CAAC,IAAI,EAAE;YAC5F,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,UAAU,MAAM,EAAE,EAAE,CAAC;YACzD,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,CAAC;YACnC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,cAAc,EAAE,EAAE,YAAY,EAAE,2CAA2C,EAAE,GAAG,OAAO,CAAC,cAAc,EAAE;YACxG,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,QAAQ,EAAE,EAAI;SACf,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvD,CAAC;CACF;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,GAAG,GAAI,UAAyE;SACnF,OAAO,EAAE,GAAG,CAAC;IAChB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,2EAA2E;AAC3E,4EAA4E;AAC5E,sEAAsE;AACtE,SAAS,aAAa,CACpB,KAAa,EACb,QAA4B,EAC5B,GAAuB;IAEvB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,sFAAsF,CAC/F,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,SAAS,CAAC;AAClC,CAAC","sourcesContent":["// Generated by redwood. Do not edit by hand.\n\nimport { HttpClient } from './core/http.js';\nimport type { Logger, LogLevel } from './core/http.js';\nimport { Config } from './resources/config.js';\nimport { Conversations } from './resources/conversations.js';\n\nexport interface ClientOptions {\n /** API key. Defaults to the CADENYAWIDGETS_API_KEY environment variable. */\n apiKey?: string;\n /** Override the API base URL. Defaults to . */\n baseURL?: string;\n /** Max automatic retries for retryable failures. Defaults to 0. */\n maxRetries?: number;\n /**\n * Deadline for ordinary (non-streaming) requests in milliseconds; override\n * per request with `options.timeout`. Streams bound only response-header\n * acquisition — body lifetime stays under the caller's AbortSignal.\n * Defaults to 60000; a non-finite or <= 0 value disables the deadline.\n */\n timeout?: number;\n /** Headers sent with every request. */\n defaultHeaders?: Record<string, string>;\n /** Custom fetch implementation. */\n fetch?: typeof fetch;\n /** Destination for SDK logs. Defaults to `console`. */\n logger?: Logger;\n /** 'debug' | 'warn' (default) | 'off'. Never logs headers or bodies. */\n logLevel?: LogLevel;\n}\n\nexport class CadenyaWidgets {\n readonly config: Config;\n readonly conversations: Conversations;\n\n private readonly _client: HttpClient;\n\n constructor(options: ClientOptions = {}) {\n // Presence is not validity: empty strings from options or environment\n // are configuration mistakes, not credentials.\n const apiKey = resolveOption('apiKey', options.apiKey, readEnv('CADENYAWIDGETS_API_KEY'));\n if (!apiKey) {\n throw new Error(\n \"Missing API key. Pass it with `new CadenyaWidgets({ apiKey })` or set the CADENYAWIDGETS_API_KEY environment variable.\",\n );\n }\n this._client = new HttpClient({\n baseURL: resolveOption('baseURL', options.baseURL, readEnv('CADENYAWIDGETS_BASE_URL')) ?? '',\n authHeader: () => ({ Authorization: `Bearer ${apiKey}` }),\n maxRetries: options.maxRetries ?? 0,\n timeout: options.timeout,\n defaultHeaders: { 'User-Agent': 'cadenyawidgets-typescript/1.0.0 (api 1.0)', ...options.defaultHeaders },\n fetch: options.fetch,\n logger: options.logger,\n logLevel: options.logLevel,\n defaults: { },\n });\n this.config = new Config(this._client);\n this.conversations = new Conversations(this._client);\n }\n}\n\nfunction readEnv(name: string): string | undefined {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env;\n return env?.[name];\n}\n\n// Presence is not validity: an explicitly provided option must be usable —\n// a blank value is a configuration mistake and never silently falls back to\n// the environment or a default. Only an OMITTED option reads the env.\nfunction resolveOption(\n label: string,\n explicit: string | undefined,\n env: string | undefined,\n): string | undefined {\n if (explicit !== undefined) {\n const value = explicit.trim();\n if (!value) {\n throw new Error(\n `${label} must not be blank when provided explicitly; omit it to use the environment instead.`,\n );\n }\n return value;\n }\n return env?.trim() || undefined;\n}\n"]}
@@ -0,0 +1,39 @@
1
+ /** Error model. The API reports failures as a google.rpc.Status payload. */
2
+ export interface ErrorStatus {
3
+ code?: number;
4
+ message?: string;
5
+ details?: Array<Record<string, unknown>>;
6
+ }
7
+ export declare class APIError extends Error {
8
+ readonly status: number;
9
+ readonly code: number | undefined;
10
+ readonly details: Array<Record<string, unknown>> | undefined;
11
+ constructor(status: number, body: ErrorStatus | undefined, message?: string);
12
+ }
13
+ export declare class APIConnectionError extends Error {
14
+ constructor(cause: unknown);
15
+ }
16
+ export declare class APIUserAbortError extends Error {
17
+ constructor();
18
+ }
19
+ /**
20
+ * The request could not be constructed locally (unserializable body, invalid
21
+ * argument). No network attempt was made and the call is never retried.
22
+ */
23
+ export declare class APIRequestError extends Error {
24
+ constructor(message: string, cause: unknown);
25
+ }
26
+ /** The configured request deadline elapsed before the response completed. */
27
+ export declare class APITimeoutError extends Error {
28
+ constructor(timeoutMs: number);
29
+ }
30
+ /**
31
+ * The server answered outside the declared protocol: an empty/null body
32
+ * where a JSON document was promised, malformed JSON, or a 204 on an
33
+ * output-bearing operation.
34
+ */
35
+ export declare class APIResponseError extends Error {
36
+ readonly status: number;
37
+ constructor(status: number, message: string, cause?: unknown);
38
+ }
39
+ //# sourceMappingURL=error.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/core/error.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAE5E,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC1C;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;gBAEjD,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM;CAO5E;AAED,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,KAAK,EAAE,OAAO;CAI3B;AAED,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAI5C;AAED,6EAA6E;AAC7E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,SAAS,EAAE,MAAM;CAI9B;AAED;;;;GAIG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAK7D"}
@@ -0,0 +1,56 @@
1
+ /** Error model. The API reports failures as a google.rpc.Status payload. */
2
+ export class APIError extends Error {
3
+ status;
4
+ code;
5
+ details;
6
+ constructor(status, body, message) {
7
+ super(message ?? body?.message ?? `HTTP ${status}`);
8
+ this.name = 'APIError';
9
+ this.status = status;
10
+ this.code = body?.code;
11
+ this.details = body?.details;
12
+ }
13
+ }
14
+ export class APIConnectionError extends Error {
15
+ constructor(cause) {
16
+ super('Connection error', { cause });
17
+ this.name = 'APIConnectionError';
18
+ }
19
+ }
20
+ export class APIUserAbortError extends Error {
21
+ constructor() {
22
+ super('Request was aborted');
23
+ this.name = 'APIUserAbortError';
24
+ }
25
+ }
26
+ /**
27
+ * The request could not be constructed locally (unserializable body, invalid
28
+ * argument). No network attempt was made and the call is never retried.
29
+ */
30
+ export class APIRequestError extends Error {
31
+ constructor(message, cause) {
32
+ super(message, { cause });
33
+ this.name = 'APIRequestError';
34
+ }
35
+ }
36
+ /** The configured request deadline elapsed before the response completed. */
37
+ export class APITimeoutError extends Error {
38
+ constructor(timeoutMs) {
39
+ super(`Request timed out after ${timeoutMs}ms`);
40
+ this.name = 'APITimeoutError';
41
+ }
42
+ }
43
+ /**
44
+ * The server answered outside the declared protocol: an empty/null body
45
+ * where a JSON document was promised, malformed JSON, or a 204 on an
46
+ * output-bearing operation.
47
+ */
48
+ export class APIResponseError extends Error {
49
+ status;
50
+ constructor(status, message, cause) {
51
+ super(message, cause === undefined ? undefined : { cause });
52
+ this.name = 'APIResponseError';
53
+ this.status = status;
54
+ }
55
+ }
56
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/core/error.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAQ5E,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,MAAM,CAAS;IACf,IAAI,CAAqB;IACzB,OAAO,CAA6C;IAE7D,YAAY,MAAc,EAAE,IAA6B,EAAE,OAAgB;QACzE,KAAK,CAAC,OAAO,IAAI,IAAI,EAAE,OAAO,IAAI,QAAQ,MAAM,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,KAAc;QACxB,KAAK,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IAC1C;QACE,KAAK,CAAC,qBAAqB,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe,EAAE,KAAc;QACzC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,6EAA6E;AAC7E,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,SAAiB;QAC3B,KAAK,CAAC,2BAA2B,SAAS,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,MAAM,CAAS;IAExB,YAAY,MAAc,EAAE,OAAe,EAAE,KAAe;QAC1D,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF","sourcesContent":["/** Error model. The API reports failures as a google.rpc.Status payload. */\n\nexport interface ErrorStatus {\n code?: number;\n message?: string;\n details?: Array<Record<string, unknown>>;\n}\n\nexport class APIError extends Error {\n readonly status: number;\n readonly code: number | undefined;\n readonly details: Array<Record<string, unknown>> | undefined;\n\n constructor(status: number, body: ErrorStatus | undefined, message?: string) {\n super(message ?? body?.message ?? `HTTP ${status}`);\n this.name = 'APIError';\n this.status = status;\n this.code = body?.code;\n this.details = body?.details;\n }\n}\n\nexport class APIConnectionError extends Error {\n constructor(cause: unknown) {\n super('Connection error', { cause });\n this.name = 'APIConnectionError';\n }\n}\n\nexport class APIUserAbortError extends Error {\n constructor() {\n super('Request was aborted');\n this.name = 'APIUserAbortError';\n }\n}\n\n/**\n * The request could not be constructed locally (unserializable body, invalid\n * argument). No network attempt was made and the call is never retried.\n */\nexport class APIRequestError extends Error {\n constructor(message: string, cause: unknown) {\n super(message, { cause });\n this.name = 'APIRequestError';\n }\n}\n\n/** The configured request deadline elapsed before the response completed. */\nexport class APITimeoutError extends Error {\n constructor(timeoutMs: number) {\n super(`Request timed out after ${timeoutMs}ms`);\n this.name = 'APITimeoutError';\n }\n}\n\n/**\n * The server answered outside the declared protocol: an empty/null body\n * where a JSON document was promised, malformed JSON, or a 204 on an\n * output-bearing operation.\n */\nexport class APIResponseError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string, cause?: unknown) {\n super(message, cause === undefined ? undefined : { cause });\n this.name = 'APIResponseError';\n this.status = status;\n }\n}\n"]}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Minimal, dependency-less HTTP core built on global fetch (Node 18+,
3
+ * browsers, Deno, Bun). Handles auth, query serialization, retries with
4
+ * backoff, timeouts, and error mapping.
5
+ */
6
+ export interface RequestOptions {
7
+ /**
8
+ * Auto-reconnect for SSE streams (default true): a mid-stream transport
9
+ * drop resumes from the last received event id, like EventSource. Clean
10
+ * stream end, close(), and abort never reconnect. Set false to surface
11
+ * drops as APIConnectionError instead.
12
+ */
13
+ reconnect?: boolean;
14
+ headers?: Record<string, string>;
15
+ signal?: AbortSignal;
16
+ maxRetries?: number;
17
+ /**
18
+ * Per-request deadline in milliseconds (overrides the client default).
19
+ * Non-finite or <= 0 disables the deadline for this request.
20
+ */
21
+ timeout?: number;
22
+ /**
23
+ * For streaming (SSE) requests: resume after the event with this id by
24
+ * sending it as the Last-Event-ID request header.
25
+ */
26
+ lastEventId?: string;
27
+ }
28
+ export type QueryPrimitive = string | number | boolean;
29
+ export type QueryValue = QueryPrimitive | QueryPrimitive[] | undefined | null;
30
+ export interface RequestSpec {
31
+ method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
32
+ path: string;
33
+ query?: Record<string, QueryValue>;
34
+ body?: unknown;
35
+ stream?: boolean;
36
+ /** The operation declares no response body (void): 204/empty succeed. */
37
+ void?: boolean;
38
+ }
39
+ export type LogLevel = 'debug' | 'warn' | 'off';
40
+ /** Minimal logger surface; `console` satisfies it. */
41
+ export interface Logger {
42
+ debug(...args: unknown[]): void;
43
+ warn(...args: unknown[]): void;
44
+ }
45
+ export interface HttpClientOptions {
46
+ baseURL: string;
47
+ authHeader: () => Record<string, string>;
48
+ maxRetries?: number;
49
+ /** Deadline for ordinary (non-streaming) requests in ms. Default 60000. */
50
+ timeout?: number;
51
+ defaultHeaders?: Record<string, string>;
52
+ fetch?: typeof fetch;
53
+ /** Client-level default values for prominent params (e.g. a tenant/scope id). */
54
+ defaults?: Record<string, string | undefined>;
55
+ /** Destination for SDK logs. Defaults to `console`. */
56
+ logger?: Logger;
57
+ /**
58
+ * 'debug' logs every request/response line (method, path, status,
59
+ * duration — never headers or bodies); 'warn' (default) logs only
60
+ * retries; 'off' silences the SDK entirely.
61
+ */
62
+ logLevel?: LogLevel;
63
+ }
64
+ /**
65
+ * Encode one path segment, rejecting empty/whitespace values at the boundary
66
+ * — an empty segment would silently rewrite the route (/parents//children).
67
+ */
68
+ export declare function pathSegment(name: string, value: string | undefined): string;
69
+ /**
70
+ * Snapshot list params at call time so pagination cannot observe later
71
+ * caller mutations (array-valued filters are copied too). Auto-iteration
72
+ * must never combine pages from different result sets.
73
+ */
74
+ export declare function snapshotParams<T>(params: T): T;
75
+ /** Claim (and remove) the cleanup registered for a streaming response. */
76
+ export declare function takeStreamCleanup(response: Response): (() => void) | undefined;
77
+ /**
78
+ * A lazily-parsing promise for one API call. Awaiting it (or `.then`) yields
79
+ * the decoded value exactly like a plain promise; `withResponse()` yields the
80
+ * decoded value together with the raw `Response` (status, headers); and
81
+ * `asResponse()` yields the raw `Response` WITHOUT consuming the body, so
82
+ * the caller owns reading it.
83
+ */
84
+ export declare class APIPromise<T> implements Promise<T> {
85
+ private readonly responsePromise;
86
+ private readonly parseFn;
87
+ private readonly onRawAccess;
88
+ private parsed;
89
+ private observedRaw;
90
+ constructor(responsePromise: Promise<Response>, parseFn: (response: Response) => Promise<T>, onRawAccess: () => void);
91
+ /**
92
+ * The raw `Response` after status checking and retries; the body is NOT
93
+ * consumed. Reading the body (and its timing) becomes the caller's
94
+ * responsibility — the request deadline stops at header acquisition.
95
+ */
96
+ asResponse(): Promise<Response>;
97
+ /** The decoded value together with the `Response` its body came from. */
98
+ withResponse(): Promise<{
99
+ data: T;
100
+ response: Response;
101
+ }>;
102
+ private parse;
103
+ then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
104
+ catch<TResult = never>(onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null): Promise<T | TResult>;
105
+ finally(onfinally?: (() => void) | null): Promise<T>;
106
+ readonly [Symbol.toStringTag] = "APIPromise";
107
+ }
108
+ export declare class HttpClient {
109
+ private readonly baseURL;
110
+ private readonly authHeader;
111
+ private readonly maxRetries;
112
+ private readonly timeout;
113
+ private readonly defaultHeaders;
114
+ private readonly fetchFn;
115
+ private readonly logger;
116
+ private readonly logLevel;
117
+ /** Method + path + status only — headers and bodies are never logged. */
118
+ private logDebug;
119
+ private logWarn;
120
+ readonly defaults: Record<string, string | undefined>;
121
+ constructor(options: HttpClientOptions);
122
+ /**
123
+ * The APIPromise pipeline for JSON and void operations. The spec thunk
124
+ * runs inside the async context so synchronous setup failures (e.g. a
125
+ * missing client-default param) surface as rejections, exactly like the
126
+ * plain-promise path. The deadline spans fetch through decode; raw-access
127
+ * consumers release it at header acquisition and own body timing.
128
+ */
129
+ requestAPI<T>(makeSpec: () => RequestSpec, options?: RequestOptions): APIPromise<T>;
130
+ request<T>(spec: RequestSpec, options?: RequestOptions): Promise<T>;
131
+ rawRequest(spec: RequestSpec, options?: RequestOptions): Promise<Response>;
132
+ /**
133
+ * Merge the caller's signal with the configured deadline. The caller's
134
+ * abort always forwards; the timer marks timedOut so the thrown error can
135
+ * be classified as APITimeoutError rather than APIUserAbortError.
136
+ */
137
+ private deadline;
138
+ private buildURL;
139
+ }
140
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"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"}