@liqhtworks/sophon-sdk 0.1.3 → 0.1.5
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/.github/CODEOWNERS +6 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +34 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/CHANGELOG.md +51 -0
- package/README.md +133 -131
- package/dist/esm/helpers/index.d.ts +1 -0
- package/dist/esm/helpers/index.js +1 -0
- package/dist/esm/helpers/sources.d.ts +8 -0
- package/dist/esm/helpers/sources.js +6 -0
- package/dist/helpers/index.d.ts +1 -0
- package/dist/helpers/index.js +1 -0
- package/dist/helpers/sources.d.ts +8 -0
- package/dist/helpers/sources.js +10 -0
- package/examples/encode-file.mjs +70 -0
- package/examples/upload-node-path.mjs +32 -0
- package/examples/webhook-server/package.json +13 -0
- package/examples/webhook-server/src/server.mjs +51 -0
- package/package.json +1 -1
- package/src/helpers/index.ts +1 -0
- package/src/helpers/sources.ts +14 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# This repository is generated from Liqhtworks/sophon-api at every
|
|
2
|
+
# release. Source-side changes (helpers, OpenAPI spec, generator
|
|
3
|
+
# config) belong in that repo, not here. Filing issues / PRs against
|
|
4
|
+
# this repo is fine — Liqhtworks engineers triage them and route the
|
|
5
|
+
# fix upstream as needed.
|
|
6
|
+
* @Liqhtworks/engineering
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Something the SDK does that contradicts the docs or its types.
|
|
4
|
+
title: "[bug] "
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What happened
|
|
9
|
+
|
|
10
|
+
<!-- What you tried to do, what the SDK did instead. -->
|
|
11
|
+
|
|
12
|
+
## Reproducer
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
// Minimum code that reproduces. Strip secrets.
|
|
16
|
+
import { Configuration, JobsApi } from "@liqhtworks/sophon-sdk";
|
|
17
|
+
// …
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Environment
|
|
21
|
+
|
|
22
|
+
- `@liqhtworks/sophon-sdk` version: `0.1.x`
|
|
23
|
+
- Node / Bun / Deno version: `…`
|
|
24
|
+
- OS: `…`
|
|
25
|
+
- Running in: server-side / browser / edge runtime
|
|
26
|
+
|
|
27
|
+
## Expected vs. actual
|
|
28
|
+
|
|
29
|
+
- Expected: `…`
|
|
30
|
+
- Actual: `…` (paste error message + stack trace inside a fenced block)
|
|
31
|
+
|
|
32
|
+
## Anything else
|
|
33
|
+
|
|
34
|
+
<!-- Logs, X-Request-Id headers from the response, network captures, etc. -->
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: SOPHON API documentation
|
|
4
|
+
url: https://registry.scalar.com/@liqhtworks/apis/sophon-encoding-api/latest
|
|
5
|
+
about: Reference for every endpoint the SDK wraps.
|
|
6
|
+
- name: API + spec issues (sophon-api)
|
|
7
|
+
url: https://github.com/Liqhtworks/sophon-api/issues
|
|
8
|
+
about: For server-side bugs or OpenAPI questions, file there. SDK bugs stay here.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: A capability you want from the SDK that isn't shipped.
|
|
4
|
+
title: "[feature] "
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## What would you like the SDK to do
|
|
9
|
+
|
|
10
|
+
<!-- Describe the customer-facing behavior, not the implementation. -->
|
|
11
|
+
|
|
12
|
+
## What you tried instead
|
|
13
|
+
|
|
14
|
+
<!-- Working around it today, or which competitor / hand-rolled tool you'd
|
|
15
|
+
otherwise reach for. -->
|
|
16
|
+
|
|
17
|
+
## Is this an SDK-level fit, or upstream?
|
|
18
|
+
|
|
19
|
+
- [ ] SDK-level (helper, type, ergonomic)
|
|
20
|
+
- [ ] Spec / API-level (would also need a sophon-api change)
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@liqhtworks/sophon-sdk` are recorded here. The
|
|
4
|
+
package follows [SemVer](https://semver.org/) — see `README.md` for the
|
|
5
|
+
versioning policy applied during the v0.x pre-1.0 phase.
|
|
6
|
+
|
|
7
|
+
## [0.1.4] — 2026-05-08
|
|
8
|
+
|
|
9
|
+
- `JobSource.upload(uploadId)` constructor — typed alternative to the
|
|
10
|
+
fragile `{ type: "upload", upload_id: "..." }` literal.
|
|
11
|
+
- Generated exports tightened so all helpers and discriminated-union
|
|
12
|
+
constructors are reachable at the top level.
|
|
13
|
+
- Build-test coverage extended over the new surface.
|
|
14
|
+
|
|
15
|
+
## [0.1.3] — 2026-05-08
|
|
16
|
+
|
|
17
|
+
- `UploadsApiLike` helper interface narrowed to only the fields the
|
|
18
|
+
helpers actually read. The previous declaration typed `expires_at`
|
|
19
|
+
as `string`, but the generated `UploadsApi` returns `Date`, so the
|
|
20
|
+
helper compiled in isolation but not against the real client. Fixed.
|
|
21
|
+
|
|
22
|
+
## [0.1.2] — 2026-04-23
|
|
23
|
+
|
|
24
|
+
- Per-route idempotency keys in `uploadFile`. Earlier releases reused
|
|
25
|
+
one key for both `createUpload` and `completeUpload`; SOPHON scopes
|
|
26
|
+
idempotency keys per route and rejected the second call with HTTP 409.
|
|
27
|
+
Now derives `${idem}/create` and `${idem}/complete` from the caller's
|
|
28
|
+
seed so retries still reach the server's idempotent path.
|
|
29
|
+
- Build-test fixtures regenerate as real ffprobe-able media via ffmpeg.
|
|
30
|
+
|
|
31
|
+
## [0.1.0] — 2026-04-23
|
|
32
|
+
|
|
33
|
+
Initial public release.
|
|
34
|
+
|
|
35
|
+
- Generated transport (`Configuration`, `JobsApi`, `UploadsApi`,
|
|
36
|
+
`WebhooksApi`, `DownloadsApi`, `HealthApi`) from the SOPHON OpenAPI
|
|
37
|
+
spec.
|
|
38
|
+
- Hand-written helpers spliced on top of the generated client:
|
|
39
|
+
- `uploadFile` — chunked, concurrent, resumable upload with progress
|
|
40
|
+
reporting and bounded retry.
|
|
41
|
+
- `waitForJob` — typed terminal-state polling with backoff and
|
|
42
|
+
timeout.
|
|
43
|
+
- `verifyWebhookSignature` — constant-time HMAC-SHA256 verification
|
|
44
|
+
with a default replay window. Uses Web Crypto so it runs on Node
|
|
45
|
+
18+ and modern browsers.
|
|
46
|
+
- Provenance signed via npm sigstore on every publish.
|
|
47
|
+
|
|
48
|
+
[0.1.4]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.4
|
|
49
|
+
[0.1.3]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.3
|
|
50
|
+
[0.1.2]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.2
|
|
51
|
+
[0.1.0]: https://github.com/Liqhtworks/sophon-sdk-typescript/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,164 +1,166 @@
|
|
|
1
|
-
# @liqhtworks/sophon-sdk
|
|
1
|
+
# @liqhtworks/sophon-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official TypeScript SDK for the SOPHON Encoding API.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This repository is generated from `Liqhtworks/sophon-api`. The curated
|
|
6
|
+
`README.md` and `examples/` directory are preserved across SDK regeneration.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Install
|
|
8
9
|
|
|
9
10
|
```bash
|
|
10
|
-
npm install @liqhtworks/sophon-sdk
|
|
11
|
+
npm install @liqhtworks/sophon-sdk
|
|
11
12
|
```
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Requires Node 18+ or a runtime with `fetch`, `Blob`, `AbortController`, and Web
|
|
15
|
+
Crypto.
|
|
14
16
|
|
|
17
|
+
## Get an API key
|
|
18
|
+
|
|
19
|
+
1. Sign in at <https://liqhtworks.xyz/account/general>.
|
|
20
|
+
2. In **API keys**, create a key for your server-side integration.
|
|
21
|
+
3. Copy the `xt_live_...` token when it is shown. It is only shown once.
|
|
22
|
+
4. Store it as an environment variable:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export SOPHON_API_KEY=xt_live_...
|
|
26
|
+
export SOPHON_BASE_URL=https://api.liqhtworks.xyz
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Keep API keys on the server. Do not ship them in browser bundles, mobile apps,
|
|
30
|
+
public repos, logs, or analytics events.
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
This is the smallest complete server-side flow: upload a local video, create an
|
|
35
|
+
encode job, wait for completion, and download the MP4 output.
|
|
15
36
|
|
|
16
37
|
```ts
|
|
17
38
|
import {
|
|
18
39
|
Configuration,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
JobProfile,
|
|
41
|
+
JobSource,
|
|
42
|
+
JobStatus,
|
|
43
|
+
JobsApi,
|
|
44
|
+
UploadsApi,
|
|
45
|
+
uploadFile,
|
|
46
|
+
waitForJob,
|
|
47
|
+
} from "@liqhtworks/sophon-sdk";
|
|
48
|
+
import { Blob } from "node:buffer";
|
|
49
|
+
import { randomUUID } from "node:crypto";
|
|
50
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
51
|
+
import { basename } from "node:path";
|
|
52
|
+
|
|
53
|
+
const inputPath = process.argv[2] ?? "./source.mov";
|
|
54
|
+
const apiKey = process.env.SOPHON_API_KEY;
|
|
55
|
+
if (!apiKey) throw new Error("SOPHON_API_KEY is required");
|
|
56
|
+
|
|
57
|
+
const basePath = process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz";
|
|
58
|
+
const config = new Configuration({
|
|
59
|
+
basePath,
|
|
60
|
+
accessToken: apiKey,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const uploads = new UploadsApi(config);
|
|
64
|
+
const jobs = new JobsApi(config);
|
|
65
|
+
|
|
66
|
+
const bytes = await readFile(inputPath);
|
|
67
|
+
const mimeType = inputPath.endsWith(".mov") ? "video/quicktime" : "video/mp4";
|
|
68
|
+
const source = new Blob([bytes], { type: mimeType });
|
|
69
|
+
|
|
70
|
+
const upload = await uploadFile({
|
|
71
|
+
api: uploads,
|
|
72
|
+
source,
|
|
73
|
+
fileName: basename(inputPath),
|
|
74
|
+
mimeType,
|
|
75
|
+
concurrency: 4,
|
|
76
|
+
onProgress: (p) => console.log(`${p.partsDone}/${p.partsTotal} parts`),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const job = await jobs.createJob({
|
|
80
|
+
idempotencyKey: randomUUID(),
|
|
81
|
+
createJobRequest: {
|
|
82
|
+
source: JobSource.upload(upload.uploadId),
|
|
83
|
+
profile: JobProfile.SOPHON_ESPRESSO,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
const final = await waitForJob({
|
|
88
|
+
api: jobs,
|
|
89
|
+
jobId: job.id,
|
|
90
|
+
timeoutMs: 30 * 60 * 1000,
|
|
91
|
+
});
|
|
92
|
+
if (final.status !== JobStatus.COMPLETED) {
|
|
93
|
+
throw new Error(`job ended in ${final.status}`);
|
|
38
94
|
}
|
|
39
95
|
|
|
40
|
-
|
|
41
|
-
|
|
96
|
+
const redirect = await fetch(`${basePath}/v1/jobs/${final.id}/output`, {
|
|
97
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
98
|
+
redirect: "manual",
|
|
99
|
+
});
|
|
100
|
+
const location = redirect.headers.get("location");
|
|
101
|
+
if (!location) throw new Error("missing output redirect");
|
|
102
|
+
|
|
103
|
+
const download = await fetch(new URL(location, basePath));
|
|
104
|
+
await writeFile("sophon-output.mp4", Buffer.from(await download.arrayBuffer()));
|
|
105
|
+
|
|
106
|
+
console.log(`wrote sophon-output.mp4 from ${final.id}`);
|
|
42
107
|
```
|
|
43
108
|
|
|
109
|
+
For a runnable copy of this flow, see
|
|
110
|
+
[`examples/encode-file.mjs`](./examples/encode-file.mjs).
|
|
44
111
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
### API Endpoints
|
|
48
|
-
|
|
49
|
-
All URIs are relative to *https://api.liqhtworks.xyz*
|
|
50
|
-
|
|
51
|
-
| Class | Method | HTTP request | Description
|
|
52
|
-
| ----- | ------ | ------------ | -------------
|
|
53
|
-
*DownloadsApi* | [**download**](docs/DownloadsApi.md#download) | **GET** /v1/downloads/{token} | Download an output file via signed token
|
|
54
|
-
*HealthApi* | [**healthz**](docs/HealthApi.md#healthz) | **GET** /healthz | Liveness probe
|
|
55
|
-
*HealthApi* | [**readyz**](docs/HealthApi.md#readyz) | **GET** /readyz | Readiness probe
|
|
56
|
-
*JobsApi* | [**cancelJob**](docs/JobsApi.md#canceljob) | **DELETE** /v1/jobs/{id} | Cancel a job
|
|
57
|
-
*JobsApi* | [**createJob**](docs/JobsApi.md#createjoboperation) | **POST** /v1/jobs | Submit an encoding job
|
|
58
|
-
*JobsApi* | [**getJob**](docs/JobsApi.md#getjob) | **GET** /v1/jobs/{id} | Get a single job by ID
|
|
59
|
-
*JobsApi* | [**getJobOutput**](docs/JobsApi.md#getjoboutput) | **GET** /v1/jobs/{id}/output | Get the encoded output file
|
|
60
|
-
*JobsApi* | [**listJobs**](docs/JobsApi.md#listjobs) | **GET** /v1/jobs | List jobs with cursor pagination
|
|
61
|
-
*UploadsApi* | [**cancelUpload**](docs/UploadsApi.md#cancelupload) | **DELETE** /v1/uploads/{id} | Cancel an upload session
|
|
62
|
-
*UploadsApi* | [**completeUpload**](docs/UploadsApi.md#completeupload) | **POST** /v1/uploads/{id}/complete | Finalize a chunked upload
|
|
63
|
-
*UploadsApi* | [**createUpload**](docs/UploadsApi.md#createuploadoperation) | **POST** /v1/uploads | Initialize a chunked upload session
|
|
64
|
-
*UploadsApi* | [**getUpload**](docs/UploadsApi.md#getupload) | **GET** /v1/uploads/{id} | Get upload session status
|
|
65
|
-
*UploadsApi* | [**uploadPart**](docs/UploadsApi.md#uploadpart) | **PUT** /v1/uploads/{id}/parts/{part_number} | Upload a single chunk
|
|
66
|
-
*WebhooksApi* | [**createWebhook**](docs/WebhooksApi.md#createwebhookoperation) | **POST** /v1/webhooks | Register a webhook endpoint
|
|
67
|
-
*WebhooksApi* | [**deleteWebhook**](docs/WebhooksApi.md#deletewebhook) | **DELETE** /v1/webhooks/{id} | Soft-delete a webhook endpoint
|
|
68
|
-
*WebhooksApi* | [**listWebhooks**](docs/WebhooksApi.md#listwebhooks) | **GET** /v1/webhooks | List active webhook endpoints
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
### Models
|
|
72
|
-
|
|
73
|
-
- [CompleteUploadResponse](docs/CompleteUploadResponse.md)
|
|
74
|
-
- [CreateJobOutputOptions](docs/CreateJobOutputOptions.md)
|
|
75
|
-
- [CreateJobRequest](docs/CreateJobRequest.md)
|
|
76
|
-
- [CreateUploadRequest](docs/CreateUploadRequest.md)
|
|
77
|
-
- [CreateUploadResponse](docs/CreateUploadResponse.md)
|
|
78
|
-
- [CreateWebhookRequest](docs/CreateWebhookRequest.md)
|
|
79
|
-
- [ErrorBody](docs/ErrorBody.md)
|
|
80
|
-
- [ErrorEnvelope](docs/ErrorEnvelope.md)
|
|
81
|
-
- [JobOutputInfo](docs/JobOutputInfo.md)
|
|
82
|
-
- [JobProfile](docs/JobProfile.md)
|
|
83
|
-
- [JobProgress](docs/JobProgress.md)
|
|
84
|
-
- [JobResponse](docs/JobResponse.md)
|
|
85
|
-
- [JobSourceInfo](docs/JobSourceInfo.md)
|
|
86
|
-
- [JobSourceType](docs/JobSourceType.md)
|
|
87
|
-
- [JobStatus](docs/JobStatus.md)
|
|
88
|
-
- [ListJobsResponse](docs/ListJobsResponse.md)
|
|
89
|
-
- [OutputContainer](docs/OutputContainer.md)
|
|
90
|
-
- [ReadyResponse](docs/ReadyResponse.md)
|
|
91
|
-
- [UploadJobSource](docs/UploadJobSource.md)
|
|
92
|
-
- [UploadPartResponse](docs/UploadPartResponse.md)
|
|
93
|
-
- [UploadStatusResponse](docs/UploadStatusResponse.md)
|
|
94
|
-
- [WebhookDeliveryPayload](docs/WebhookDeliveryPayload.md)
|
|
95
|
-
- [WebhookListItem](docs/WebhookListItem.md)
|
|
96
|
-
- [WebhookListResponse](docs/WebhookListResponse.md)
|
|
97
|
-
- [WebhookResponse](docs/WebhookResponse.md)
|
|
98
|
-
|
|
99
|
-
### Authorization
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Authentication schemes defined for the API:
|
|
103
|
-
<a id="bearerApiKey"></a>
|
|
104
|
-
#### bearerApiKey
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- **Type**: HTTP Bearer Token authentication
|
|
108
|
-
<a id="sessionCookie"></a>
|
|
109
|
-
#### sessionCookie
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
- **Type**: API key
|
|
113
|
-
- **API key parameter name**: `sophon_api_session`
|
|
114
|
-
- **Location**:
|
|
115
|
-
|
|
116
|
-
## About
|
|
117
|
-
|
|
118
|
-
This TypeScript SDK client supports the [Fetch API](https://fetch.spec.whatwg.org/)
|
|
119
|
-
and is automatically generated by the
|
|
120
|
-
[OpenAPI Generator](https://openapi-generator.tech) project:
|
|
121
|
-
|
|
122
|
-
- API version: `1.0.0`
|
|
123
|
-
- Package version: `0.1.0`
|
|
124
|
-
- Generator version: `7.21.0`
|
|
125
|
-
- Build package: `org.openapitools.codegen.languages.TypeScriptFetchClientCodegen`
|
|
126
|
-
|
|
127
|
-
The generated npm module supports the following:
|
|
128
|
-
|
|
129
|
-
- Environments
|
|
130
|
-
* Node.js
|
|
131
|
-
* Webpack
|
|
132
|
-
* Browserify
|
|
133
|
-
- Language levels
|
|
134
|
-
* ES5 - you must have a Promises/A+ library installed
|
|
135
|
-
* ES6
|
|
136
|
-
- Module systems
|
|
137
|
-
* CommonJS
|
|
138
|
-
* ES6 module system
|
|
139
|
-
|
|
140
|
-
For more information, please visit [https://liqhtworks.xyz](https://liqhtworks.xyz)
|
|
112
|
+
For upload-only integration work, see
|
|
113
|
+
[`examples/upload-node-path.mjs`](./examples/upload-node-path.mjs).
|
|
141
114
|
|
|
142
|
-
|
|
115
|
+
### Profile choice
|
|
143
116
|
|
|
144
|
-
|
|
117
|
+
Use `sophon-auto` for production unless you need deterministic encoder
|
|
118
|
+
settings. The quickstart uses `sophon-espresso` because it is the fastest
|
|
119
|
+
smoke-test profile and always produces a new encoded output.
|
|
145
120
|
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
## Webhooks
|
|
122
|
+
|
|
123
|
+
Use `verifyWebhookSignature` with the raw request body before JSON parsing.
|
|
124
|
+
|
|
125
|
+
See [`examples/webhook-server`](./examples/webhook-server) for an Express
|
|
126
|
+
handler that preserves the raw body, verifies `X-Turbo-Signature-256`, and only
|
|
127
|
+
then parses JSON.
|
|
128
|
+
|
|
129
|
+
## Helpers
|
|
130
|
+
|
|
131
|
+
| Helper | Purpose |
|
|
132
|
+
|---|---|
|
|
133
|
+
| `uploadFile` | Chunked upload orchestration with bounded concurrency, retries, resume, and progress callbacks. |
|
|
134
|
+
| `waitForJob` | Poll until terminal status with timeout and typed errors. |
|
|
135
|
+
| `verifyWebhookSignature` | Constant-time HMAC verification plus replay-window enforcement. |
|
|
136
|
+
|
|
137
|
+
## API Docs
|
|
138
|
+
|
|
139
|
+
Generated endpoint/model docs live under [`docs/`](./docs).
|
|
140
|
+
|
|
141
|
+
## Development
|
|
148
142
|
|
|
149
143
|
```bash
|
|
150
144
|
npm install
|
|
151
145
|
npm run build
|
|
152
146
|
```
|
|
153
147
|
|
|
154
|
-
|
|
148
|
+
## Versioning
|
|
155
149
|
|
|
156
|
-
|
|
150
|
+
`@liqhtworks/sophon-sdk` follows [SemVer](https://semver.org/), with one
|
|
151
|
+
pre-1.0 caveat: while we are at `v0.x`, **minor bumps may include
|
|
152
|
+
breaking changes**. Pin a tilde range until 1.0:
|
|
157
153
|
|
|
158
154
|
```bash
|
|
159
|
-
npm
|
|
155
|
+
npm install @liqhtworks/sophon-sdk@~0.1
|
|
160
156
|
```
|
|
161
157
|
|
|
158
|
+
Patch releases (`0.1.x`) are always backward-compatible — they ship bug
|
|
159
|
+
fixes, helper-layer improvements, and additive types. Once we cut
|
|
160
|
+
`v1.0.0`, regular SemVer applies and breaking changes only land on
|
|
161
|
+
major bumps. See [`CHANGELOG.md`](./CHANGELOG.md) for the per-release
|
|
162
|
+
log.
|
|
163
|
+
|
|
162
164
|
## License
|
|
163
165
|
|
|
164
|
-
[
|
|
166
|
+
Proprietary. See [`LICENSE`](./LICENSE).
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
|
@@ -16,4 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./uploads"), exports);
|
|
18
18
|
__exportStar(require("./jobs"), exports);
|
|
19
|
+
__exportStar(require("./sources"), exports);
|
|
19
20
|
__exportStar(require("./webhooks"), exports);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.JobSource = void 0;
|
|
4
|
+
exports.uploadJobSource = uploadJobSource;
|
|
5
|
+
function uploadJobSource(uploadId) {
|
|
6
|
+
return { type: "upload", upload_id: uploadId };
|
|
7
|
+
}
|
|
8
|
+
exports.JobSource = Object.freeze({
|
|
9
|
+
upload: uploadJobSource,
|
|
10
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Configuration,
|
|
3
|
+
JobProfile,
|
|
4
|
+
JobSource,
|
|
5
|
+
JobStatus,
|
|
6
|
+
JobsApi,
|
|
7
|
+
UploadsApi,
|
|
8
|
+
uploadFile,
|
|
9
|
+
waitForJob,
|
|
10
|
+
} from "@liqhtworks/sophon-sdk";
|
|
11
|
+
import { Blob } from "node:buffer";
|
|
12
|
+
import { randomUUID } from "node:crypto";
|
|
13
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
14
|
+
import { basename } from "node:path";
|
|
15
|
+
|
|
16
|
+
const inputPath = process.argv[2];
|
|
17
|
+
if (!inputPath) {
|
|
18
|
+
throw new Error("usage: node examples/encode-file.mjs /path/to/video.mov");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const apiKey = process.env.SOPHON_API_KEY;
|
|
22
|
+
if (!apiKey) throw new Error("SOPHON_API_KEY is required");
|
|
23
|
+
|
|
24
|
+
const basePath = process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz";
|
|
25
|
+
const config = new Configuration({ basePath, accessToken: apiKey });
|
|
26
|
+
const uploads = new UploadsApi(config);
|
|
27
|
+
const jobs = new JobsApi(config);
|
|
28
|
+
|
|
29
|
+
const bytes = await readFile(inputPath);
|
|
30
|
+
const mimeType = inputPath.endsWith(".mov") ? "video/quicktime" : "video/mp4";
|
|
31
|
+
const upload = await uploadFile({
|
|
32
|
+
api: uploads,
|
|
33
|
+
source: new Blob([bytes], { type: mimeType }),
|
|
34
|
+
fileName: basename(inputPath),
|
|
35
|
+
mimeType,
|
|
36
|
+
concurrency: 4,
|
|
37
|
+
onProgress: (p) => console.log(`upload ${p.partsDone}/${p.partsTotal}`),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const job = await jobs.createJob({
|
|
41
|
+
idempotencyKey: randomUUID(),
|
|
42
|
+
createJobRequest: {
|
|
43
|
+
source: JobSource.upload(upload.uploadId),
|
|
44
|
+
profile: JobProfile.SOPHON_ESPRESSO,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
console.log(`created ${job.id}`);
|
|
48
|
+
|
|
49
|
+
const final = await waitForJob({
|
|
50
|
+
api: jobs,
|
|
51
|
+
jobId: job.id,
|
|
52
|
+
timeoutMs: 30 * 60 * 1000,
|
|
53
|
+
onProgress: (j) => console.log(`job ${j.id}: ${j.status}`),
|
|
54
|
+
});
|
|
55
|
+
if (final.status !== JobStatus.COMPLETED) {
|
|
56
|
+
throw new Error(`job ended in ${final.status}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const redirect = await fetch(`${basePath}/v1/jobs/${final.id}/output`, {
|
|
60
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
61
|
+
redirect: "manual",
|
|
62
|
+
});
|
|
63
|
+
const location = redirect.headers.get("location");
|
|
64
|
+
if (!location) throw new Error("missing output redirect");
|
|
65
|
+
|
|
66
|
+
const download = await fetch(new URL(location, basePath));
|
|
67
|
+
if (!download.ok) throw new Error(`download failed: ${download.status}`);
|
|
68
|
+
|
|
69
|
+
await writeFile("sophon-output.mp4", Buffer.from(await download.arrayBuffer()));
|
|
70
|
+
console.log("wrote sophon-output.mp4");
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Configuration,
|
|
3
|
+
UploadsApi,
|
|
4
|
+
uploadFile,
|
|
5
|
+
} from "@liqhtworks/sophon-sdk";
|
|
6
|
+
import { Blob } from "node:buffer";
|
|
7
|
+
import { basename } from "node:path";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
|
|
10
|
+
const path = process.argv[2];
|
|
11
|
+
if (!path) {
|
|
12
|
+
throw new Error("usage: node examples/upload-node-path.mjs /path/to/video.mov");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const config = new Configuration({
|
|
16
|
+
basePath: process.env.SOPHON_BASE_URL ?? "https://api.liqhtworks.xyz",
|
|
17
|
+
accessToken: process.env.SOPHON_API_KEY,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const bytes = await readFile(path);
|
|
21
|
+
const source = new Blob([bytes], { type: "video/quicktime" });
|
|
22
|
+
const uploads = new UploadsApi(config);
|
|
23
|
+
|
|
24
|
+
const upload = await uploadFile({
|
|
25
|
+
api: uploads,
|
|
26
|
+
source,
|
|
27
|
+
fileName: basename(path),
|
|
28
|
+
mimeType: "video/quicktime",
|
|
29
|
+
concurrency: 4,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(upload.uploadId);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sophon-webhook-server-example",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "node src/server.mjs",
|
|
7
|
+
"check": "node --check src/server.mjs"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@liqhtworks/sophon-sdk": "latest",
|
|
11
|
+
"express": "^4.19.2"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import {
|
|
3
|
+
WebhookSignatureError,
|
|
4
|
+
verifyWebhookSignature,
|
|
5
|
+
} from "@liqhtworks/sophon-sdk";
|
|
6
|
+
|
|
7
|
+
const secret = process.env.SOPHON_WEBHOOK_SECRET;
|
|
8
|
+
if (!secret) {
|
|
9
|
+
throw new Error("SOPHON_WEBHOOK_SECRET is required");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
app.post(
|
|
15
|
+
"/webhooks/sophon",
|
|
16
|
+
express.raw({ type: "application/json", limit: "2mb" }),
|
|
17
|
+
async (req, res) => {
|
|
18
|
+
if (!Buffer.isBuffer(req.body)) {
|
|
19
|
+
res.status(400).send("raw body required");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await verifyWebhookSignature({
|
|
25
|
+
rawBody: req.body,
|
|
26
|
+
signatureHeader: req.get("X-Turbo-Signature-256"),
|
|
27
|
+
timestampHeader: req.get("X-Turbo-Timestamp"),
|
|
28
|
+
secret,
|
|
29
|
+
});
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err instanceof WebhookSignatureError) {
|
|
32
|
+
console.warn("rejected SOPHON webhook", { reason: err.reason });
|
|
33
|
+
}
|
|
34
|
+
res.status(401).send("invalid signature");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const event = JSON.parse(req.body.toString("utf8"));
|
|
39
|
+
console.log("accepted SOPHON webhook", {
|
|
40
|
+
type: event.type,
|
|
41
|
+
id: event.id,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
res.sendStatus(204);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const port = Number(process.env.PORT ?? 3000);
|
|
49
|
+
app.listen(port, () => {
|
|
50
|
+
console.log(`listening on :${port}`);
|
|
51
|
+
});
|
package/package.json
CHANGED
package/src/helpers/index.ts
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface UploadJobSourceLike {
|
|
2
|
+
// `any` keeps the helper assignable to the generated string enum type while
|
|
3
|
+
// still centralizing the only valid runtime discriminator value.
|
|
4
|
+
type: any;
|
|
5
|
+
upload_id: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function uploadJobSource(uploadId: string): UploadJobSourceLike {
|
|
9
|
+
return { type: "upload", upload_id: uploadId };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const JobSource = Object.freeze({
|
|
13
|
+
upload: uploadJobSource,
|
|
14
|
+
});
|