@statedelta/launcher
v0.2.0
Published
StateDelta launcher — resolves a chain of documents into a closed, cacheable Artifact
Maintainers
Readme
@statedelta/launcher
O launcher do StateDelta — resolve uma cadeia de documentos JSON DSL e termina num Artifact fechado e cacheável. É "um navegador" do ecossistema: orquestra Chain + Gateway + Assembly contra um
StateDeltaEnvironment, e entrega o Artifact a uma engine via umLaunchContext(protocolo 1.0).
O que é
O launcher pega a URL de um DeltaDoc (ou StateDoc), resolve a cadeia de
extends root→head, baixa os documentos, valida requires e
assembly, e produz um Artifact — 1 main resolvido + N deps
paralelas. Em launch(), ele monta um LaunchContext (artifact +
config + host + protocolo) e invoca create(ctx) da engine. A engine
se auto-monta; o launcher nunca a dirige.
URL → [chain → gateway → requires → assembly] → Artifact
│
▼
LaunchContext → engine.create(ctx)Ver docs/ARCHITECTURE.md para os internals e
DOCUMENT-PROTOCOL.md +
@statedelta/protocol para o contrato.
Instalação
pnpm add @statedelta/launcherQuick Start
import {
createLauncher,
createEnvironment,
type EngineFactory,
} from "@statedelta/launcher";
import { FileProvider, HttpProvider } from "@statedelta/gateway";
const myEngineFactory: EngineFactory = (ctx) => {
// ctx.artifact — conteúdo resolvido
// ctx.config — env-bag
// ctx.host — schema/cache + getDependency/getResource
// ctx.protocol — "1.0"
return createMyRunningEngine(ctx);
};
const environment = createEnvironment({
engines: { "[email protected]": myEngineFactory },
providers: [new FileProvider(), new HttpProvider()],
resources: { fetch: globalThis.fetch },
}).freeze();
const launcher = createLauncher({ environment });
// resolve() — só Artifact, sem engine instanciada.
const artifact = await launcher.resolve("./save.json");
// launch() — pipeline completo, devolve o handle da engine.
const engine = await launcher.launch("./save.json");API Pública
createLauncher(options?): Launcher
Cria um launcher. options é LauncherOptions (ver abaixo).
Launcher
resolve(uri, options?): Promise<Artifact>
Resolve a cadeia → Artifact. Sem engine instanciada. Valida
requires e assembly — lança ResolutionError em
engine/resource/dep ausente ou modo de assembly divergente. É a saída
cacheável.
launch<TRunning>(uri, options?): Promise<TRunning>
Pipeline completo: resolve() + montagem do LaunchContext +
environment.createEngine(ctx). Devolve o handle de engine produzido
pelo EngineFactory. TRunning é genérico — o launcher nunca nomeia
a API da engine.
launchWithResult<TRunning>(uri, options?): Promise<LaunchResult<TRunning>>
Como launch(), mas devolve { artifact, engine }.
getChain(): Chain · getGateway(): Gateway
Acesso aos componentes configurados, para inspeção.
on(event, handler) · off(event, handler)
Assinatura de eventos do ciclo de resolução.
O Artifact
Saída de resolve() — discriminado por assembly:
type Artifact = {
info: { chain: string[]; requires: { engine, resources }; duration? };
assembly: "merge" | "tail";
main: unknown; // merge: estado | tail: { body, entries[] }
deps: Record<string, { source; integrity; size }>;
engine: string; // engine selecionada (não instanciada)
};- merge —
mainé o estado consolidado:bodyda raiz deepMergeado com cadapatchda chain. - tail —
mainé{ body, entries[] }. A engine replaya osinputopacos de cada entry.
O Host
launch() constrói um Host (PROPOSAL-PROTOCOL-1.0 §4) e o injeta no
LaunchContext:
| Campo | Tipo | Notas |
|---|---|---|
| host.schema | SchemaService | ROOT — validação JSON Schema draft 2020-12 (ajv) |
| host.cache | CacheService | ROOT — cache best-effort, in-memory (Map + TTL) |
| host.getDependency(name) | Promise<unknown> | À la carte — conteúdo de requires.deps[name] via gateway |
| host.getResource(name) | unknown | À la carte — instância de requires.resources[name] no environment |
getDependency/getResource lançam quando o nome não foi
declarado no requires correspondente — erro de programador.
import {
createLauncherHost,
AjvSchemaService,
MapCacheService,
} from "@statedelta/launcher";Você pode substituir schema/cache em createLauncherHost({ schema, cache, ... })
quando precisar (testes, runtime alternativo).
LauncherOptions
| Opção | Descrição |
|---|---|
| environment | FrozenStateDeltaEnvironment — engines + providers + resources. Obrigatório para launch(). |
| gateway / gatewayOptions | Gateway pré-configurado, ou opções para criar um. |
| maxDepth | Profundidade máxima da chain (default 100). |
| extractRefs / validateHeader | Customização da resolução de chain. |
| defaultEngine | Engine fallback quando a chain não declara uma. |
| env | EngineConfig global — env-bag mesclado por LaunchOptions.env por chamada. |
| prefetchDeps | Prefetch paralelo de deps durante a chain (default true). |
| onNodeLoaded / onDepsDiscovered | Callbacks de progresso. |
Eventos
chain:start · chain:node · chain:complete · fetch:start ·
fetch:complete · requires:resolved · requires:failed ·
engine:start · engine:ready · deps:discovered · deps:prefetched ·
deps:prefetch-error · error
launcher.on("chain:complete", (result) => { /* ... */ });
launcher.on("engine:ready", (engine) => { /* ... */ });
launcher.on("error", (err) => { /* ... */ });Progress + Cancel
await launcher.launch("./save.json", {
onProgress: (p) => console.log(`${p.phase}: ${p.percent}%`),
signal: abortController.signal,
});Erros
import {
ChainResolutionError, GatewayLoadError, EngineCreateError,
LauncherConfigError, AbortError,
} from "@statedelta/launcher";
import { ResolutionError, isResolutionError } from "@statedelta/protocol";ResolutionError.code: ENGINE_NOT_AVAILABLE,
ENGINE_NOT_DECLARED, ENGINE_DECLARATION_CONFLICT,
ENGINE_PROTOCOL_INCOMPATIBLE, RESOURCE_NOT_AVAILABLE,
DEP_NOT_AVAILABLE, ASSEMBLY_MODE_CONFLICT.
Autoria de engine
Quem cria uma engine usa o launcher como harness de dev e teste: ele
monta o LaunchContext e invoca o create(ctx) da engine.
| Pacote | Tipo | Papel |
|---|---|---|
| @statedelta/protocol | dependency | O contrato que a engine mira (EngineFactory, LaunchContext, …) — vai inlinado no bundle |
| @statedelta/launcher | devDependency | O harness — exercita o create(ctx); não entra no runtime da engine |
A engine — uma EngineFactory que o bundle exporta como create:
// src/create.ts
import { isMergeArtifact, type EngineFactory } from "@statedelta/protocol";
export const create: EngineFactory = (ctx) => {
const world = isMergeArtifact(ctx.artifact)
? ctx.artifact.main // merge: estado consolidado
: ctx.artifact.main.body; // tail: body (a engine replaya os entries)
return bootMyRuntime(world); // devolve o handle de execução
};O teste — registra o create no mapa engines in-memory e dá launch:
import { createEnvironment, createTestLauncher } from "@statedelta/launcher";
import { create } from "../src/create.js";
const environment = createEnvironment({
engines: { "[email protected]": create },
}).freeze();
const launcher = createTestLauncher({
environment,
fixtures: {
"world.json": {
type: "state",
name: "world",
requires: { engine: "[email protected]" },
body: { /* o DSL da sua engine */ },
},
},
});
const engine = await launcher.launch("world.json"); // → o seu create(ctx) rodacreateTestLauncher serve documentos in-memory (sem I/O); launch()
resolve a chain → Artifact → monta o LaunchContext (com um Host real
— schema ajv + cache) → invoca o seu create. O teste afirma sobre o
handle devolvido.
Pontos:
- Sua própria engine vai no mapa
engines— sem registry, sem manifest, sem buildar o.cjs. Testa ocreate.tsdireto do source; loop de dev rápido. - O
createEngineRegistry/manifest é o caminho de consumo de engines publicadas — não de um autor testando a sua. - Para o release, você builda o bundle
.cjsexportando{ protocol, create }— separado do harness. - Conformance formal de engine (
runEngineConformance) é follow-up do@statedelta/conformance— hoje a verificação são os testes que você escreve com o launcher.
Ver também
docs/ARCHITECTURE.md— internals@statedelta/protocol— contratos (EngineFactory,LaunchContext,Host,Artifact)@statedelta/chain·@statedelta/gatewayDOCUMENT-PROTOCOL.md
Licença
MIT
