@upvoiced/sdk 0.1.0 → 1.32.3

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.
@@ -2,6 +2,6 @@
2
2
  * Generated from the UpVoiced API OpenAPI document (version 0.1.0)
3
3
  * by openapi-typescript. Do not edit by hand (P2-015, ADR-0067):
4
4
  * run `pnpm --filter @upvoiced/sdk generate` after changing routes.
5
- * Document sha256: b2e98a3bd5edbe654e2e232efd73a133e4323e3ebc5263b931d0d0b9dbe01144
5
+ * Document sha256: b1a1581db6735eb4d0165c44c07c0217d67ee532d17a69413123ddbc727cd973
6
6
  */
7
7
  export {};
@@ -0,0 +1,33 @@
1
+ type ImageMime = 'image/png' | 'image/jpeg' | 'image/webp' | 'image/svg+xml';
2
+ export type DownloadedImage = {
3
+ readonly buffer: ArrayBuffer;
4
+ readonly mime: 'image/png' | 'image/jpeg' | 'image/webp';
5
+ };
6
+ export type DownloadedSocialImage = {
7
+ readonly buffer: ArrayBuffer;
8
+ readonly mime: 'image/png' | 'image/svg+xml';
9
+ };
10
+ /** Safe fixed messages only; never retain an error body, token-bearing URL or provider response. */
11
+ export declare class SocialImageDownloadError extends Error {
12
+ readonly status: number;
13
+ readonly code: string;
14
+ constructor(status: number);
15
+ }
16
+ type ImageDownloadOptions = Parameters<typeof readImage>[0];
17
+ /** Existing changelog images keep their original raster-only5MiB contract. */
18
+ export declare function downloadImageBytes(options: ImageDownloadOptions): Promise<DownloadedImage>;
19
+ /** Social SVG comes only from the fixed authenticated endpoint, never a user SVG URL. */
20
+ export declare function downloadSocialImageBytes(options: ImageDownloadOptions): Promise<DownloadedSocialImage>;
21
+ /** One authenticated image GET. Size/type are read only from the fixed API response,
22
+ * bounded to the native5MiB image cap; never issue a metadata GET or a second body request. */
23
+ declare function readImage<M extends ImageMime>(options: {
24
+ readonly url: string;
25
+ readonly headers: HeadersInit;
26
+ readonly fetch?: (url: string, init: RequestInit) => Promise<Response>;
27
+ readonly assertCurrent?: () => void;
28
+ readonly signal?: AbortSignal;
29
+ }, allowed: readonly M[], maxBytes: number): Promise<{
30
+ readonly buffer: ArrayBuffer;
31
+ readonly mime: M;
32
+ }>;
33
+ export {};
@@ -0,0 +1,132 @@
1
+ import { downloadExportBytes } from './export-download.js';
2
+ /** Safe fixed messages only; never retain an error body, token-bearing URL or provider response. */
3
+ export class SocialImageDownloadError extends Error {
4
+ status;
5
+ code;
6
+ constructor(status) {
7
+ const failure = status === 422
8
+ ? [
9
+ 'unsupported_image_text',
10
+ 'Image export needs nonempty text in the bundled Latin-script font.',
11
+ ]
12
+ : status === 409
13
+ ? ['image_changed', 'The quote or workspace theme changed. Prepare the current version.']
14
+ : status === 404
15
+ ? ['not_found', 'This testimonial is unavailable or is no longer approved.']
16
+ : status === 429
17
+ ? ['image_busy', 'Image export is busy. Try again shortly.']
18
+ : status === 401
19
+ ? ['unauthenticated', 'Sign in again to export this testimonial.']
20
+ : status === 403
21
+ ? ['forbidden', 'Your current workspace role cannot export this testimonial.']
22
+ : ['image_unavailable', 'Image export is unavailable. Try again shortly.'];
23
+ super(failure[1]);
24
+ this.name = 'SocialImageDownloadError';
25
+ this.status = status;
26
+ this.code = failure[0] ?? 'image_unavailable';
27
+ }
28
+ }
29
+ /** Existing changelog images keep their original raster-only5MiB contract. */
30
+ export function downloadImageBytes(options) {
31
+ return readImage(options, ['image/png', 'image/jpeg', 'image/webp'], 5242880);
32
+ }
33
+ /** Social SVG comes only from the fixed authenticated endpoint, never a user SVG URL. */
34
+ export function downloadSocialImageBytes(options) {
35
+ return readImage(options, ['image/png', 'image/svg+xml'], 2097152);
36
+ }
37
+ /** One authenticated image GET. Size/type are read only from the fixed API response,
38
+ * bounded to the native5MiB image cap; never issue a metadata GET or a second body request. */
39
+ async function readImage(options, allowed, maxBytes) {
40
+ const url = options.url, headers = new Headers(options.headers), send = options.fetch ?? fetch, assertCurrent = options.assertCurrent, callerSignal = options.signal;
41
+ const controller = new AbortController();
42
+ const signal = callerSignal
43
+ ? AbortSignal.any([controller.signal, callerSignal])
44
+ : controller.signal;
45
+ const deadline = performance.now() + 120000;
46
+ const refused = () => new Error('Image download is unavailable.');
47
+ let response, handed = false;
48
+ const held = new Set();
49
+ const retainsResponse = () => !handed;
50
+ const timer = setTimeout(() => {
51
+ controller.abort();
52
+ void response?.body?.cancel().catch(() => { });
53
+ }, 120000);
54
+ const current = () => {
55
+ if (signal.aborted || performance.now() >= deadline)
56
+ throw refused();
57
+ assertCurrent?.();
58
+ };
59
+ try {
60
+ current();
61
+ const pending = send(url, {
62
+ method: 'GET',
63
+ headers,
64
+ credentials: 'omit',
65
+ cache: 'no-store',
66
+ redirect: 'error',
67
+ signal,
68
+ });
69
+ void pending.then((value) => {
70
+ if (signal.aborted)
71
+ void value.body?.cancel().catch(() => { });
72
+ }, () => { });
73
+ response = await new Promise((resolve, reject) => {
74
+ const abort = () => {
75
+ reject(refused());
76
+ };
77
+ if (signal.aborted) {
78
+ void pending.catch(() => { });
79
+ reject(refused());
80
+ return;
81
+ }
82
+ signal.addEventListener('abort', abort, { once: true });
83
+ void pending.then((value) => {
84
+ signal.removeEventListener('abort', abort);
85
+ resolve(value);
86
+ }, () => {
87
+ signal.removeEventListener('abort', abort);
88
+ reject(refused());
89
+ });
90
+ });
91
+ current();
92
+ if (response.status !== 200 && allowed.includes('image/svg+xml'))
93
+ throw new SocialImageDownloadError(response.status);
94
+ const mime = response.headers.get('content-type'), length = response.headers.get('content-length');
95
+ if (response.status !== 200 ||
96
+ !length ||
97
+ !/^[1-9][0-9]{0,7}$/.test(length) ||
98
+ mime === null ||
99
+ !allowed.includes(mime))
100
+ throw refused();
101
+ const bytes = Number(length);
102
+ if (!Number.isSafeInteger(bytes) || bytes > maxBytes)
103
+ throw refused();
104
+ const accepted = response;
105
+ const buffer = await downloadExportBytes({
106
+ url,
107
+ headers,
108
+ bytes,
109
+ mime: mime,
110
+ signal,
111
+ assertCurrent: current,
112
+ fetch: () => {
113
+ if (handed)
114
+ return Promise.reject(refused());
115
+ handed = true;
116
+ return Promise.resolve(accepted);
117
+ },
118
+ });
119
+ held.add(buffer);
120
+ current();
121
+ held.delete(buffer);
122
+ return { buffer, mime: mime };
123
+ }
124
+ finally {
125
+ for (const buffer of held)
126
+ new Uint8Array(buffer).fill(0);
127
+ clearTimeout(timer);
128
+ controller.abort();
129
+ if (retainsResponse())
130
+ void response?.body?.cancel().catch(() => { });
131
+ }
132
+ }
package/dist/index.d.ts CHANGED
@@ -8,3 +8,6 @@ export { UpVoicedError } from './errors.js';
8
8
  export type { ErrorDetail } from './errors.js';
