@loom-forge/forge 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,746 @@
1
+ import { DescriptorRegistry, CommandScope, CommandAlias, EventBinding, DefNode, BindingEngine, ResolveTreeOptions, ResolvedNode } from '@loom-forge/core';
2
+ export { DefNode, Descriptor, DescriptorRegistry, EventBinding, ResolvedNode, createDescriptorRegistry } from '@loom-forge/core';
3
+ import { CycleInfo } from '@tslite/graph';
4
+ export { CycleInfo } from '@tslite/graph';
5
+
6
+ /** O que se deriva de UM componente. */
7
+ interface ComponentFacts {
8
+ /** tags próprias — as que o autor declarou, e as que a forma implica. */
9
+ ownTags: {
10
+ declared: readonly string[];
11
+ inferred: readonly string[];
12
+ };
13
+ /** arestas de saída por eixo. `contains` fica FORA do `outgoing` (containment ≠ dep). */
14
+ deps: Readonly<Record<DepAxis, readonly string[]>>;
15
+ }
16
+ /** O que se deriva de UM nó. */
17
+ interface NodeFacts {
18
+ /** de-para que este nó cria. */
19
+ edges: readonly IrEdge[];
20
+ /** comportamento inferido a nível de nó (ex.: `interactive` = tem evento). */
21
+ tags?: readonly string[];
22
+ }
23
+ /** Referência a um alvo que não existe em registry nenhum. */
24
+ interface DependencyError {
25
+ componentId: string;
26
+ code: "UNRESOLVED_TARGET";
27
+ target: string;
28
+ axis: DepAxis;
29
+ message: string;
30
+ }
31
+ interface Facts {
32
+ /** local, por componente. */
33
+ components: ReadonlyMap<string, ComponentFacts>;
34
+ /** local, por nó. */
35
+ nodes: ReadonlyMap<Address, NodeFacts>;
36
+ /** `uses ∪ usesRegistered ∪ actions ∪ dataRefs` — o grafo que propaga. */
37
+ outgoing: ReadonlyMap<string, ReadonlySet<string>>;
38
+ /** alvos ausentes. Fato ("não existe"), não veredito ("é proibido"). */
39
+ errors: readonly DependencyError[];
40
+ /** transitivo: dependentes ("quem usa isto") — por onde o dirty sobe. */
41
+ incoming?: ReadonlyMap<string, readonly string[]>;
42
+ /** transitivo: tags efetivas, canônicas (ordenadas, sem repetição). */
43
+ tags?: ReadonlyMap<string, readonly string[]>;
44
+ /** transitivo: componentes fortemente conexos. */
45
+ cycles?: CycleInfo;
46
+ }
47
+ /** Os fatos de um componente, ou `undefined` se ele não está no conjunto. */
48
+ declare const factsOf: (facts: Facts, componentId: string) => ComponentFacts | undefined;
49
+ /** Os fatos de um nó, pelo endereço. */
50
+ declare const nodeFactsOf: (facts: Facts, address: Address) => NodeFacts | undefined;
51
+ /** As tags EFETIVAS (próprias + herdadas) de um nó de grafo. Vazio sem o overlay. */
52
+ declare const effectiveTags: (facts: Facts, id: string) => readonly string[];
53
+ interface DeriveFactsOptions {
54
+ /** registry de descriptors — own-tags de React registrado (`meta.behavior`) + detecção de ausente. */
55
+ registry?: DescriptorRegistry;
56
+ /** tags de uma action externa (via `transparentActionProvider`). Futuro. */
57
+ actionTags?: (id: string) => readonly string[];
58
+ /** ids de actions conhecidas — habilita a detecção de ausente no eixo `actions`. */
59
+ knownActions?: ReadonlySet<string>;
60
+ /**
61
+ * O vocabulário de comando que o HOST instala na raiz (o `commands` do `ForgeView`). É a
62
+ * borda: um nome de comando que chega a um componente que o host monta é resolvido aqui, e,
63
+ * sem ele, vale como o id que vai para a porta.
64
+ */
65
+ commands?: CommandScope;
66
+ /**
67
+ * As camadas TRANSITIVAS. Custo zero quando ausentes: quem só quer as arestas locais não
68
+ * paga o ponto-fixo. Default: todas ligadas (é o que o `buildSystemIR` sempre fez).
69
+ */
70
+ overlays?: {
71
+ tags?: boolean;
72
+ incoming?: boolean;
73
+ cycles?: boolean;
74
+ };
75
+ }
76
+ /**
77
+ * Deriva os fatos de um conjunto de componentes compilados.
78
+ *
79
+ * Tudo sai da árvore normalizada — nenhum dado a mais entra. As camadas locais (arestas, deps,
80
+ * own-tags) são um salto; as transitivas (tags efetivas, invertido, ciclos) são ponto-fixo sobre
81
+ * o grafo, que TEM ciclo (um componente compõe outro que o compõe de volta) — daí o
82
+ * `@tslite/graph`, e não uma ordem topológica.
83
+ */
84
+ declare function deriveFacts(components: Iterable<ComponentIR>, opts?: DeriveFactsOptions): Facts;
85
+
86
+ type Address = string;
87
+ declare const componentAddress: (component: string) => Address;
88
+ /** `comp#0` = raiz do corpo; `comp#0.1.2` = caminho de índices de filho a partir da raiz. */
89
+ declare const nodeAddress: (component: string, indexPath: readonly number[]) => Address;
90
+ declare const parseAddress: (addr: Address) => {
91
+ component: string;
92
+ path: number[] | null;
93
+ };
94
+ /** Tipo declarado de uma prop/state/context. DADO (shorthand | Schema do TSLite | JSON Schema).
95
+ * A resolução p/ Schema do TSLite é do `./check` — o core IR não importa TSLite. */
96
+ type TypeRef = string | Record<string, unknown>;
97
+ type PropsDecl = Record<string, TypeRef>;
98
+ interface StateDecl {
99
+ type: TypeRef;
100
+ /**
101
+ * O que SEMEIA esta chave — expressão sobre `props` e `ctx`, avaliada **uma vez, no mount**.
102
+ *
103
+ * É a semântica do `useState(props.x)` do React: lê na montagem e o setter assume dali em
104
+ * diante. O escopo NÃO tem `state` (é o que está sendo definido) nem `params` (não há
105
+ * chamada), e o valor é provado contra o `type` declarado ao lado.
106
+ *
107
+ * Constante continua constante: `0` atravessa o bind intacto.
108
+ */
109
+ init: unknown;
110
+ }
111
+ /** Uma ação de UPDATE local (a DSL de estado, v1): seta campos do estado a valores/expressões
112
+ * (`{{ }}` avaliado contra as raízes `props`/`state`/`params`). Vocabulário no espírito das
113
+ * directives do statedelta; v1 só `set`. Port-ready: trocar o interpretador é reimplementar
114
+ * `applyUpdate`. */
115
+ /**
116
+ * Um EFEITO — a intenção que um componente dispara por conta própria.
117
+ *
118
+ * **É um events-out cujo gatilho é uma mudança de dependência, em vez de um gesto.** Mesma
119
+ * `action` (local primeiro, senão o `ActionPort` do host), mesmo `params`, mesma aresta no
120
+ * grafo, mesma tipagem. O que muda é quem puxa o gatilho.
121
+ *
122
+ * É o que torna o componente impuro de verdade: quem declara efeito é VIVO, e a tag
123
+ * `effectful` propaga pelo grafo como qualquer outra.
124
+ */
125
+ interface EffectDecl {
126
+ /**
127
+ * Os CAMINHOS que disparam — `"props.id"`, `"state.aberto"`, `"ctx.tema"`.
128
+ *
129
+ * `[]` = só no mount: é a convenção do React, e sem string mágica — sem dependência, não há
130
+ * o que reexecutar. Caminho pontilhado; nome com ponto dentro não é suportado.
131
+ */
132
+ on: string[];
133
+ /** a intenção: ação LOCAL primeiro, senão o `ActionPort` do host. */
134
+ action: string;
135
+ /** os params da intenção — `event` é `undefined` aqui: um efeito não recebe nada (ADR-018). */
136
+ params?: unknown;
137
+ /**
138
+ * A cadeia, como num evento (ADR-018): o `then` roda quando a intenção resolve, com `event` =
139
+ * o `data` da ação (ou `undefined` depois de um update local); o `catch`, quando o despacho ao
140
+ * host falha, com `event` = o erro. É por aqui que "carregar no mount" GUARDA o que carregou.
141
+ */
142
+ then?: EventBinding;
143
+ catch?: EventBinding;
144
+ /** no unmount, e ANTES de reexecutar. Mesma forma de um `EventBinding`, cadeia inclusa. */
145
+ cleanup?: EventBinding;
146
+ }
147
+ interface UpdateDecl {
148
+ /** os ARGUMENTOS que este update aceita — os mesmos tipos de uma `PropsDecl`.
149
+ *
150
+ * Sem eles, `params` é dado de fora **não declarado**: vale `unknown`, e escrevê-lo direto
151
+ * num estado tipado não passa. Não é aspereza — é a mesma fronteira das props: dado que
152
+ * entra sem contrato precisa ser declarado ou provado. Declará-los também tipa o call-site
153
+ * do evento que o dispara. */
154
+ params?: PropsDecl;
155
+ set?: Record<string, unknown>;
156
+ }
157
+ /** Contrato de um SLOT de composição (children tipado). Um pai declara o que aceita como
158
+ * filho — sem conhecer o filho concreto (composição ≠ dependência; o filho é opaco, só o
159
+ * CONTRATO é conhecido). Checado no SITE de composição. NOVO vs React (lá todo filho é leaf
160
+ * opaco). Eixos ESTRUTURAIS (puros). O eixo de PROPS (`Component<C>` por subtype) é do
161
+ * `./check` e vem com a integração do tipo — ver decisão deferida. */
162
+ interface SlotContract {
163
+ /** o filho deve ter categoria ∈ (declarada do filho). string ou lista. */
164
+ category?: string | string[];
165
+ /** o filho deve ter (pelo menos) estas tags de comportamento EFETIVAS (propagadas). */
166
+ tags?: string[];
167
+ /** o filho não pode ter filhos próprios no site de composição (deve ser folha). */
168
+ leaf?: boolean;
169
+ /** no máximo 1 filho neste slot (não lista). */
170
+ single?: boolean;
171
+ /** cardinalidade. */
172
+ min?: number;
173
+ max?: number;
174
+ }
175
+ /** Um componente custom declarado em JSON. Isomorfo a uma Action: `id + árvore` (fn JS). */
176
+ interface ComponentDef {
177
+ /** nome = id do nó de grafo (estável). */
178
+ component: string;
179
+ /** público/rota vs privado (sessão interna). Default: false. */
180
+ entry?: boolean;
181
+ /** categoria arquitetural (ui/data/feature…) — eixo das regras de direção (fase 2). */
182
+ category?: string;
183
+ /** tipos das props que aceita. */
184
+ props?: PropsDecl;
185
+ /** os contextos que este componente CONSOME — nomes do catálogo do sistema. É a declaração
186
+ * que põe `ctx` no escopo dele: nada entra implicitamente. */
187
+ context?: {
188
+ uses: string[];
189
+ };
190
+ /** os contextos que este componente PROVÊ para o próprio corpo — `nome → expressão`.
191
+ * Açúcar para um `provides` na raiz da árvore: é um mecanismo só, em dois lugares. */
192
+ provides?: Record<string, unknown>;
193
+ /** o vocabulário de COMANDOS que este componente instala para o próprio corpo. Mesmo açúcar do
194
+ * `provides`, no eixo do comportamento: uma sessão declara os comandos dela aqui. */
195
+ commands?: Record<string, CommandAlias>;
196
+ /** estado local declarado, escopado ao componente (NUNCA global). */
197
+ state?: Record<string, StateDecl>;
198
+ /** os UPDATES locais (a lógica "suja"): mutam o estado local da instância. Presença de `updates`
199
+ * torna o componente VIVO → boundary React por instância; `events.action` resolve LOCAL
200
+ * primeiro. NÃO é uma action no sentido do node-actions: não tem id no runtime, não entra em
201
+ * palette e não tem disponibilidade. Ver ADR-014. */
202
+ updates?: Record<string, UpdateDecl>;
203
+ /** EFEITOS: intenções disparadas por MUDANÇA, não por gesto. Presença também torna o
204
+ * componente VIVO — é literalmente o que o torna impuro. */
205
+ effects?: Record<string, EffectDecl>;
206
+ /** contratos dos slots de composição (children tipado). Chave = nome do slot ("children"). */
207
+ slots?: Record<string, SlotContract>;
208
+ /** tags de comportamento DECLARADAS (own.declared). */
209
+ tags?: string[];
210
+ /** gate de capability: `cap → versão | "*"`. */
211
+ requires?: Record<string, string>;
212
+ /** o corpo. */
213
+ body: DefNode;
214
+ version?: string;
215
+ }
216
+ /** Registry de DEFINIÇÕES de componente custom (SoT). Headless — só ComponentDefs, sem React.
217
+ * É o `type → árvore` que a expansão consome no render, e o `names()` alimenta o
218
+ * `CompileCtx.components` da análise. Encadeável. */
219
+ interface ComponentDefRegistry {
220
+ register(def: ComponentDef): ComponentDefRegistry;
221
+ get(name: string): ComponentDef | undefined;
222
+ has(name: string): boolean;
223
+ list(): ComponentDef[];
224
+ /** os nomes registrados — uso direto como `CompileCtx.components`. */
225
+ names(): Set<string>;
226
+ }
227
+ declare function createComponentDefRegistry(initial?: ComponentDef[]): ComponentDefRegistry;
228
+ type NodeKind = "component" | "registered" | "element";
229
+ /** Eixos de dependência, separados (§6/decisão #1). `outgoing` = todos MENOS `contains`. */
230
+ type DepAxis = "uses" | "usesRegistered" | "actions" | "dataRefs" | "contains";
231
+ interface IrEdge {
232
+ to: string;
233
+ /**
234
+ * `action` é um id que sai para a aplicação; `update`, uma chamada interna; `command`, um nome
235
+ * de vocabulário que este documento NÃO resolve — o que ele executa depende de onde o
236
+ * componente montar, e quem responde é o componente que o monta.
237
+ */
238
+ rel: "uses" | "action" | "update" | "command" | "dataref";
239
+ }
240
+ /** O nó normalizado (a "diretiva" endereçável). Molde do GuideNode do overlay. */
241
+ interface NodeIR {
242
+ address: Address;
243
+ /** id autorado (estável). */
244
+ id: string;
245
+ type: string;
246
+ kind: NodeKind;
247
+ /** props com ilhas {{}}/#expr INLINE, não avaliadas (decisão #6). */
248
+ props?: Record<string, unknown>;
249
+ events?: Record<string, EventBinding>;
250
+ layout?: string;
251
+ /** opaco pro core (lido pelo planner no resolve). */
252
+ placement?: unknown;
253
+ /** filhos por ENDEREÇO (flat, não aninhado). */
254
+ children: Address[];
255
+ /** each/when (expr inline); a EXPANSÃO acontece no resolve (decisão #6). */
256
+ template?: {
257
+ data?: unknown;
258
+ when?: unknown;
259
+ };
260
+ /** contextos que ESTE nó instala para a subárvore (expr inline, como as props). */
261
+ provides?: Record<string, unknown>;
262
+ /** o vocabulário de comando que ESTE nó instala para a subárvore. */
263
+ commands?: Record<string, CommandAlias>;
264
+ fallback?: "drop";
265
+ requires?: Record<string, string>;
266
+ }
267
+ /** Nível componente — o nó do grafo. */
268
+ interface ComponentIR {
269
+ id: string;
270
+ entry: boolean;
271
+ /** categoria arquitetural — eixo das regras de direção (fase 2). */
272
+ category?: string;
273
+ /** superfície de tipo — formas DECLARADAS (dado). Resolução p/ Schema é do `./check`. */
274
+ props?: PropsDecl;
275
+ context: {
276
+ uses: string[];
277
+ };
278
+ /** o que o componente provê ao próprio corpo (`ComponentDef.provides`, inline). */
279
+ provides?: Record<string, unknown>;
280
+ /** o vocabulário de comando que o componente instala no próprio corpo (inline). */
281
+ commands?: Record<string, CommandAlias>;
282
+ /** os efeitos declarados (inline) — a fonte das arestas de ação de nível componente. */
283
+ effects?: Record<string, EffectDecl>;
284
+ /** os updates locais declarados (inline). Autorados, como `state` e `effects`. */
285
+ updates?: Record<string, UpdateDecl>;
286
+ state: Record<string, StateDecl>;
287
+ /** contratos dos slots de composição (children tipado). */
288
+ slots?: Record<string, SlotContract>;
289
+ /** tags de comportamento DECLARADAS pelo autor, como ele as escreveu. As PRÓPRIAS
290
+ * (declaradas + inferidas) e as EFETIVAS (propagadas) são fato, e moram em `./facts`. */
291
+ tags?: string[];
292
+ requires: Record<string, string>;
293
+ root: Address;
294
+ nodes: Map<Address, NodeIR>;
295
+ version?: string;
296
+ }
297
+ interface SystemIR {
298
+ /** as ÁRVORES normalizadas — só o que o autor escreveu. */
299
+ components: Map<string, ComponentIR>;
300
+ /** o que se DERIVOU sobre elas: arestas, deps, tags, ciclos, alvos ausentes. Dois artefatos
301
+ * que viajam juntos, não um — ver o cabeçalho de `./facts`. */
302
+ facts: Facts;
303
+ }
304
+ interface CompileCtx {
305
+ /** ids de componentes custom conhecidos — classifica `type` como "component". */
306
+ components: ReadonlySet<string>;
307
+ /** registry de descriptors — `type` registrado → "registered" (senão "element"). */
308
+ registry?: DescriptorRegistry;
309
+ }
310
+ /**
311
+ * Compila UMA definição → `ComponentIR`: a árvore **normalizada** — aninhado vira plano e
312
+ * endereçável, o `el` é consumido (a tag vira `type`), cada nó é classificado. PURO e
313
+ * data-independente.
314
+ *
315
+ * **Nada derivado sai daqui.** Arestas, deps e tags são FATO, e moram em `./facts` — a árvore
316
+ * tem o que o autor escreveu, em outra forma. Ver a linha no cabeçalho de `facts.ts`.
317
+ */
318
+ declare function compileComponent(def: ComponentDef, ctx: CompileCtx): ComponentIR;
319
+
320
+ /**
321
+ * Indexa os componentes compilados e **deriva os fatos** sobre eles.
322
+ *
323
+ * O artefato tem duas metades que viajam juntas: a árvore (`components`) e o que se calculou
324
+ * sobre ela (`facts`). Fundir as duas foi o que a decisão #7 do `IR.md` desfez.
325
+ */
326
+ declare function buildSystemIR(components: Iterable<ComponentIR>, opts?: DeriveFactsOptions): SystemIR;
327
+
328
+ /** O papel de um campo da definição. */
329
+ type ComponentFieldRole =
330
+ /** identidade — nunca tocada. */
331
+ "identity"
332
+ /** dado do documento: nome, flag, versão, categoria. Não é código. */
333
+ | "data"
334
+ /** DECLARAÇÃO de tipo (`PropsDecl`, `SlotContract`) — vocabulário do checker, não expressão. */
335
+ | "decl"
336
+ /** carrega nó(s) — a árvore, que a tabela do core cobre. */
337
+ | "tree"
338
+ /** aceita EXPRESSÃO em algum lugar dentro de si — ver `componentSlotsOf`. */
339
+ | "slot";
340
+ declare const COMPONENT_FIELDS: Readonly<Record<keyof ComponentDef, ComponentFieldRole>>;
341
+ /** Um lugar de expressão fora da árvore. O caminho É o endereço, e é o que evita duas verdades. */
342
+ interface ComponentSlot {
343
+ /** `["updates", "add", "set"]` — e o endereço reportado é ele, pontilhado. */
344
+ readonly path: readonly string[];
345
+ readonly value: unknown;
346
+ }
347
+ /**
348
+ * O caminho como o autor o lê: `#0.1.props.title` na árvore (a raiz é o nó `0`), `updates.add.set`
349
+ * fora dela. Derivado — ninguém o lê de volta; quem precisa do caminho guarda a lista.
350
+ */
351
+ declare const addressOf: (path: CompiledPath) => string;
352
+ /**
353
+ * Os lugares de expressão de uma definição, FORA da árvore.
354
+ *
355
+ * A árvore é do `compile()` da linguagem, que já a percorre pela tabela do core. Isto cobre o
356
+ * resto — e existe separado porque quem precisa da resposta não é um só: o runtime (para avaliar),
357
+ * o eject (para traduzir) e a autoria (para diagnosticar no endereço certo).
358
+ */
359
+ declare function componentSlotsOf(def: ComponentDef): ComponentSlot[];
360
+ /** O caminho de um campo, como lista: índice e chave são distintos no tipo. */
361
+ type CompiledPath = readonly (string | number)[];
362
+ /** Algo que a linguagem endereça num nó: um diagnóstico, uma ilha. O forge só reancora. */
363
+ interface Addressed {
364
+ /** o caminho a partir da raiz do nó traduzido — `[0, "props", "value", …]` na porta. */
365
+ readonly path: CompiledPath;
366
+ /** o caminho como o autor o lê. Derivado do `path`; o forge o reescreve junto. */
367
+ readonly address: string;
368
+ }
369
+ /**
370
+ * O que a linguagem devolve ao traduzir um nó: a forma, e — quando ela tem o que dizer — os
371
+ * diagnósticos e as ilhas dele. Os dois últimos são opcionais porque o port do binder
372
+ * (`BindingEngine.compile`) fala só `DefNode`: quem traduz pelo port não inventa listas vazias.
373
+ */
374
+ interface CompiledNode<D extends Addressed = Addressed, I extends Addressed = Addressed> {
375
+ readonly def: DefNode;
376
+ readonly diagnostics?: readonly D[];
377
+ readonly islands?: readonly I[];
378
+ }
379
+ /** Traduz um nó — a metade que a LINGUAGEM sabe fazer, e que entra como função. */
380
+ type CompileNode<D extends Addressed = Addressed, I extends Addressed = Addressed> = (node: DefNode) => CompiledNode<D, I>;
381
+ /** A definição compilada, com o que a linguagem disse sobre ela — na árvore e fora dela. */
382
+ interface CompiledDefinition<D extends Addressed = Addressed, I extends Addressed = Addressed> {
383
+ readonly def: ComponentDef;
384
+ /** os da árvore com o endereço de nó (`#0.1.props.x`); os de fora com o caminho do slot
385
+ * (`updates.somar.set.n`). */
386
+ readonly diagnostics: readonly D[];
387
+ readonly islands: readonly I[];
388
+ }
389
+ /**
390
+ * O tradutor de um PORT — `BindingEngine.compile`, que fala só `DefNode`. Um por binder, sempre
391
+ * o mesmo objeto: é o que faz a memória do `compileDefinition` acertar de um render para o outro.
392
+ * Sem `compile` no port, o nó atravessa como está (a definição já vem compilada, ou ninguém
393
+ * compila).
394
+ */
395
+ declare function compileNodeOf(binder: {
396
+ compile?(node: DefNode): DefNode;
397
+ }): CompileNode;
398
+ /**
399
+ * A definição COMPILADA: mesma forma, ilhas no lugar do texto — na árvore e fora dela —, com
400
+ * os diagnósticos e as ilhas que a linguagem devolveu, cada um no endereço do campo que o
401
+ * produziu.
402
+ *
403
+ * Simétrico ao `compile(node)` da linguagem, e o par dos dois é o artefato derivado inteiro. O
404
+ * SoT continua sendo a definição autorada: esta é recomputada, nunca editada à mão.
405
+ *
406
+ * Memoizado por identidade, como o `bodyOf` — montar um objeto novo a cada render derrubaria o
407
+ * cache de compilação do binder (ADR-109), que é memoizado do mesmo jeito.
408
+ */
409
+ declare function compileDefinition<D extends Addressed = Addressed, I extends Addressed = Addressed>(def: ComponentDef, compile: CompileNode<D, I>): CompiledDefinition<D, I>;
410
+
411
+ /** tipo do nó-marcador que a expansão emite no lugar de um componente VIVO; a view o materializa
412
+ * num boundary React. Carrega `props: { def, callProps }`. */
413
+ declare const STATEFUL_TYPE = "$stateful";
414
+ /**
415
+ * Um componente é VIVO (sujo) se tem UPDATES locais ou efeitos.
416
+ *
417
+ * Sem nenhum dos dois, o estado nunca muda e nada dispara sozinho → expande estaticamente
418
+ * (`state.init` entra no escopo, como um componente puro). Com qualquer um, vira boundary React
419
+ * por instância — e o EFEITO é literalmente o que torna o componente impuro: ele age sem que
420
+ * ninguém o toque.
421
+ */
422
+ declare const isLive: (def: ComponentDef) => boolean;
423
+ /**
424
+ * Estado inicial: o `init` de cada chave, avaliado contra `props` e `ctx`.
425
+ *
426
+ * **Uma vez, no mount** — é a semântica do `useState(props.x)` do React, que é o padrão para
427
+ * SEMEAR: lê a prop na montagem e o setter assume dali em diante. O escopo NÃO tem `state` (é o
428
+ * que está sendo definido) nem `params` (não há chamada).
429
+ *
430
+ * Um `init` constante atravessa o bind intacto, então declarar `0` continua sendo `0`.
431
+ */
432
+ declare const initState: (def: ComponentDef, scope: Readonly<Record<string, unknown>>, binder: BindingEngine) => Record<string, unknown>;
433
+ /** Avalia um payload (os `params` de um efeito, de um cleanup) contra o escopo. */
434
+ declare const bindPayload: (params: unknown, scope: Record<string, unknown>, binder: BindingEngine) => unknown;
435
+ /**
436
+ * Aplica um update ao estado: avalia as expressões do `set` contra o escopo
437
+ * (`{...props, ...state, ...params}`) e devolve o novo estado (merge imutável, PARCIAL).
438
+ *
439
+ * É o port: o v1 é `set`; um interpretador de directives entra aqui.
440
+ */
441
+ declare const applyUpdate: (prev: Record<string, unknown>, update: UpdateDecl, scope: Record<string, unknown>, binder: BindingEngine) => Record<string, unknown>;
442
+
443
+ interface SlotViolation {
444
+ rule: "slot-cardinality" | "slot-leaf" | "slot-category" | "slot-tags";
445
+ /** o COMPOSITOR (componente onde o pai foi usado com filhos). */
446
+ componentId: string;
447
+ /** tipo do nó-pai cujo slot foi violado. */
448
+ parent: string;
449
+ /** nome do slot ("children"). */
450
+ slot: string;
451
+ /** tipo do filho infrator (quando aplicável). */
452
+ child?: string;
453
+ message: string;
454
+ }
455
+ interface CheckSlotsOptions {
456
+ /** registry de descriptors — slots/categoria/behavior de React registrado (via `meta`). */
457
+ registry?: DescriptorRegistry;
458
+ }
459
+ /**
460
+ * Valida cada SITE de composição contra o contrato de slot do pai. Caminha todos os nós de
461
+ * todos os componentes; quando um nó (cujo `type` declara slots) tem filhos, confere-os.
462
+ * Pai/filho podem ser custom (IR) ou React registrado (via `descriptor.meta`).
463
+ */
464
+ declare function checkSlots(sys: SystemIR, opts?: CheckSlotsOptions): SlotViolation[];
465
+
466
+ /** O que o documento instala num ponto da árvore, acima do que chega de fora. */
467
+ interface MountScope {
468
+ /** os contextos providos — só o nome: o tipo é do catálogo, e o valor é de runtime. */
469
+ contexts: ReadonlySet<string>;
470
+ /** o vocabulário de comando, com o mais próximo já vencendo. */
471
+ commands: CommandScope;
472
+ }
473
+ /** a raiz de um componente: nada do documento ainda — tudo o que vale ali veio de fora. */
474
+ declare const EMPTY_MOUNT: MountScope;
475
+ /** A montagem de um SISTEMA de definições — o que cada componente instala nos próprios slots. */
476
+ interface Mounting {
477
+ /**
478
+ * Os escopos em que os filhos de slot de um componente MONTAM, relativos ao nó de chamada —
479
+ * um por `$slot`. `[]` quando eles não montam em lugar nenhum; `undefined` quando o `type`
480
+ * não é componente deste sistema, e aí não há slot: os filhos são filhos comuns.
481
+ */
482
+ slotScopesOf(component: string): readonly MountScope[] | undefined;
483
+ /**
484
+ * Os nomes que ALGUM vocabulário do sistema declara. Um nome livre que não está aqui só pode
485
+ * chegar à porta; um que está depende de onde o componente montar.
486
+ */
487
+ readonly vocabulary: ReadonlySet<string>;
488
+ }
489
+ /**
490
+ * A montagem de um conjunto de componentes: onde cada um monta os filhos de slot, e que nomes
491
+ * algum vocabulário declara. `host` é o vocabulário que a aplicação instala na raiz.
492
+ */
493
+ declare function mountingOf(components: Iterable<ComponentIR>, opts?: {
494
+ host?: CommandScope;
495
+ }): Mounting;
496
+ /** Os escopos de montagem de cada nó de uma árvore `DefNode`, por identidade do nó. */
497
+ declare function mountScopesOf(root: DefNode, mounting: Pick<Mounting, "slotScopesOf">): Map<DefNode, readonly MountScope[]>;
498
+
499
+ /** O catálogo de contextos do sistema: `nome → tipo`. O nome é NOMINAL (com namespace
500
+ * quando o componente cruza a fronteira do app); o tipo é global, e o valor é posicional. */
501
+ type ContextCatalog = Record<string, TypeRef>;
502
+ interface ContextFacts {
503
+ /** por componente: o que ele exige de FORA — o próprio, mais o que escapou dos filhos. */
504
+ required: ReadonlyMap<string, readonly string[]>;
505
+ /**
506
+ * por nó: o que estava provido naquele ponto, contando o que o próprio nó instala. É o que o
507
+ * painel mostra ao selecionar. Um nó que monta em mais de um lugar mostra o que TODOS os
508
+ * pontos proveem; um nó que não monta não aparece.
509
+ */
510
+ providedAt: ReadonlyMap<Address, readonly string[]>;
511
+ /** por nó de CHAMADA: o que o chamado exige e não estava provido ali — o que escapa. */
512
+ unsatisfiedAt: ReadonlyMap<Address, readonly string[]>;
513
+ }
514
+ /**
515
+ * Deriva, para cada componente, o que ele exige de fora — e por qual nó cada exigência
516
+ * escapou.
517
+ *
518
+ * Um componente que PROVÊ um nome não precisa dele de fora: `uses ∖ provides`. As duas
519
+ * declarações têm direções diferentes (o que preciso × o que dou), e quando coincidem o
520
+ * autor está dizendo que se basta.
521
+ */
522
+ declare function deriveContext(components: Iterable<ComponentIR>): ContextFacts;
523
+
524
+ /** O que uma intenção executa, visto de UM lugar onde ela monta. */
525
+ type IntentRoute =
526
+ /** chamada interna: um update do componente que escreveu a intenção. */
527
+ {
528
+ kind: "update";
529
+ action: string;
530
+ bound?: unknown;
531
+ }
532
+ /** um id que sai para a aplicação. */
533
+ | {
534
+ kind: "action";
535
+ action: string;
536
+ bound?: unknown;
537
+ }
538
+ /** um nome de vocabulário que este lugar não resolve: depende de onde o componente monta. */
539
+ | {
540
+ kind: "command";
541
+ name: string;
542
+ };
543
+ /** O que a rota precisa saber do componente e do sistema. */
544
+ interface RouteContext {
545
+ /** os updates do componente que ESCREVEU a intenção. */
546
+ updates: ReadonlySet<string>;
547
+ /** os nomes que algum vocabulário do sistema declara — ver `Mounting.vocabulary`. */
548
+ vocabulary: ReadonlySet<string>;
549
+ }
550
+ /**
551
+ * A rota de UMA intenção num lugar de montagem — os três degraus da expansão, sobre nomes.
552
+ *
553
+ * `bound` são os params que um alias já liga (a aplicação parcial): o disparo os recebe por baixo
554
+ * dos seus, e quem prova ou traduz a chamada precisa deles.
555
+ */
556
+ declare function routeIntent(action: string, scope: MountScope, ctx: RouteContext): IntentRoute;
557
+ /**
558
+ * A rota de uma intenção quando TODO lugar onde ela monta concorda.
559
+ *
560
+ * `undefined` quando ela não monta em lugar nenhum, ou quando dois lugares a resolvem de jeitos
561
+ * diferentes — aí não há UMA ação contra a qual provar ou para a qual traduzir.
562
+ */
563
+ declare function settledRoute(action: string, scopes: readonly MountScope[], ctx: RouteContext): IntentRoute | undefined;
564
+ interface CommandFacts {
565
+ /** por nó: o que cada intenção dele executa, em cada lugar onde ele monta. */
566
+ intentsAt: ReadonlyMap<Address, readonly IrEdge[]>;
567
+ /** por componente: os ids de ação que ele roteia para a aplicação. */
568
+ actions: ReadonlyMap<string, ReadonlySet<string>>;
569
+ /** por componente: os nomes de comando que escapam dele — quem resolve é quem o monta. */
570
+ free: ReadonlyMap<string, readonly string[]>;
571
+ }
572
+ /**
573
+ * Deriva o roteamento de todas as intenções de um conjunto de componentes.
574
+ *
575
+ * `host` é o vocabulário que a aplicação instala na raiz — o `commands` do `ForgeView`.
576
+ */
577
+ declare function deriveCommands(components: Iterable<ComponentIR>, mounting: Mounting, host?: CommandScope): CommandFacts;
578
+
579
+ interface RuleViolation {
580
+ /** id da regra (`category-policy` / `route-entry` / `forbidden-tag`…). */
581
+ rule: string;
582
+ /** componente infrator. */
583
+ componentId: string;
584
+ message: string;
585
+ data?: Record<string, unknown>;
586
+ }
587
+ type Decision = "allow" | "deny";
588
+ /** Categoria de um nó de grafo: custom via `ComponentIR.category`; registrado via
589
+ * `descriptor.meta.category` (declarado, confiável — não se infere de React). */
590
+ type CategoryResolver = (id: string) => string | undefined;
591
+ declare function categoryOf(sys: SystemIR, registry?: DescriptorRegistry): CategoryResolver;
592
+ interface CategoryPolicy {
593
+ categoryOf: CategoryResolver;
594
+ /** matriz por categoria: `matrix[from][to]` = allow|deny. */
595
+ matrix: Record<string, Record<string, Decision>>;
596
+ /** decisão quando o par não está na matriz (default: `allow` — estilo deny-list). */
597
+ defaultPolicy?: Decision;
598
+ }
599
+ /**
600
+ * Regra arquitetural POR ARESTA (categoria/direção): "UI pode usar Data, Data não pode usar
601
+ * UI". Caminha o `outgoing` (componente compõe componente/registrado); pares sem categoria
602
+ * são ignorados. Sem propagação — só a aresta direta (igual `analysis.policies`).
603
+ */
604
+ declare function checkCategoryPolicy(sys: SystemIR, policy: CategoryPolicy): RuleViolation[];
605
+ interface UnableSources {
606
+ /** capabilities indisponíveis — um componente que as `requires` fica unable. */
607
+ capabilities?: ReadonlySet<string>;
608
+ /** actions indisponíveis — um componente que as invoca fica unable. */
609
+ actions?: ReadonlySet<string>;
610
+ /** componentes explicitamente indisponíveis. */
611
+ components?: Iterable<string>;
612
+ /**
613
+ * A PORTA de ação — quem sabe o que pode rodar AGORA (o `isAvailable` do Tier 1).
614
+ *
615
+ * Com ela, ninguém precisa montar a lista de indisponíveis à mão: pergunta-se por ação que o
616
+ * grafo de fato invoca. Uma porta sem `isAvailable` não torna nada unable — não saber não é
617
+ * saber que não.
618
+ *
619
+ * **É um retrato, não uma assinatura.** Disponibilidade muda em runtime (cota, permissão,
620
+ * provider caído) e isto é análise do relógio LENTO: quem quiser acompanhar, recomputa.
621
+ */
622
+ port?: {
623
+ isAvailable?(id: string): boolean;
624
+ };
625
+ }
626
+ /**
627
+ * Availability/unable TRANSITIVO: a partir das fontes off, marca todo componente que delas
628
+ * depende — o Razor das Actions. Capability/action off → o componente que as exige/invoca é
629
+ * fonte; e todo dependente herda `unable`.
630
+ */
631
+ declare function computeUnable(sys: SystemIR, off: UnableSources): Set<string>;
632
+ /**
633
+ * Um id REPETIDO no documento de um componente.
634
+ *
635
+ * O id é a identidade do nó: é por ele que o editor muta, que o patch acha o alvo e que a tela
636
+ * liga o foco ao documento. Dois nós com o mesmo id fazem os três apontarem para o primeiro — e
637
+ * nada falha na hora, porque a árvore renderiza igual. O escopo é o DOCUMENTO: componentes
638
+ * diferentes repetem id à vontade, e a expansão os separa por instância.
639
+ */
640
+ declare function checkIdentity(sys: SystemIR): RuleViolation[];
641
+ /**
642
+ * Um componente no escopo não pode ter nenhuma das tags proibidas (efetivas/propagadas).
643
+ * Ex.: páginas (entry) não podem ser `unsafe` → `checkForbiddenTags(sys, ["unsafe"], c => c.entry)`.
644
+ * É a prova de que "tag declarada propaga aos dependentes e as regras caem sobre o propagado".
645
+ */
646
+ declare function checkForbiddenTags(sys: SystemIR, forbidden: Iterable<string>, scope?: (comp: ComponentIR) => boolean): RuleViolation[];
647
+ interface ContextIssue extends RuleViolation {
648
+ rule: "context-undeclared" | "context-unsatisfied" | "context-unused";
649
+ /** os nomes em questão. É o dado que a correção usa para agir sem adivinhar. */
650
+ data: {
651
+ contexts: string[];
652
+ node?: string;
653
+ };
654
+ }
655
+ interface CheckContextsOptions {
656
+ /** os contextos que o HOST provê de ambiente (`auth`, `route`…). Um componente `entry`
657
+ * que exija algo fora disto não tem onde viver: é a fronteira da prova. */
658
+ ambient?: Iterable<string>;
659
+ }
660
+ /**
661
+ * Julga o que a análise de contexto derivou. A assimetria é a de sempre — **você só pode ser
662
+ * contrariado se tiver afirmado**:
663
+ *
664
+ * | declarou MENOS que a realidade | mentira → erro |
665
+ * | declarou MAIS | desperdício → warning |
666
+ * | não declarou | a inferência decide |
667
+ *
668
+ * O erro aponta o NÓ por onde a exigência escapou, e carrega os nomes: promover a obrigação
669
+ * para o componente de cima é correção mecânica, e quem conserta não deve ter de re-derivar.
670
+ */
671
+ declare function checkContexts(sys: SystemIR, ctx: ContextFacts, opts?: CheckContextsOptions): ContextIssue[];
672
+ interface EffectIssue extends RuleViolation {
673
+ rule: "effect-loop";
674
+ data: {
675
+ effect: string;
676
+ action: string;
677
+ paths: string[];
678
+ };
679
+ }
680
+ /**
681
+ * Um efeito que ESCREVE o que ele OBSERVA é laço infinito — e aqui dá para provar sem rodar,
682
+ * porque os dois lados são declarados: o `on` diz o que ele observa, e o `set` da ação local
683
+ * diz o que ele escreve.
684
+ *
685
+ * O React só descobre isso em runtime, com o navegador travado. Nós descobrimos porque o
686
+ * programa é dado.
687
+ *
688
+ * Só ação LOCAL é julgada: o que o `ActionPort` do host faz com o estado dele não é daqui.
689
+ */
690
+ declare function checkEffects(sys: SystemIR): EffectIssue[];
691
+
692
+ declare const bodyOf: (def: ComponentDef) => DefNode;
693
+ interface ExpandOptions {
694
+ /** boundary de entrada: componente custom PRIVADO entrado como RAIZ não expande → `unknown`. */
695
+ requireEntryRoot?: boolean;
696
+ /** os contextos de AMBIENTE — o que o host provê na raiz (`auth`, `route`…). É a fronteira
697
+ * declarada: dentro da árvore a prova é total, na borda ela depende disto. */
698
+ contexts?: Readonly<Record<string, unknown>>;
699
+ /** o vocabulário de comando da RAIZ — o que a aplicação instala antes de qualquer sessão. */
700
+ commands?: CommandScope;
701
+ /**
702
+ * Os UPDATES do componente cujo corpo É esta raiz — só existe quando quem expande é o boundary
703
+ * de um componente vivo.
704
+ *
705
+ * Um nome que resolve aqui é chamada interna e não passa pelo escopo de comando. Sem isto, um
706
+ * alias de fora com o mesmo nome reescreveria o update local — e o boundary é o ÚNICO lugar onde
707
+ * updates existem, porque ter update é o que torna o componente vivo.
708
+ */
709
+ updates?: ReadonlySet<string>;
710
+ /** o componente cujo corpo É esta raiz — o boundary de um componente vivo o informa, para que os
711
+ * nós do corpo mantenham a origem. Ausente, a raiz é do host. */
712
+ component?: string;
713
+ }
714
+ /**
715
+ * Expande os componentes custom de `root` (árvore já data-bound) numa árvore PURA de `DefNode`.
716
+ * `system` = registry de definições; `binder` = pra ligar o corpo de cada componente.
717
+ */
718
+ declare function expandComponents(root: DefNode, system: ComponentDefRegistry, binder: BindingEngine, opts?: ExpandOptions): DefNode;
719
+ interface ResolveComponentTreeOptions extends ResolveTreeOptions {
720
+ /** registry de definições de componente custom. Ausente → nenhum `type` é custom, e a expansão
721
+ * só consome as diretivas de escopo. */
722
+ componentDefs?: ComponentDefRegistry;
723
+ requireEntryRoot?: boolean;
724
+ /** os contextos de AMBIENTE que o host provê na raiz. Ver `ExpandOptions.contexts`. */
725
+ contexts?: Readonly<Record<string, unknown>>;
726
+ /** o vocabulário de comando da RAIZ — repassado à expansão. */
727
+ commands?: CommandScope;
728
+ /** os updates do componente cujo corpo é esta raiz. Ver `ExpandOptions.updates`. */
729
+ updates?: ReadonlySet<string>;
730
+ /** o componente cujo corpo é esta raiz. Ver `ExpandOptions.component`. */
731
+ component?: string;
732
+ }
733
+ /**
734
+ * Render do construtor: `bind` (dados) → `expand` (componentes custom → árvore pura) →
735
+ * `resolveTree` (compositor, com `identityBinder` — a árvore já está ligada). É o que o
736
+ * `@loom-forge/forge-react` usa por baixo; também serve headless.
737
+ *
738
+ * **A expansão roda SEMPRE, mesmo sem `componentDefs`.** Ela não é só "trocar custom por corpo":
739
+ * é também onde as diretivas de ESCOPO são consumidas — o `provides` e o `commands`. Uma árvore
740
+ * só de componentes registrados e `el` continua tendo sessão, contexto e vocabulário de comando,
741
+ * e sem esta passada eles ficariam declarados e inertes, em silêncio. Sem definição nenhuma o
742
+ * custo é um walk, e nenhum id muda (o prefixo só cresce quando um custom é expandido).
743
+ */
744
+ declare function resolveComponentTree(node: DefNode, opts: ResolveComponentTreeOptions, data?: unknown): ResolvedNode;
745
+
746
+ export { type Address, type Addressed, type DeriveFactsOptions as BuildSystemOptions, COMPONENT_FIELDS, type CategoryPolicy, type CategoryResolver, type CheckContextsOptions, type CheckSlotsOptions, type CommandFacts, type CompileCtx, type CompileNode, type CompiledDefinition, type CompiledNode, type CompiledPath, type ComponentDef, type ComponentDefRegistry, type ComponentFacts, type ComponentFieldRole, type ComponentIR, type ComponentSlot, type ContextCatalog, type ContextFacts, type ContextIssue, type Decision, type DepAxis, type DependencyError, type DeriveFactsOptions, EMPTY_MOUNT, type EffectDecl, type EffectIssue, type ExpandOptions, type Facts, type IntentRoute, type IrEdge, type MountScope, type Mounting, type NodeFacts, type NodeIR, type NodeKind, type PropsDecl, type ResolveComponentTreeOptions, type RouteContext, type RuleViolation, STATEFUL_TYPE, type SlotContract, type SlotViolation, type StateDecl, type SystemIR, type TypeRef, type UnableSources, type UpdateDecl, addressOf, applyUpdate, bindPayload, bodyOf, buildSystemIR, categoryOf, checkCategoryPolicy, checkContexts, checkEffects, checkForbiddenTags, checkIdentity, checkSlots, compileComponent, compileDefinition, compileNodeOf, componentAddress, componentSlotsOf, computeUnable, createComponentDefRegistry, deriveCommands, deriveContext, deriveFacts, effectiveTags, expandComponents, factsOf, initState, isLive, mountScopesOf, mountingOf, nodeAddress, nodeFactsOf, parseAddress, resolveComponentTree, routeIntent, settledRoute };