@edgestore/sdk 1.0.0-next.1 → 1.0.0-next.2

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 (59) hide show
  1. package/dist/errors.d.ts +17 -0
  2. package/dist/errors.d.ts.map +1 -1
  3. package/dist/errors.js +15 -1
  4. package/dist/generated/api-v2.d.ts +71 -26
  5. package/dist/generated/api-v2.d.ts.map +1 -1
  6. package/dist/index.d.ts +2 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/internal/managementUploadOperations.d.ts +64 -0
  10. package/dist/internal/managementUploadOperations.d.ts.map +1 -0
  11. package/dist/internal/managementUploadOperations.js +72 -0
  12. package/dist/internal/multipartUpload.d.ts +6 -6
  13. package/dist/internal/multipartUpload.d.ts.map +1 -1
  14. package/dist/internal/runtimeOperations.d.ts.map +1 -1
  15. package/dist/internal/transport.d.ts.map +1 -1
  16. package/dist/internal/transport.js +20 -7
  17. package/dist/internal/uploadLifecycle.d.ts +57 -0
  18. package/dist/internal/uploadLifecycle.d.ts.map +1 -0
  19. package/dist/internal/uploadLifecycle.js +91 -0
  20. package/dist/internal/uploadProcessing.d.ts +20 -0
  21. package/dist/internal/uploadProcessing.d.ts.map +1 -0
  22. package/dist/internal/uploadProcessing.js +25 -0
  23. package/dist/internal/uploadSource.d.ts +18 -0
  24. package/dist/internal/uploadSource.d.ts.map +1 -0
  25. package/dist/internal/uploadSource.js +75 -0
  26. package/dist/internal/uploadTransfer.js +15 -1
  27. package/dist/managementClient.d.ts +2 -1
  28. package/dist/managementClient.d.ts.map +1 -1
  29. package/dist/managementClient.js +2 -2
  30. package/dist/managementResources.d.ts +5 -1
  31. package/dist/managementResources.d.ts.map +1 -1
  32. package/dist/managementResources.js +14 -49
  33. package/dist/managementUpload.d.ts +36 -0
  34. package/dist/managementUpload.d.ts.map +1 -0
  35. package/dist/managementUpload.js +74 -0
  36. package/dist/sdk.js +1 -1
  37. package/dist/upload.d.ts.map +1 -1
  38. package/dist/upload.js +42 -189
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. package/package.json +2 -2
  42. package/src/errors.ts +28 -0
  43. package/src/generated/api-v2.ts +71 -26
  44. package/src/generated/openapi-v2.json +237 -16
  45. package/src/generated/source.ts +2 -2
  46. package/src/index.ts +6 -0
  47. package/src/internal/managementUploadOperations.ts +99 -0
  48. package/src/internal/multipartUpload.ts +8 -8
  49. package/src/internal/transport.ts +21 -12
  50. package/src/internal/uploadLifecycle.ts +154 -0
  51. package/src/internal/uploadProcessing.ts +48 -0
  52. package/src/internal/uploadSource.ts +104 -0
  53. package/src/internal/uploadTransfer.ts +20 -1
  54. package/src/managementClient.ts +6 -2
  55. package/src/managementResources.ts +21 -51
  56. package/src/managementUpload.ts +139 -0
  57. package/src/sdk.ts +1 -1
  58. package/src/upload.ts +49 -274
  59. package/src/version.ts +1 -1