9
9
  export type { ApiResponse, CommonRequestOptions, HttpMethod, Operation, PageItem, PaginatedPath, PathParams, PathsWith, QueryParams, RequestArgs, RequestBody, RequestOptions, ResponseData, } from './types.js';
10
10
  export type { components, operations, paths, webhooks } from './generated/schema.js';
11
+ export { downloadExportBytes } from './export-download.js';
12
+ export { downloadImageBytes, downloadSocialImageBytes, SocialImageDownloadError, } from './image-download.js';
13
+ export type { DownloadedImage, DownloadedSocialImage } from './image-download.js';
package/dist/index.js CHANGED
@@ -4,3 +4,5 @@
4
4
  */
5
5
  export { UpVoiced, defaultBaseUrl, defaultMaxRetries, defaultMaxRetryDelayMs } from './client.js';
6
6
  export { UpVoicedError } from './errors.js';
7
+ export { downloadExportBytes } from './export-download.js';
8
+ export { downloadImageBytes, downloadSocialImageBytes, SocialImageDownloadError, } from './image-download.js';
package/dist/types.d.ts CHANGED
@@ -9,9 +9,9 @@ export type HttpMethod = 'get' | 'put' | 'post' | 'patch' | 'delete';
9
9
  export type ManagedHeader = 'authorization' | 'x-workspace' | 'idempotency-key';
10
10
  type Empty<T> = [T] extends [never] ? true : [T] extends [undefined] ? true : false;
11
11
  /** Paths that define `method`. */
12
- export type PathsWith<M extends HttpMethod> = {
12
+ export type PathsWith<M extends HttpMethod> = Exclude<{
13
13
  [P in keyof paths]: Empty<paths[P][M]> extends true ? never : P;
14
- }[keyof paths];
14
+ }[keyof paths], '/v1/changelog/{entryId}/media/{id}' | '/v1/unlisted/changelog/preview/{token}/media/{id}' | '/v1/branding/previews/{id}' | '/v1/exports/{id}/content' | '/v1/public/branding/media/{id}' | '/v1/public/changelog/media/{id}'>;
15
15
  export type Operation<P extends keyof paths, M extends HttpMethod> = Exclude<paths[P][M], undefined>;
16
16
  type ParametersOf<O> = O extends {
17
17
  parameters: infer X;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upvoiced/sdk",
3
- "version": "0.1.0",
3
+ "version": "1.32.3",
4
4
  "description": "Typed TypeScript client for the UpVoiced API, generated from its OpenAPI document.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://docs.upvoiced.com",