@yetter/client 0.0.11 → 0.0.13

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/dist/client.js CHANGED
@@ -1,38 +1,127 @@
1
1
  var _a;
2
2
  import { YetterImageClient } from "./api.js";
3
- import { EventSourcePolyfill } from 'event-source-polyfill';
4
- export class yetter {
5
- static configure(options) {
6
- if (options.apiKey) {
7
- if (options.is_bearer) {
8
- _a.apiKey = 'Bearer ' + options.apiKey;
9
- }
10
- else {
11
- _a.apiKey = 'Key ' + options.apiKey;
3
+ import { EventSourcePolyfill } from "event-source-polyfill";
4
+ const DEFAULT_POLL_INTERVAL_MS = 2000;
5
+ const DEFAULT_UPLOAD_TIMEOUT_MS = 5 * 60 * 1000;
6
+ const STREAM_HEARTBEAT_TIMEOUT_MS = 120000;
7
+ const DEFAULT_ENDPOINT = "https://api.yetter.ai";
8
+ function sleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ function getEnvApiKey() {
12
+ return process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "";
13
+ }
14
+ export class YetterClient {
15
+ constructor(options = {}) {
16
+ this.queue = {
17
+ submit: async (model, options) => {
18
+ const client = this.createApiClient();
19
+ return client.generate({
20
+ model,
21
+ ...options.input,
22
+ });
23
+ },
24
+ status: async (model, options) => {
25
+ const client = this.createApiClient();
26
+ const endpoint = client.getApiEndpoint();
27
+ const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
28
+ const statusData = await client.getStatus({ url: statusUrl });
29
+ return {
30
+ data: statusData,
31
+ requestId: options.requestId,
32
+ };
33
+ },
34
+ result: async (model, options) => {
35
+ const client = this.createApiClient();
36
+ const endpoint = client.getApiEndpoint();
37
+ const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
38
+ const responseData = await client.getResponse({ url: responseUrl });
39
+ return {
40
+ data: responseData,
41
+ requestId: options.requestId,
42
+ };
43
+ },
44
+ };
45
+ this.apiKey = "";
46
+ this.endpoint = options.endpoint || DEFAULT_ENDPOINT;
47
+ const envKey = getEnvApiKey();
48
+ if (envKey) {
49
+ this.apiKey = `Key ${envKey}`;
50
+ }
51
+ if (typeof options.apiKey === "string") {
52
+ this.setApiKey(options.apiKey, options.is_bearer);
53
+ }
54
+ }
55
+ setApiKey(rawApiKey, isBearer) {
56
+ const normalizedApiKey = rawApiKey.trim();
57
+ if (!normalizedApiKey) {
58
+ this.apiKey = "";
59
+ return;
60
+ }
61
+ this.apiKey = isBearer ? `Bearer ${normalizedApiKey}` : `Key ${normalizedApiKey}`;
62
+ }
63
+ assertApiKeyConfigured() {
64
+ if (!this.apiKey) {
65
+ throw new Error("API key is not configured. Call configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
66
+ }
67
+ }
68
+ createApiClient() {
69
+ this.assertApiKeyConfigured();
70
+ return new YetterImageClient({
71
+ apiKey: this.apiKey,
72
+ endpoint: this.endpoint,
73
+ });
74
+ }
75
+ getUploadTimeoutMs(options) {
76
+ if (typeof options.timeout === "number" && Number.isFinite(options.timeout) && options.timeout > 0) {
77
+ return options.timeout;
78
+ }
79
+ return DEFAULT_UPLOAD_TIMEOUT_MS;
80
+ }
81
+ async putWithTimeout(url, body, headers, timeoutMs) {
82
+ const controller = new AbortController();
83
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
84
+ try {
85
+ return await fetch(url, {
86
+ method: "PUT",
87
+ headers,
88
+ body,
89
+ signal: controller.signal,
90
+ });
91
+ }
92
+ catch (error) {
93
+ if ((error === null || error === void 0 ? void 0 : error.name) === "AbortError") {
94
+ throw new Error(`Upload request timed out after ${timeoutMs}ms`);
12
95
  }
96
+ throw error;
97
+ }
98
+ finally {
99
+ clearTimeout(timeout);
100
+ }
101
+ }
102
+ configure(options) {
103
+ if (typeof options.apiKey === "string") {
104
+ this.setApiKey(options.apiKey, options.is_bearer);
13
105
  }
14
106
  if (options.endpoint) {
15
- _a.endpoint = options.endpoint;
107
+ this.endpoint = options.endpoint;
16
108
  }
17
109
  }
18
- static async subscribe(model, options) {
110
+ async subscribe(model, options) {
19
111
  var _b;
20
- if (!_a.apiKey) {
21
- throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
22
- }
23
- const client = new YetterImageClient({
24
- apiKey: _a.apiKey,
25
- endpoint: _a.endpoint
26
- });
112
+ const client = this.createApiClient();
27
113
  const generateResponse = await client.generate({
28
- model: model,
114
+ model,
29
115
  ...options.input,
30
116
  });
31
117
  let status = generateResponse.status;
32
118
  let lastStatusResponse;
33
119
  const startTime = Date.now();
34
- const timeoutMilliseconds = 30 * 60 * 1000; // 30 minutes
35
- while (status !== "COMPLETED" && status !== "FAILED") {
120
+ const timeoutMilliseconds = 30 * 60 * 1000;
121
+ const pollIntervalMs = typeof options.pollIntervalMs === "number" && Number.isFinite(options.pollIntervalMs) && options.pollIntervalMs > 0
122
+ ? options.pollIntervalMs
123
+ : DEFAULT_POLL_INTERVAL_MS;
124
+ while (status !== "COMPLETED" && status !== "ERROR" && status !== "CANCELLED") {
36
125
  if (Date.now() - startTime > timeoutMilliseconds) {
37
126
  console.warn(`Subscription timed out after 30 minutes for request ID: ${generateResponse.request_id}. Attempting to cancel.`);
38
127
  try {
@@ -53,40 +142,38 @@ export class yetter {
53
142
  if (options.onQueueUpdate) {
54
143
  options.onQueueUpdate(lastStatusResponse);
55
144
  }
145
+ if (status !== "COMPLETED" && status !== "ERROR" && status !== "CANCELLED") {
146
+ await sleep(pollIntervalMs);
147
+ }
56
148
  }
57
149
  catch (error) {
58
150
  console.error("Error during status polling:", error);
59
151
  throw error;
60
152
  }
61
153
  }
62
- if (status === "FAILED") {
63
- const errorMessage = ((_b = lastStatusResponse === null || lastStatusResponse === void 0 ? void 0 : lastStatusResponse.logs) === null || _b === void 0 ? void 0 : _b.map(log => log.message).join("\n")) || "Image generation failed.";
154
+ if (status === "ERROR") {
155
+ const errorMessage = ((_b = lastStatusResponse === null || lastStatusResponse === void 0 ? void 0 : lastStatusResponse.logs) === null || _b === void 0 ? void 0 : _b.map((log) => log.message).join("\n")) || "Image generation failed.";
64
156
  throw new Error(errorMessage);
65
157
  }
66
- const finalResponse = await client.getResponse({
158
+ if (status === "CANCELLED") {
159
+ throw new Error("Image generation was cancelled by user.");
160
+ }
161
+ return client.getResponse({
67
162
  url: generateResponse.response_url,
68
163
  });
69
- return finalResponse;
70
164
  }
71
- static async stream(model, options) {
72
- if (!_a.apiKey) {
73
- throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
74
- }
75
- const client = new YetterImageClient({
76
- apiKey: _a.apiKey,
77
- endpoint: _a.endpoint
78
- });
165
+ async stream(model, options) {
166
+ const client = this.createApiClient();
79
167
  const initialApiResponse = await client.generate({
80
- model: model,
168
+ model,
81
169
  ...options.input,
82
170
  });
83
171
  const requestId = initialApiResponse.request_id;
84
172
  const responseUrl = initialApiResponse.response_url;
173
+ const statusUrl = initialApiResponse.status_url;
85
174
  const cancelUrl = initialApiResponse.cancel_url;
86
175
  const sseStreamUrl = `${client.getApiEndpoint()}/${model}/requests/${requestId}/status/stream`;
87
176
  let eventSource;
88
- let streamEnded = false;
89
- // Setup the promise for the done() method
90
177
  let resolveDonePromise;
91
178
  let rejectDonePromise;
92
179
  const donePromise = new Promise((resolve, reject) => {
@@ -94,13 +181,24 @@ export class yetter {
94
181
  rejectDonePromise = reject;
95
182
  });
96
183
  const controller = {
97
- // This will be used by the async iterator to pull events
98
- // It needs a way to buffer events or signal availability
99
184
  events: [],
100
185
  resolvers: [],
101
186
  isClosed: false,
187
+ isSettled: false,
188
+ currentStatus: "",
189
+ callResolver(value) {
190
+ if (this.isSettled)
191
+ return;
192
+ this.isSettled = true;
193
+ resolveDonePromise(value);
194
+ },
195
+ callRejecter(reason) {
196
+ if (this.isSettled)
197
+ return;
198
+ this.isSettled = true;
199
+ rejectDonePromise(reason);
200
+ },
102
201
  push(event) {
103
- var _b;
104
202
  if (this.isClosed)
105
203
  return;
106
204
  if (this.resolvers.length > 0) {
@@ -109,54 +207,43 @@ export class yetter {
109
207
  else {
110
208
  this.events.push(event);
111
209
  }
112
- // Check for terminal events to resolve/reject the donePromise
113
- if (event.status === "COMPLETED") {
114
- streamEnded = true;
115
- client.getResponse({ url: responseUrl })
116
- .then(resolveDonePromise)
117
- .catch(rejectDonePromise)
118
- .finally(() => this.close());
119
- }
120
- else if (event.status === "FAILED") {
121
- streamEnded = true;
122
- rejectDonePromise(new Error(((_b = event.logs) === null || _b === void 0 ? void 0 : _b.map(l => l.message).join('\n')) || `Stream reported FAILED for ${requestId}`));
123
- this.close();
124
- }
210
+ this.currentStatus = event.status;
125
211
  },
126
212
  error(err) {
127
213
  if (this.isClosed)
128
214
  return;
129
- // If streamEnded is true, it means a terminal event (COMPLETED/FAILED)
130
- // has already been processed and is handling the donePromise.
131
- // This error is likely a secondary effect of the connection closing.
132
- if (!streamEnded) {
133
- rejectDonePromise(err); // Only reject if no terminal event was processed
215
+ if (this.resolvers.length > 0) {
216
+ this.resolvers.shift()({ value: undefined, done: true });
134
217
  }
135
- else {
136
- console.warn("SSE 'onerror' event after stream was considered ended (COMPLETED/FAILED). This error will not alter the done() promise.", err);
218
+ this._close();
219
+ const errorToReport = err instanceof Error ? err : new Error("Stream closed prematurely or unexpectedly.");
220
+ this.callRejecter(errorToReport);
221
+ },
222
+ done(value) {
223
+ if (this.isClosed)
224
+ return;
225
+ if (this.resolvers.length > 0) {
226
+ this.resolvers.shift()({ value: undefined, done: true });
137
227
  }
138
- streamEnded = true; // Ensure it's marked as ended
228
+ this._close();
229
+ this.callResolver(value);
230
+ },
231
+ cancel() {
232
+ if (this.isClosed)
233
+ return;
139
234
  if (this.resolvers.length > 0) {
140
- this.resolvers.shift()({ value: undefined, done: true }); // Signal error to iterator consumer
235
+ this.resolvers.shift()({ value: undefined, done: true });
141
236
  }
142
- this.close(); // Proceed to close EventSource etc.
237
+ this._close();
238
+ this.callRejecter(new Error("Stream was cancelled by user."));
143
239
  },
144
- close() {
240
+ _close() {
145
241
  if (this.isClosed)
146
242
  return;
147
243
  this.isClosed = true;
148
- this.resolvers.forEach(resolve => resolve({ value: undefined, done: true }));
244
+ this.resolvers.forEach((resolve) => resolve({ value: undefined, done: true }));
149
245
  this.resolvers = [];
150
246
  eventSource === null || eventSource === void 0 ? void 0 : eventSource.close();
151
- // If donePromise is still pending, reject it as stream closed prematurely
152
- // Use a timeout to allow any final event processing for COMPLETED/FAILED to settle it first
153
- setTimeout(() => {
154
- donePromise.catch(() => { }).finally(() => {
155
- if (!streamEnded) { // If not explicitly completed or failed
156
- rejectDonePromise(new Error("Stream closed prematurely or unexpectedly."));
157
- }
158
- });
159
- }, 100);
160
247
  },
161
248
  next() {
162
249
  if (this.events.length > 0) {
@@ -165,46 +252,98 @@ export class yetter {
165
252
  if (this.isClosed) {
166
253
  return Promise.resolve({ value: undefined, done: true });
167
254
  }
168
- return new Promise(resolve => this.resolvers.push(resolve));
169
- }
170
- };
171
- eventSource = new EventSourcePolyfill(sseStreamUrl, {
172
- headers: { 'Authorization': `${_a.apiKey}` },
173
- });
174
- eventSource.onopen = (event) => {
175
- console.log("SSE Connection Opened:", event);
176
- };
177
- eventSource.addEventListener('data', (event) => {
178
- // console.log("SSE 'data' event received, raw data:", event.data);
179
- try {
180
- const statusData = JSON.parse(event.data);
181
- controller.push(statusData);
182
- }
183
- catch (e) {
184
- console.error("Error parsing SSE 'data' event:", e, "Raw data:", event.data);
185
- controller.error(new Error(`Error parsing SSE 'data' event: ${e.message}`));
186
- }
187
- });
188
- eventSource.addEventListener('done', (event) => {
189
- // console.log("SSE 'done' event received, raw data:", event.data);
190
- controller.close();
191
- });
192
- eventSource.onerror = (err) => {
193
- var _b;
194
- const message = ((_b = err === null || err === void 0 ? void 0 : err.error) === null || _b === void 0 ? void 0 : _b.message) || (err === null || err === void 0 ? void 0 : err.message) || '';
195
- const isIdleTimeout = typeof message === 'string' && message.includes('No activity within') && message.includes('Reconnecting');
196
- if (isIdleTimeout) {
197
- console.warn("SSE idle timeout; letting EventSource auto-reconnect.", err);
198
- return;
199
- }
200
- console.warn("SSE Connection Error (onerror) - will allow auto-reconnect:", err);
255
+ return new Promise((resolve) => this.resolvers.push(resolve));
256
+ },
201
257
  };
202
- // Handle if API immediately returns a terminal status in initialApiResponse (e.g. already completed/failed)
203
258
  if (initialApiResponse.status === "COMPLETED") {
204
259
  controller.push(initialApiResponse);
260
+ const finalResponse = await client.getResponse({ url: responseUrl });
261
+ controller.done(finalResponse);
205
262
  }
206
- else if (initialApiResponse.status === "FAILED") {
263
+ else if (initialApiResponse.status === "ERROR") {
207
264
  controller.push(initialApiResponse);
265
+ controller.error(new Error(`Stream reported ERROR for ${requestId}`));
266
+ }
267
+ else if (initialApiResponse.status === "CANCELLED") {
268
+ controller.push(initialApiResponse);
269
+ controller.cancel();
270
+ }
271
+ else {
272
+ eventSource = new EventSourcePolyfill(sseStreamUrl, {
273
+ headers: { Authorization: this.apiKey },
274
+ heartbeatTimeout: STREAM_HEARTBEAT_TIMEOUT_MS,
275
+ });
276
+ eventSource.onopen = (event) => {
277
+ console.log("SSE Connection Opened:", event);
278
+ };
279
+ eventSource.addEventListener("data", (event) => {
280
+ try {
281
+ const statusData = JSON.parse(event.data);
282
+ controller.push(statusData);
283
+ }
284
+ catch (e) {
285
+ console.error("Error parsing SSE 'data' event:", e, "Raw data:", event.data);
286
+ controller.error(new Error(`Error parsing SSE 'data' event: ${e.message}`));
287
+ }
288
+ });
289
+ eventSource.addEventListener("done", async (event) => {
290
+ try {
291
+ try {
292
+ eventSource === null || eventSource === void 0 ? void 0 : eventSource.close();
293
+ }
294
+ catch { }
295
+ if (controller.currentStatus === "COMPLETED") {
296
+ const response = await client.getResponse({ url: responseUrl });
297
+ controller.done(response);
298
+ return;
299
+ }
300
+ if (controller.currentStatus === "CANCELLED") {
301
+ controller.cancel();
302
+ return;
303
+ }
304
+ if (controller.currentStatus === "ERROR") {
305
+ controller.error(new Error(`Stream reported ERROR for ${requestId}`));
306
+ return;
307
+ }
308
+ const latestStatus = await client.getStatus({ url: statusUrl });
309
+ controller.push(latestStatus);
310
+ if (latestStatus.status === "COMPLETED") {
311
+ const response = await client.getResponse({ url: responseUrl });
312
+ controller.done(response);
313
+ }
314
+ else if (latestStatus.status === "CANCELLED") {
315
+ controller.cancel();
316
+ }
317
+ else {
318
+ controller.error(new Error(`Stream ended without terminal status for ${requestId}`));
319
+ }
320
+ }
321
+ catch (e) {
322
+ console.error("Error parsing SSE 'done' event:", e, "Raw data:", event.data);
323
+ controller.error(new Error(`Error parsing SSE 'done' event: ${e.message}`));
324
+ }
325
+ });
326
+ eventSource.addEventListener("error", (event) => {
327
+ const rawData = event === null || event === void 0 ? void 0 : event.data;
328
+ if (!rawData) {
329
+ console.warn("SSE transport error event; waiting for reconnect.");
330
+ return;
331
+ }
332
+ console.log("SSE application 'error' event received, raw data:", rawData);
333
+ controller.error(new Error(`Stream reported ERROR for ${requestId}: ${rawData}`));
334
+ });
335
+ eventSource.onerror = (err) => {
336
+ var _b;
337
+ const message = ((_b = err === null || err === void 0 ? void 0 : err.error) === null || _b === void 0 ? void 0 : _b.message) || (err === null || err === void 0 ? void 0 : err.message) || "";
338
+ const isIdleTimeout = typeof message === "string" &&
339
+ message.includes("No activity within") &&
340
+ message.includes("Reconnecting");
341
+ if (isIdleTimeout) {
342
+ console.warn("SSE idle timeout; letting EventSource auto-reconnect.", err);
343
+ return;
344
+ }
345
+ console.warn("SSE connection issue (onerror); waiting for auto-reconnect.", err);
346
+ };
208
347
  }
209
348
  return {
210
349
  async *[Symbol.asyncIterator]() {
@@ -217,7 +356,6 @@ export class yetter {
217
356
  },
218
357
  done: () => donePromise,
219
358
  cancel: async () => {
220
- controller.close();
221
359
  try {
222
360
  await client.cancel({ url: cancelUrl });
223
361
  console.log(`Stream for ${requestId} - underlying request cancelled.`);
@@ -225,63 +363,181 @@ export class yetter {
225
363
  catch (e) {
226
364
  console.error(`Error cancelling underlying request for stream ${requestId}:`, e.message);
227
365
  }
228
- // Ensure donePromise is settled if not already
229
- if (!streamEnded) {
230
- rejectDonePromise(new Error("Stream was cancelled by user."));
231
- }
232
366
  },
233
367
  getRequestId: () => requestId,
234
368
  };
235
369
  }
236
- }
237
- _a = yetter;
238
- yetter.apiKey = 'Key ' + (process.env.YTR_API_KEY || process.env.REACT_APP_YTR_API_KEY || "");
239
- yetter.endpoint = "https://api.yetter.ai";
240
- yetter.queue = {
241
- submit: async (model, options) => {
242
- if (!_a.apiKey) {
243
- throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
370
+ async uploadFile(fileOrPath, options = {}) {
371
+ this.assertApiKeyConfigured();
372
+ if (typeof fileOrPath === "string") {
373
+ return this._uploadFromPath(fileOrPath, options);
244
374
  }
245
- const client = new YetterImageClient({
246
- apiKey: _a.apiKey,
247
- endpoint: _a.endpoint
375
+ return this._uploadFromBlob(fileOrPath, options);
376
+ }
377
+ async uploadBlob(file, options = {}) {
378
+ return this.uploadFile(file, options);
379
+ }
380
+ async _uploadFromPath(filePath, options) {
381
+ const fs = await import("fs");
382
+ const mime = await import("mime-types");
383
+ if (!fs.existsSync(filePath)) {
384
+ throw new Error(`File not found: ${filePath}`);
385
+ }
386
+ const stats = fs.statSync(filePath);
387
+ const fileSize = stats.size;
388
+ const fileName = filePath.split(/[\\/]/).pop() || "upload";
389
+ const mimeType = mime.default.lookup(filePath) || "application/octet-stream";
390
+ const uploadTimeoutMs = this.getUploadTimeoutMs(options);
391
+ const client = this.createApiClient();
392
+ const uploadUrlResponse = await client.getUploadUrl({
393
+ file_name: fileName,
394
+ content_type: mimeType,
395
+ size: fileSize,
248
396
  });
249
- const generateResponse = await client.generate({
250
- model: model,
251
- ...options.input,
397
+ if (uploadUrlResponse.mode === "single") {
398
+ await this._uploadFileSingle(filePath, uploadUrlResponse.put_url, mimeType, fileSize, options, fs, uploadTimeoutMs);
399
+ }
400
+ else {
401
+ await this._uploadFileMultipart(filePath, uploadUrlResponse.part_urls, uploadUrlResponse.part_size, fileSize, options, fs, uploadTimeoutMs);
402
+ }
403
+ const result = await client.uploadComplete({
404
+ key: uploadUrlResponse.key,
252
405
  });
253
- return generateResponse;
254
- },
255
- status: async (model, options) => {
256
- if (!_a.apiKey) {
257
- throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
406
+ if (options.onProgress) {
407
+ options.onProgress(100);
258
408
  }
259
- const client = new YetterImageClient({
260
- apiKey: _a.apiKey,
261
- endpoint: _a.endpoint
409
+ return result;
410
+ }
411
+ async _uploadFromBlob(file, options) {
412
+ const fileSize = file.size;
413
+ const fileName = options.filename || (file instanceof File ? file.name : "blob-upload");
414
+ const mimeType = file.type || "application/octet-stream";
415
+ const uploadTimeoutMs = this.getUploadTimeoutMs(options);
416
+ const client = this.createApiClient();
417
+ const uploadUrlResponse = await client.getUploadUrl({
418
+ file_name: fileName,
419
+ content_type: mimeType,
420
+ size: fileSize,
262
421
  });
263
- const endpoint = client.getApiEndpoint();
264
- const statusUrl = `${endpoint}/${model}/requests/${options.requestId}/status`;
265
- const statusData = await client.getStatus({ url: statusUrl });
266
- return {
267
- data: statusData,
268
- requestId: options.requestId,
269
- };
270
- },
271
- result: async (model, options) => {
272
- if (!_a.apiKey) {
273
- throw new Error("API key is not configured. Call yetter.configure({ apiKey: 'your_key' }) or set YTR_API_KEY.");
422
+ if (uploadUrlResponse.mode === "single") {
423
+ await this._uploadBlobSingle(file, uploadUrlResponse.put_url, mimeType, fileSize, options, uploadTimeoutMs);
274
424
  }
275
- const client = new YetterImageClient({
276
- apiKey: _a.apiKey,
277
- endpoint: _a.endpoint
425
+ else {
426
+ await this._uploadBlobMultipart(file, uploadUrlResponse.part_urls, uploadUrlResponse.part_size, fileSize, options, uploadTimeoutMs);
427
+ }
428
+ const result = await client.uploadComplete({
429
+ key: uploadUrlResponse.key,
278
430
  });
279
- const endpoint = client.getApiEndpoint();
280
- const responseUrl = `${endpoint}/${model}/requests/${options.requestId}`;
281
- const responseData = await client.getResponse({ url: responseUrl });
282
- return {
283
- data: responseData,
284
- requestId: options.requestId,
285
- };
431
+ if (options.onProgress) {
432
+ options.onProgress(100);
433
+ }
434
+ return result;
435
+ }
436
+ async _uploadFileSingle(filePath, presignedUrl, contentType, totalSize, options, fs, timeoutMs) {
437
+ const fileBuffer = fs.readFileSync(filePath);
438
+ const response = await this.putWithTimeout(presignedUrl, fileBuffer, {
439
+ "Content-Type": contentType,
440
+ "Content-Length": String(totalSize),
441
+ }, timeoutMs);
442
+ if (!response.ok) {
443
+ const errorText = await response.text();
444
+ throw new Error(`Single-part upload failed (${response.status}): ${errorText}`);
445
+ }
446
+ if (options.onProgress) {
447
+ options.onProgress(90);
448
+ }
286
449
  }
450
+ async _uploadFileMultipart(filePath, partUrls, partSize, totalSize, options, fs, timeoutMs) {
451
+ const sortedParts = [...partUrls].sort((a, b) => a.part_number - b.part_number);
452
+ const fileHandle = fs.openSync(filePath, "r");
453
+ try {
454
+ for (let i = 0; i < sortedParts.length; i++) {
455
+ const part = sortedParts[i];
456
+ const buffer = Buffer.alloc(partSize);
457
+ const offset = (part.part_number - 1) * partSize;
458
+ const bytesRead = fs.readSync(fileHandle, buffer, 0, partSize, offset);
459
+ const chunk = buffer.slice(0, bytesRead);
460
+ if (chunk.length === 0) {
461
+ break;
462
+ }
463
+ const response = await this.putWithTimeout(part.url, chunk, {
464
+ "Content-Length": String(chunk.length),
465
+ }, timeoutMs);
466
+ if (!response.ok) {
467
+ const errorText = await response.text();
468
+ throw new Error(`Multipart upload failed at part ${part.part_number} (${response.status}): ${errorText}`);
469
+ }
470
+ if (options.onProgress) {
471
+ const progress = Math.min(90, Math.floor(((i + 1) / sortedParts.length) * 90));
472
+ options.onProgress(progress);
473
+ }
474
+ }
475
+ }
476
+ finally {
477
+ fs.closeSync(fileHandle);
478
+ }
479
+ }
480
+ async _uploadBlobSingle(blob, presignedUrl, contentType, _totalSize, options, timeoutMs) {
481
+ const response = await this.putWithTimeout(presignedUrl, blob, {
482
+ "Content-Type": contentType,
483
+ }, timeoutMs);
484
+ if (!response.ok) {
485
+ const errorText = await response.text();
486
+ throw new Error(`Single-part upload failed (${response.status}): ${errorText}`);
487
+ }
488
+ if (options.onProgress) {
489
+ options.onProgress(90);
490
+ }
491
+ }
492
+ async _uploadBlobMultipart(blob, partUrls, partSize, totalSize, options, timeoutMs) {
493
+ const sortedParts = [...partUrls].sort((a, b) => a.part_number - b.part_number);
494
+ for (let i = 0; i < sortedParts.length; i++) {
495
+ const part = sortedParts[i];
496
+ const start = (part.part_number - 1) * partSize;
497
+ const end = Math.min(start + partSize, totalSize);
498
+ const chunk = blob.slice(start, end);
499
+ if (chunk.size === 0) {
500
+ break;
501
+ }
502
+ const response = await this.putWithTimeout(part.url, chunk, {}, timeoutMs);
503
+ if (!response.ok) {
504
+ const errorText = await response.text();
505
+ throw new Error(`Multipart upload failed at part ${part.part_number} (${response.status}): ${errorText}`);
506
+ }
507
+ if (options.onProgress) {
508
+ const progress = Math.min(90, Math.floor(((i + 1) / sortedParts.length) * 90));
509
+ options.onProgress(progress);
510
+ }
511
+ }
512
+ }
513
+ }
514
+ export class yetter {
515
+ static configure(options) {
516
+ this.client.configure(options);
517
+ }
518
+ static subscribe(model, options) {
519
+ return this.client.subscribe(model, options);
520
+ }
521
+ static stream(model, options) {
522
+ return this.client.stream(model, options);
523
+ }
524
+ static uploadFile(fileOrPath, options = {}) {
525
+ return this.client.uploadFile(fileOrPath, options);
526
+ }
527
+ static uploadBlob(file, options = {}) {
528
+ return this.client.uploadBlob(file, options);
529
+ }
530
+ }
531
+ _a = yetter;
532
+ yetter.client = new YetterClient();
533
+ yetter.queue = {
534
+ submit: async (model, options) => {
535
+ return _a.client.queue.submit(model, options);
536
+ },
537
+ status: async (model, options) => {
538
+ return _a.client.queue.status(model, options);
539
+ },
540
+ result: async (model, options) => {
541
+ return _a.client.queue.result(model, options);
542
+ },
287
543
  };