@xformmedia/sdk 0.5.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.
@@ -0,0 +1,187 @@
1
+ import { XformError } from './errors.js';
2
+ import { createEventStream } from './event-stream.js';
3
+ export class XformAdminClient {
4
+ organizationId;
5
+ apiKey;
6
+ baseUrl;
7
+ constructor(options) {
8
+ this.organizationId = options.organizationId;
9
+ this.apiKey = options.apiKey;
10
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
11
+ }
12
+ get headers() {
13
+ return {
14
+ 'Authorization': `Bearer ${this.apiKey}`,
15
+ 'x-organization-id': this.organizationId,
16
+ 'Content-Type': 'application/json',
17
+ };
18
+ }
19
+ async request(method, path, body) {
20
+ const url = `${this.baseUrl}${path}`;
21
+ const res = await fetch(url, {
22
+ method,
23
+ headers: this.headers,
24
+ body: body !== undefined ? JSON.stringify(body) : undefined,
25
+ });
26
+ if (!res.ok) {
27
+ let message = `Request failed with status ${res.status}`;
28
+ let responseBody;
29
+ try {
30
+ responseBody = await res.json();
31
+ if (typeof responseBody === 'object' && responseBody !== null && 'message' in responseBody) {
32
+ message = responseBody.message;
33
+ }
34
+ }
35
+ catch { }
36
+ throw new XformError(res.status, message, responseBody);
37
+ }
38
+ return res.json();
39
+ }
40
+ /** Source management operations (requires org-scoped API key) */
41
+ sources = {
42
+ /**
43
+ * List all sources for this organization.
44
+ */
45
+ list: () => {
46
+ return this.request('GET', '/api/sources').then(r => r.data);
47
+ },
48
+ /**
49
+ * Get a specific source by ID.
50
+ */
51
+ get: (sourceId) => {
52
+ return this.request('GET', `/api/sources/${sourceId}`).then(r => r.data);
53
+ },
54
+ /**
55
+ * Create a new source. This provisions a subdomain, Cloudflare DNS record,
56
+ * and Stripe subscription item.
57
+ */
58
+ create: (input) => {
59
+ return this.request('POST', '/api/sources', input).then(r => r.data);
60
+ },
61
+ /**
62
+ * Update an existing source's settings.
63
+ */
64
+ update: (sourceId, input) => {
65
+ return this.request('PATCH', `/api/sources/${sourceId}`, input).then(r => r.data);
66
+ },
67
+ /**
68
+ * Check if a subdomain is available before creating a source.
69
+ */
70
+ checkSubdomain: (subdomain) => {
71
+ const params = new URLSearchParams({ subdomain });
72
+ return this.request('GET', `/api/sources/check-subdomain?${params}`);
73
+ },
74
+ };
75
+ /**
76
+ * Returns a set of video operations scoped to a specific source.
77
+ * Use this after creating or looking up a source.
78
+ */
79
+ source(sourceId) {
80
+ const req = this.request.bind(this);
81
+ const baseUrl = this.baseUrl;
82
+ const apiKey = this.apiKey;
83
+ const organizationId = this.organizationId;
84
+ return {
85
+ /**
86
+ * Open an SSE connection to receive real-time video status updates for this source.
87
+ */
88
+ events() {
89
+ const params = new URLSearchParams({
90
+ api_key: apiKey,
91
+ organization_id: organizationId,
92
+ });
93
+ return createEventStream(`${baseUrl}/api/sources/${sourceId}/videos/stream?${params}`, {});
94
+ },
95
+ /**
96
+ * Ingest a video from the source's connected S3/R2 bucket.
97
+ * Call this after uploading the file to the bucket.
98
+ */
99
+ async ingest(options) {
100
+ const result = await req('POST', `/api/sources/${sourceId}/videos/ingest`, options);
101
+ return result.data;
102
+ },
103
+ /**
104
+ * Create a presigned URL for uploading a file directly to xform's R2 storage.
105
+ * After uploading, call `ingest()` with the returned `key`.
106
+ */
107
+ async createUploadUrl(options) {
108
+ const result = await req('POST', `/api/sources/${sourceId}/upload-url`, options);
109
+ return result.data;
110
+ },
111
+ /**
112
+ * Get storage and bandwidth usage for this source.
113
+ */
114
+ usage() {
115
+ return req('GET', `/api/sources/${sourceId}/videos/usage`);
116
+ },
117
+ /**
118
+ * Change the source's plan, billing cycle, or add-ons.
119
+ * Omit any field to leave it unchanged.
120
+ *
121
+ * Upgrades are prorated immediately.
122
+ * Downgrades take effect at the next billing cycle (no credit issued).
123
+ * Downgrading from Pro to Starter automatically removes any custom domain.
124
+ */
125
+ updatePlan(input) {
126
+ return req('PATCH', `/api/sources/${sourceId}/plan`, input).then(r => r.data);
127
+ },
128
+ /** Video operations for this source */
129
+ videos: {
130
+ /**
131
+ * List all videos for this source.
132
+ */
133
+ list(options = {}) {
134
+ const params = new URLSearchParams();
135
+ if (options.limit !== undefined)
136
+ params.set('limit', String(options.limit));
137
+ if (options.skip !== undefined)
138
+ params.set('skip', String(options.skip));
139
+ const qs = params.toString() ? `?${params}` : '';
140
+ return req('GET', `/api/sources/${sourceId}/videos${qs}`);
141
+ },
142
+ /**
143
+ * Get the status and metadata of a specific video.
144
+ */
145
+ get(videoKey) {
146
+ return req('GET', `/api/sources/${sourceId}/videos/${videoKey}`).then(r => r.data);
147
+ },
148
+ },
149
+ /** Webhook configuration for this source */
150
+ webhook: {
151
+ /**
152
+ * Get the current webhook configuration (secret is masked).
153
+ * Returns null if no webhook is configured.
154
+ */
155
+ get() {
156
+ return req('GET', `/api/sources/${sourceId}/webhook`).then(r => r.data);
157
+ },
158
+ /**
159
+ * Set or update the webhook configuration.
160
+ * If `events` is omitted, all event types are subscribed.
161
+ */
162
+ set(input) {
163
+ return req('PUT', `/api/sources/${sourceId}/webhook`, input).then(r => r.data);
164
+ },
165
+ /**
166
+ * Remove the webhook configuration. No more webhooks will be sent.
167
+ */
168
+ delete() {
169
+ return req('DELETE', `/api/sources/${sourceId}/webhook`);
170
+ },
171
+ },
172
+ };
173
+ }
174
+ }
175
+ /**
176
+ * Create an organization-scoped xform client.
177
+ * Use this for programmatic source management (enterprise / server-to-server).
178
+ * Requires an organization-level API key.
179
+ *
180
+ * @example
181
+ * const admin = createXformAdminClient({ organizationId, apiKey, baseUrl })
182
+ * const source = await admin.sources.create({ name: 'My Source', ... })
183
+ * const { uploadUrl, key } = await admin.source(source.id).createUploadUrl({ ... })
184
+ */
185
+ export function createXformAdminClient(options) {
186
+ return new XformAdminClient(options);
187
+ }
@@ -0,0 +1,100 @@
1
+ import type { XformClientOptions, Video, VideoEventStream, IngestResult, UploadUrlResult, ListVideosResult, VideoUsageResult, VideoAnalyticsSummary, VideoAnalyticsDetailed, VideoAnalyticsOptions, UpdatePlanInput, SourceBilling, EmbedOptions } from './types.js';
2
+ export declare class XformClient {
3
+ private readonly sourceId;
4
+ private readonly organizationId;
5
+ private readonly apiKey;
6
+ private readonly baseUrl;
7
+ private readonly streamBaseUrl?;
8
+ constructor(options: XformClientOptions);
9
+ private get headers();
10
+ private request;
11
+ /**
12
+ * Ingest a video from your connected S3/R2 bucket.
13
+ * Call this after uploading the file to your bucket.
14
+ */
15
+ ingest(options: {
16
+ key: string;
17
+ callbackUrl?: string;
18
+ captionsVTT?: string;
19
+ }): Promise<IngestResult>;
20
+ /**
21
+ * Create a presigned URL for uploading a file directly to xform's R2 storage.
22
+ * After uploading, call `ingest()` with the returned `key`.
23
+ */
24
+ createUploadUrl(options: {
25
+ filename: string;
26
+ contentType: string;
27
+ }): Promise<UploadUrlResult>;
28
+ /**
29
+ * Get storage and bandwidth usage for this source.
30
+ */
31
+ usage(): Promise<VideoUsageResult>;
32
+ /**
33
+ * Change the source's plan, billing cycle, or add-ons.
34
+ * Omit any field to leave it unchanged.
35
+ *
36
+ * Upgrades are prorated immediately.
37
+ * Downgrades take effect at the next billing cycle (no credit issued).
38
+ * Downgrading from Pro to Starter automatically removes any custom domain.
39
+ */
40
+ updatePlan(input: UpdatePlanInput): Promise<SourceBilling>;
41
+ /**
42
+ * Open an SSE connection to receive real-time video status updates.
43
+ * Events are emitted as videos transition through: pending → processing → ready/failed.
44
+ *
45
+ * @example
46
+ * const stream = client.events()
47
+ * stream.on('video.ready', (video) => console.log(`${video.videoKey} is ready`))
48
+ * stream.on('video.failed', (video) => console.error(video.errorMessage))
49
+ * // Later: stream.close()
50
+ */
51
+ events(): VideoEventStream;
52
+ /** Video operations for this source */
53
+ readonly videos: {
54
+ /**
55
+ * List all videos for this source.
56
+ */
57
+ list: (options?: {
58
+ limit?: number;
59
+ skip?: number;
60
+ }) => Promise<ListVideosResult>;
61
+ /**
62
+ * Get the status and metadata of a specific video.
63
+ */
64
+ get: (videoKey: string) => Promise<Video>;
65
+ /**
66
+ * Delete a video and all its renditions by database ID.
67
+ * Use the `id` returned from `ingest()` or `videos.get()`.
68
+ */
69
+ delete: (videoId: string) => Promise<void>;
70
+ /**
71
+ * Upload or replace captions (WebVTT format) for a video.
72
+ */
73
+ uploadCaptions: (videoKey: string, vtt: string) => Promise<{
74
+ success: boolean;
75
+ }>;
76
+ /**
77
+ * Delete captions for a video.
78
+ */
79
+ deleteCaptions: (videoKey: string) => Promise<{
80
+ success: boolean;
81
+ }>;
82
+ /**
83
+ * Get analytics summary for this source's videos.
84
+ * Optionally filter by videoKey and/or date range.
85
+ */
86
+ analytics: <T extends VideoAnalyticsOptions>(options?: T) => Promise<T extends {
87
+ detailed: true;
88
+ } ? VideoAnalyticsDetailed : VideoAnalyticsSummary>;
89
+ };
90
+ /**
91
+ * Generate an embed iframe HTML string for a video.
92
+ * Requires `streamBaseUrl` to be set in the client options.
93
+ *
94
+ * @example
95
+ * const html = client.embed('my-video', { autoplay: true, muted: true, logo: 'https://example.com/logo.svg' })
96
+ * // Returns: <iframe src="https://acme.xform.media/_embed/my-video?autoplay=1&muted=1&logo=..." ...></iframe>
97
+ */
98
+ embed(videoKey: string, options?: EmbedOptions): string;
99
+ }
100
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,kBAAkB,EAClB,KAAK,EACL,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,YAAY,EACb,MAAM,YAAY,CAAA;AAEnB,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAQ;IACvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAQ;gBAE3B,OAAO,EAAE,kBAAkB;IAQvC,OAAO,KAAK,OAAO,GAMlB;YAEa,OAAO;IAuBrB;;;OAGG;IACG,MAAM,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IASzG;;;OAGG;IACG,eAAe,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC;IASnG;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAOxC;;;;;;;OAOG;IACG,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,CAAC;IAShE;;;;;;;;;OASG;IACH,MAAM,IAAI,gBAAgB;IAS1B,uCAAuC;IACvC,QAAQ,CAAC,MAAM;QACb;;WAEG;yBACa;YAAE,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,KAAQ,OAAO,CAAC,gBAAgB,CAAC;QAQlF;;WAEG;wBACa,MAAM,KAAG,OAAO,CAAC,KAAK,CAAC;QAOvC;;;WAGG;0BACe,MAAM,KAAG,OAAO,CAAC,IAAI,CAAC;QAOxC;;WAEG;mCACwB,MAAM,OAAO,MAAM,KAAG,OAAO,CAAC;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;QAQ9E;;WAEG;mCACwB,MAAM,KAAG,OAAO,CAAC;YAAE,OAAO,EAAE,OAAO,CAAA;SAAE,CAAC;QAOjE;;;WAGG;oBACS,CAAC,SAAS,qBAAqB,YAAW,CAAC,KAAa,OAAO,CAAC,CAAC,SAAS;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,GAAG,sBAAsB,GAAG,qBAAqB,CAAC;MAY3J;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM;CAwB5D"}
package/dist/client.js ADDED
@@ -0,0 +1,194 @@
1
+ import { XformError } from './errors.js';
2
+ import { createEventStream } from './event-stream.js';
3
+ export class XformClient {
4
+ sourceId;
5
+ organizationId;
6
+ apiKey;
7
+ baseUrl;
8
+ streamBaseUrl;
9
+ constructor(options) {
10
+ this.sourceId = options.sourceId;
11
+ this.organizationId = options.organizationId;
12
+ this.apiKey = options.apiKey;
13
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
14
+ this.streamBaseUrl = options.streamBaseUrl?.replace(/\/$/, '');
15
+ }
16
+ get headers() {
17
+ return {
18
+ 'Authorization': `Bearer ${this.apiKey}`,
19
+ 'x-organization-id': this.organizationId,
20
+ 'Content-Type': 'application/json',
21
+ };
22
+ }
23
+ async request(method, path, body) {
24
+ const url = `${this.baseUrl}${path}`;
25
+ const res = await fetch(url, {
26
+ method,
27
+ headers: this.headers,
28
+ body: body !== undefined ? JSON.stringify(body) : undefined,
29
+ });
30
+ if (!res.ok) {
31
+ let message = `Request failed with status ${res.status}`;
32
+ let responseBody;
33
+ try {
34
+ responseBody = await res.json();
35
+ if (typeof responseBody === 'object' && responseBody !== null && 'message' in responseBody) {
36
+ message = responseBody.message;
37
+ }
38
+ }
39
+ catch { }
40
+ throw new XformError(res.status, message, responseBody);
41
+ }
42
+ return res.json();
43
+ }
44
+ /**
45
+ * Ingest a video from your connected S3/R2 bucket.
46
+ * Call this after uploading the file to your bucket.
47
+ */
48
+ async ingest(options) {
49
+ const result = await this.request('POST', `/api/sources/${this.sourceId}/videos/ingest`, options);
50
+ return result.data;
51
+ }
52
+ /**
53
+ * Create a presigned URL for uploading a file directly to xform's R2 storage.
54
+ * After uploading, call `ingest()` with the returned `key`.
55
+ */
56
+ async createUploadUrl(options) {
57
+ const result = await this.request('POST', `/api/sources/${this.sourceId}/upload-url`, options);
58
+ return result.data;
59
+ }
60
+ /**
61
+ * Get storage and bandwidth usage for this source.
62
+ */
63
+ async usage() {
64
+ return this.request('GET', `/api/sources/${this.sourceId}/videos/usage`);
65
+ }
66
+ /**
67
+ * Change the source's plan, billing cycle, or add-ons.
68
+ * Omit any field to leave it unchanged.
69
+ *
70
+ * Upgrades are prorated immediately.
71
+ * Downgrades take effect at the next billing cycle (no credit issued).
72
+ * Downgrading from Pro to Starter automatically removes any custom domain.
73
+ */
74
+ async updatePlan(input) {
75
+ const result = await this.request('PATCH', `/api/sources/${this.sourceId}/plan`, input);
76
+ return result.data;
77
+ }
78
+ /**
79
+ * Open an SSE connection to receive real-time video status updates.
80
+ * Events are emitted as videos transition through: pending → processing → ready/failed.
81
+ *
82
+ * @example
83
+ * const stream = client.events()
84
+ * stream.on('video.ready', (video) => console.log(`${video.videoKey} is ready`))
85
+ * stream.on('video.failed', (video) => console.error(video.errorMessage))
86
+ * // Later: stream.close()
87
+ */
88
+ events() {
89
+ const params = new URLSearchParams({
90
+ api_key: this.apiKey,
91
+ organization_id: this.organizationId,
92
+ });
93
+ const url = `${this.baseUrl}/api/sources/${this.sourceId}/videos/stream?${params}`;
94
+ return createEventStream(url, {});
95
+ }
96
+ /** Video operations for this source */
97
+ videos = {
98
+ /**
99
+ * List all videos for this source.
100
+ */
101
+ list: (options = {}) => {
102
+ const params = new URLSearchParams();
103
+ if (options.limit !== undefined)
104
+ params.set('limit', String(options.limit));
105
+ if (options.skip !== undefined)
106
+ params.set('skip', String(options.skip));
107
+ const qs = params.toString() ? `?${params}` : '';
108
+ return this.request('GET', `/api/sources/${this.sourceId}/videos${qs}`);
109
+ },
110
+ /**
111
+ * Get the status and metadata of a specific video.
112
+ */
113
+ get: (videoKey) => {
114
+ return this.request('GET', `/api/sources/${this.sourceId}/videos/${videoKey}`).then(r => r.data);
115
+ },
116
+ /**
117
+ * Delete a video and all its renditions by database ID.
118
+ * Use the `id` returned from `ingest()` or `videos.get()`.
119
+ */
120
+ delete: (videoId) => {
121
+ return this.request('DELETE', `/api/videos/${videoId}`);
122
+ },
123
+ /**
124
+ * Upload or replace captions (WebVTT format) for a video.
125
+ */
126
+ uploadCaptions: (videoKey, vtt) => {
127
+ return this.request('PUT', `/api/sources/${this.sourceId}/videos/${videoKey}/captions`, { vtt });
128
+ },
129
+ /**
130
+ * Delete captions for a video.
131
+ */
132
+ deleteCaptions: (videoKey) => {
133
+ return this.request('DELETE', `/api/sources/${this.sourceId}/videos/${videoKey}/captions`);
134
+ },
135
+ /**
136
+ * Get analytics summary for this source's videos.
137
+ * Optionally filter by videoKey and/or date range.
138
+ */
139
+ analytics: (options = {}) => {
140
+ const params = new URLSearchParams();
141
+ if (options.videoKey)
142
+ params.set('videoKey', options.videoKey);
143
+ if (options.since)
144
+ params.set('since', options.since);
145
+ if (options.until)
146
+ params.set('until', options.until);
147
+ if (options.detailed)
148
+ params.set('detailed', '1');
149
+ const qs = params.toString() ? `?${params}` : '';
150
+ return this.request('GET', `/api/sources/${this.sourceId}/videos/analytics${qs}`).then(r => r.data);
151
+ },
152
+ };
153
+ /**
154
+ * Generate an embed iframe HTML string for a video.
155
+ * Requires `streamBaseUrl` to be set in the client options.
156
+ *
157
+ * @example
158
+ * const html = client.embed('my-video', { autoplay: true, muted: true, logo: 'https://example.com/logo.svg' })
159
+ * // Returns: <iframe src="https://acme.xform.media/_embed/my-video?autoplay=1&muted=1&logo=..." ...></iframe>
160
+ */
161
+ embed(videoKey, options = {}) {
162
+ if (!this.streamBaseUrl) {
163
+ throw new XformError(0, 'streamBaseUrl is required for embed(). Pass it in the client constructor.');
164
+ }
165
+ const params = new URLSearchParams();
166
+ if (options.autoplay)
167
+ params.set('autoplay', '1');
168
+ if (options.muted)
169
+ params.set('muted', '1');
170
+ if (options.startAt)
171
+ params.set('t', String(options.startAt));
172
+ if (options.loop)
173
+ params.set('loop', '1');
174
+ if (options.controls === false)
175
+ params.set('controls', '0');
176
+ if (options.quality && options.quality !== 'auto')
177
+ params.set('quality', options.quality);
178
+ if (options.logo)
179
+ params.set('logo', options.logo);
180
+ if (options.color)
181
+ params.set('color', options.color);
182
+ if (options.poster === false)
183
+ params.set('poster', '0');
184
+ if (options.title)
185
+ params.set('title', options.title);
186
+ if (options.captions)
187
+ params.set('captions', '1');
188
+ if (options.search === false)
189
+ params.set('search', '0');
190
+ const qs = params.toString() ? `?${params}` : '';
191
+ const src = `${this.streamBaseUrl}/_embed/${videoKey}${qs}`;
192
+ return `<iframe src="${src}" style="width:100%;aspect-ratio:16/9;border:none" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>`;
193
+ }
194
+ }
@@ -0,0 +1,6 @@
1
+ export declare class XformError extends Error {
2
+ readonly statusCode: number;
3
+ readonly body: unknown;
4
+ constructor(statusCode: number, message: string, body?: unknown);
5
+ }
6
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO;CAMhE"}
package/dist/errors.js ADDED
@@ -0,0 +1,10 @@
1
+ export class XformError extends Error {
2
+ statusCode;
3
+ body;
4
+ constructor(statusCode, message, body) {
5
+ super(message);
6
+ this.name = 'XformError';
7
+ this.statusCode = statusCode;
8
+ this.body = body;
9
+ }
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { VideoEventStream } from './types.js';
2
+ /**
3
+ * Creates a VideoEventStream by connecting to an SSE endpoint via fetch.
4
+ * Zero-dependency SSE parser that works in both Node.js and browsers.
5
+ */
6
+ export declare function createEventStream(url: string, headers: Record<string, string>): VideoEventStream;
7
+ //# sourceMappingURL=event-stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-stream.d.ts","sourceRoot":"","sources":["../src/event-stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAI9E;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,gBAAgB,CAkFhG"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Creates a VideoEventStream by connecting to an SSE endpoint via fetch.
3
+ * Zero-dependency SSE parser that works in both Node.js and browsers.
4
+ */
5
+ export function createEventStream(url, headers) {
6
+ const listeners = new Map();
7
+ let abortController = new AbortController();
8
+ const connect = async () => {
9
+ if (!abortController)
10
+ return;
11
+ try {
12
+ const res = await fetch(url, {
13
+ headers: { ...headers, 'Accept': 'text/event-stream' },
14
+ signal: abortController.signal,
15
+ });
16
+ if (!res.ok || !res.body)
17
+ return;
18
+ const reader = res.body.getReader();
19
+ const decoder = new TextDecoder();
20
+ let buffer = '';
21
+ let currentEvent = '';
22
+ let currentData = '';
23
+ while (true) {
24
+ const { done, value } = await reader.read();
25
+ if (done)
26
+ break;
27
+ buffer += decoder.decode(value, { stream: true });
28
+ const lines = buffer.split('\n');
29
+ buffer = lines.pop() || '';
30
+ for (const line of lines) {
31
+ if (line.startsWith('event:')) {
32
+ currentEvent = line.slice(6).trim();
33
+ }
34
+ else if (line.startsWith('data:')) {
35
+ currentData = line.slice(5).trim();
36
+ }
37
+ else if (line === '') {
38
+ // Empty line = end of message
39
+ if (currentData) {
40
+ try {
41
+ const parsed = JSON.parse(currentData);
42
+ const eventType = currentEvent || `video.${parsed.transcodeStatus}`;
43
+ // Notify specific listeners
44
+ const specific = listeners.get(eventType);
45
+ if (specific)
46
+ specific.forEach(cb => cb(parsed));
47
+ // Notify wildcard listeners
48
+ const wildcard = listeners.get('*');
49
+ if (wildcard)
50
+ wildcard.forEach(cb => cb(parsed));
51
+ }
52
+ catch {
53
+ // Skip malformed messages
54
+ }
55
+ }
56
+ currentEvent = '';
57
+ currentData = '';
58
+ }
59
+ }
60
+ }
61
+ }
62
+ catch (err) {
63
+ if (err instanceof Error && err.name === 'AbortError')
64
+ return;
65
+ // Connection lost — could add reconnect logic here
66
+ }
67
+ };
68
+ // Start connecting immediately
69
+ connect();
70
+ return {
71
+ on(event, callback) {
72
+ let set = listeners.get(event);
73
+ if (!set) {
74
+ set = new Set();
75
+ listeners.set(event, set);
76
+ }
77
+ set.add(callback);
78
+ return this;
79
+ },
80
+ close() {
81
+ abortController?.abort();
82
+ abortController = null;
83
+ listeners.clear();
84
+ },
85
+ };
86
+ }
@@ -0,0 +1,5 @@
1
+ export { XformClient } from './client.js';
2
+ export { XformAdminClient, createXformAdminClient } from './admin-client.js';
3
+ export { XformError } from './errors.js';
4
+ export type { XformClientOptions, XformAdminClientOptions, Video, VideoEvent, VideoEventType, VideoEventStream, VideoRendition, VideoTranscodeStatus, VideoRenditionQuality, IngestResult, UploadUrlResult, ListVideosResult, VideoUsageResult, Source, SourceCredentials, SourceBilling, SourcePlan, SourceProvider, SourceStatus, BillingCycle, BillingStatus, PlanName, CreateSourceInput, UpdateSourceInput, UpdatePlanInput, CheckSubdomainResult, WebhookEventType, WebhookConfig, SetWebhookInput, EmbedOptions, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,YAAY,EAEV,kBAAkB,EAClB,uBAAuB,EAEvB,KAAK,EACL,UAAU,EACV,cAAc,EACd,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,qBAAqB,EACrB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EACN,iBAAiB,EACjB,aAAa,EACb,UAAU,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,QAAQ,EACR,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,oBAAoB,EAEpB,gBAAgB,EAChB,aAAa,EACb,eAAe,EAEf,YAAY,GACb,MAAM,YAAY,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { XformClient } from './client.js';
2
+ export { XformAdminClient, createXformAdminClient } from './admin-client.js';
3
+ export { XformError } from './errors.js';