@sequio/runtime 0.1.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.
@@ -0,0 +1,46 @@
1
+ /**
2
+ * **Local media assets** for a composition.
3
+ *
4
+ * A composition run by the runtime describes its video imperatively and can pull
5
+ * in local media — an `image` / `video` sitting next to it — through a single
6
+ * host-provided hook. The composition asks for a file by a project-relative path
7
+ * and gets back a `Blob`, which both `ImageSource` and `VideoSource` accept:
8
+ *
9
+ * ```ts
10
+ * import { defineComposition, loadAsset } from '@sequio/runtime';
11
+ * export default defineComposition(async () => {
12
+ * const video = new VideoSource({ src: await loadAsset('./clip.mp4') });
13
+ * const image = new ImageSource({ src: await loadAsset('./assets/photo.jpg') });
14
+ * // …
15
+ * });
16
+ * ```
17
+ *
18
+ * The runtime owns the *contract* (the `loadAsset` symbol on `@sequio/runtime`
19
+ * and the path rules here); the **host** supplies the actual bytes via
20
+ * {@link RuntimeOptions.loadAsset}, so the same `loadAsset('./clip.mp4')` resolves
21
+ * identically wherever the composition runs (contract #3): the browser preview
22
+ * fetches it from the dev server, the Node render reads it off disk, an editor
23
+ * serves it from an upload store. Assets stay **out of the source bundle** (which
24
+ * is text-only), so a large local `.mp4` never bloats what's transferred or
25
+ * committed.
26
+ */
27
+ /**
28
+ * A host's binary-asset resolver: map a project-relative media path (already
29
+ * cleaned by {@link resolveAssetPath}) to its bytes as a `Blob`.
30
+ */
31
+ export type AssetLoader = (path: string) => Promise<Blob>;
32
+ /**
33
+ * Canonicalise a composition's asset reference to a clean project-relative path.
34
+ * Accepts `./clip.mp4`, `clip.mp4` or `/clip.mp4` and returns `clip.mp4`;
35
+ * `./assets/x.png` → `assets/x.png`. Paths resolve against the **project root**
36
+ * (the bundle's virtual `/`). Rejects anything that escapes it (`..`), so a host
37
+ * loader can never be steered outside the project directory.
38
+ */
39
+ export declare function resolveAssetPath(path: string): string;
40
+ /**
41
+ * The default `loadAsset` when a host wires no {@link AssetLoader}: fail loudly
42
+ * (per the repo's "throw, don't render silent black" convention) so a composition
43
+ * that references a local file isn't silently handed nothing.
44
+ */
45
+ export declare const NO_ASSET_LOADER: AssetLoader;
46
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1,16 @@
1
+ /** Which language a file is written in, inferred from its extension. */
2
+ export type SourceLang = 'ts' | 'tsx' | 'js' | 'jsx' | 'json';
3
+ /** Infer the language from a file path's extension (defaults to `ts`). */
4
+ export declare function langOf(path: string): SourceLang;
5
+ export interface CompileResult {
6
+ /** The emitted CommonJS code. */
7
+ code: string;
8
+ /** Syntactic diagnostics as human-readable strings (empty when clean). */
9
+ diagnostics: string[];
10
+ }
11
+ /**
12
+ * Transpile `code` (a TS/JS source) to CommonJS. `.json` is wrapped as a module
13
+ * exporting the parsed value. Throws on a hard parse failure with the file name.
14
+ */
15
+ export declare function compileModule(code: string, fileName: string): CompileResult;
16
+ //# sourceMappingURL=compile.d.ts.map
@@ -0,0 +1,74 @@
1
+ import { AudioEngine, Compositor, ExportOptions, RealtimeClock } from '@sequio/engine';
2
+ import { Composition, CompositionEnv } from './composition';
3
+ /** A portable snapshot of the program: its source files and entry path. */
4
+ export interface RuntimeBundle {
5
+ /** Absolute virtual path → source text. */
6
+ files: Record<string, string>;
7
+ /** Entry module path (e.g. `/index.ts`). */
8
+ entry: string;
9
+ }
10
+ /** A built, initialized composition graph plus a teardown. */
11
+ export interface BuiltComposition {
12
+ compositor: Compositor;
13
+ audioEngine: AudioEngine;
14
+ /** Timeline duration in seconds (builder value, else derived from clip ends). */
15
+ duration: number;
16
+ /** Dispose the compositor and its audio engine. */
17
+ dispose(): void;
18
+ }
19
+ /** A live preview: the built graph, its clock, and media-element-style controls. */
20
+ export interface PreviewHandle {
21
+ readonly built: BuiltComposition;
22
+ readonly clock: RealtimeClock;
23
+ readonly view: HTMLCanvasElement;
24
+ readonly duration: number;
25
+ play(): void;
26
+ pause(): void;
27
+ seek(t: number): void;
28
+ readonly playing: boolean;
29
+ dispose(): void;
30
+ }
31
+ /**
32
+ * Re-links + runs the program for a given environment and returns its
33
+ * {@link Composition}. The runtime supplies this so each build gets a fresh graph
34
+ * whose engine `Compositor` already has the environment's options folded in —
35
+ * which is why user code just writes `new Compositor({ width, height, timebase })`
36
+ * with no env plumbing.
37
+ */
38
+ export type CompositionLinker = (env: CompositionEnv) => Composition;
39
+ export declare class Composer {
40
+ private readonly link;
41
+ private readonly bundle;
42
+ constructor(link: CompositionLinker, bundle: RuntimeBundle);
43
+ /** The entry module path the program ran from. */
44
+ get entry(): string;
45
+ /** The source files, as a fresh copy (edit freely; the Composer keeps its own). */
46
+ get files(): Record<string, string>;
47
+ /**
48
+ * The portable code bundle to hand to server-side rendering: the same files +
49
+ * entry, run by a runtime on the server. This replaces "serialize a spec" —
50
+ * the code itself is what ships, so there's no schema to drift.
51
+ */
52
+ toBundle(): RuntimeBundle;
53
+ /** `JSON.stringify(composer)` yields the portable bundle. */
54
+ toJSON(): RuntimeBundle;
55
+ /**
56
+ * Run the builder to a fresh, initialized graph. The caller owns the result and
57
+ * must {@link BuiltComposition.dispose} it. `env` lets a host inject a renderer
58
+ * (Node) or an output scale; it defaults to the browser (`{}`).
59
+ */
60
+ build(env?: Partial<CompositionEnv>): Promise<BuiltComposition>;
61
+ /**
62
+ * Build the graph and start a preview loop. If `container` is given the
63
+ * compositor's canvas is appended to it. Returns a {@link PreviewHandle} with
64
+ * play/pause/seek; the first frame renders immediately so the canvas is never
65
+ * blank before playback starts.
66
+ */
67
+ preview(container?: HTMLElement, env?: Partial<CompositionEnv>): Promise<PreviewHandle>;
68
+ /**
69
+ * Render the composition to a video `Blob` in the client. Builds a fresh graph,
70
+ * runs the engine's {@link Exporter} over `[0, duration]`, then disposes it.
71
+ */
72
+ export(options?: Partial<ExportOptions>, onProgress?: (progress: number) => void): Promise<Blob>;
73
+ }
74
+ //# sourceMappingURL=composer.d.ts.map
@@ -0,0 +1,49 @@
1
+ import { AudioEngine, Compositor, CompositorOptions } from '@sequio/engine';
2
+ /** Brand marking a value produced by {@link defineComposition}. */
3
+ export declare const COMPOSITION_TAG: "@sequio/runtime:composition";
4
+ /**
5
+ * The live object graph a builder returns: an initialized {@link Compositor} plus
6
+ * (optionally) the {@link AudioEngine} driving its audio, and the timeline length.
7
+ */
8
+ export interface CompositionResult {
9
+ /** The built + `init()`-ed compositor to preview / export / server-render. */
10
+ compositor: Compositor;
11
+ /** The audio engine for preview playback and the export mix, if any. */
12
+ audioEngine?: AudioEngine;
13
+ /**
14
+ * Timeline duration in seconds. Optional — when omitted it's derived from the
15
+ * largest clip end across the compositor's tracks (see {@link deriveDuration}).
16
+ */
17
+ duration?: number;
18
+ }
19
+ /**
20
+ * The environment a composition is built in. The same builder runs for client
21
+ * preview, client export and server render; `compositorOptions` is how the host
22
+ * injects what differs (a Node WebGPU renderer, an output scale), so portable
23
+ * code spreads it into `new Compositor({ ... })`.
24
+ */
25
+ export interface CompositionEnv {
26
+ /** Merge into the `Compositor` options. Browser: `{}`; Node: `{ createRenderer, resolution }`. */
27
+ readonly compositorOptions: Partial<CompositorOptions>;
28
+ /** Where this build runs, in case user code wants to branch (e.g. output scale). */
29
+ readonly target: 'preview' | 'export' | 'server';
30
+ }
31
+ /** A function that builds a live composition. May be async (it `await`s `init()`). */
32
+ export type CompositionBuilder = (env: CompositionEnv) => CompositionResult | Promise<CompositionResult>;
33
+ /** A tagged builder returned by {@link defineComposition}. */
34
+ export interface Composition {
35
+ readonly __tag: typeof COMPOSITION_TAG;
36
+ readonly build: CompositionBuilder;
37
+ }
38
+ /** Whether `value` is a {@link Composition} produced by {@link defineComposition}. */
39
+ export declare function isComposition(value: unknown): value is Composition;
40
+ /**
41
+ * Tag a builder function as a composition. Kept intentionally thin — all the
42
+ * power is in the builder body (real engine classes, real control flow); this
43
+ * just marks the value so the runtime can tell "the user returned a composition"
44
+ * from "the user returned something else", and fails loudly if handed a non-function.
45
+ */
46
+ export declare function defineComposition(build: CompositionBuilder): Composition;
47
+ /** Largest clip end across every track of a compositor (the timeline duration). */
48
+ export declare function deriveDuration(compositor: Compositor): number;
49
+ //# sourceMappingURL=composition.d.ts.map
package/dist/index.cjs ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const d=require("typescript"),f=require("@sequio/engine");function R(s){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(s){for(const t in s)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(s,t);Object.defineProperty(e,t,n.get?n:{enumerable:!0,get:()=>s[t]})}}return e.default=s,Object.freeze(e)}const x=R(f);function u(s){const e=s.startsWith("/"),t=[];for(const n of s.split("/"))n===""||n==="."||(n===".."?t.length>0&&t[t.length-1]!==".."?t.pop():e||t.push(".."):t.push(n));return(e?"/":"")+t.join("/")}function y(s){const e=u(s),t=e.lastIndexOf("/");return t<=0?"/":e.slice(0,t)}function T(s,e){return e.startsWith("/")?u(e):u(`${s}/${e}`)}class m{files=new Map;constructor(e={}){for(const[t,n]of Object.entries(e))this.writeFile(t,n)}writeFile(e,t){this.files.set(u(e.startsWith("/")?e:`/${e}`),t)}deleteFile(e){return this.files.delete(u(e.startsWith("/")?e:`/${e}`))}readFile(e){const t=this.files.get(u(e));return t===void 0?null:t}exists(e){return this.files.has(u(e))}listFiles(){return[...this.files.keys()].sort()}}function S(s){return s.endsWith(".tsx")?"tsx":s.endsWith(".ts")?"ts":s.endsWith(".jsx")?"jsx":s.endsWith(".json")?"json":s.endsWith(".js")||s.endsWith(".mjs")||s.endsWith(".cjs")?"js":"ts"}function O(s,e){const t=S(e);if(t==="json"){let r;try{r=JSON.parse(s)}catch(l){throw new Error(`Failed to parse JSON module ${e}: ${l.message}`)}return{code:`module.exports = ${JSON.stringify(r)};`,diagnostics:[]}}const n=d.transpileModule(s,{fileName:e,reportDiagnostics:!0,compilerOptions:{module:d.ModuleKind.CommonJS,target:d.ScriptTarget.ES2020,esModuleInterop:!0,isolatedModules:!0,jsx:t==="tsx"||t==="jsx"?d.JsxEmit.Preserve:d.JsxEmit.None,allowJs:!0}}),o=(n.diagnostics??[]).map(r=>d.flattenDiagnosticMessageText(r.messageText,`
2
+ `));return{code:n.outputText,diagnostics:o}}const M=[".ts",".tsx",".js",".jsx",".mjs",".cjs",".json"];class p extends Error{constructor(e,t){super(`Cannot resolve module '${e}' from '${t}'`),this.specifier=e,this.from=t,this.name="ModuleResolutionError"}}class C{fs;externals;compile;cache=new Map;constructor(e){this.fs=e.fs,this.externals=e.externals??{},this.compile=e.compile??((t,n)=>O(t,n).code)}resolve(e,t){const n=T(t,e),o=[n,...M.map(r=>n+r),...M.map(r=>u(`${n}/index${r}`))];for(const r of o)if(this.fs.exists(r))return r;return null}isBare(e){return!e.startsWith("./")&&!e.startsWith("../")&&!e.startsWith("/")}require(e,t){if(this.isBare(e)){if(e in this.externals)return this.externals[e];throw new p(e,t)}const n=this.resolve(e,y(t));if(!n)throw new p(e,t);return this.load(n)}load(e){const t=u(e),n=this.cache.get(t);if(n)return n.exports;const o=this.fs.readFile(t);if(o===null)throw new Error(`File not found: ${t}`);const r={exports:{}};this.cache.set(t,r);const l=this.compile(o,t),h=c=>this.require(c,t),i=y(t),a=new Function("exports","require","module","__filename","__dirname",l);try{a(r.exports,h,r,t,i)}catch(c){if(this.cache.delete(t),c instanceof p)throw c;const g=c instanceof Error?c.message:String(c);throw new Error(`Error executing module '${t}': ${g}`)}return r.exports}run(e){const t=this.fs.exists(e)?u(e):this.resolve(e.startsWith(".")?e:`./${e}`,"/");if(!t)throw new Error(`Entry module not found: ${e}`);return this.load(t)}}function I(s){const e=[];for(const t of s.replace(/\\/g,"/").split("/"))if(!(t===""||t===".")){if(t==="..")throw new Error(`asset path escapes the project root: ${s}`);e.push(t)}if(e.length===0)throw new Error(`empty asset path: ${s}`);return e.join("/")}const b=s=>{throw new Error(`loadAsset('${s}'): this runtime has no asset loader. The host must pass RuntimeOptions.loadAsset (the sequio CLI does this for both preview and render). To avoid a local file entirely, reference a network URL instead.`)},E="@sequio/runtime:composition";function j(s){return typeof s=="object"&&s!==null&&s.__tag===E&&typeof s.build=="function"}function v(s){if(typeof s!="function")throw new Error("defineComposition expects a builder function (env) => { compositor, duration }.");return{__tag:E,build:s}}function $(s){let e=0;for(const t of s.getTracks())for(const n of t.clips)n.end>e&&(e=n.end);return e}const W={compositorOptions:{},target:"export"};class k{constructor(e,t){this.link=e,this.bundle=t}get entry(){return this.bundle.entry}get files(){return{...this.bundle.files}}toBundle(){return{files:{...this.bundle.files},entry:this.bundle.entry}}toJSON(){return this.toBundle()}async build(e={}){const t={...W,...e},n=await this.link(t).build(t),o=n.compositor,r=n.duration??$(o),l=n.audioEngine??new f.AudioEngine(new f.Timebase(30));return{compositor:o,audioEngine:l,duration:r,dispose(){l.dispose(),o.dispose()}}}async preview(e,t={}){const n=await this.build({target:"preview",...t}),{compositor:o,audioEngine:r,duration:l}=n,h=o.view;e&&e.append(h);const i=new f.RealtimeClock;i.duration=l;let a=!1;const c=i.onTick(w=>o.renderPreview(w)),g=i.onEnded(()=>{a=!1,r.pause()});return i.seek(0),o.renderPreview(0),{built:n,clock:i,view:h,duration:l,get playing(){return a},play(){a=!0,i.play(),r.play(i.currentTime)},pause(){a=!1,i.pause(),r.pause()},seek(w){i.seek(w),o.renderPreview(i.currentTime),a&&r.seek(i.currentTime)},dispose(){a=!1,c.unsubscribe(),g.unsubscribe(),i.pause(),n.dispose()}}}async export(e={},t){const n=await this.build({target:"export"});try{return await new f.Exporter(n.compositor,n.audioEngine).export({fps:30,range:[0,n.duration],audio:!1,...e},t)}finally{n.dispose()}}}const A={defineComposition:v,isComposition:j,COMPOSITION_TAG:E,loadAsset:b},D="@sequio/runtime",F="@sequio/engine",_=["/index.ts","/index.tsx","/index.js","/main.ts","/main.js"];function L(s){if(Object.keys(s).length===0)return x;class e extends x.Compositor{constructor(n){super({...n,...s})}}return{...x,Compositor:e}}function U(s){return s?s instanceof m||typeof s.readFile=="function"?s:new m(s):new m}function P(s){let e=s;if(e&&typeof e=="object"&&"default"in e&&(e=e.default),j(e))return e;if(typeof e=="function")return v(e);throw new Error("Entry module must export (as default) a Composition from defineComposition(builder) or a builder function.")}class N{fs;entry;hostExternals;compileCache=new Map;constructor(e={}){this.fs=U(e.files),this.entry=e.entry;const t=e.loadAsset??b,n={...A,loadAsset:o=>t(I(o))};this.hostExternals={[D]:n,...e.externals}}get fileSystem(){return this.fs}resolveEntry(){if(this.entry)return this.entry;for(const e of _)if(this.fs.exists(e))return e;throw new Error(`No entry module. Provide one, or add ${_.join(" / ")}. Have: ${this.fs.listFiles().join(", ")||"(no files)"}`)}toBundle(e){const t={};for(const n of this.fs.listFiles()){const o=this.fs.readFile(n);o!==null&&(t[n]=o)}return{files:t,entry:e}}compile=(e,t)=>{const n=`${t}\0${e}`;let o=this.compileCache.get(n);return o===void 0&&(o=O(e,t).code,this.compileCache.set(n,o)),o};link(e){const t={[F]:L(e.compositorOptions),...this.hostExternals},n=new C({fs:this.fs,externals:t,compile:this.compile});return P(n.run(this.resolveEntry()))}runToComposition(){return this.link({compositorOptions:{},target:"preview"})}async run(){const e=this.resolveEntry();return this.runToComposition(),new k(t=>this.link(t),this.toBundle(e))}}async function q(s,e={}){return new N({files:s,...e}).run()}exports.COMPOSITION_TAG=E;exports.Composer=k;exports.ENGINE_MODULE_ID=F;exports.InMemoryFileSystem=m;exports.ModuleResolutionError=p;exports.ModuleRuntime=C;exports.NO_ASSET_LOADER=b;exports.RUNTIME_MODULE_API=A;exports.RUNTIME_MODULE_ID=D;exports.Runtime=N;exports.compileModule=O;exports.defineComposition=v;exports.deriveDuration=$;exports.dirname=y;exports.isComposition=j;exports.joinPath=T;exports.langOf=S;exports.normalizePath=u;exports.resolveAssetPath=I;exports.runComposition=q;
3
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,26 @@
1
+ /**
2
+ * `@sequio/runtime` — compile and run multi-file TS/JS into a
3
+ * {@link Composer}.
4
+ *
5
+ * The runtime takes a set of source files (an in-memory {@link InMemoryFileSystem}
6
+ * or an injected real one), transpiles each with the TypeScript compiler, links
7
+ * them with a tiny CommonJS loader, and runs the entry. The program describes a
8
+ * video **imperatively** — it `new`s the engine's own classes inside a
9
+ * `defineComposition(builder)` (same style as the `example/` demos), so a user
10
+ * can bring their own `Clip` / `Effect` subclasses with no schema to keep in
11
+ * sync. The result is a {@link Composer} that previews and exports in the browser
12
+ * and whose `toBundle()` (the source files themselves) is what feeds server-side
13
+ * rendering — one object, three destinations.
14
+ *
15
+ * Everything here is browser-safe. A Node/real-filesystem adapter lives in
16
+ * `@sequio/runtime/node-fs` (imports `node:fs`, kept out of this
17
+ * barrel) so importing the runtime never drags Node built-ins into a bundle.
18
+ */
19
+ export { type FileSystem, InMemoryFileSystem, normalizePath, dirname, joinPath, } from './vfs';
20
+ export { compileModule, langOf, type CompileResult, type SourceLang, } from './compile';
21
+ export { ModuleRuntime, ModuleResolutionError, type Externals, type ModuleRuntimeOptions, } from './module-runtime';
22
+ export { type AssetLoader, resolveAssetPath, NO_ASSET_LOADER, } from './assets';
23
+ export { defineComposition, isComposition, deriveDuration, COMPOSITION_TAG, type Composition, type CompositionBuilder, type CompositionEnv, type CompositionResult, } from './composition';
24
+ export { Runtime, runComposition, RUNTIME_MODULE_API, RUNTIME_MODULE_ID, ENGINE_MODULE_ID, type RuntimeOptions, } from './runtime';
25
+ export { Composer, type PreviewHandle, type RuntimeBundle, type BuiltComposition, } from './composer';
26
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,397 @@
1
+ import d from "typescript";
2
+ import * as m from "@sequio/engine";
3
+ import { AudioEngine as $, Timebase as C, RealtimeClock as M, Exporter as _ } from "@sequio/engine";
4
+ function u(s) {
5
+ const t = s.startsWith("/"), e = [];
6
+ for (const n of s.split("/"))
7
+ n === "" || n === "." || (n === ".." ? e.length > 0 && e[e.length - 1] !== ".." ? e.pop() : t || e.push("..") : e.push(n));
8
+ return (t ? "/" : "") + e.join("/");
9
+ }
10
+ function g(s) {
11
+ const t = u(s), e = t.lastIndexOf("/");
12
+ return e <= 0 ? "/" : t.slice(0, e);
13
+ }
14
+ function k(s, t) {
15
+ return t.startsWith("/") ? u(t) : u(`${s}/${t}`);
16
+ }
17
+ class w {
18
+ files = /* @__PURE__ */ new Map();
19
+ constructor(t = {}) {
20
+ for (const [e, n] of Object.entries(t)) this.writeFile(e, n);
21
+ }
22
+ /** Add/replace a file. Keys are normalized to absolute paths. */
23
+ writeFile(t, e) {
24
+ this.files.set(u(t.startsWith("/") ? t : `/${t}`), e);
25
+ }
26
+ /** Remove a file; returns whether it existed. */
27
+ deleteFile(t) {
28
+ return this.files.delete(u(t.startsWith("/") ? t : `/${t}`));
29
+ }
30
+ readFile(t) {
31
+ const e = this.files.get(u(t));
32
+ return e === void 0 ? null : e;
33
+ }
34
+ exists(t) {
35
+ return this.files.has(u(t));
36
+ }
37
+ listFiles() {
38
+ return [...this.files.keys()].sort();
39
+ }
40
+ }
41
+ function F(s) {
42
+ return s.endsWith(".tsx") ? "tsx" : s.endsWith(".ts") ? "ts" : s.endsWith(".jsx") ? "jsx" : s.endsWith(".json") ? "json" : s.endsWith(".js") || s.endsWith(".mjs") || s.endsWith(".cjs") ? "js" : "ts";
43
+ }
44
+ function v(s, t) {
45
+ const e = F(t);
46
+ if (e === "json") {
47
+ let r;
48
+ try {
49
+ r = JSON.parse(s);
50
+ } catch (l) {
51
+ throw new Error(`Failed to parse JSON module ${t}: ${l.message}`);
52
+ }
53
+ return { code: `module.exports = ${JSON.stringify(r)};`, diagnostics: [] };
54
+ }
55
+ const n = d.transpileModule(s, {
56
+ fileName: t,
57
+ reportDiagnostics: !0,
58
+ compilerOptions: {
59
+ module: d.ModuleKind.CommonJS,
60
+ target: d.ScriptTarget.ES2020,
61
+ esModuleInterop: !0,
62
+ // Keep the transform isolated & fast — no cross-file inference. Matches how
63
+ // the linker treats each file independently.
64
+ isolatedModules: !0,
65
+ jsx: e === "tsx" || e === "jsx" ? d.JsxEmit.Preserve : d.JsxEmit.None,
66
+ // `require`/`module`/`exports` are provided by the linker's wrapper.
67
+ allowJs: !0
68
+ }
69
+ }), o = (n.diagnostics ?? []).map(
70
+ (r) => d.flattenDiagnosticMessageText(r.messageText, `
71
+ `)
72
+ );
73
+ return { code: n.outputText, diagnostics: o };
74
+ }
75
+ const y = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
76
+ class x extends Error {
77
+ constructor(t, e) {
78
+ super(`Cannot resolve module '${t}' from '${e}'`), this.specifier = t, this.from = e, this.name = "ModuleResolutionError";
79
+ }
80
+ }
81
+ class S {
82
+ fs;
83
+ externals;
84
+ compile;
85
+ cache = /* @__PURE__ */ new Map();
86
+ constructor(t) {
87
+ this.fs = t.fs, this.externals = t.externals ?? {}, this.compile = t.compile ?? ((e, n) => v(e, n).code);
88
+ }
89
+ /**
90
+ * Resolve a relative specifier against the VFS to a concrete file path, or
91
+ * `null` if nothing matches. Bare specifiers are handled by {@link require}
92
+ * against `externals` and are not resolved here.
93
+ */
94
+ resolve(t, e) {
95
+ const n = k(e, t), o = [
96
+ n,
97
+ ...y.map((r) => n + r),
98
+ ...y.map((r) => u(`${n}/index${r}`))
99
+ ];
100
+ for (const r of o)
101
+ if (this.fs.exists(r)) return r;
102
+ return null;
103
+ }
104
+ /** Whether a specifier is bare (a package name, not a path). */
105
+ isBare(t) {
106
+ return !t.startsWith("./") && !t.startsWith("../") && !t.startsWith("/");
107
+ }
108
+ /**
109
+ * `require` as seen from a module located at `fromPath`. Returns the resolved
110
+ * module's `exports` (bare specifiers return the injected external verbatim).
111
+ */
112
+ require(t, e) {
113
+ if (this.isBare(t)) {
114
+ if (t in this.externals) return this.externals[t];
115
+ throw new x(t, e);
116
+ }
117
+ const n = this.resolve(t, g(e));
118
+ if (!n) throw new x(t, e);
119
+ return this.load(n);
120
+ }
121
+ /** Load (and cache) the module at an already-resolved absolute path. */
122
+ load(t) {
123
+ const e = u(t), n = this.cache.get(e);
124
+ if (n) return n.exports;
125
+ const o = this.fs.readFile(e);
126
+ if (o === null) throw new Error(`File not found: ${e}`);
127
+ const r = { exports: {} };
128
+ this.cache.set(e, r);
129
+ const l = this.compile(o, e), f = (c) => this.require(c, e), i = g(e), a = new Function(
130
+ "exports",
131
+ "require",
132
+ "module",
133
+ "__filename",
134
+ "__dirname",
135
+ l
136
+ );
137
+ try {
138
+ a(r.exports, f, r, e, i);
139
+ } catch (c) {
140
+ if (this.cache.delete(e), c instanceof x) throw c;
141
+ const h = c instanceof Error ? c.message : String(c);
142
+ throw new Error(`Error executing module '${e}': ${h}`);
143
+ }
144
+ return r.exports;
145
+ }
146
+ /** Load the entry module and return its `exports`. */
147
+ run(t) {
148
+ const e = this.fs.exists(t) ? u(t) : this.resolve(t.startsWith(".") ? t : `./${t}`, "/");
149
+ if (!e) throw new Error(`Entry module not found: ${t}`);
150
+ return this.load(e);
151
+ }
152
+ }
153
+ function A(s) {
154
+ const t = [];
155
+ for (const e of s.replace(/\\/g, "/").split("/"))
156
+ if (!(e === "" || e === ".")) {
157
+ if (e === "..") throw new Error(`asset path escapes the project root: ${s}`);
158
+ t.push(e);
159
+ }
160
+ if (t.length === 0) throw new Error(`empty asset path: ${s}`);
161
+ return t.join("/");
162
+ }
163
+ const b = (s) => {
164
+ throw new Error(
165
+ `loadAsset('${s}'): this runtime has no asset loader. The host must pass RuntimeOptions.loadAsset (the sequio CLI does this for both preview and render). To avoid a local file entirely, reference a network URL instead.`
166
+ );
167
+ }, E = "@sequio/runtime:composition";
168
+ function O(s) {
169
+ return typeof s == "object" && s !== null && s.__tag === E && typeof s.build == "function";
170
+ }
171
+ function T(s) {
172
+ if (typeof s != "function")
173
+ throw new Error("defineComposition expects a builder function (env) => { compositor, duration }.");
174
+ return { __tag: E, build: s };
175
+ }
176
+ function W(s) {
177
+ let t = 0;
178
+ for (const e of s.getTracks())
179
+ for (const n of e.clips) n.end > t && (t = n.end);
180
+ return t;
181
+ }
182
+ const I = { compositorOptions: {}, target: "export" };
183
+ class R {
184
+ constructor(t, e) {
185
+ this.link = t, this.bundle = e;
186
+ }
187
+ /** The entry module path the program ran from. */
188
+ get entry() {
189
+ return this.bundle.entry;
190
+ }
191
+ /** The source files, as a fresh copy (edit freely; the Composer keeps its own). */
192
+ get files() {
193
+ return { ...this.bundle.files };
194
+ }
195
+ /**
196
+ * The portable code bundle to hand to server-side rendering: the same files +
197
+ * entry, run by a runtime on the server. This replaces "serialize a spec" —
198
+ * the code itself is what ships, so there's no schema to drift.
199
+ */
200
+ toBundle() {
201
+ return { files: { ...this.bundle.files }, entry: this.bundle.entry };
202
+ }
203
+ /** `JSON.stringify(composer)` yields the portable bundle. */
204
+ toJSON() {
205
+ return this.toBundle();
206
+ }
207
+ /**
208
+ * Run the builder to a fresh, initialized graph. The caller owns the result and
209
+ * must {@link BuiltComposition.dispose} it. `env` lets a host inject a renderer
210
+ * (Node) or an output scale; it defaults to the browser (`{}`).
211
+ */
212
+ async build(t = {}) {
213
+ const e = { ...I, ...t }, n = await this.link(e).build(e), o = n.compositor, r = n.duration ?? W(o), l = n.audioEngine ?? new $(new C(30));
214
+ return {
215
+ compositor: o,
216
+ audioEngine: l,
217
+ duration: r,
218
+ dispose() {
219
+ l.dispose(), o.dispose();
220
+ }
221
+ };
222
+ }
223
+ /**
224
+ * Build the graph and start a preview loop. If `container` is given the
225
+ * compositor's canvas is appended to it. Returns a {@link PreviewHandle} with
226
+ * play/pause/seek; the first frame renders immediately so the canvas is never
227
+ * blank before playback starts.
228
+ */
229
+ async preview(t, e = {}) {
230
+ const n = await this.build({ target: "preview", ...e }), { compositor: o, audioEngine: r, duration: l } = n, f = o.view;
231
+ t && t.append(f);
232
+ const i = new M();
233
+ i.duration = l;
234
+ let a = !1;
235
+ const c = i.onTick((p) => o.renderPreview(p)), h = i.onEnded(() => {
236
+ a = !1, r.pause();
237
+ });
238
+ return i.seek(0), o.renderPreview(0), {
239
+ built: n,
240
+ clock: i,
241
+ view: f,
242
+ duration: l,
243
+ get playing() {
244
+ return a;
245
+ },
246
+ play() {
247
+ a = !0, i.play(), r.play(i.currentTime);
248
+ },
249
+ pause() {
250
+ a = !1, i.pause(), r.pause();
251
+ },
252
+ seek(p) {
253
+ i.seek(p), o.renderPreview(i.currentTime), a && r.seek(i.currentTime);
254
+ },
255
+ dispose() {
256
+ a = !1, c.unsubscribe(), h.unsubscribe(), i.pause(), n.dispose();
257
+ }
258
+ };
259
+ }
260
+ /**
261
+ * Render the composition to a video `Blob` in the client. Builds a fresh graph,
262
+ * runs the engine's {@link Exporter} over `[0, duration]`, then disposes it.
263
+ */
264
+ async export(t = {}, e) {
265
+ const n = await this.build({ target: "export" });
266
+ try {
267
+ return await new _(n.compositor, n.audioEngine).export(
268
+ { fps: 30, range: [0, n.duration], audio: !1, ...t },
269
+ e
270
+ );
271
+ } finally {
272
+ n.dispose();
273
+ }
274
+ }
275
+ }
276
+ const N = {
277
+ defineComposition: T,
278
+ isComposition: O,
279
+ COMPOSITION_TAG: E,
280
+ loadAsset: b
281
+ }, D = "@sequio/runtime", L = "@sequio/engine", j = ["/index.ts", "/index.tsx", "/index.js", "/main.ts", "/main.js"];
282
+ function q(s) {
283
+ if (Object.keys(s).length === 0) return m;
284
+ class t extends m.Compositor {
285
+ constructor(n) {
286
+ super({ ...n, ...s });
287
+ }
288
+ }
289
+ return { ...m, Compositor: t };
290
+ }
291
+ function J(s) {
292
+ return s ? s instanceof w || typeof s.readFile == "function" ? s : new w(s) : new w();
293
+ }
294
+ function U(s) {
295
+ let t = s;
296
+ if (t && typeof t == "object" && "default" in t && (t = t.default), O(t)) return t;
297
+ if (typeof t == "function") return T(t);
298
+ throw new Error(
299
+ "Entry module must export (as default) a Composition from defineComposition(builder) or a builder function."
300
+ );
301
+ }
302
+ class B {
303
+ fs;
304
+ entry;
305
+ hostExternals;
306
+ /** Transpiled output cached across re-links (linking runs once per build). */
307
+ compileCache = /* @__PURE__ */ new Map();
308
+ constructor(t = {}) {
309
+ this.fs = J(t.files), this.entry = t.entry;
310
+ const e = t.loadAsset ?? b, n = {
311
+ ...N,
312
+ loadAsset: (o) => e(A(o))
313
+ };
314
+ this.hostExternals = {
315
+ [D]: n,
316
+ ...t.externals
317
+ };
318
+ }
319
+ /** The filesystem backing this runtime (add/edit files then re-`run`). */
320
+ get fileSystem() {
321
+ return this.fs;
322
+ }
323
+ resolveEntry() {
324
+ if (this.entry) return this.entry;
325
+ for (const t of j) if (this.fs.exists(t)) return t;
326
+ throw new Error(
327
+ `No entry module. Provide one, or add ${j.join(" / ")}. Have: ${this.fs.listFiles().join(", ") || "(no files)"}`
328
+ );
329
+ }
330
+ /** Snapshot the current files into a portable {@link RuntimeBundle}. */
331
+ toBundle(t) {
332
+ const e = {};
333
+ for (const n of this.fs.listFiles()) {
334
+ const o = this.fs.readFile(n);
335
+ o !== null && (e[n] = o);
336
+ }
337
+ return { files: e, entry: t };
338
+ }
339
+ compile = (t, e) => {
340
+ const n = `${e}\0${t}`;
341
+ let o = this.compileCache.get(n);
342
+ return o === void 0 && (o = v(t, e).code, this.compileCache.set(n, o)), o;
343
+ };
344
+ /**
345
+ * Compile + link + run the entry for a given environment and return the
346
+ * {@link Composition} it produced. Called once per build so the injected engine
347
+ * `Compositor` carries that environment's options (implicit `compositorOptions`)
348
+ * and each build gets independent module state. Transpilation is cached, so
349
+ * re-linking only re-executes the (cheap) module bodies.
350
+ */
351
+ link(t) {
352
+ const e = {
353
+ [L]: q(t.compositorOptions),
354
+ ...this.hostExternals
355
+ }, n = new S({ fs: this.fs, externals: e, compile: this.compile });
356
+ return U(n.run(this.resolveEntry()));
357
+ }
358
+ /**
359
+ * Compile + run the program and return the {@link Composition} its entry
360
+ * produced (the tagged builder), without wrapping it in a `Composer`. Uses the
361
+ * plain browser environment (no injected renderer).
362
+ */
363
+ runToComposition() {
364
+ return this.link({ compositorOptions: {}, target: "preview" });
365
+ }
366
+ /** Compile + run the program and wrap its result in a {@link Composer}. */
367
+ async run() {
368
+ const t = this.resolveEntry();
369
+ return this.runToComposition(), new R((e) => this.link(e), this.toBundle(t));
370
+ }
371
+ }
372
+ async function G(s, t = {}) {
373
+ return new B({ files: s, ...t }).run();
374
+ }
375
+ export {
376
+ E as COMPOSITION_TAG,
377
+ R as Composer,
378
+ L as ENGINE_MODULE_ID,
379
+ w as InMemoryFileSystem,
380
+ x as ModuleResolutionError,
381
+ S as ModuleRuntime,
382
+ b as NO_ASSET_LOADER,
383
+ N as RUNTIME_MODULE_API,
384
+ D as RUNTIME_MODULE_ID,
385
+ B as Runtime,
386
+ v as compileModule,
387
+ T as defineComposition,
388
+ W as deriveDuration,
389
+ g as dirname,
390
+ O as isComposition,
391
+ k as joinPath,
392
+ F as langOf,
393
+ u as normalizePath,
394
+ A as resolveAssetPath,
395
+ G as runComposition
396
+ };
397
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,41 @@
1
+ import { FileSystem } from './vfs';
2
+ /** A module value keyed by a bare specifier the sandbox can `require`. */
3
+ export type Externals = Record<string, unknown>;
4
+ export interface ModuleRuntimeOptions {
5
+ fs: FileSystem;
6
+ /** Bare-specifier → module value (e.g. the runtime API, the engine). */
7
+ externals?: Externals;
8
+ /** Transform a source file to CommonJS. Defaults to {@link compileModule}. */
9
+ compile?: (code: string, fileName: string) => string;
10
+ }
11
+ /** A resolution failure that names the specifier and the importer. */
12
+ export declare class ModuleResolutionError extends Error {
13
+ readonly specifier: string;
14
+ readonly from: string;
15
+ constructor(specifier: string, from: string);
16
+ }
17
+ export declare class ModuleRuntime {
18
+ private readonly fs;
19
+ private readonly externals;
20
+ private readonly compile;
21
+ private readonly cache;
22
+ constructor(options: ModuleRuntimeOptions);
23
+ /**
24
+ * Resolve a relative specifier against the VFS to a concrete file path, or
25
+ * `null` if nothing matches. Bare specifiers are handled by {@link require}
26
+ * against `externals` and are not resolved here.
27
+ */
28
+ resolve(specifier: string, fromDir: string): string | null;
29
+ /** Whether a specifier is bare (a package name, not a path). */
30
+ private isBare;
31
+ /**
32
+ * `require` as seen from a module located at `fromPath`. Returns the resolved
33
+ * module's `exports` (bare specifiers return the injected external verbatim).
34
+ */
35
+ require(specifier: string, fromPath: string): unknown;
36
+ /** Load (and cache) the module at an already-resolved absolute path. */
37
+ load(path: string): unknown;
38
+ /** Load the entry module and return its `exports`. */
39
+ run(entryPath: string): unknown;
40
+ }
41
+ //# sourceMappingURL=module-runtime.d.ts.map
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("node:fs"),o=require("node:path");function l(s,t){const e=t.replace(/^\/+/,"");return o.resolve(s,e)}function u(s,t){return"/"+o.relative(s,t).split(o.sep).join("/")}class a{constructor(t){this.rootDir=t}readFile(t){const e=l(this.rootDir,t);try{return!r.existsSync(e)||!r.statSync(e).isFile()?null:r.readFileSync(e,"utf8")}catch{return null}}exists(t){const e=l(this.rootDir,t);try{return r.existsSync(e)&&r.statSync(e).isFile()}catch{return!1}}listFiles(){const t=[],e=n=>{for(const i of r.readdirSync(n,{withFileTypes:!0})){if(i.name==="node_modules"||i.name.startsWith("."))continue;const c=o.join(n,i.name);i.isDirectory()?e(c):i.isFile()&&t.push(u(this.rootDir,c))}};try{e(this.rootDir)}catch{}return t.sort()}}exports.NodeFileSystem=a;
2
+ //# sourceMappingURL=node-fs.cjs.map
@@ -0,0 +1,9 @@
1
+ import { FileSystem } from './vfs';
2
+ export declare class NodeFileSystem implements FileSystem {
3
+ private readonly rootDir;
4
+ constructor(rootDir: string);
5
+ readFile(path: string): string | null;
6
+ exists(path: string): boolean;
7
+ listFiles(): string[];
8
+ }
9
+ //# sourceMappingURL=node-fs.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { existsSync as n, statSync as l, readFileSync as u, readdirSync as a } from "node:fs";
2
+ import { join as f, resolve as h, relative as y, sep as m } from "node:path";
3
+ function c(i, t) {
4
+ const r = t.replace(/^\/+/, "");
5
+ return h(i, r);
6
+ }
7
+ function d(i, t) {
8
+ return "/" + y(i, t).split(m).join("/");
9
+ }
10
+ class D {
11
+ constructor(t) {
12
+ this.rootDir = t;
13
+ }
14
+ readFile(t) {
15
+ const r = c(this.rootDir, t);
16
+ try {
17
+ return !n(r) || !l(r).isFile() ? null : u(r, "utf8");
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+ exists(t) {
23
+ const r = c(this.rootDir, t);
24
+ try {
25
+ return n(r) && l(r).isFile();
26
+ } catch {
27
+ return !1;
28
+ }
29
+ }
30
+ listFiles() {
31
+ const t = [], r = (s) => {
32
+ for (const e of a(s, { withFileTypes: !0 })) {
33
+ if (e.name === "node_modules" || e.name.startsWith(".")) continue;
34
+ const o = f(s, e.name);
35
+ e.isDirectory() ? r(o) : e.isFile() && t.push(d(this.rootDir, o));
36
+ }
37
+ };
38
+ try {
39
+ r(this.rootDir);
40
+ } catch {
41
+ }
42
+ return t.sort();
43
+ }
44
+ }
45
+ export {
46
+ D as NodeFileSystem
47
+ };
48
+ //# sourceMappingURL=node-fs.js.map
@@ -0,0 +1,93 @@
1
+ import { CompositorOptions } from '@sequio/engine';
2
+ import { Composer } from './composer';
3
+ import { Composition, CompositionEnv, defineComposition, isComposition } from './composition';
4
+ import { Externals } from './module-runtime';
5
+ import { FileSystem } from './vfs';
6
+ import { AssetLoader } from './assets';
7
+ /**
8
+ * The module object exposed to sandboxed code as `@sequio/runtime`.
9
+ *
10
+ * `loadAsset` here is the *default* (no host loader → throws {@link NO_ASSET_LOADER});
11
+ * a {@link Runtime} instance overrides it with the host's {@link AssetLoader} so a
12
+ * composition's `loadAsset('./clip.mp4')` reaches real bytes.
13
+ */
14
+ export declare const RUNTIME_MODULE_API: {
15
+ defineComposition: typeof defineComposition;
16
+ isComposition: typeof isComposition;
17
+ COMPOSITION_TAG: "@sequio/runtime:composition";
18
+ loadAsset: AssetLoader;
19
+ };
20
+ /** Bare specifier under which the authoring API is injected. */
21
+ export declare const RUNTIME_MODULE_ID = "@sequio/runtime";
22
+ /** Bare specifier under which the engine namespace is injected. */
23
+ export declare const ENGINE_MODULE_ID = "@sequio/engine";
24
+ export interface RuntimeOptions {
25
+ /** Source files. Either a ready {@link FileSystem} or a path→content map. */
26
+ files?: FileSystem | Record<string, string>;
27
+ /** Entry module path. @default '/index.ts' (falls back to `/index.js`, `/main.ts`). */
28
+ entry?: string;
29
+ /**
30
+ * Extra bare modules user code may `import`. Merged over the built-in
31
+ * `@sequio/engine` and `@sequio/runtime` modules
32
+ * (host entries win on key collision), so a host can expose more libraries.
33
+ */
34
+ externals?: Externals;
35
+ /**
36
+ * Resolver for **local media assets** a composition pulls in with
37
+ * `loadAsset('./clip.mp4')` (imported from `@sequio/runtime`). The host maps a
38
+ * project-relative path to its bytes as a `Blob`; the runtime normalizes the
39
+ * path ({@link resolveAssetPath}) before calling it. Left unset, `loadAsset`
40
+ * throws a clear error — assets are opt-in and never bundled. See
41
+ * [`assets.ts`](./assets.ts).
42
+ */
43
+ loadAsset?: AssetLoader;
44
+ }
45
+ /**
46
+ * A copy of the engine namespace whose `Compositor` folds `overrides` into every
47
+ * construction. This is how `env.compositorOptions` is injected **implicitly**:
48
+ * user code writes a plain `new Compositor({ width, height, timebase })` — exactly
49
+ * like a demo — and a host (a Node WebGPU renderer, an output scale) still reaches
50
+ * it. Overrides win on conflict so a server's `createRenderer` takes precedence.
51
+ * With no overrides (the browser default) the real namespace is returned as-is.
52
+ */
53
+ export declare function engineForEnv(overrides: Partial<CompositorOptions>): Externals[string];
54
+ export declare class Runtime {
55
+ private readonly fs;
56
+ private readonly entry;
57
+ private readonly hostExternals;
58
+ /** Transpiled output cached across re-links (linking runs once per build). */
59
+ private readonly compileCache;
60
+ constructor(options?: RuntimeOptions);
61
+ /** The filesystem backing this runtime (add/edit files then re-`run`). */
62
+ get fileSystem(): FileSystem;
63
+ private resolveEntry;
64
+ /** Snapshot the current files into a portable {@link RuntimeBundle}. */
65
+ private toBundle;
66
+ private compile;
67
+ /**
68
+ * Compile + link + run the entry for a given environment and return the
69
+ * {@link Composition} it produced. Called once per build so the injected engine
70
+ * `Compositor` carries that environment's options (implicit `compositorOptions`)
71
+ * and each build gets independent module state. Transpilation is cached, so
72
+ * re-linking only re-executes the (cheap) module bodies.
73
+ */
74
+ link(env: CompositionEnv): Composition;
75
+ /**
76
+ * Compile + run the program and return the {@link Composition} its entry
77
+ * produced (the tagged builder), without wrapping it in a `Composer`. Uses the
78
+ * plain browser environment (no injected renderer).
79
+ */
80
+ runToComposition(): Composition;
81
+ /** Compile + run the program and wrap its result in a {@link Composer}. */
82
+ run(): Promise<Composer>;
83
+ }
84
+ /**
85
+ * One-shot convenience: build a {@link Runtime} from `files`/options and run it.
86
+ *
87
+ * ```ts
88
+ * const composer = await runComposition({ '/index.ts': "export default defineComposition(async () => {…})" });
89
+ * await composer.preview(document.body);
90
+ * ```
91
+ */
92
+ export declare function runComposition(files: RuntimeOptions['files'], options?: Omit<RuntimeOptions, 'files'>): Promise<Composer>;
93
+ //# sourceMappingURL=runtime.d.ts.map
package/dist/vfs.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A minimal **virtual filesystem** the module runtime reads source files from.
3
+ *
4
+ * The runtime never touches `node:fs` directly — it resolves and reads modules
5
+ * through this interface. That keeps the core browser-safe (the studio "code
6
+ * mode" runs entirely in the tab against an {@link InMemoryFileSystem}) while
7
+ * letting a host inject a **real** filesystem instead: any object satisfying
8
+ * {@link FileSystem} works, so a Node caller can wrap `node:fs` (see
9
+ * `src/node-fs.ts`, kept out of the browser barrel).
10
+ *
11
+ * Paths are POSIX-style, absolute from a virtual root `/`. The runtime
12
+ * normalizes relative imports to absolute paths before calling {@link readFile}.
13
+ */
14
+ export interface FileSystem {
15
+ /** Return the file's text, or `null` if it doesn't exist. */
16
+ readFile(path: string): string | null;
17
+ /** Whether a regular file exists at `path`. */
18
+ exists(path: string): boolean;
19
+ /** Every file path this filesystem can serve (used for diagnostics). */
20
+ listFiles(): string[];
21
+ }
22
+ /** Collapse `.`/`..` segments and force a single leading `/`. */
23
+ export declare function normalizePath(path: string): string;
24
+ /** Directory portion of a normalized path (`/a/b/c.ts` → `/a/b`). */
25
+ export declare function dirname(path: string): string;
26
+ /** Join a base directory with a (possibly relative) specifier, normalized. */
27
+ export declare function joinPath(base: string, specifier: string): string;
28
+ /**
29
+ * An in-memory {@link FileSystem} backed by a plain path→content map. This is
30
+ * the default for the browser (the code-mode editor keeps its files here) and
31
+ * makes the module runtime unit-testable with no disk.
32
+ */
33
+ export declare class InMemoryFileSystem implements FileSystem {
34
+ private readonly files;
35
+ constructor(files?: Record<string, string>);
36
+ /** Add/replace a file. Keys are normalized to absolute paths. */
37
+ writeFile(path: string, content: string): void;
38
+ /** Remove a file; returns whether it existed. */
39
+ deleteFile(path: string): boolean;
40
+ readFile(path: string): string | null;
41
+ exists(path: string): boolean;
42
+ listFiles(): string[];
43
+ }
44
+ //# sourceMappingURL=vfs.d.ts.map
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@sequio/runtime",
3
+ "version": "0.1.0",
4
+ "description": "Compile and run multi-file TS/JS (virtual or injected real filesystem) into a Composer that renders/exports in the client or feeds server-side rendering.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "slightc",
8
+ "homepage": "https://sequio-demo.vercel.app/",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/slightc/sequio.git",
12
+ "directory": "packages/runtime"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/slightc/sequio/issues"
16
+ },
17
+ "main": "./dist/index.cjs",
18
+ "module": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "require": "./dist/index.cjs"
25
+ },
26
+ "./node-fs": {
27
+ "types": "./dist/node-fs.d.ts",
28
+ "import": "./dist/node-fs.js",
29
+ "require": "./dist/node-fs.cjs"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "!dist/**/*.map"
35
+ ],
36
+ "sideEffects": false,
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "keywords": [
44
+ "typescript",
45
+ "compile",
46
+ "virtual-filesystem",
47
+ "video-editor",
48
+ "composer"
49
+ ],
50
+ "dependencies": {
51
+ "typescript": "^5.7.0",
52
+ "@sequio/engine": "^0.1.1"
53
+ },
54
+ "scripts": {
55
+ "dev": "vite",
56
+ "build": "vite build",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
60
+ "verify:runtime": "node scripts/verify-page.cjs example/runtime-test.html __RUNTIME_TEST__ \"runtime compile+run\""
61
+ }
62
+ }