@loom-forge/forge-react 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.
- package/README.md +118 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +216 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# @loom-forge/forge-react
|
|
2
|
+
|
|
3
|
+
**forge** · a camada React do construtor. Envolve um engine do
|
|
4
|
+
[`@loom-forge/react`](../react/README.md) com um registry de definições, expande os componentes
|
|
5
|
+
custom (pré-passo headless) e renderiza o resultado pelo compositor — que continua sem saber o
|
|
6
|
+
que é um `ComponentDef`.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm add @loom-forge/forge-react react
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { createForge, ForgeProvider, ForgeView } from "@loom-forge/forge-react";
|
|
14
|
+
import { createTsliteBinder } from "@loom-forge/tslite";
|
|
15
|
+
|
|
16
|
+
const forge = createForge({ binder: createTsliteBinder() })
|
|
17
|
+
.register("Card", CardComp)
|
|
18
|
+
.defineComponent(userCard); // a definição JSON
|
|
19
|
+
|
|
20
|
+
<ForgeProvider forge={forge}>
|
|
21
|
+
<ForgeView
|
|
22
|
+
node={{ id: "u1", type: "UserCard", props: { id: "42" } }}
|
|
23
|
+
data={data}
|
|
24
|
+
/>
|
|
25
|
+
</ForgeProvider>;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
| export | o quê |
|
|
29
|
+
| ------------------------------ | --------------------------------------------------------------------------------------- |
|
|
30
|
+
| `createForge(opts?)` | engine do compositor + registry de definições. `register`/`defineComponent` encadeáveis |
|
|
31
|
+
| `ForgeProvider` · `useForge()` | o contexto |
|
|
32
|
+
| `ForgeView` | `node` + `data` (+ `contexts` e `commands` do host) → expande e renderiza |
|
|
33
|
+
|
|
34
|
+
`createForge` aceita tudo que o `createEngine` aceita (`binder`, `resolver`, `planners`,
|
|
35
|
+
`layoutRenderers`, `actions`, `capabilities`…), mais `requireEntryRoot`: um componente custom
|
|
36
|
+
PRIVADO entrado como RAIZ degrada para `unknown` em vez de expandir.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## O boundary de estado
|
|
41
|
+
|
|
42
|
+
Componente com `updates` ou `effects` é **vivo**: a expansão não o expande, emite o marcador
|
|
43
|
+
`$stateful` (dado puro). Quem o materializa é este package — `createForge` registra o
|
|
44
|
+
`StatefulBoundary`, que segura `useState(initState(def))` por **instância**, re-resolve o próprio
|
|
45
|
+
corpo a cada mudança e roteia os eventos: **update local primeiro**, senão o `ActionPort` global.
|
|
46
|
+
|
|
47
|
+
O corpo é expandido com os mesmos insumos da expansão estática: o mesmo `bodyOf` e os `updates` do
|
|
48
|
+
componente, para que um alias de comando vindo de fora não reescreva um update local. O efeito
|
|
49
|
+
resolve nos mesmos degraus do evento, pelo vocabulário de onde o componente montou.
|
|
50
|
+
|
|
51
|
+
O boundary não intercepta a porta. A resolução marca `local` a intenção que é update DESTE
|
|
52
|
+
componente, e o engine do corpo ganha `updates`, que a aplica; o resto sai pela porta do host.
|
|
53
|
+
É por ser marca, e não nome, que o evento de um componente puro inlinado no corpo não alcança os
|
|
54
|
+
updates de quem o compôs (ADR-016 do repo).
|
|
55
|
+
|
|
56
|
+
### A cadeia de uma intenção, e o pendente (ADR-018 do repo)
|
|
57
|
+
|
|
58
|
+
Tudo o que uma intenção recebe entra pelos `params`, e o autor escreve de onde: a raiz `event` é
|
|
59
|
+
o valor recebido — o que o widget emitiu, o `data` da ação anterior num `then`, o `error` num
|
|
60
|
+
`catch`. Os `params` são avaliados **no despacho**, sobre o escopo do render mais `event` (o
|
|
61
|
+
`bind` os deixa compilados e guarda o escopo no binding); quem avalia é o mesmo binder do render.
|
|
62
|
+
|
|
63
|
+
```jsonc
|
|
64
|
+
"onClick": {
|
|
65
|
+
"action": "iniciar", // local: set validando = true
|
|
66
|
+
"then": {
|
|
67
|
+
"action": "corretor/validar-email", // host, async
|
|
68
|
+
"params": { "email": "{{ state.dados.email }}" },
|
|
69
|
+
"then": { "action": "marcar", "params": { "valido": "{{ event.valido }}" } },
|
|
70
|
+
"catch": { "action": "falhou", "params": { "motivo": "{{ event.code }}" } }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
O boundary executa a cadeia (`runIntent` do core): um update local resolve quando o estado foi
|
|
76
|
+
escrito (`event` = `undefined`); um despacho ao host resolve com `data` no `then` ou falha com
|
|
77
|
+
`error` no `catch` — falha de domínio ou o `UNHANDLED_ERROR` de um port que lançou. Um fluxo
|
|
78
|
+
cancelado (`aborted` com sucesso) não é resultado e fica só com o host. **O host recebe todo
|
|
79
|
+
resultado pelo `onActionResult`, com ou sem continuação.** Sem `catch`, a falha vai só ao host,
|
|
80
|
+
como sempre foi.
|
|
81
|
+
|
|
82
|
+
E o **pendente** é fato da instância, não estado que o autor escreve: o boundary guarda
|
|
83
|
+
`pending[ação]` — `true` do despacho ao resultado de uma ação do host — e o expõe como raiz do
|
|
84
|
+
escopo do corpo, para um `when`/`disabled` ler:
|
|
85
|
+
|
|
86
|
+
```jsonc
|
|
87
|
+
{
|
|
88
|
+
"type": "Button",
|
|
89
|
+
"props": {
|
|
90
|
+
"label": "{{ pending['corretor/validar-email'] ? 'Validando…' : 'Validar' }}",
|
|
91
|
+
"disabled": "{{ pending['corretor/validar-email'] }}",
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Um componente puro não tem boundary e não tem `pending`: é raiz só de quem é vivo. O efeito
|
|
97
|
+
segue a mesma cadeia — `then`/`catch` valem nele, e o `event` de um efeito é `undefined`:
|
|
98
|
+
|
|
99
|
+
```jsonc
|
|
100
|
+
"effects": {
|
|
101
|
+
"carregar": {
|
|
102
|
+
"on": ["props.id"],
|
|
103
|
+
"action": "user/load",
|
|
104
|
+
"params": { "id": "{{ props.id }}" },
|
|
105
|
+
"then": { "action": "guardar", "params": { "nome": "{{ event.nome }}" } },
|
|
106
|
+
"catch": { "action": "falhou", "params": { "erro": "{{ event.code }}" } }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
É "carregar no mount e guardar o que carregou" sem o host reinjetar nada — e `pending["user/load"]`
|
|
112
|
+
cobre o mount, então um `"{{ pending['user/load'] ? 'carregando' : state.nome }}"` mostra o
|
|
113
|
+
estado certo do primeiro render ao resultado. O `cleanup` tem a cadeia dele.
|
|
114
|
+
|
|
115
|
+
É por isso que estado mora na camada React e não no headless: o pré-passo captura a estrutura
|
|
116
|
+
estática; quem faz viver é a view. Componentes aninhados — inclusive outros vivos — viram seus
|
|
117
|
+
próprios boundaries, por recursão natural. Ver
|
|
118
|
+
[forge/docs/STATE.md](../forge/docs/STATE.md).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ComponentType, ReactNode } from 'react';
|
|
2
|
+
import { CreateEngineOptions, Engine, EngineDescriptor } from '@loom-forge/react';
|
|
3
|
+
import { DefNode, CommandScope } from '@loom-forge/core';
|
|
4
|
+
import { ComponentDefRegistry, ComponentDef } from '@loom-forge/forge';
|
|
5
|
+
|
|
6
|
+
interface Forge {
|
|
7
|
+
/** o engine do compositor por baixo (componentes React registrados + render). */
|
|
8
|
+
engine: Engine;
|
|
9
|
+
/** definições de componente custom (JSON). */
|
|
10
|
+
componentDefs: ComponentDefRegistry;
|
|
11
|
+
/** boundary de entrada: componente custom privado como RAIZ degrada pra `unknown`. */
|
|
12
|
+
requireEntryRoot: boolean;
|
|
13
|
+
/** registra um componente React (delega ao compositor). Encadeável. */
|
|
14
|
+
register(type: string, component: ComponentType<any>, descriptor?: EngineDescriptor): Forge;
|
|
15
|
+
/** declara um componente custom em JSON. Encadeável. */
|
|
16
|
+
defineComponent(def: ComponentDef): Forge;
|
|
17
|
+
}
|
|
18
|
+
interface CreateForgeOptions extends CreateEngineOptions {
|
|
19
|
+
requireEntryRoot?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare function createForge(opts?: CreateForgeOptions): Forge;
|
|
22
|
+
declare function ForgeProvider({ forge, children, }: {
|
|
23
|
+
forge: Forge;
|
|
24
|
+
children?: ReactNode;
|
|
25
|
+
}): ReactNode;
|
|
26
|
+
declare function useForge(): Forge;
|
|
27
|
+
/** Entrada app-facing do construtor: expande os componentes custom e renderiza via o compositor. */
|
|
28
|
+
declare function ForgeView({ node, data, contexts, commands, }: {
|
|
29
|
+
node: DefNode;
|
|
30
|
+
data?: unknown;
|
|
31
|
+
/** os contextos de AMBIENTE que o host provê nesta raiz (`auth`, `route`…). É a fronteira
|
|
32
|
+
* declarada: dentro da árvore a prova de contexto é total; aqui ela depende disto. */
|
|
33
|
+
contexts?: Readonly<Record<string, unknown>>;
|
|
34
|
+
/** o vocabulário de COMANDO da aplicação — o que vale antes de qualquer sessão instalar o seu. */
|
|
35
|
+
commands?: CommandScope;
|
|
36
|
+
}): ReactNode;
|
|
37
|
+
|
|
38
|
+
export { type CreateForgeOptions, type Forge, ForgeProvider, ForgeView, createForge, useForge };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, useState, useEffect } from 'react';
|
|
2
|
+
import { createEngine, ResolverProvider, Render, intentRuntimeOf } from '@loom-forge/react';
|
|
3
|
+
import { resolveIntent, EMPTY_COMMANDS, runIntent, intentParams } from '@loom-forge/core';
|
|
4
|
+
import { STATEFUL_TYPE, createComponentDefRegistry, resolveComponentTree, initState, bodyOf, compileDefinition, compileNodeOf, applyUpdate } from '@loom-forge/forge';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
// src/index.tsx
|
|
8
|
+
function createForge(opts = {}) {
|
|
9
|
+
const engine = createEngine(opts);
|
|
10
|
+
engine.register(STATEFUL_TYPE, StatefulBoundary);
|
|
11
|
+
const componentDefs = createComponentDefRegistry();
|
|
12
|
+
const forge = {
|
|
13
|
+
engine,
|
|
14
|
+
componentDefs,
|
|
15
|
+
requireEntryRoot: opts.requireEntryRoot ?? false,
|
|
16
|
+
register(type, component, descriptor) {
|
|
17
|
+
engine.register(type, component, descriptor);
|
|
18
|
+
return forge;
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* A definição entra no registry **compilada** — árvore e o que mora fora dela (o `provides`,
|
|
22
|
+
* o `set` de um update, os `params` de um efeito).
|
|
23
|
+
*
|
|
24
|
+
* É uma vez, na entrada, em vez de uma vez por disparo: antes o `applyUpdate` e o
|
|
25
|
+
* `EffectRunner` compilavam na hora de agir, cada um com o próprio nó-fantasma. O SoT
|
|
26
|
+
* continua sendo o que o autor escreveu; isto é derivado, e memoizado por identidade.
|
|
27
|
+
*/
|
|
28
|
+
defineComponent(def) {
|
|
29
|
+
componentDefs.register(
|
|
30
|
+
compileDefinition(def, compileNodeOf(engine.binder)).def
|
|
31
|
+
);
|
|
32
|
+
return forge;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return forge;
|
|
36
|
+
}
|
|
37
|
+
var ForgeContext = createContext(null);
|
|
38
|
+
function ForgeProvider({
|
|
39
|
+
forge,
|
|
40
|
+
children
|
|
41
|
+
}) {
|
|
42
|
+
return /* @__PURE__ */ jsx(ForgeContext.Provider, { value: forge, children: /* @__PURE__ */ jsx(ResolverProvider, { engine: forge.engine, children }) });
|
|
43
|
+
}
|
|
44
|
+
function useForge() {
|
|
45
|
+
const forge = useContext(ForgeContext);
|
|
46
|
+
if (!forge) throw new Error("useForge: faltou <ForgeProvider>.");
|
|
47
|
+
return forge;
|
|
48
|
+
}
|
|
49
|
+
function ForgeView({
|
|
50
|
+
node,
|
|
51
|
+
data,
|
|
52
|
+
contexts,
|
|
53
|
+
commands
|
|
54
|
+
}) {
|
|
55
|
+
const forge = useForge();
|
|
56
|
+
const resolved = useMemo(
|
|
57
|
+
() => resolveComponentTree(
|
|
58
|
+
node,
|
|
59
|
+
{
|
|
60
|
+
registry: forge.engine.descriptors,
|
|
61
|
+
resolver: forge.engine.resolver,
|
|
62
|
+
binder: forge.engine.binder,
|
|
63
|
+
planners: forge.engine.planners,
|
|
64
|
+
defaultLayout: forge.engine.defaultLayout,
|
|
65
|
+
componentDefs: forge.componentDefs,
|
|
66
|
+
requireEntryRoot: forge.requireEntryRoot,
|
|
67
|
+
...contexts ? { contexts } : {},
|
|
68
|
+
...commands ? { commands } : {}
|
|
69
|
+
},
|
|
70
|
+
// as raízes do escopo do host: os dados e, se ele provê, o ambiente.
|
|
71
|
+
{ data, ...contexts ? { ctx: contexts } : {} }
|
|
72
|
+
),
|
|
73
|
+
[forge, node, data, contexts, commands]
|
|
74
|
+
);
|
|
75
|
+
return /* @__PURE__ */ jsx(Render, { node: resolved });
|
|
76
|
+
}
|
|
77
|
+
var EMPTY_CONTEXTS = {};
|
|
78
|
+
var readPath = (scope, path) => {
|
|
79
|
+
let cur = scope;
|
|
80
|
+
for (const key of path.split(".")) {
|
|
81
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
82
|
+
cur = cur[key];
|
|
83
|
+
}
|
|
84
|
+
return cur;
|
|
85
|
+
};
|
|
86
|
+
function EffectRunner({
|
|
87
|
+
effect,
|
|
88
|
+
scope,
|
|
89
|
+
dispatch
|
|
90
|
+
}) {
|
|
91
|
+
const deps = effect.on.map((path) => readPath(scope, path));
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
dispatch(effect, scope);
|
|
94
|
+
const cleanup = effect.cleanup;
|
|
95
|
+
if (!cleanup) return void 0;
|
|
96
|
+
return () => dispatch(cleanup, scope);
|
|
97
|
+
}, deps);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
var pickContexts = (contexts, uses) => {
|
|
101
|
+
const out = {};
|
|
102
|
+
for (const name of uses ?? [])
|
|
103
|
+
if (name in contexts) out[name] = contexts[name];
|
|
104
|
+
return out;
|
|
105
|
+
};
|
|
106
|
+
function StatefulBoundary({
|
|
107
|
+
def,
|
|
108
|
+
callProps,
|
|
109
|
+
contexts,
|
|
110
|
+
commands
|
|
111
|
+
}) {
|
|
112
|
+
const forge = useForge();
|
|
113
|
+
const [pending, setPending] = useState({});
|
|
114
|
+
const [state, setState] = useState(
|
|
115
|
+
() => (
|
|
116
|
+
// o `init` é avaliado UMA vez, contra `props` e `ctx` — a semântica do `useState(props.x)`.
|
|
117
|
+
initState(
|
|
118
|
+
def,
|
|
119
|
+
{
|
|
120
|
+
props: callProps,
|
|
121
|
+
ctx: pickContexts(contexts ?? EMPTY_CONTEXTS, def.context?.uses)
|
|
122
|
+
},
|
|
123
|
+
forge.engine.binder
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
const applyLocal = (name, params) => {
|
|
128
|
+
const act = def.updates?.[name];
|
|
129
|
+
if (act === void 0) return;
|
|
130
|
+
const extra = params && typeof params === "object" ? params : {};
|
|
131
|
+
setState(
|
|
132
|
+
(prev) => applyUpdate(
|
|
133
|
+
prev,
|
|
134
|
+
act,
|
|
135
|
+
{ props: callProps, state: prev, params: extra },
|
|
136
|
+
forge.engine.binder
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
const inherited = contexts ?? EMPTY_CONTEXTS;
|
|
141
|
+
const updates = useMemo(() => new Set(Object.keys(def.updates ?? {})), [def]);
|
|
142
|
+
const resolved = useMemo(
|
|
143
|
+
() => resolveComponentTree(
|
|
144
|
+
// o MESMO corpo que a expansão estática vê: `provides` e `commands` do componente na raiz.
|
|
145
|
+
bodyOf(def),
|
|
146
|
+
{
|
|
147
|
+
registry: forge.engine.descriptors,
|
|
148
|
+
resolver: forge.engine.resolver,
|
|
149
|
+
binder: forge.engine.binder,
|
|
150
|
+
planners: forge.engine.planners,
|
|
151
|
+
defaultLayout: forge.engine.defaultLayout,
|
|
152
|
+
componentDefs: forge.componentDefs,
|
|
153
|
+
contexts: inherited,
|
|
154
|
+
...commands ? { commands } : {},
|
|
155
|
+
updates,
|
|
156
|
+
// o corpo é DESTE componente: os nós dele mantêm a origem
|
|
157
|
+
component: def.component
|
|
158
|
+
},
|
|
159
|
+
// as raízes do corpo — `ctx` é a projeção do que ELE declarou usar
|
|
160
|
+
{
|
|
161
|
+
props: callProps,
|
|
162
|
+
state,
|
|
163
|
+
ctx: pickContexts(inherited, def.context?.uses),
|
|
164
|
+
pending
|
|
165
|
+
}
|
|
166
|
+
),
|
|
167
|
+
[forge, def, callProps, state, pending, inherited, commands, updates]
|
|
168
|
+
);
|
|
169
|
+
const trigger = (intent, effectScope) => {
|
|
170
|
+
const resolved2 = resolveIntent(intent, commands ?? EMPTY_COMMANDS, updates);
|
|
171
|
+
runIntent(resolved2, void 0, {
|
|
172
|
+
...intentRuntimeOf(localEngine),
|
|
173
|
+
params: (binding, event) => intentParams(
|
|
174
|
+
forge.engine.binder,
|
|
175
|
+
binding.scope === void 0 ? { ...binding, scope: effectScope } : binding,
|
|
176
|
+
event
|
|
177
|
+
)
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
const localEngine = {
|
|
181
|
+
...forge.engine,
|
|
182
|
+
updates: {
|
|
183
|
+
apply: applyLocal,
|
|
184
|
+
pending: (action, flag) => setPending(
|
|
185
|
+
(prev) => prev[action] === flag ? prev : { ...prev, [action]: flag }
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const scope = {
|
|
190
|
+
props: callProps,
|
|
191
|
+
state,
|
|
192
|
+
ctx: pickContexts(inherited, def.context?.uses),
|
|
193
|
+
pending
|
|
194
|
+
};
|
|
195
|
+
return /* @__PURE__ */ jsxs(ResolverProvider, { engine: localEngine, children: [
|
|
196
|
+
Object.entries(def.effects ?? {}).map(([name, effect]) => (
|
|
197
|
+
// a key inclui a FORMA do `on`: o React exige que o array de dependências tenha
|
|
198
|
+
// tamanho constante entre renders, e no canvas o autor edita o `on` ao vivo. Mudar o
|
|
199
|
+
// que se observa é outra assinatura — remontar é o que de fato aconteceu.
|
|
200
|
+
/* @__PURE__ */ jsx(
|
|
201
|
+
EffectRunner,
|
|
202
|
+
{
|
|
203
|
+
effect,
|
|
204
|
+
scope,
|
|
205
|
+
dispatch: trigger
|
|
206
|
+
},
|
|
207
|
+
`${name}|${effect.on.length}`
|
|
208
|
+
)
|
|
209
|
+
)),
|
|
210
|
+
/* @__PURE__ */ jsx(Render, { node: resolved })
|
|
211
|
+
] });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { ForgeProvider, ForgeView, createForge, useForge };
|
|
215
|
+
//# sourceMappingURL=index.js.map
|
|
216
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.tsx"],"names":["resolved"],"mappings":"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","file":"index.js","sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ComponentType,\n type ReactNode,\n} from \"react\";\nimport {\n createEngine,\n ResolverProvider,\n Render,\n intentRuntimeOf,\n type CreateEngineOptions,\n type Engine,\n type EngineDescriptor,\n} from \"@loom-forge/react\";\nimport {\n intentParams,\n resolveIntent,\n runIntent,\n EMPTY_COMMANDS,\n type DefNode,\n type CommandScope,\n type EventBinding,\n} from \"@loom-forge/core\";\nimport {\n bodyOf,\n compileDefinition,\n compileNodeOf,\n createComponentDefRegistry,\n resolveComponentTree,\n applyUpdate,\n initState,\n STATEFUL_TYPE,\n type ComponentDef,\n type ComponentDefRegistry,\n type EffectDecl,\n} from \"@loom-forge/forge\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n A camada React do CONSTRUTOR. `createForge` envolve um engine do compositor + um registry\n de definições; `ForgeView` expande os componentes custom (pré-passo) e renderiza via o\n compositor. O compositor (`createEngine`/`ResolverView`) NÃO conhece componente custom.\n ────────────────────────────────────────────────────────────────────────────── */\n\nexport interface Forge {\n /** o engine do compositor por baixo (componentes React registrados + render). */\n engine: Engine;\n /** definições de componente custom (JSON). */\n componentDefs: ComponentDefRegistry;\n /** boundary de entrada: componente custom privado como RAIZ degrada pra `unknown`. */\n requireEntryRoot: boolean;\n /** registra um componente React (delega ao compositor). Encadeável. */\n register(\n type: string,\n component: ComponentType<any>,\n descriptor?: EngineDescriptor,\n ): Forge;\n /** declara um componente custom em JSON. Encadeável. */\n defineComponent(def: ComponentDef): Forge;\n}\n\nexport interface CreateForgeOptions extends CreateEngineOptions {\n requireEntryRoot?: boolean;\n}\n\nexport function createForge(opts: CreateForgeOptions = {}): Forge {\n const engine = createEngine(opts);\n // o boundary dos componentes VIVOS é um componente React registrado no compositor: a expansão\n // emite `$stateful` no lugar de um componente com `updates`, e a view o materializa aqui.\n engine.register(STATEFUL_TYPE, StatefulBoundary);\n const componentDefs = createComponentDefRegistry();\n const forge: Forge = {\n engine,\n componentDefs,\n requireEntryRoot: opts.requireEntryRoot ?? false,\n register(type, component, descriptor) {\n engine.register(type, component, descriptor);\n return forge;\n },\n /**\n * A definição entra no registry **compilada** — árvore e o que mora fora dela (o `provides`,\n * o `set` de um update, os `params` de um efeito).\n *\n * É uma vez, na entrada, em vez de uma vez por disparo: antes o `applyUpdate` e o\n * `EffectRunner` compilavam na hora de agir, cada um com o próprio nó-fantasma. O SoT\n * continua sendo o que o autor escreveu; isto é derivado, e memoizado por identidade.\n */\n defineComponent(def) {\n componentDefs.register(\n compileDefinition(def, compileNodeOf(engine.binder)).def,\n );\n return forge;\n },\n };\n return forge;\n}\n\nconst ForgeContext = createContext<Forge | null>(null);\n\nexport function ForgeProvider({\n forge,\n children,\n}: {\n forge: Forge;\n children?: ReactNode;\n}): ReactNode {\n return (\n <ForgeContext.Provider value={forge}>\n <ResolverProvider engine={forge.engine}>{children}</ResolverProvider>\n </ForgeContext.Provider>\n );\n}\n\nexport function useForge(): Forge {\n const forge = useContext(ForgeContext);\n if (!forge) throw new Error(\"useForge: faltou <ForgeProvider>.\");\n return forge;\n}\n\n/** Entrada app-facing do construtor: expande os componentes custom e renderiza via o compositor. */\nexport function ForgeView({\n node,\n data,\n contexts,\n commands,\n}: {\n node: DefNode;\n data?: unknown;\n /** os contextos de AMBIENTE que o host provê nesta raiz (`auth`, `route`…). É a fronteira\n * declarada: dentro da árvore a prova de contexto é total; aqui ela depende disto. */\n contexts?: Readonly<Record<string, unknown>>;\n /** o vocabulário de COMANDO da aplicação — o que vale antes de qualquer sessão instalar o seu. */\n commands?: CommandScope;\n}): ReactNode {\n const forge = useForge();\n const resolved = useMemo(\n () =>\n resolveComponentTree(\n node,\n {\n registry: forge.engine.descriptors,\n resolver: forge.engine.resolver,\n binder: forge.engine.binder,\n planners: forge.engine.planners,\n defaultLayout: forge.engine.defaultLayout,\n componentDefs: forge.componentDefs,\n requireEntryRoot: forge.requireEntryRoot,\n ...(contexts ? { contexts } : {}),\n ...(commands ? { commands } : {}),\n },\n // as raízes do escopo do host: os dados e, se ele provê, o ambiente.\n { data, ...(contexts ? { ctx: contexts } : {}) },\n ),\n [forge, node, data, contexts, commands],\n );\n return <Render node={resolved} />;\n}\n\nconst EMPTY_CONTEXTS: Readonly<Record<string, unknown>> = {};\n\n/** lê um caminho pontilhado (`\"props.id\"`) do escopo. Ausente → `undefined`, sem inventar. */\nconst readPath = (scope: Record<string, unknown>, path: string): unknown => {\n let cur: unknown = scope;\n for (const key of path.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") return undefined;\n cur = (cur as Record<string, unknown>)[key];\n }\n return cur;\n};\n\n/**\n * UM efeito, num componente próprio — e isso é desenho, não estilo.\n *\n * Um `useEffect` por entrada de um mapa dentro do boundary tornaria a CONTAGEM de hooks\n * dependente do documento: editar a definição mudaria o número de hooks entre renders, que é\n * erro do React. Com um componente por efeito, keyed pelo nome, entrar e sair é montar e\n * desmontar — que é o que de fato aconteceu.\n *\n * Ele não renderiza nada: efeito é intenção, não view.\n */\nfunction EffectRunner({\n effect,\n scope,\n dispatch,\n}: {\n effect: EffectDecl;\n scope: Record<string, unknown>;\n /** dispara UMA intenção — e a cadeia dela — no escopo do componente. */\n dispatch: (intent: EventBinding, scope: Record<string, unknown>) => void;\n}): ReactNode {\n // as dependências são os VALORES nos caminhos declarados. `on: []` → só no mount, que é a\n // convenção do React — sem dependência, não há o que reexecutar.\n const deps = effect.on.map((path) => readPath(scope, path));\n useEffect(() => {\n dispatch(effect, scope);\n const cleanup = effect.cleanup;\n if (!cleanup) return undefined;\n return () => dispatch(cleanup, scope);\n // o escopo e o dispatch são lidos NO disparo; quem decide quando disparar é o `on`.\n }, deps);\n return null;\n}\n\n/** só o que o componente DECLAROU usar entra no `ctx` dele — provisão não vaza. */\nconst pickContexts = (\n contexts: Readonly<Record<string, unknown>>,\n uses: readonly string[] | undefined,\n): Record<string, unknown> => {\n const out: Record<string, unknown> = {};\n for (const name of uses ?? [])\n if (name in contexts) out[name] = contexts[name];\n return out;\n};\n\n/**\n * Boundary de um componente VIVO (`updates` ou `effects`): segura o estado local POR INSTÂNCIA\n * (`useState`), re-resolve o corpo contra as raízes `props`/`state`/`ctx` quando o estado muda, e\n * religa os eventos → updates locais (dispatch) — o que não é update local cai no `ActionPort`\n * global. É o puro/sujo no runtime.\n */\nfunction StatefulBoundary({\n def,\n callProps,\n contexts,\n commands,\n}: {\n def: ComponentDef;\n callProps: Record<string, unknown>;\n /** os contextos em vigor no PONTO onde este componente foi montado — a expansão os\n * carrega no marcador `$stateful`, porque aqui o corpo é expandido de novo, ao vivo. */\n contexts?: Readonly<Record<string, unknown>>;\n /** o vocabulário de comando daquele ponto, pelo mesmo motivo. */\n commands?: CommandScope;\n}): ReactNode {\n const forge = useForge();\n // o PENDENTE da instância: por ação do host, `true` do despacho ao resultado. É raiz do\n // escopo (`pending[\"x/y\"]`), para um `when`/`disabled` ler sem update nenhum (ADR-018).\n const [pending, setPending] = useState<Record<string, boolean>>({});\n const [state, setState] = useState(() =>\n // o `init` é avaliado UMA vez, contra `props` e `ctx` — a semântica do `useState(props.x)`.\n initState(\n def,\n {\n props: callProps,\n ctx: pickContexts(contexts ?? EMPTY_CONTEXTS, def.context?.uses),\n },\n forge.engine.binder,\n ),\n );\n\n /**\n * Um UPDATE deste componente — escrita de estado, síncrona.\n *\n * Só chega aqui o que a resolução MARCOU como local (`binding.local`), e ela marca com os\n * updates DESTE componente. O evento de um componente puro inlinado no corpo não é marcado, e\n * sai pela porta como qualquer outro: o boundary não intercepta nada por nome.\n */\n const applyLocal = (name: string, params?: unknown): void => {\n const act = def.updates?.[name];\n // a marca veio da expansão com estes mesmos updates, então o update existe\n if (act === undefined) return;\n const extra = params && typeof params === \"object\" ? params : {};\n setState((prev) =>\n applyUpdate(\n prev,\n act,\n { props: callProps, state: prev, params: extra },\n forge.engine.binder,\n ),\n );\n };\n\n const inherited = contexts ?? EMPTY_CONTEXTS;\n // os nomes que resolvem AQUI dentro, antes de qualquer escopo de comando: o corpo é deste\n // componente, e um alias de fora com o mesmo nome não pode reescrever o update dele.\n const updates = useMemo(() => new Set(Object.keys(def.updates ?? {})), [def]);\n const resolved = useMemo(\n () =>\n resolveComponentTree(\n // o MESMO corpo que a expansão estática vê: `provides` e `commands` do componente na raiz.\n bodyOf(def),\n {\n registry: forge.engine.descriptors,\n resolver: forge.engine.resolver,\n binder: forge.engine.binder,\n planners: forge.engine.planners,\n defaultLayout: forge.engine.defaultLayout,\n componentDefs: forge.componentDefs,\n contexts: inherited,\n ...(commands ? { commands } : {}),\n updates,\n // o corpo é DESTE componente: os nós dele mantêm a origem\n component: def.component,\n },\n // as raízes do corpo — `ctx` é a projeção do que ELE declarou usar\n {\n props: callProps,\n state,\n ctx: pickContexts(inherited, def.context?.uses),\n pending,\n },\n ),\n [forge, def, callProps, state, pending, inherited, commands, updates],\n );\n\n /**\n * O disparo de um EFEITO. Evento chega aqui já roteado pela expansão; efeito não passa por\n * ela — então resolve aqui, pelo mesmo `resolveIntent` e na mesma ordem.\n *\n * O escopo é o do lugar onde o componente MONTOU: o efeito é intenção do componente, não de um\n * nó do corpo, e o `commands` do componente instala para o corpo, não para si — como o\n * `provides`.\n */\n const trigger = (\n intent: EventBinding,\n effectScope: Record<string, unknown>,\n ): void => {\n // a cadeia inteira resolve aqui — cada elo pela mesma régua da expansão —, e roda pelo mesmo\n // runtime dos eventos: o `params` de cada elo é avaliado no despacho, sobre o escopo do\n // componente mais `event`. O efeito não passa pelo `bind`, então o escopo entra por aqui.\n const resolved = resolveIntent(intent, commands ?? EMPTY_COMMANDS, updates);\n runIntent(resolved, undefined, {\n ...intentRuntimeOf(localEngine),\n params: (binding, event) =>\n intentParams(\n forge.engine.binder,\n binding.scope === undefined\n ? { ...binding, scope: effectScope }\n : binding,\n event,\n ),\n });\n };\n\n // o engine do corpo: a porta continua a do host, e a intenção MARCADA local vem para cá —\n // e o PENDENTE também: `true` do despacho ao resultado de uma ação do host (ADR-018).\n const localEngine: Engine = {\n ...forge.engine,\n updates: {\n apply: applyLocal,\n pending: (action, flag) =>\n setPending((prev) =>\n prev[action] === flag ? prev : { ...prev, [action]: flag },\n ),\n },\n };\n // o escopo dos efeitos é o MESMO do corpo — um efeito vê o que o componente vê.\n const scope = {\n props: callProps,\n state,\n ctx: pickContexts(inherited, def.context?.uses),\n pending,\n };\n return (\n <ResolverProvider engine={localEngine}>\n {Object.entries(def.effects ?? {}).map(([name, effect]) => (\n // a key inclui a FORMA do `on`: o React exige que o array de dependências tenha\n // tamanho constante entre renders, e no canvas o autor edita o `on` ao vivo. Mudar o\n // que se observa é outra assinatura — remontar é o que de fato aconteceu.\n <EffectRunner\n key={`${name}|${effect.on.length}`}\n effect={effect}\n scope={scope}\n dispatch={trigger}\n />\n ))}\n <Render node={resolved} />\n </ResolverProvider>\n );\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@loom-forge/forge-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "forge · a camada React do construtor: createForge/ForgeProvider/ForgeView e o boundary de estado por instância ($stateful).",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react",
|
|
8
|
+
"nocode",
|
|
9
|
+
"components",
|
|
10
|
+
"state"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": {
|
|
14
|
+
"name": "Anderson D. Rosa",
|
|
15
|
+
"url": "https://github.com/andersondrosa"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/andersondrosa/loom-forge.git",
|
|
20
|
+
"directory": "packages/forge-react"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/andersondrosa/loom-forge/tree/main/packages/forge-react#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/andersondrosa/loom-forge/issues"
|
|
25
|
+
},
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"import": "./dist/index.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
],
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@loom-forge/core": "0.1.0",
|
|
41
|
+
"@loom-forge/forge": "0.1.0",
|
|
42
|
+
"@loom-forge/react": "0.1.0"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"react": "^18 || ^19"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react": "^19.0.0",
|
|
49
|
+
"@types/react-dom": "^19.0.0",
|
|
50
|
+
"jsdom": "^29.1.1",
|
|
51
|
+
"react": "^19.0.0",
|
|
52
|
+
"react-dom": "^19.0.0",
|
|
53
|
+
"tsup": "^8.0.0",
|
|
54
|
+
"typescript": "^5.9.3",
|
|
55
|
+
"@loom-forge/tslite": "0.1.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsup",
|
|
59
|
+
"dev": "tsup --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"clean": "rm -rf dist"
|
|
64
|
+
}
|
|
65
|
+
}
|