@kumwe/studio-media 0.1.0-beta.3 → 0.1.0-beta.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/README.md CHANGED
@@ -1,7 +1,21 @@
1
1
  # `@kumwe/studio-media`
2
2
 
3
+ [![npm beta][version-badge]][package]
4
+ [![Build][build-badge]][build]
5
+ [![License][license-badge]][license]
6
+
7
+ For build-time beta evaluation, install an exact resolved version:
8
+
9
+ ```sh
10
+ npm install --save-exact @kumwe/studio-media@beta
11
+ ```
12
+
13
+ Keep all Studio packages on the same coordinated release. Hosts deploy prebuilt browser assets;
14
+ Node.js and npm are contributor and build tools. [Integration guidance][integration] describes
15
+ host authority, the release pin and qualification requirements.
16
+
3
17
  Status: governed beta development, still pre-Gate-A and not production-supported. The exact coordinated
4
- version is in the workspace `studio-release.json`; this package supplies orchestration primitives, not a
18
+ version is in the [coordinated release record](https://github.com/kumwe/studio/blob/main/studio-release.json); this package supplies orchestration primitives, not a
5
19
  production media backend.
6
20
 
7
21
  Host-neutral media orchestration. Hosts provide authentication, storage, upload, processing, and
@@ -31,3 +45,11 @@ conforms to the protocol's `media-upload-session` schema. A file larger than the
31
45
  transport rejections surface as a generic `studio.media/upload-failed` diagnostic and never leak
32
46
  raw error text. `cancel()` aborts in-flight work and requests best-effort server-side
33
47
  cancellation, and `retry()` restarts a failed session from authorization under a fresh session id.
48
+
49
+ [version-badge]: https://img.shields.io/npm/v/@kumwe/studio-media/beta
50
+ [package]: https://www.npmjs.com/package/@kumwe/studio-media
51
+ [build-badge]: https://github.com/kumwe/studio/actions/workflows/ci.yml/badge.svg?branch=main
52
+ [build]: https://github.com/kumwe/studio/actions/workflows/ci.yml?query=branch%3Amain
53
+ [license-badge]: https://img.shields.io/github/license/kumwe/studio
54
+ [license]: https://github.com/kumwe/studio/blob/main/LICENSE
55
+ [integration]: https://github.com/kumwe/studio/blob/main/docs/integration/README.md
@@ -1 +1 @@
1
- {"version":3,"file":"upload-controller.d.ts","sourceRoot":"","sources":["../src/upload-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,gBAAgB,CAG1D,CAAC;AAEH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,gBAAgB,CAG5D,CAAC;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,SAAS,CAAC,OAAO,EAAE,4BAA4B,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACjG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACrF,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,MAAM,CAAC;CAC/D;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;AASxE,qBAAa,qBAAqB;;gBAQb,SAAS,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,4BAA4B;IAK1F,IAAW,OAAO,IAAI,kBAAkB,CAKvC;IAEM,MAAM,IAAI,IAAI;IAUR,KAAK,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAS1C,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAU9C,MAAM,CACjB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,CAAC,4BAA4B,EAAE,UAAU,CAAC,GACtD,OAAO,CAAC,kBAAkB,CAAC;CAiJ/B"}
1
+ {"version":3,"file":"upload-controller.d.ts","sourceRoot":"","sources":["../src/upload-controller.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,kBAAkB,EAEvB,KAAK,gBAAgB,EACtB,MAAM,wBAAwB,CAAC;AAEhC,eAAO,MAAM,oBAAoB,EAAE,QAAQ,CAAC,gBAAgB,CAG1D,CAAC;AAEH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,gBAAgB,CAG5D,CAAC;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,SAAS,CAAC,OAAO,EAAE,4BAA4B,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACjG,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACrF,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AAED,MAAM,WAAW,4BAA4B;IAC3C,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,4BAA4B,KAAK,MAAM,CAAC;CAC/D;AAED,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,IAAI,CAAC;AASxE,qBAAa,qBAAqB;;gBAQb,SAAS,EAAE,oBAAoB,EAAE,OAAO,CAAC,EAAE,4BAA4B;IAK1F,IAAW,OAAO,IAAI,kBAAkB,CAKvC;IAEM,MAAM,IAAI,IAAI;IAUR,KAAK,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAS1C,SAAS,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM,IAAI;IAU9C,MAAM,CACjB,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,IAAI,CAAC,4BAA4B,EAAE,UAAU,CAAC,GACtD,OAAO,CAAC,kBAAkB,CAAC;CAkJ/B"}
@@ -1 +1 @@
1
- import{STUDIO_CONTRACT_VERSION as e}from"@kumwe/studio-protocol";export const MEDIA_UPLOAD_FAILURE=Object.freeze({defaultMessage:`The upload could not be completed.`,key:`studio.media/upload-failed`});export const MEDIA_UPLOAD_TOO_LARGE=Object.freeze({defaultMessage:`The file is larger than the host allows for this upload.`,key:`studio.media/upload-too-large`});const r=new Set([`authorized`,`requested`,`transferring`,`verifying`]);export class MediaUploadController{#listeners=new Set;#sessionId;#transport;#abortController;#file;#session;constructor(e,t){this.#transport=e,this.#sessionId=t?.sessionId??(()=>crypto.randomUUID())}get session(){if(this.#session===void 0)throw Error(`No upload session has been started.`);return structuredClone(this.#session)}cancel(){let e=this.#session;e===void 0||!r.has(e.state)||(this.#abortController?.abort(),this.#setSession({...structuredClone(e),state:`cancelled`}),this.#transport.abort(e.id).catch(()=>void 0))}async retry(){let e=this.#session,t=this.#file;if(e?.state!==`failed`||t===void 0)throw Error(`Only a failed upload session can be retried.`);return this.#run(t,structuredClone(e.request))}subscribe(e){return this.#listeners.add(e),this.#session!==void 0&&e(this.session),()=>{this.#listeners.delete(e)}}async upload(e,t){if(this.#session!==void 0&&r.has(this.#session.state))throw Error(`An upload session is already in progress.`);if(e.size<1)throw Error(`Cannot upload an empty file.`);let n={byteSize:e.size,filename:t.filename,mediaType:t.mediaType,purpose:t.purpose};return t.checksum!==void 0&&(n.checksum=t.checksum),this.#file=e,this.#run(e,n)}#fail(){let e=this.#session;if(e===void 0)return;let n={contractVersion:e.contractVersion,failure:{code:`studio.media/upload-failed`,message:{...MEDIA_UPLOAD_FAILURE},severity:`error`},id:e.id,kind:`media-upload-session`,progress:{...e.progress},request:{...e.request},state:`failed`};e.plan!==void 0&&(n.plan={...e.plan}),this.#setSession(n)}async#run(t,r){let i=new AbortController;this.#abortController=i;let a=r.byteSize,o={contractVersion:e,id:this.#sessionId(r),kind:`media-upload-session`,request:r};this.#setSession({...o,progress:{totalBytes:a,transferredBytes:0},state:`requested`});try{let e=await this.#transport.authorize(r,i.signal);if(i.signal.aborted)return this.session;if(a>e.maximumBytes)return this.#setSession({...o,failure:{code:`studio.media/upload-too-large`,message:{...MEDIA_UPLOAD_TOO_LARGE},parameters:{byteSize:a,maximumBytes:e.maximumBytes},severity:`error`},plan:e,progress:{totalBytes:a,transferredBytes:0},state:`failed`}),this.session;this.#setSession({...o,plan:e,progress:{totalBytes:a,transferredBytes:0},state:`authorized`}),this.#setSession({...o,plan:e,progress:{totalBytes:a,transferredBytes:0},state:`transferring`});let s=Math.max(1,e.chunkBytes??a),c=0;for(;c<a;){let n=t.slice(c,Math.min(c+s,a));if(await this.#transport.transfer({data:n,offset:c,sessionId:o.id},i.signal),i.signal.aborted)return this.session;c=Math.min(c+n.size,a),this.#setSession({...o,plan:e,progress:{totalBytes:a,transferredBytes:c},state:`transferring`})}this.#setSession({...o,plan:e,progress:{totalBytes:a,transferredBytes:a},state:`verifying`});let l=await this.#transport.finalize(o.id,i.signal);if(i.signal.aborted)return this.session;this.#setSession({...o,asset:l,plan:e,progress:{totalBytes:a,transferredBytes:a},state:`complete`})}catch{i.signal.aborted||this.#fail()}return this.session}#setSession(e){this.#session=e;for(let e of this.#listeners)e(this.session)}}
1
+ import{STUDIO_CONTRACT_VERSION as e}from"@kumwe/studio-protocol";export const MEDIA_UPLOAD_FAILURE=Object.freeze({defaultMessage:`The upload could not be completed.`,key:`studio.media/upload-failed`});export const MEDIA_UPLOAD_TOO_LARGE=Object.freeze({defaultMessage:`The file is larger than the host allows for this upload.`,key:`studio.media/upload-too-large`});const r=new Set([`authorized`,`requested`,`transferring`,`verifying`]);export class MediaUploadController{#listeners=new Set;#sessionId;#transport;#abortController;#file;#session;constructor(e,t){this.#transport=e,this.#sessionId=t?.sessionId??(()=>crypto.randomUUID())}get session(){if(this.#session===void 0)throw Error(`No upload session has been started.`);return structuredClone(this.#session)}cancel(){let e=this.#session;e===void 0||!r.has(e.state)||(this.#abortController?.abort(),this.#setSession({...structuredClone(e),state:`cancelled`}),this.#transport.abort(e.id).catch(()=>void 0))}async retry(){let e=this.#session,t=this.#file;if(e?.state!==`failed`||t===void 0)throw Error(`Only a failed upload session can be retried.`);return this.#run(t,structuredClone(e.request))}subscribe(e){return this.#listeners.add(e),this.#session!==void 0&&e(this.session),()=>{this.#listeners.delete(e)}}async upload(e,t){if(this.#session!==void 0&&r.has(this.#session.state))throw Error(`An upload session is already in progress.`);if(e.size<1)throw Error(`Cannot upload an empty file.`);let n={byteSize:e.size,filename:t.filename,mediaType:t.mediaType,purpose:t.purpose};return t.checksum!==void 0&&(n.checksum=t.checksum),this.#file=e,this.#run(e,n)}#fail(){let e=this.#session;if(e===void 0)return;let n={contractVersion:e.contractVersion,failure:{code:`studio.media/upload-failed`,message:{...MEDIA_UPLOAD_FAILURE},severity:`error`},id:e.id,kind:`media-upload-session`,progress:{...e.progress},request:{...e.request},state:`failed`};e.plan!==void 0&&(n.plan={...e.plan}),this.#setSession(n)}async#run(t,r){let i=new AbortController;this.#abortController=i;let o=r.byteSize,s={contractVersion:e,id:this.#sessionId(r),kind:`media-upload-session`,request:r};this.#setSession({...s,progress:{totalBytes:o,transferredBytes:0},state:`requested`});try{let e=await this.#transport.authorize(r,i.signal);if(i.signal.aborted)return this.session;let c=a(e);if(o>c.maximumBytes)return this.#setSession({...s,failure:{code:`studio.media/upload-too-large`,message:{...MEDIA_UPLOAD_TOO_LARGE},parameters:{byteSize:o,maximumBytes:c.maximumBytes},severity:`error`},plan:c,progress:{totalBytes:o,transferredBytes:0},state:`failed`}),this.session;this.#setSession({...s,plan:c,progress:{totalBytes:o,transferredBytes:0},state:`authorized`}),this.#setSession({...s,plan:c,progress:{totalBytes:o,transferredBytes:0},state:`transferring`});let l=c.chunkBytes??o,u=0;for(;u<o;){let e=t.slice(u,Math.min(u+l,o));if(await this.#transport.transfer({data:e,offset:u,sessionId:s.id},i.signal),i.signal.aborted)return this.session;u=Math.min(u+e.size,o),this.#setSession({...s,plan:c,progress:{totalBytes:o,transferredBytes:u},state:`transferring`})}this.#setSession({...s,plan:c,progress:{totalBytes:o,transferredBytes:o},state:`verifying`});let d=await this.#transport.finalize(s.id,i.signal);if(i.signal.aborted)return this.session;this.#setSession({...s,asset:d,plan:c,progress:{totalBytes:o,transferredBytes:o},state:`complete`})}catch{i.signal.aborted||this.#fail()}return this.session}#setSession(e){this.#session=e;for(let e of this.#listeners)e(this.session)}}function a(e){if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`The host returned an invalid bounded upload plan.`);let t=Object.getPrototypeOf(e);if(t!==Object.prototype&&t!==null||Object.getOwnPropertySymbols(e).length!==0)throw TypeError(`The host returned an invalid bounded upload plan.`);let n=Object.getOwnPropertyDescriptors(e);if(!Object.hasOwn(n,`maximumBytes`)||!Object.hasOwn(n,`resumable`)||Object.keys(n).some(e=>![`chunkBytes`,`maximumBytes`,`resumable`].includes(e))||Object.values(n).some(e=>!(`value`in e)||!e.enumerable))throw TypeError(`The host returned an invalid bounded upload plan.`);let r=n.maximumBytes?.value,i=n.resumable?.value,a=Object.hasOwn(n,`chunkBytes`)?n.chunkBytes?.value:void 0;if(typeof r!=`number`||!Number.isInteger(r)||r<1||r>1099511627776||typeof i!=`boolean`||Object.hasOwn(n,`chunkBytes`)&&(typeof a!=`number`||!Number.isInteger(a)||a<1024||a>1073741824))throw TypeError(`The host returned an invalid bounded upload plan.`);return{maximumBytes:r,resumable:i,...typeof a==`number`?{chunkBytes:a}:{}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kumwe/studio-media",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/kumwe/studio.git",
@@ -28,7 +28,7 @@
28
28
  "build": "tsc -b && node ../../scripts/minify-package-dist.mjs --package packages/media"
29
29
  },
30
30
  "dependencies": {
31
- "@kumwe/studio-protocol": "0.1.0-beta.3"
31
+ "@kumwe/studio-protocol": "0.1.0-beta.5"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public",