@@ -0,0 +1,139 @@
1
+ import type { ManagementUploadOperations } from './internal/managementUploadOperations';
2
+ import type { OperationBody, OperationResult } from './internal/operationTypes';
3
+ import type { Transport } from './internal/transport';
4
+ import { executeUploadLifecycle } from './internal/uploadLifecycle';
5
+ import { throwIfAborted } from './internal/uploadRetry';
6
+ import {
7
+ normalizeUploadMetadata,
8
+ prepareUploadSource,
9
+ reportUploadProgress,
10
+ } from './internal/uploadSource';
11
+ import { assertNonNegative } from './internal/uploadValidation';
12
+ import { planMultipartUpload } from './multipartPlan';
13
+ import type {
14
+ UploadDefaults,
15
+ UploadMetadataValue,
16
+ UploadProgress,
17
+ UploadSource,
18
+ } from './uploadTypes';
19
+ import { DEFAULT_PROCESSING_TIMEOUT_MS } from './uploadTypes';
20
+
21
+ type RequestBody = OperationBody<'v2.management.uploads.request'>;
22
+ type RequestResult = OperationResult<'v2.management.uploads.request'>;
23
+ type GetResult = OperationResult<'v2.management.uploads.get'>;
24
+ type CompletedUpload = Extract<GetResult, { upload: { status: 'completed' } }>;
25
+
26
+ /** Input for a complete administrative upload managed by the SDK. */
27
+ export type ManagementUploadInput = Omit<
28
+ RequestBody,
29
+ 'sizeBytes' | 'metadata' | 'multipart'
30
+ > & {
31
+ project: string;
32
+ bucket: string;
33
+ source: UploadSource;
34
+ metadata?: Record<string, UploadMetadataValue>;
35
+ signal?: AbortSignal;
36
+ onProgress?: (progress: UploadProgress) => void;
37
+ multipart?:
38
+ | boolean
39
+ | {
40
+ partSizeBytes?: number;
41
+ concurrency?: number;
42
+ };
43
+ processingTimeoutMs?: number;
44
+ };
45
+
46
+ /** Completed administrative file upload. */
47
+ export type ManagementUploadResult = CompletedUpload & {
48
+ signedReadUrl?: RequestResult['signedReadUrl'];
49
+ };
50
+
51
+ export async function uploadManagementFile(
52
+ context: {
53
+ transport: Transport;
54
+ operations: ManagementUploadOperations;
55
+ },
56
+ input: ManagementUploadInput,
57
+ defaults: UploadDefaults = {},
58
+ ): Promise<ManagementUploadResult> {
59
+ const { transport, operations } = context;
60
+ const {
61
+ project,
62
+ bucket,
63
+ source,
64
+ metadata,
65
+ signal,
66
+ onProgress,
67
+ multipart,
68
+ processingTimeoutMs = defaults.processingTimeoutMs ??
69
+ DEFAULT_PROCESSING_TIMEOUT_MS,
70
+ ...requestInput
71
+ } = input;
72
+ const prepared = prepareUploadSource(source);
73
+ const totalBytes = prepared.sizeBytes;
74
+ assertNonNegative(processingTimeoutMs, 'processingTimeoutMs');
75
+ throwIfAborted(signal);
76
+ reportUploadProgress(onProgress, {
77
+ transferredBytes: 0,
78
+ totalBytes,
79
+ phase: 'preparing',
80
+ });
81
+
82
+ const multipartPlan = planMultipartUpload({
83
+ sizeBytes: totalBytes,
84
+ thresholdBytes: defaults.multipartThresholdBytes,
85
+ preferredPartSizeBytes:
86
+ typeof multipart === 'object'
87
+ ? (multipart.partSizeBytes ?? defaults.multipartPartSizeBytes)
88
+ : defaults.multipartPartSizeBytes,
89
+ forceMultipart:
90
+ prepared.kind === 'stream' ||
91
+ multipart === true ||
92
+ typeof multipart === 'object',
93
+ });
94
+ const requested = await operations.request({
95
+ project,
96
+ bucket,
97
+ signal,
98
+ ...requestInput,
99
+ fileName: requestInput.fileName ?? prepared.fileName,
100
+ mimeType: requestInput.mimeType ?? prepared.mimeType,
101
+ sizeBytes: totalBytes,
102
+ metadata: normalizeUploadMetadata(metadata),
103
+ multipart: multipartPlan
104
+ ? { partNumbers: multipartPlan.partNumbers }
105
+ : undefined,
106
+ });
107
+ const completed = await executeUploadLifecycle<GetResult>({
108
+ transport,
109
+ requested,
110
+ prepared,
111
+ multipartPlan,
112
+ multipartConcurrency:
113
+ typeof multipart === 'object'
114
+ ? multipart.concurrency
115
+ : defaults.multipartConcurrency,
116
+ defaultMultipartConcurrency: defaults.multipartConcurrency,
117
+ processingTimeoutMs,
118
+ signal,
119
+ onProgress,
120
+ operations: {
121
+ completeMultipart: ({ uploadId, parts, signal: requestSignal }) =>
122
+ operations.completeMultipart({
123
+ project,
124
+ uploadId,
125
+ parts,
126
+ signal: requestSignal,
127
+ }),
128
+ get: ({ uploadId, signal: requestSignal }) =>
129
+ operations.getWithResponse({
130
+ project,
131
+ uploadId,
132
+ signal: requestSignal,
133
+ }),
134
+ cancel: ({ uploadId }) => operations.cancel({ project, uploadId }),
135
+ },
136
+ cleanupPolicy: 'report-failure',
137
+ });
138
+ return { ...completed, signedReadUrl: requested.signedReadUrl };
139
+ }
package/src/sdk.ts CHANGED
@@ -80,7 +80,7 @@ export function createEdgeStoreSdk(
80
80
  return credentials.kind === 'bearer'
81
81
  ? {
82
82
  runtime: createExplicitProjectRuntimeClient(transport, options.upload),
83
- management: createManagementClient(transport),
83
+ management: createManagementClient(transport, options.upload),
84
84
  system,
85
85
  }
86
86
  : {
package/src/upload.ts CHANGED
@@ -1,39 +1,25 @@
1
- import {
2
- EdgeStoreAbortError,
3
- EdgeStoreNetworkError,
4
- EdgeStoreUploadCanceledError,
5
- EdgeStoreUploadProcessingTimeoutError,
6
- } from './errors';
7
- import { uploadParts, uploadStreamParts } from './internal/multipartUpload';
1
+ import { EdgeStoreAbortError, EdgeStoreNetworkError } from './errors';
8
2
  import {
9
3
  createGetUploadRequest,
10
4
  type RuntimeOperations,
11
5
  } from './internal/runtimeOperations';
12
6
  import type { Transport } from './internal/transport';
7
+ import { executeUploadLifecycle } from './internal/uploadLifecycle';
8
+ import { isAbortError, throwIfAborted } from './internal/uploadRetry';
13
9
  import {
14
- getRetryAfterMs,
15
- isAbortError,
16
- retryOperation,
17
- sleep,
18
- throwIfAborted,
19
- } from './internal/uploadRetry';
20
- import { putWithRetry } from './internal/uploadTransfer';
21
- import {
22
- assertNonNegative,
23
- getPositiveInteger,
24
- } from './internal/uploadValidation';
10
+ normalizeUploadMetadata,
11
+ prepareUploadSource,
12
+ reportUploadProgress,
13
+ } from './internal/uploadSource';
14
+ import { assertNonNegative } from './internal/uploadValidation';
25
15
  import { planMultipartUpload } from './multipartPlan';
26
16
  import {
27
- DEFAULT_MULTIPART_CONCURRENCY,
28
17
  DEFAULT_PROCESSING_TIMEOUT_MS,
29
- type CompletedUpload,
30
18
  type RuntimeUploadFromUrlInput,
31
19
  type RuntimeUploadInput,
32
20
  type RuntimeUploadResult,
33
21
  type UploadDefaults,
34
- type UploadMetadataValue,
35
22
  type UploadSource,
36
- type UploadStreamSource,
37
23
  } from './uploadTypes';
38
24
 
39
25
  type ExplicitUploadInput = RuntimeUploadInput & { project: string };
@@ -41,17 +27,6 @@ type ExplicitUploadFromUrlInput = RuntimeUploadFromUrlInput & {
41
27
  project: string;
42
28
  };
43
29
 
44
- type PreparedUploadDetails = {
45
- sizeBytes: number;
46
- fileName?: string;
47
- mimeType?: string;
48
- };
49
- type PreparedUploadSource =
50
- | (PreparedUploadDetails & { kind: 'body'; body: Blob })
51
- | (PreparedUploadDetails & {
52
- kind: 'stream';
53
- stream: ReadableStream<Uint8Array>;
54
- });
55
30
  type UploadContext = {
56
31
  transport: Transport;
57
32
  operations: RuntimeOperations;
@@ -81,12 +56,12 @@ export async function uploadRuntimeFile(
81
56
  processingTimeoutMs = defaults.processingTimeoutMs ??
82
57
  DEFAULT_PROCESSING_TIMEOUT_MS,
83
58
  } = input;
84
- const prepared = prepareSource(source);
59
+ const prepared = prepareUploadSource(source);
85
60
  const totalBytes = prepared.sizeBytes;
86
61
  assertNonNegative(processingTimeoutMs, 'processingTimeoutMs');
87
62
 
88
63
  throwIfAborted(signal);
89
- reportProgress(onProgress, {
64
+ reportUploadProgress(onProgress, {
90
65
  transferredBytes: 0,
91
66
  totalBytes,
92
67
  phase: 'preparing',
@@ -123,101 +98,51 @@ export async function uploadRuntimeFile(
123
98
  temporary,
124
99
  path,
125
100
  extension,
126
- metadata: normalizeMetadata(metadata),
101
+ metadata: normalizeUploadMetadata(metadata),
127
102
  replaceTarget,
128
103
  multipart: partNumbers ? { partNumbers } : undefined,
129
104
  signedReadUrl,
130
105
  signal,
131
106
  });
132
- const uploadId = requested.upload.id;
133
-
134
- try {
135
- reportProgress(onProgress, {
136
- transferredBytes: 0,
137
- totalBytes,
138
- phase: 'uploading',
139
- });
140
-
141
- if (prepared.kind === 'body' && requested.upload.kind === 'single') {
142
- await putWithRetry(transport, {
143
- uploadId,
144
- url: requested.upload.signedUrl,
145
- body: prepared.body,
146
- signal,
147
- });
148
- reportProgress(onProgress, {
149
- transferredBytes: totalBytes,
150
- totalBytes,
151
- phase: 'uploading',
152
- });
153
- } else {
154
- if (requested.upload.kind !== 'multipart') {
155
- throw new TypeError(
156
- 'The API returned a single upload URL for a stream source.',
157
- );
158
- }
159
- if (!multipartPlan) {
160
- throw new TypeError(
161
- 'The API returned a multipart upload for a single-upload request.',
162
- );
163
- }
164
- const concurrency = getPositiveInteger(
165
- typeof multipart === 'object'
166
- ? multipart.concurrency
167
- : defaults.multipartConcurrency,
168
- defaults.multipartConcurrency ?? DEFAULT_MULTIPART_CONCURRENCY,
169
- 'multipart.concurrency',
170
- );
171
- const transferOptions = {
172
- uploadId,
173
- parts: requested.upload.parts,
174
- partSizeBytes: multipartPlan.partSizeBytes,
175
- signal,
176
- onProgress,
177
- };
178
- const completedParts =
179
- prepared.kind === 'stream'
180
- ? await uploadStreamParts(transport, {
181
- ...transferOptions,
182
- stream: prepared.stream,
183
- totalBytes,
184
- })
185
- : await uploadParts(transport, {
186
- ...transferOptions,
187
- body: prepared.body,
188
- concurrency,
189
- });
190
- await operations.uploads.completeMultipart({
191
- project,
192
- uploadId,
193
- parts: completedParts,
194
- signal,
195
- });
196
- }
197
-
198
- reportProgress(onProgress, {
199
- transferredBytes: totalBytes,
200
- totalBytes,
201
- phase: 'processing',
202
- });
203
-
204
- const completed = await waitForUpload(context, {
205
- project,
206
- uploadId,
207
- signal,
208
- timeoutMs: processingTimeoutMs,
209
- });
107
+ const completed = await executeUploadLifecycle({
108
+ transport,
109
+ requested,
110
+ prepared,
111
+ multipartPlan,
112
+ multipartConcurrency:
113
+ typeof multipart === 'object'
114
+ ? multipart.concurrency
115
+ : defaults.multipartConcurrency,
116
+ defaultMultipartConcurrency: defaults.multipartConcurrency,
117
+ processingTimeoutMs,
118
+ signal,
119
+ onProgress,
120
+ operations: {
121
+ completeMultipart: ({ uploadId, parts, signal: requestSignal }) =>
122
+ operations.uploads.completeMultipart({
123
+ project,
124
+ uploadId,
125
+ parts,
126
+ signal: requestSignal,
127
+ }),
128
+ get: ({ uploadId, signal: requestSignal }) =>
129
+ context.transport.executeWithResponse(
130
+ createGetUploadRequest({
131
+ project,
132
+ uploadId,
133
+ signal: requestSignal,
134
+ }),
135
+ ),
136
+ cancel: ({ uploadId }) =>
137
+ operations.uploads.cancel({ project, uploadId }),
138
+ },
139
+ cleanupPolicy: 'preserve-original',
140
+ });
210
141
 
211
- return {
212
- ...completed,
213
- signedReadUrl: requested.signedReadUrl,
214
- };
215
- } catch (error) {
216
- if (!(error instanceof EdgeStoreUploadProcessingTimeoutError)) {
217
- await cancelUpload(operations, project, uploadId);
218
- }
219
- throw error;
220
- }
142
+ return {
143
+ ...completed,
144
+ signedReadUrl: requested.signedReadUrl,
145
+ };
221
146
  }
222
147
 
223
148
  export async function uploadRuntimeFileFromUrl(
@@ -303,156 +228,6 @@ async function cancelResponseBody(
303
228
  await body.cancel().catch(() => undefined);
304
229
  }
305
230
 
306
- async function waitForUpload(
307
- context: UploadContext,
308
- options: {
309
- project: string;
310
- uploadId: string;
311
- signal?: AbortSignal;
312
- timeoutMs: number;
313
- },
314
- ): Promise<CompletedUpload> {
315
- const deadline = Date.now() + options.timeoutMs;
316
- const timeoutError = () =>
317
- new EdgeStoreUploadProcessingTimeoutError(
318
- 'Timed out while EdgeStore was processing the upload.',
319
- options.uploadId,
320
- );
321
-
322
- while (true) {
323
- const { data, response } = await retryOperation(
324
- (signal) =>
325
- context.transport.executeWithResponse(
326
- createGetUploadRequest({
327
- project: options.project,
328
- uploadId: options.uploadId,
329
- signal,
330
- }),
331
- ),
332
- {
333
- signal: options.signal,
334
- deadline,
335
- timeoutError,
336
- },
337
- );
338
-
339
- if (data.upload.status === 'completed' && 'file' in data) return data;
340
- if (data.upload.status === 'canceled') {
341
- throw new EdgeStoreUploadCanceledError(
342
- 'The EdgeStore upload was canceled.',
343
- options.uploadId,
344
- );
345
- }
346
-
347
- const delayMs = getRetryAfterMs(response) ?? 1000;
348
- if (Date.now() + delayMs > deadline) {
349
- throw timeoutError();
350
- }
351
- await sleep(delayMs, options.signal);
352
- }
353
- }
354
-
355
- async function cancelUpload(
356
- operations: RuntimeOperations,
357
- project: string,
358
- uploadId: string,
359
- ) {
360
- try {
361
- await operations.uploads.cancel({ project, uploadId });
362
- } catch {
363
- // Preserve the original upload failure.
364
- }
365
- }
366
-
367
- function normalizeMetadata(
368
- metadata?: Record<string, UploadMetadataValue>,
369
- ): Record<string, string> | undefined {
370
- if (!metadata) return undefined;
371
- const entries = Object.entries(metadata).flatMap(([key, value]) =>
372
- value === null || value === undefined ? [] : [[key, String(value)]],
373
- );
374
- return entries.length ? Object.fromEntries(entries) : undefined;
375
- }
376
-
377
- function prepareSource(source: UploadSource): PreparedUploadSource {
378
- if (typeof source === 'string') {
379
- const body = new Blob([source], { type: 'text/plain' });
380
- return {
381
- kind: 'body',
382
- body,
383
- sizeBytes: body.size,
384
- mimeType: body.type,
385
- };
386
- }
387
- if (isStreamSource(source)) {
388
- if (!Number.isSafeInteger(source.sizeBytes) || source.sizeBytes < 0) {
389
- throw new RangeError('source.sizeBytes must be a non-negative integer.');
390
- }
391
- return {
392
- kind: 'stream',
393
- stream: source.stream,
394
- sizeBytes: source.sizeBytes,
395
- };
396
- }
397
- if (source instanceof Blob) {
398
- return {
399
- kind: 'body',
400
- body: source,
401
- sizeBytes: source.size,
402
- fileName:
403
- 'name' in source && typeof source.name === 'string'
404
- ? source.name
405
- : undefined,
406
- mimeType: source.type || undefined,
407
- };
408
- }
409
- if (source instanceof ArrayBuffer) {
410
- const body = new Blob([source]);
411
- return { kind: 'body', body, sizeBytes: body.size };
412
- }
413
- const body = new Blob([
414
- Uint8Array.from(
415
- new Uint8Array(source.buffer, source.byteOffset, source.byteLength),
416
- ),
417
- ]);
418
- return { kind: 'body', body, sizeBytes: body.size };
419
- }
420
-
421
- function isStreamSource(source: UploadSource): source is UploadStreamSource {
422
- const stream =
423
- typeof source === 'object' && source !== null && 'stream' in source
424
- ? Reflect.get(source, 'stream')
425
- : undefined;
426
- return (
427
- typeof stream === 'object' &&
428
- stream !== null &&
429
- 'getReader' in stream &&
430
- typeof Reflect.get(stream, 'getReader') === 'function'
431
- );
432
- }
433
-
434
- function reportProgress(
435
- onProgress: RuntimeUploadInput['onProgress'],
436
- progress: {
437
- transferredBytes: number;
438
- totalBytes: number;
439
- phase: 'preparing' | 'uploading' | 'processing';
440
- },
441
- ) {
442
- const { transferredBytes, totalBytes, phase } = progress;
443
- onProgress?.({
444
- transferredBytes,
445
- totalBytes,
446
- percentage:
447
- totalBytes === 0
448
- ? phase === 'preparing'
449
- ? 0
450
- : 100
451
- : Math.round((transferredBytes / totalBytes) * 10_000) / 100,
452
- phase,
453
- });
454
- }
455
-
456
231
  function getFileNameFromUrl(url: string): string | undefined {
457
232
  const name = new URL(url).pathname.split('/').filter(Boolean).at(-1);
458
233
  return name ? decodeURIComponent(name) : undefined;
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export const EDGE_STORE_PACKAGE_NAME = '@edgestore/sdk';
2
- export const EDGE_STORE_PACKAGE_VERSION = '1.0.0-next.1';
2
+ export const EDGE_STORE_PACKAGE_VERSION = '1.0.0-next.2';