@transloadit/node 4.1.9 → 4.3.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.
- package/README.md +81 -1
- package/dist/Transloadit.d.ts +36 -5
- package/dist/Transloadit.d.ts.map +1 -1
- package/dist/Transloadit.js +228 -39
- package/dist/Transloadit.js.map +1 -1
- package/dist/alphalib/assembly-linter.d.ts +123 -0
- package/dist/alphalib/assembly-linter.d.ts.map +1 -0
- package/dist/alphalib/assembly-linter.js +1142 -0
- package/dist/alphalib/assembly-linter.js.map +1 -0
- package/dist/alphalib/assembly-linter.lang.en.d.ts +87 -0
- package/dist/alphalib/assembly-linter.lang.en.d.ts.map +1 -0
- package/dist/alphalib/assembly-linter.lang.en.js +326 -0
- package/dist/alphalib/assembly-linter.lang.en.js.map +1 -0
- package/dist/alphalib/mcache.d.ts.map +1 -1
- package/dist/alphalib/mcache.js +22 -7
- package/dist/alphalib/mcache.js.map +1 -1
- package/dist/alphalib/object.d.ts +20 -0
- package/dist/alphalib/object.d.ts.map +1 -0
- package/dist/alphalib/object.js +23 -0
- package/dist/alphalib/object.js.map +1 -0
- package/dist/alphalib/stepParsing.d.ts +93 -0
- package/dist/alphalib/stepParsing.d.ts.map +1 -0
- package/dist/alphalib/stepParsing.js +1154 -0
- package/dist/alphalib/stepParsing.js.map +1 -0
- package/dist/alphalib/templateMerge.d.ts +4 -0
- package/dist/alphalib/templateMerge.d.ts.map +1 -0
- package/dist/alphalib/templateMerge.js +22 -0
- package/dist/alphalib/templateMerge.js.map +1 -0
- package/dist/alphalib/types/assemblyReplay.d.ts +56 -0
- package/dist/alphalib/types/assemblyReplay.d.ts.map +1 -1
- package/dist/alphalib/types/assemblyReplayNotification.d.ts +56 -0
- package/dist/alphalib/types/assemblyReplayNotification.d.ts.map +1 -1
- package/dist/alphalib/types/assemblyStatus.d.ts +63 -57
- package/dist/alphalib/types/assemblyStatus.d.ts.map +1 -1
- package/dist/alphalib/types/assemblyStatus.js +9 -1
- package/dist/alphalib/types/assemblyStatus.js.map +1 -1
- package/dist/alphalib/types/assemblyUrls.d.ts +1 -1
- package/dist/alphalib/types/assemblyUrls.d.ts.map +1 -1
- package/dist/alphalib/types/assemblyUrls.js.map +1 -1
- package/dist/alphalib/types/robots/_index.d.ts +608 -81
- package/dist/alphalib/types/robots/_index.d.ts.map +1 -1
- package/dist/alphalib/types/robots/_index.js +4 -0
- package/dist/alphalib/types/robots/_index.js.map +1 -1
- package/dist/alphalib/types/robots/_instructions-primitives.d.ts +4 -4
- package/dist/alphalib/types/robots/_instructions-primitives.d.ts.map +1 -1
- package/dist/alphalib/types/robots/_instructions-primitives.js +1 -0
- package/dist/alphalib/types/robots/_instructions-primitives.js.map +1 -1
- package/dist/alphalib/types/robots/document-optimize.d.ts +489 -0
- package/dist/alphalib/types/robots/document-optimize.d.ts.map +1 -0
- package/dist/alphalib/types/robots/document-optimize.js +151 -0
- package/dist/alphalib/types/robots/document-optimize.js.map +1 -0
- package/dist/alphalib/types/template.d.ts +1050 -174
- package/dist/alphalib/types/template.d.ts.map +1 -1
- package/dist/cli/commands/assemblies.d.ts +20 -1
- package/dist/cli/commands/assemblies.d.ts.map +1 -1
- package/dist/cli/commands/assemblies.js +137 -2
- package/dist/cli/commands/assemblies.js.map +1 -1
- package/dist/cli/commands/auth.d.ts.map +1 -1
- package/dist/cli/commands/auth.js +19 -19
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/docs/assemblyLintingExamples.d.ts +2 -0
- package/dist/cli/docs/assemblyLintingExamples.d.ts.map +1 -0
- package/dist/cli/docs/assemblyLintingExamples.js +10 -0
- package/dist/cli/docs/assemblyLintingExamples.js.map +1 -0
- package/dist/cli/helpers.d.ts +11 -0
- package/dist/cli/helpers.d.ts.map +1 -1
- package/dist/cli/helpers.js +29 -0
- package/dist/cli/helpers.js.map +1 -1
- package/dist/lintAssemblyInput.d.ts +10 -0
- package/dist/lintAssemblyInput.d.ts.map +1 -0
- package/dist/lintAssemblyInput.js +73 -0
- package/dist/lintAssemblyInput.js.map +1 -0
- package/dist/lintAssemblyInstructions.d.ts +29 -0
- package/dist/lintAssemblyInstructions.d.ts.map +1 -0
- package/dist/lintAssemblyInstructions.js +33 -0
- package/dist/lintAssemblyInstructions.js.map +1 -0
- package/dist/tus.d.ts +2 -1
- package/dist/tus.d.ts.map +1 -1
- package/dist/tus.js +2 -1
- package/dist/tus.js.map +1 -1
- package/package.json +5 -2
- package/src/Transloadit.ts +318 -49
- package/src/alphalib/assembly-linter.lang.en.ts +393 -0
- package/src/alphalib/assembly-linter.ts +1475 -0
- package/src/alphalib/mcache.ts +26 -7
- package/src/alphalib/object.ts +27 -0
- package/src/alphalib/stepParsing.ts +1465 -0
- package/src/alphalib/templateMerge.ts +32 -0
- package/src/alphalib/types/assemblyStatus.ts +9 -1
- package/src/alphalib/types/assemblyUrls.ts +2 -5
- package/src/alphalib/types/robots/_index.ts +14 -0
- package/src/alphalib/types/robots/_instructions-primitives.ts +1 -0
- package/src/alphalib/types/robots/document-optimize.ts +180 -0
- package/src/alphalib/typings/json-to-ast.d.ts +34 -0
- package/src/cli/commands/assemblies.ts +161 -2
- package/src/cli/commands/auth.ts +19 -22
- package/src/cli/commands/index.ts +2 -0
- package/src/cli/docs/assemblyLintingExamples.ts +9 -0
- package/src/cli/helpers.ts +50 -0
- package/src/lintAssemblyInput.ts +89 -0
- package/src/lintAssemblyInstructions.ts +72 -0
- package/src/tus.ts +3 -0
package/README.md
CHANGED
|
@@ -112,6 +112,28 @@ npx transloadit assemblies replay --steps new-steps.json ASSEMBLY_ID
|
|
|
112
112
|
npx transloadit assemblies replay --reparse-template ASSEMBLY_ID
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
### Linting Assembly Instructions
|
|
116
|
+
|
|
117
|
+
Lint Assembly Instructions locally using the same linter as the API.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# From a JSON file (full instructions or steps-only)
|
|
121
|
+
npx transloadit assemblies lint --steps steps.json
|
|
122
|
+
|
|
123
|
+
# From stdin
|
|
124
|
+
cat steps.json | npx transloadit assemblies lint
|
|
125
|
+
|
|
126
|
+
# Merge template content before linting
|
|
127
|
+
npx transloadit assemblies lint --template TEMPLATE_ID --steps steps.json
|
|
128
|
+
|
|
129
|
+
# Treat warnings as fatal; apply fixes (overwrites files / stdout for stdin)
|
|
130
|
+
npx transloadit assemblies lint --fatal warning --fix --steps steps.json
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
When both `--template` and steps input are provided, Transloadit merges the template content with
|
|
134
|
+
the provided steps before linting, matching the API's runtime behavior. If the template sets
|
|
135
|
+
`allow_steps_override=false`, providing steps will fail with `TEMPLATE_DENIES_STEPS_OVERRIDE`.
|
|
136
|
+
|
|
115
137
|
### Managing Templates
|
|
116
138
|
|
|
117
139
|
```bash
|
|
@@ -260,6 +282,28 @@ try {
|
|
|
260
282
|
|
|
261
283
|
You can find [details about your executed Assemblies here](https://transloadit.com/assemblies).
|
|
262
284
|
|
|
285
|
+
### Resuming interrupted uploads
|
|
286
|
+
|
|
287
|
+
If an upload was interrupted, you can resume it by providing the original `assemblyUrl` and the
|
|
288
|
+
same input mapping. Resume relies on matching `fieldname`, `filename`, and `size`, so keep input
|
|
289
|
+
names stable and pass the same files. Only path-based inputs resume; Buffer/string/stream uploads
|
|
290
|
+
start a new tus upload automatically.
|
|
291
|
+
|
|
292
|
+
You can pass the same upload and progress options as `createAssembly` (such as `chunkSize`,
|
|
293
|
+
`uploadConcurrency`, `waitForCompletion`, `timeout`, `onUploadProgress`, and `onAssemblyProgress`).
|
|
294
|
+
When `waitForCompletion` is `true`, the SDK will poll and resolve once the Assembly is finished.
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
const status = await transloadit.resumeAssemblyUploads({
|
|
298
|
+
assemblyUrl: 'https://api2.transloadit.com/assemblies/ASSEMBLY_ID',
|
|
299
|
+
files: {
|
|
300
|
+
file1: '/PATH/TO/FILE.jpg',
|
|
301
|
+
file2: '/PATH/TO/FILE2.jpg',
|
|
302
|
+
},
|
|
303
|
+
uploadConcurrency: 2,
|
|
304
|
+
})
|
|
305
|
+
```
|
|
306
|
+
|
|
263
307
|
## Examples
|
|
264
308
|
|
|
265
309
|
- [Upload and resize image](https://github.com/transloadit/node-sdk/blob/main/examples/resize_an_image.ts)
|
|
@@ -394,6 +438,42 @@ See also:
|
|
|
394
438
|
- [API documentation](https://transloadit.com/docs/api/assemblies-post/)
|
|
395
439
|
- Error codes and retry logic below
|
|
396
440
|
|
|
441
|
+
#### async lintAssemblyInstructions(options)
|
|
442
|
+
|
|
443
|
+
Lint Assembly Instructions locally using the same linter as the API.
|
|
444
|
+
If you provide a `templateId`, the template content is fetched and merged with your instructions
|
|
445
|
+
before linting (matching the API's runtime merge behavior). If the template sets
|
|
446
|
+
`allow_steps_override=false`, providing steps will throw `TEMPLATE_DENIES_STEPS_OVERRIDE`.
|
|
447
|
+
|
|
448
|
+
The `options` object accepts:
|
|
449
|
+
|
|
450
|
+
- `assemblyInstructions` - Assembly Instructions as a JSON string, a full instructions object, or a steps-only object.
|
|
451
|
+
If no `steps` property is present, the object is treated as steps.
|
|
452
|
+
- `templateId` - Optional template ID to merge before linting.
|
|
453
|
+
- `fatal` - `'error' | 'warning'` (default: `'error'`). When set to `'warning'`, warnings are treated as fatal.
|
|
454
|
+
- `fix` - Apply auto-fixes where possible. If `true`, the result includes `fixedInstructions`.
|
|
455
|
+
|
|
456
|
+
The method returns:
|
|
457
|
+
|
|
458
|
+
- `success` - `true` when no fatal issues are found.
|
|
459
|
+
- `issues` - Array of lint issues (each includes `code`, `type`, `row`, `column`, and `desc`).
|
|
460
|
+
- `fixedInstructions` - The fixed JSON string when `fix` is `true` (steps-only inputs return steps-only JSON).
|
|
461
|
+
|
|
462
|
+
Example:
|
|
463
|
+
|
|
464
|
+
```js
|
|
465
|
+
const result = await transloadit.lintAssemblyInstructions({
|
|
466
|
+
assemblyInstructions: {
|
|
467
|
+
resize: { robot: '/image/resize', use: ':original', width: 100, height: 100 },
|
|
468
|
+
},
|
|
469
|
+
fatal: 'warning',
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
if (!result.success) {
|
|
473
|
+
console.log(result.issues)
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
397
477
|
#### async listAssemblies(params)
|
|
398
478
|
|
|
399
479
|
Retrieve Assemblies according to the given `params`.
|
|
@@ -677,7 +757,7 @@ If you want to retry on other errors, please see the [retry example code](exampl
|
|
|
677
757
|
This project uses [debug](https://github.com/visionmedia/debug) so you can run node with the `DEBUG=transloadit` evironment variable to enable verbose logging. Example:
|
|
678
758
|
|
|
679
759
|
```bash
|
|
680
|
-
DEBUG=transloadit*
|
|
760
|
+
DEBUG=transloadit* node examples/template_api.ts
|
|
681
761
|
```
|
|
682
762
|
|
|
683
763
|
## Maintainers
|
package/dist/Transloadit.d.ts
CHANGED
|
@@ -5,18 +5,19 @@ import { ApiError } from './ApiError.ts';
|
|
|
5
5
|
import type { AssemblyIndexItem, AssemblyStatus } from './alphalib/types/assemblyStatus.ts';
|
|
6
6
|
import type { BaseResponse, BillResponse, CreateAssemblyParams, CreateTemplateCredentialParams, CreateTemplateParams, EditTemplateParams, ListAssembliesParams, ListedTemplate, ListTemplateCredentialsParams, ListTemplatesParams, OptionalAuthParams, PaginationListWithCount, ReplayAssemblyNotificationParams, ReplayAssemblyNotificationResponse, ReplayAssemblyParams, ReplayAssemblyResponse, TemplateCredentialResponse, TemplateCredentialsResponse, TemplateResponse } from './apiTypes.ts';
|
|
7
7
|
import InconsistentResponseError from './InconsistentResponseError.ts';
|
|
8
|
+
import type { LintAssemblyInstructionsInput, LintAssemblyInstructionsResult } from './lintAssemblyInstructions.ts';
|
|
8
9
|
import PaginationStream from './PaginationStream.ts';
|
|
9
10
|
export { HTTPError, MaxRedirectsError, ParseError, ReadError, RequestError, TimeoutError, UploadError, } from 'got';
|
|
10
11
|
export type { AssemblyStatus } from './alphalib/types/assemblyStatus.ts';
|
|
11
12
|
export * from './apiTypes.ts';
|
|
12
13
|
export { InconsistentResponseError, ApiError };
|
|
14
|
+
export type { LintAssemblyInstructionsResult, LintFatalLevel } from './lintAssemblyInstructions.ts';
|
|
13
15
|
export interface UploadProgress {
|
|
14
16
|
uploadedBytes?: number | undefined;
|
|
15
17
|
totalBytes?: number | undefined;
|
|
16
18
|
}
|
|
17
19
|
export type AssemblyProgress = (assembly: AssemblyStatus) => void;
|
|
18
|
-
|
|
19
|
-
params?: CreateAssemblyParams;
|
|
20
|
+
interface AssemblyUploadOptions {
|
|
20
21
|
files?: {
|
|
21
22
|
[name: string]: string;
|
|
22
23
|
};
|
|
@@ -29,18 +30,29 @@ export interface CreateAssemblyOptions {
|
|
|
29
30
|
timeout?: number;
|
|
30
31
|
onUploadProgress?: (uploadProgress: UploadProgress) => void;
|
|
31
32
|
onAssemblyProgress?: AssemblyProgress;
|
|
32
|
-
assemblyId?: string;
|
|
33
33
|
/**
|
|
34
|
-
* Optional AbortSignal to cancel the
|
|
34
|
+
* Optional AbortSignal to cancel the upload and any follow-up polling.
|
|
35
35
|
* When aborted, any in-flight HTTP requests and TUS uploads will be cancelled.
|
|
36
36
|
*/
|
|
37
37
|
signal?: AbortSignal;
|
|
38
38
|
}
|
|
39
|
+
export interface CreateAssemblyOptions extends AssemblyUploadOptions {
|
|
40
|
+
params?: CreateAssemblyParams;
|
|
41
|
+
assemblyId?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ResumeAssemblyUploadsOptions extends AssemblyUploadOptions {
|
|
44
|
+
assemblyUrl: string;
|
|
45
|
+
}
|
|
39
46
|
export interface AwaitAssemblyCompletionOptions {
|
|
40
47
|
onAssemblyProgress?: AssemblyProgress;
|
|
41
48
|
timeout?: number;
|
|
42
49
|
interval?: number;
|
|
43
50
|
startTimeMs?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Optional assembly URL to poll instead of the configured client endpoint.
|
|
53
|
+
* Useful when resuming an Assembly created on a different host/region.
|
|
54
|
+
*/
|
|
55
|
+
assemblyUrl?: string;
|
|
44
56
|
/**
|
|
45
57
|
* Optional AbortSignal to cancel polling.
|
|
46
58
|
* When aborted, the polling loop will stop and throw an AbortError.
|
|
@@ -53,6 +65,12 @@ export interface AwaitAssemblyCompletionOptions {
|
|
|
53
65
|
*/
|
|
54
66
|
onPoll?: () => boolean | undefined;
|
|
55
67
|
}
|
|
68
|
+
export interface LintAssemblyInstructionsOptions extends Omit<LintAssemblyInstructionsInput, 'template'> {
|
|
69
|
+
/**
|
|
70
|
+
* Template ID to merge with the provided instructions before linting.
|
|
71
|
+
*/
|
|
72
|
+
templateId?: string;
|
|
73
|
+
}
|
|
56
74
|
export interface SmartCDNUrlOptions {
|
|
57
75
|
/**
|
|
58
76
|
* Workspace slug
|
|
@@ -107,7 +125,19 @@ export declare class Transloadit {
|
|
|
107
125
|
* @param opts assembly options
|
|
108
126
|
*/
|
|
109
127
|
createAssembly(opts?: CreateAssemblyOptions): CreateAssemblyPromise;
|
|
110
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Lint Assembly Instructions locally.
|
|
130
|
+
*
|
|
131
|
+
* If a templateId is provided, the template content is merged with the instructions,
|
|
132
|
+
* just like the API. When a template sets `allow_steps_override=false`, providing
|
|
133
|
+
* `steps` will throw a TEMPLATE_DENIES_STEPS_OVERRIDE error.
|
|
134
|
+
*
|
|
135
|
+
* The `assemblyInstructions` input may be a JSON string, a full instructions object,
|
|
136
|
+
* or a steps-only object (missing the `steps` property).
|
|
137
|
+
*/
|
|
138
|
+
lintAssemblyInstructions(options: LintAssemblyInstructionsOptions): Promise<LintAssemblyInstructionsResult>;
|
|
139
|
+
resumeAssemblyUploads(opts: ResumeAssemblyUploadsOptions): Promise<AssemblyStatus>;
|
|
140
|
+
awaitAssemblyCompletion(assemblyId: string, { onAssemblyProgress, timeout, startTimeMs, interval, assemblyUrl, signal, onPoll, }?: AwaitAssemblyCompletionOptions): Promise<AssemblyStatus>;
|
|
111
141
|
maybeThrowInconsistentResponseError(message: string): void;
|
|
112
142
|
/**
|
|
113
143
|
* Cancel the assembly
|
|
@@ -150,6 +180,7 @@ export declare class Transloadit {
|
|
|
150
180
|
getAssembly(assemblyId: string, options?: {
|
|
151
181
|
signal?: AbortSignal;
|
|
152
182
|
}): Promise<AssemblyStatus>;
|
|
183
|
+
private _fetchAssemblyStatus;
|
|
153
184
|
/**
|
|
154
185
|
* Create a Credential
|
|
155
186
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transloadit.d.ts","sourceRoot":"","sources":["../src/Transloadit.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Transloadit.d.ts","sourceRoot":"","sources":["../src/Transloadit.ts"],"names":[],"mappings":"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"}
|
package/dist/Transloadit.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as assert from 'node:assert';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { constants, createReadStream } from 'node:fs';
|
|
4
|
-
import { access } from 'node:fs/promises';
|
|
4
|
+
import { access, stat } from 'node:fs/promises';
|
|
5
|
+
import { basename } from 'node:path';
|
|
5
6
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
6
7
|
import { getSignedSmartCdnUrl, signParamsSync } from '@transloadit/utils/node';
|
|
7
8
|
import debug from 'debug';
|
|
@@ -15,6 +16,7 @@ import { ApiError } from "./ApiError.js";
|
|
|
15
16
|
import { assemblyIndexSchema, assemblyStatusSchema } from "./alphalib/types/assemblyStatus.js";
|
|
16
17
|
import { zodParseWithContext } from "./alphalib/zodParseWithContext.js";
|
|
17
18
|
import InconsistentResponseError from "./InconsistentResponseError.js";
|
|
19
|
+
import { lintAssemblyInstructions as lintAssemblyInstructionsInternal } from "./lintAssemblyInstructions.js";
|
|
18
20
|
import PaginationStream from "./PaginationStream.js";
|
|
19
21
|
import PollingTimeoutError from "./PollingTimeoutError.js";
|
|
20
22
|
import { sendTusRequest } from "./tus.js";
|
|
@@ -26,6 +28,55 @@ export { InconsistentResponseError, ApiError };
|
|
|
26
28
|
const log = debug('transloadit');
|
|
27
29
|
const logWarn = debug('transloadit:warn');
|
|
28
30
|
const { version } = packageJson;
|
|
31
|
+
const getUploadKey = (fieldname, filename, size) => {
|
|
32
|
+
if (!fieldname || !filename || size == null)
|
|
33
|
+
return null;
|
|
34
|
+
return JSON.stringify([fieldname, filename, size]);
|
|
35
|
+
};
|
|
36
|
+
const getSizeFromValue = (value) => {
|
|
37
|
+
if (typeof value === 'string')
|
|
38
|
+
return Buffer.byteLength(value);
|
|
39
|
+
if (Buffer.isBuffer(value))
|
|
40
|
+
return value.length;
|
|
41
|
+
if (value instanceof ArrayBuffer)
|
|
42
|
+
return value.byteLength;
|
|
43
|
+
if (ArrayBuffer.isView(value))
|
|
44
|
+
return value.byteLength;
|
|
45
|
+
return undefined;
|
|
46
|
+
};
|
|
47
|
+
const toReadableUpload = (label, value) => {
|
|
48
|
+
const readable = isReadableStream(value);
|
|
49
|
+
if (!readable && isStream(value)) {
|
|
50
|
+
throw new Error(`Upload named "${label}" is not a Readable stream`);
|
|
51
|
+
}
|
|
52
|
+
return readable ? value : intoStream(value);
|
|
53
|
+
};
|
|
54
|
+
const buildStreamsMap = (descriptors) => Object.fromEntries(descriptors.map((descriptor) => {
|
|
55
|
+
if (descriptor.path) {
|
|
56
|
+
const stream = createReadStream(descriptor.path);
|
|
57
|
+
return [descriptor.label, { stream, path: descriptor.path }];
|
|
58
|
+
}
|
|
59
|
+
const value = descriptor.value;
|
|
60
|
+
if (value == null) {
|
|
61
|
+
throw new Error(`Upload named "${descriptor.label}" has no data`);
|
|
62
|
+
}
|
|
63
|
+
const stream = toReadableUpload(descriptor.label, value);
|
|
64
|
+
return [descriptor.label, { stream }];
|
|
65
|
+
}));
|
|
66
|
+
const pauseStreams = (streamsMap) => {
|
|
67
|
+
for (const { stream } of Object.values(streamsMap)) {
|
|
68
|
+
stream.pause();
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
const createStreamErrorPromise = (streamsMap) => {
|
|
72
|
+
const promise = new Promise((_resolve, reject) => {
|
|
73
|
+
for (const { stream } of Object.values(streamsMap)) {
|
|
74
|
+
stream.on('error', reject);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
promise.catch(() => { });
|
|
78
|
+
return promise;
|
|
79
|
+
};
|
|
29
80
|
// Not sure if this is still a problem with the API, but throw a special error type so the user can retry if needed
|
|
30
81
|
function checkAssemblyUrls(result) {
|
|
31
82
|
if (result.assembly_url == null || result.assembly_ssl_url == null) {
|
|
@@ -35,6 +86,13 @@ function checkAssemblyUrls(result) {
|
|
|
35
86
|
function getHrTimeMs() {
|
|
36
87
|
return Number(process.hrtime.bigint() / 1000000n);
|
|
37
88
|
}
|
|
89
|
+
function getAssemblyIdFromUrl(assemblyUrl) {
|
|
90
|
+
const match = assemblyUrl.match(/\/assemblies\/([^/?#]+)/);
|
|
91
|
+
if (!match) {
|
|
92
|
+
throw new Error(`Invalid assembly URL: ${assemblyUrl}`);
|
|
93
|
+
}
|
|
94
|
+
return match[1] ?? '';
|
|
95
|
+
}
|
|
38
96
|
function checkResult(result) {
|
|
39
97
|
// In case server returned a successful HTTP status code, but an `error` in the JSON object
|
|
40
98
|
// This happens sometimes, for example when createAssembly with an invalid file (IMPORT_FILE_ERROR)
|
|
@@ -105,39 +163,29 @@ export class Transloadit {
|
|
|
105
163
|
const promise = (async () => {
|
|
106
164
|
this._lastUsedAssemblyUrl = `${this._endpoint}${urlSuffix}`;
|
|
107
165
|
await pMap(Object.entries(files), async ([, path]) => access(path, constants.F_OK | constants.R_OK), { concurrency: 5 });
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const stream = createReadStream(path);
|
|
122
|
-
allStreamsMap[label] = { stream, path }; // File streams have path
|
|
123
|
-
}
|
|
166
|
+
const descriptors = [
|
|
167
|
+
...Object.entries(files).map(([label, path]) => ({
|
|
168
|
+
label,
|
|
169
|
+
path,
|
|
170
|
+
filename: basename(path),
|
|
171
|
+
})),
|
|
172
|
+
...Object.entries(uploads).map(([label, value]) => ({
|
|
173
|
+
label,
|
|
174
|
+
filename: label,
|
|
175
|
+
value,
|
|
176
|
+
})),
|
|
177
|
+
];
|
|
178
|
+
const allStreamsMap = buildStreamsMap(descriptors);
|
|
124
179
|
const allStreams = Object.values(allStreamsMap);
|
|
125
180
|
// Pause all streams
|
|
126
|
-
|
|
127
|
-
stream.pause();
|
|
128
|
-
}
|
|
181
|
+
pauseStreams(allStreamsMap);
|
|
129
182
|
// If any stream emits error, we want to handle this and exit with error.
|
|
130
183
|
// This promise races against createAssemblyAndUpload() below via Promise.race().
|
|
131
184
|
// When createAssemblyAndUpload wins the race, this promise becomes "orphaned" -
|
|
132
185
|
// it's no longer awaited, but stream error handlers remain attached.
|
|
133
186
|
// The no-op catch prevents Node's unhandled rejection warning if a stream
|
|
134
187
|
// errors after the race is already won.
|
|
135
|
-
const streamErrorPromise =
|
|
136
|
-
for (const { stream } of allStreams) {
|
|
137
|
-
stream.on('error', reject);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
streamErrorPromise.catch(() => { });
|
|
188
|
+
const streamErrorPromise = createStreamErrorPromise(allStreamsMap);
|
|
141
189
|
const createAssemblyAndUpload = async () => {
|
|
142
190
|
const result = await this._remoteJson({
|
|
143
191
|
urlSuffix,
|
|
@@ -179,9 +227,130 @@ export class Transloadit {
|
|
|
179
227
|
// This allows the user to use or log the assemblyId even before it has been created for easier debugging
|
|
180
228
|
return Object.assign(promise, { assemblyId: effectiveAssemblyId });
|
|
181
229
|
}
|
|
182
|
-
|
|
230
|
+
/**
|
|
231
|
+
* Lint Assembly Instructions locally.
|
|
232
|
+
*
|
|
233
|
+
* If a templateId is provided, the template content is merged with the instructions,
|
|
234
|
+
* just like the API. When a template sets `allow_steps_override=false`, providing
|
|
235
|
+
* `steps` will throw a TEMPLATE_DENIES_STEPS_OVERRIDE error.
|
|
236
|
+
*
|
|
237
|
+
* The `assemblyInstructions` input may be a JSON string, a full instructions object,
|
|
238
|
+
* or a steps-only object (missing the `steps` property).
|
|
239
|
+
*/
|
|
240
|
+
async lintAssemblyInstructions(options) {
|
|
241
|
+
const { templateId, ...rest } = options;
|
|
242
|
+
if (!templateId) {
|
|
243
|
+
return await lintAssemblyInstructionsInternal(rest);
|
|
244
|
+
}
|
|
245
|
+
const template = await this.getTemplate(templateId);
|
|
246
|
+
return await lintAssemblyInstructionsInternal({
|
|
247
|
+
...rest,
|
|
248
|
+
template: template.content,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
async resumeAssemblyUploads(opts) {
|
|
252
|
+
const { assemblyUrl, files = {}, uploads = {}, chunkSize: requestedChunkSize = Number.POSITIVE_INFINITY, uploadConcurrency = 10, timeout = 24 * 60 * 60 * 1000, // 1 day
|
|
253
|
+
waitForCompletion = false, onUploadProgress = () => { }, onAssemblyProgress = () => { }, signal, } = opts;
|
|
254
|
+
const startTimeMs = getHrTimeMs();
|
|
255
|
+
getAssemblyIdFromUrl(assemblyUrl);
|
|
256
|
+
const assembly = await this._fetchAssemblyStatus({ url: assemblyUrl, signal });
|
|
257
|
+
const statusUrl = assembly.assembly_ssl_url ?? assembly.assembly_url ?? assemblyUrl;
|
|
258
|
+
const finishedKeys = new Set();
|
|
259
|
+
for (const upload of assembly.uploads ?? []) {
|
|
260
|
+
const key = getUploadKey(upload.field ?? null, upload.basename ?? null, upload.size);
|
|
261
|
+
if (key)
|
|
262
|
+
finishedKeys.add(key);
|
|
263
|
+
}
|
|
264
|
+
for (const upload of assembly.tus_uploads ?? []) {
|
|
265
|
+
if (!upload.finished)
|
|
266
|
+
continue;
|
|
267
|
+
const key = getUploadKey(upload.fieldname, upload.filename, upload.size);
|
|
268
|
+
if (key)
|
|
269
|
+
finishedKeys.add(key);
|
|
270
|
+
}
|
|
271
|
+
const resumeUrls = new Map();
|
|
272
|
+
for (const upload of assembly.tus_uploads ?? []) {
|
|
273
|
+
if (upload.finished)
|
|
274
|
+
continue;
|
|
275
|
+
if (!upload.upload_url)
|
|
276
|
+
continue;
|
|
277
|
+
const key = getUploadKey(upload.fieldname, upload.filename, upload.size);
|
|
278
|
+
if (key)
|
|
279
|
+
resumeUrls.set(key, upload.upload_url);
|
|
280
|
+
}
|
|
281
|
+
const descriptors = [];
|
|
282
|
+
await pMap(Object.entries(files), async ([label, path]) => {
|
|
283
|
+
await access(path, constants.F_OK | constants.R_OK);
|
|
284
|
+
const info = await stat(path);
|
|
285
|
+
descriptors.push({
|
|
286
|
+
label,
|
|
287
|
+
path,
|
|
288
|
+
filename: basename(path),
|
|
289
|
+
size: info.size,
|
|
290
|
+
});
|
|
291
|
+
}, { concurrency: 5 });
|
|
292
|
+
for (const [label, value] of Object.entries(uploads)) {
|
|
293
|
+
descriptors.push({
|
|
294
|
+
label,
|
|
295
|
+
filename: label,
|
|
296
|
+
size: isReadableStream(value) ? undefined : getSizeFromValue(value),
|
|
297
|
+
value,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
const descriptorsToUpload = descriptors.filter((descriptor) => {
|
|
301
|
+
const key = getUploadKey(descriptor.label, descriptor.filename, descriptor.size ?? null);
|
|
302
|
+
return key ? !finishedKeys.has(key) : true;
|
|
303
|
+
});
|
|
304
|
+
const uploadUrlsByLabel = {};
|
|
305
|
+
for (const descriptor of descriptorsToUpload) {
|
|
306
|
+
if (!descriptor.path)
|
|
307
|
+
continue;
|
|
308
|
+
const key = getUploadKey(descriptor.label, descriptor.filename, descriptor.size ?? null);
|
|
309
|
+
if (!key)
|
|
310
|
+
continue;
|
|
311
|
+
const uploadUrl = resumeUrls.get(key);
|
|
312
|
+
if (uploadUrl)
|
|
313
|
+
uploadUrlsByLabel[descriptor.label] = uploadUrl;
|
|
314
|
+
}
|
|
315
|
+
const streamsMap = buildStreamsMap(descriptorsToUpload);
|
|
316
|
+
pauseStreams(streamsMap);
|
|
317
|
+
if (Object.keys(streamsMap).length > 0) {
|
|
318
|
+
const streamErrorPromise = createStreamErrorPromise(streamsMap);
|
|
319
|
+
const uploadPromise = sendTusRequest({
|
|
320
|
+
streamsMap,
|
|
321
|
+
assembly,
|
|
322
|
+
requestedChunkSize,
|
|
323
|
+
uploadConcurrency,
|
|
324
|
+
onProgress: onUploadProgress,
|
|
325
|
+
signal,
|
|
326
|
+
uploadUrls: uploadUrlsByLabel,
|
|
327
|
+
});
|
|
328
|
+
await Promise.race([uploadPromise, streamErrorPromise]);
|
|
329
|
+
}
|
|
330
|
+
const latestAssembly = await this._fetchAssemblyStatus({ url: statusUrl, signal });
|
|
331
|
+
if (!waitForCompletion)
|
|
332
|
+
return latestAssembly;
|
|
333
|
+
if (latestAssembly.assembly_id == null) {
|
|
334
|
+
throw new InconsistentResponseError('Server returned an assembly response without an assembly_id after resuming uploads');
|
|
335
|
+
}
|
|
336
|
+
const awaitResult = await this.awaitAssemblyCompletion(latestAssembly.assembly_id, {
|
|
337
|
+
timeout,
|
|
338
|
+
onAssemblyProgress,
|
|
339
|
+
startTimeMs,
|
|
340
|
+
assemblyUrl: statusUrl,
|
|
341
|
+
signal,
|
|
342
|
+
});
|
|
343
|
+
checkResult(awaitResult);
|
|
344
|
+
return awaitResult;
|
|
345
|
+
}
|
|
346
|
+
async awaitAssemblyCompletion(assemblyId, { onAssemblyProgress = () => { }, timeout, startTimeMs = getHrTimeMs(), interval = 1000, assemblyUrl, signal, onPoll, } = {}) {
|
|
183
347
|
assert.ok(assemblyId);
|
|
184
348
|
let lastResult;
|
|
349
|
+
const fetchAssemblyStatus = () => {
|
|
350
|
+
return assemblyUrl
|
|
351
|
+
? this._fetchAssemblyStatus({ url: assemblyUrl, signal })
|
|
352
|
+
: this.getAssembly(assemblyId, { signal });
|
|
353
|
+
};
|
|
185
354
|
while (true) {
|
|
186
355
|
// Check if caller wants to stop polling early
|
|
187
356
|
if (onPoll?.() === false && lastResult) {
|
|
@@ -191,7 +360,7 @@ export class Transloadit {
|
|
|
191
360
|
if (signal?.aborted) {
|
|
192
361
|
throw signal.reason ?? new DOMException('Aborted', 'AbortError');
|
|
193
362
|
}
|
|
194
|
-
const result = await
|
|
363
|
+
const result = await fetchAssemblyStatus();
|
|
195
364
|
lastResult = result;
|
|
196
365
|
// If 'ok' is not in result, it implies a terminal state (e.g., error, completed, canceled).
|
|
197
366
|
// If 'ok' is present, then we check if it's one of the non-terminal polling states.
|
|
@@ -327,13 +496,21 @@ export class Transloadit {
|
|
|
327
496
|
* @returns the retrieved Assembly
|
|
328
497
|
*/
|
|
329
498
|
async getAssembly(assemblyId, options) {
|
|
330
|
-
|
|
331
|
-
|
|
499
|
+
return await this._fetchAssemblyStatus({
|
|
500
|
+
assemblyId,
|
|
332
501
|
signal: options?.signal,
|
|
333
502
|
});
|
|
503
|
+
}
|
|
504
|
+
async _fetchAssemblyStatus({ assemblyId, url, signal, }) {
|
|
505
|
+
const rawResult = await this._remoteJson({
|
|
506
|
+
url,
|
|
507
|
+
urlSuffix: url ? undefined : `/assemblies/${assemblyId}`,
|
|
508
|
+
signal,
|
|
509
|
+
});
|
|
334
510
|
const parsedResult = zodParseWithContext(assemblyStatusSchema, rawResult);
|
|
335
511
|
if (!parsedResult.success) {
|
|
336
|
-
|
|
512
|
+
const label = assemblyId ?? url ?? 'unknown';
|
|
513
|
+
this.maybeThrowInconsistentResponseError(`The API responded with data that does not match the expected schema while getting Assembly: ${label}.\n${parsedResult.humanReadable}`);
|
|
337
514
|
}
|
|
338
515
|
checkAssemblyUrls(rawResult);
|
|
339
516
|
return rawResult;
|
|
@@ -597,21 +774,33 @@ export class Transloadit {
|
|
|
597
774
|
logWarn('HTTP error', statusCode, body);
|
|
598
775
|
// check whether we should retry
|
|
599
776
|
// https://transloadit.com/blog/2012/04/introducing-rate-limiting/
|
|
600
|
-
|
|
777
|
+
const retryAfterHeader = err.response?.headers?.['retry-after'];
|
|
778
|
+
const retryAfterSeconds = typeof retryAfterHeader === 'string' ? Number(retryAfterHeader) : undefined;
|
|
779
|
+
const retryInFromInfo = typeof body === 'object' &&
|
|
601
780
|
body != null &&
|
|
602
|
-
'error' in body &&
|
|
603
781
|
'info' in body &&
|
|
604
782
|
typeof body.info === 'object' &&
|
|
605
783
|
body.info != null &&
|
|
606
784
|
'retryIn' in body.info &&
|
|
607
785
|
typeof body.info.retryIn === 'number' &&
|
|
608
|
-
|
|
609
|
-
|
|
786
|
+
body.info.retryIn > 0
|
|
787
|
+
? body.info.retryIn
|
|
788
|
+
: undefined;
|
|
789
|
+
const retryInSec = retryInFromInfo ??
|
|
790
|
+
(typeof retryAfterSeconds === 'number' && retryAfterSeconds > 0
|
|
791
|
+
? retryAfterSeconds
|
|
792
|
+
: undefined);
|
|
793
|
+
const shouldRetry = retryCount < this._maxRetries && // 413 taken from https://transloadit.com/blog/2012/04/introducing-rate-limiting/
|
|
610
794
|
// todo can 413 be removed?
|
|
611
|
-
((statusCode === 413 &&
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
795
|
+
((statusCode === 413 &&
|
|
796
|
+
body &&
|
|
797
|
+
typeof body === 'object' &&
|
|
798
|
+
body.error === 'RATE_LIMIT_REACHED') ||
|
|
799
|
+
statusCode === 429);
|
|
800
|
+
if (shouldRetry) {
|
|
801
|
+
const retryDelaySec = retryInSec ?? 1;
|
|
802
|
+
logWarn(`Rate limit reached, retrying request in approximately ${retryDelaySec} seconds.`);
|
|
803
|
+
const retryInMs = 1000 * (retryDelaySec * (1 + 0.1 * Math.random()));
|
|
615
804
|
await delay(retryInMs);
|
|
616
805
|
// Retry
|
|
617
806
|
}
|