@loom-forge/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.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # @loom-forge/runtime
2
+
3
+ **O motor montado.** Um install, uma versão de cada peça, e uma montagem — a que todo app React
4
+ sobre o loom-forge faz igual. Este package **não tem algoritmo**: o que ele traz é o conjunto e o
5
+ default.
6
+
7
+ ```bash
8
+ pnpm add @loom-forge/runtime react
9
+ ```
10
+
11
+ ```tsx
12
+ import { createRuntime, ForgeProvider, ForgeView } from "@loom-forge/runtime";
13
+
14
+ const runtime = createRuntime({
15
+ registry: [
16
+ ["Card", Card, { schema }],
17
+ ["Field", Field, fieldDescriptor],
18
+ ],
19
+ defs: [userCard], // os componentes declarados em JSON
20
+ actions: port, // o `ActionPort` do host (o Tier 0 do contrato de actions)
21
+ onActionResult: (id, result) => {
22
+ if (!result.success) toast(result.error?.code);
23
+ },
24
+ });
25
+
26
+ <ForgeProvider forge={runtime}>
27
+ <ForgeView node={page} data={data} contexts={contexts} />
28
+ </ForgeProvider>;
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Por que ele existe
34
+
35
+ Não é o número de `install`. São duas coisas:
36
+
37
+ **Uma fronteira de dependência que nenhuma das duas pontas pode cruzar.** O
38
+ [`forge-react`](../forge-react/README.md) não pode depender do
39
+ [`tslite`](../tslite/README.md) — arrastaria a linguagem inteira para quem trocou o binder, e o
40
+ port `BindingEngine`, que existe para o binder ser trocável, viraria enfeite. E o `tslite` não
41
+ pode depender do `forge-react`: ele é o adapter da linguagem, e não conhece view. O conjunto só
42
+ pode ser um **terceiro**.
43
+
44
+ **E uma falha silenciosa.** O default do compositor é o `identityBinder`. Um motor montado sem o
45
+ binder do TSLite trata `{{ props.x }}` como a **string** `"{{ props.x }}"`: nada lança, nada
46
+ avisa, e a tela mostra a chave. Há um teste aqui que crava os dois lados — o mesmo documento, com
47
+ e sem a montagem.
48
+
49
+ ## `createRuntime(options)`
50
+
51
+ Devolve o `Forge` do [`forge-react`](../forge-react/README.md), montado. É o `createForge` com a
52
+ montagem decidida, não um motor diferente — tudo tem override.
53
+
54
+ | opção | default | o quê |
55
+ | ------------------------------ | ---------------------- | --------------------------------------------------------- |
56
+ | `registry` | — | as peças: `[type, componente, descriptor?]` |
57
+ | `defs` | — | os componentes em JSON; entram **compilados**, uma vez |
58
+ | `actions` | — | o `ActionPort` do host |
59
+ | `onActionResult` | aviso em dev na falha | recebe **todo** envelope que voltou |
60
+ | `binder` | `createTsliteBinder()` | a linguagem. Trocar o perfil é montar o binder e passá-lo |
61
+ | `planners` / `layoutRenderers` | os do compositor | mapas `name → planner`/`renderer` |
62
+ | e o resto do `createForge` | — | `resolver`, `capabilities`, `requireEntryRoot`, `inspect` |
63
+
64
+ > [!WARNING]
65
+ > **Passe o `onActionResult`.** O envelope tem dois consumidores: o documento, pela cadeia
66
+ > `then`/`catch` que ele declarou, e o **host**, sempre. Sem ouvinte, uma falha de domínio vira
67
+ > aviso em dev e desaparece em produção.
68
+
69
+ **O que NÃO entra na montagem:** `data`, `contexts` e `commands`. Os três são dado de **render** —
70
+ mudam com o mundo, e o lugar deles é o `<ForgeView>`. Congelá-los aqui fixaria dado num artefato
71
+ do relógio lento.
72
+
73
+ ## O conjunto
74
+
75
+ | package | o quê |
76
+ | ----------------------------------------- | ----------------------------------------------- |
77
+ | [`core`](../core/README.md) | o kernel headless e os ports |
78
+ | [`react`](../react/README.md) | a view: registry de componentes e os renderers |
79
+ | [`tslite`](../tslite/README.md) | a linguagem: `{{ }}` compilado e o bind gateado |
80
+ | [`forge`](../forge/README.md) | o construtor: IR, grafo, regras, estado |
81
+ | [`forge-react`](../forge-react/README.md) | a view do construtor e o boundary de estado |
82
+
83
+ O modelo, a análise e a view saem **flat** deste package. Da linguagem sai o que se **monta** —
84
+ `createTsliteBinder`, `DEFAULT_PROFILE` —; a fase de compilação (`compile`, os diagnósticos, o
85
+ `addressOf` da árvore) fica no `@loom-forge/tslite`, que continua sendo um package: quem consolida
86
+ no servidor importa de lá e não carrega o executor.
87
+
88
+ **Fora do conjunto, e por motivos diferentes:**
89
+
90
+ - [`check`](../check/README.md) e [`language-service`](../language-service/README.md) — **outro
91
+ relógio**. Tipo é autoria e CI, nunca o hot path de render (ADR-206 do `check`), e eles arrastam
92
+ o `@tslite/checker`. Instale como `devDependency`;
93
+ - [`eject`](../eject/README.md) — traduz a definição para TSX; é ferramenta, não runtime;
94
+ - [`grid`](../grid/README.md) — tem peer próprio (`react-grid-layout`). Entra quando houver
95
+ dashboard: `planners`/`layoutRenderers` são opções do `createRuntime`;
96
+ - [`editor`](../editor/README.md) e [`address`](../address/README.md) — são de quem **edita** o
97
+ documento, e cada um tem peer próprio.
98
+
99
+ ## Licença
100
+
101
+ MIT © Anderson D. Rosa
@@ -0,0 +1,40 @@
1
+ import { CreateForgeOptions, Forge } from '@loom-forge/forge-react';
2
+ export * from '@loom-forge/forge-react';
3
+ import { ComponentType } from 'react';
4
+ import { EngineDescriptor } from '@loom-forge/react';
5
+ export * from '@loom-forge/react';
6
+ import { ComponentDef } from '@loom-forge/forge';
7
+ export * from '@loom-forge/forge';
8
+ export * from '@loom-forge/core';
9
+ export { DEFAULT_PROFILE, TsliteBinderOptions, createTsliteBinder } from '@loom-forge/tslite';
10
+
11
+ /**
12
+ * Uma peça do kit, como o host a conhece: o `type` que o documento cita, o componente React, e
13
+ * o descriptor headless (`schema`, `meta.emits`, `layout`) — a forma que o `register` recebe.
14
+ */
15
+ type RuntimePiece = readonly [
16
+ type: string,
17
+ component: ComponentType<never>,
18
+ descriptor?: EngineDescriptor
19
+ ];
20
+ interface CreateRuntimeOptions extends CreateForgeOptions {
21
+ /** as peças que o documento pode citar por `type`. */
22
+ registry?: readonly RuntimePiece[];
23
+ /** os componentes declarados em JSON. Entram COMPILADOS, uma vez (o relógio lento). */
24
+ defs?: readonly ComponentDef[];
25
+ }
26
+ /**
27
+ * O motor pronto: o binder do TSLite, os planners e os renderers default, o boundary de estado,
28
+ * o kit registrado e as definições declaradas.
29
+ *
30
+ * Tudo tem override — é o `createForge` com a montagem decidida, não um motor diferente. Passar
31
+ * `binder` troca a linguagem inteira; para só mudar o perfil ou a stdlib, monte o binder e
32
+ * passe-o: `createRuntime({ binder: createTsliteBinder({ profile }) })`.
33
+ *
34
+ * **O que NÃO entra aqui:** `data`, `contexts` e `commands`. Os três são dado de RENDER — mudam
35
+ * com o mundo, e o lugar deles é o `<ForgeView>`. Congelá-los na montagem fixaria dado num
36
+ * artefato do relógio lento, que é a classe de bug que o motor inteiro evita.
37
+ */
38
+ declare function createRuntime(opts?: CreateRuntimeOptions): Forge;
39
+
40
+ export { type CreateRuntimeOptions, type RuntimePiece, createRuntime };
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import { createForge } from '@loom-forge/forge-react';
2
+ export * from '@loom-forge/forge-react';
3
+ import { createTsliteBinder } from '@loom-forge/tslite';
4
+ export { DEFAULT_PROFILE, createTsliteBinder } from '@loom-forge/tslite';
5
+ export * from '@loom-forge/core';
6
+ export * from '@loom-forge/forge';
7
+ export * from '@loom-forge/react';
8
+
9
+ // src/index.ts
10
+ function createRuntime(opts = {}) {
11
+ const { registry, defs, binder, ...rest } = opts;
12
+ const forge = createForge({
13
+ binder: binder ?? createTsliteBinder(),
14
+ ...rest
15
+ });
16
+ for (const [type, component, descriptor] of registry ?? [])
17
+ forge.register(type, component, descriptor);
18
+ for (const def of defs ?? []) forge.defineComponent(def);
19
+ return forge;
20
+ }
21
+
22
+ export { createRuntime };
23
+ //# sourceMappingURL=index.js.map
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;AA2DO,SAAS,aAAA,CAAc,IAAA,GAA6B,EAAC,EAAU;AACpE,EAAA,MAAM,EAAE,QAAA,EAAU,IAAA,EAAM,MAAA,EAAQ,GAAG,MAAK,GAAI,IAAA;AAC5C,EAAA,MAAM,QAAQ,WAAA,CAAY;AAAA,IACxB,MAAA,EAAQ,UAAU,kBAAA,EAAmB;AAAA,IACrC,GAAG;AAAA,GACJ,CAAA;AACD,EAAA,KAAA,MAAW,CAAC,IAAA,EAAM,SAAA,EAAW,UAAU,CAAA,IAAK,YAAY,EAAC;AACvD,IAAA,KAAA,CAAM,QAAA,CAAS,IAAA,EAAM,SAAA,EAAqC,UAAU,CAAA;AACtE,EAAA,KAAA,MAAW,OAAO,IAAA,IAAQ,EAAC,EAAG,KAAA,CAAM,gBAAgB,GAAG,CAAA;AACvD,EAAA,OAAO,KAAA;AACT","file":"index.js","sourcesContent":["// ════════════════════════════════════════════════════════════════════════\n// O RUNTIME — o motor montado, e o único lugar onde a montagem é decidida.\n//\n// Este package não tem algoritmo nenhum. Ele existe por causa de uma fronteira\n// de DEPENDÊNCIA que nem o `forge-react` nem o `tslite` podem cruzar: o\n// `forge-react` não pode depender do `tslite` (arrastaria a linguagem inteira\n// para quem trocou o binder, e o port `BindingEngine` viraria enfeite), e o\n// `tslite` não pode depender do `forge-react` (ele é o adapter da linguagem, e\n// não conhece view). O conjunto só pode ser um TERCEIRO — este.\n//\n// E a razão de ele existir não é a conveniência de um `install`: sem binder, o\n// default do compositor é o `identityBinder`, e `{{ props.x }}` vira a STRING\n// `\"{{ props.x }}\"` na tela. Nada lança, nada avisa. Quem monta o motor à mão\n// erra isso uma vez e perde a tarde.\n//\n// O corte é a linha que este repo já usa: o que RODA em produção entra; o que\n// PROVA (`check`, `language-service`), o que traduz (`eject`) e o que edita\n// (`editor`, `address`) fica de fora, como o `grid`, que tem peer próprio.\n// ════════════════════════════════════════════════════════════════════════\n\nimport {\n createForge,\n type CreateForgeOptions,\n type Forge,\n} from \"@loom-forge/forge-react\";\nimport { createTsliteBinder } from \"@loom-forge/tslite\";\nimport type { ComponentType } from \"react\";\nimport type { EngineDescriptor } from \"@loom-forge/react\";\nimport type { ComponentDef } from \"@loom-forge/forge\";\n\n/**\n * Uma peça do kit, como o host a conhece: o `type` que o documento cita, o componente React, e\n * o descriptor headless (`schema`, `meta.emits`, `layout`) — a forma que o `register` recebe.\n */\nexport type RuntimePiece = readonly [\n type: string,\n component: ComponentType<never>,\n descriptor?: EngineDescriptor,\n];\n\nexport interface CreateRuntimeOptions extends CreateForgeOptions {\n /** as peças que o documento pode citar por `type`. */\n registry?: readonly RuntimePiece[];\n /** os componentes declarados em JSON. Entram COMPILADOS, uma vez (o relógio lento). */\n defs?: readonly ComponentDef[];\n}\n\n/**\n * O motor pronto: o binder do TSLite, os planners e os renderers default, o boundary de estado,\n * o kit registrado e as definições declaradas.\n *\n * Tudo tem override — é o `createForge` com a montagem decidida, não um motor diferente. Passar\n * `binder` troca a linguagem inteira; para só mudar o perfil ou a stdlib, monte o binder e\n * passe-o: `createRuntime({ binder: createTsliteBinder({ profile }) })`.\n *\n * **O que NÃO entra aqui:** `data`, `contexts` e `commands`. Os três são dado de RENDER — mudam\n * com o mundo, e o lugar deles é o `<ForgeView>`. Congelá-los na montagem fixaria dado num\n * artefato do relógio lento, que é a classe de bug que o motor inteiro evita.\n */\nexport function createRuntime(opts: CreateRuntimeOptions = {}): Forge {\n const { registry, defs, binder, ...rest } = opts;\n const forge = createForge({\n binder: binder ?? createTsliteBinder(),\n ...rest,\n });\n for (const [type, component, descriptor] of registry ?? [])\n forge.register(type, component as ComponentType<unknown>, descriptor);\n for (const def of defs ?? []) forge.defineComponent(def);\n return forge;\n}\n\n/* ── o conjunto ────────────────────────────────────────────────────────────────\n Re-exportado para que o host declare UM package e receba uma versão só de cada\n peça. O modelo, a análise e a view saem FLAT; da linguagem sai o que se monta\n (`createTsliteBinder`, `DEFAULT_PROFILE`), porque o resto dela — `compile`, o\n `addressOf` da árvore, os diagnósticos — é de quem consolida ou tipa, e o\n `addressOf` do `forge` (que também endereça o que mora FORA da árvore) é o que\n vale neste nível. Quem precisa da fase de compilação importa\n `@loom-forge/tslite` direto: ele continua sendo um package. */\n\nexport * from \"@loom-forge/core\";\nexport * from \"@loom-forge/forge\";\nexport * from \"@loom-forge/react\";\nexport * from \"@loom-forge/forge-react\";\nexport {\n createTsliteBinder,\n DEFAULT_PROFILE,\n type TsliteBinderOptions,\n} from \"@loom-forge/tslite\";\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@loom-forge/runtime",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "loom-forge · o RUNTIME de um app React: o motor montado com o binder do TSLite, os planners e os renderers default, e o boundary de estado. Um install, uma versão, uma montagem.",
6
+ "keywords": [
7
+ "loom-forge",
8
+ "runtime",
9
+ "json-ui",
10
+ "react",
11
+ "tslite"
12
+ ],
13
+ "license": "MIT",
14
+ "author": {
15
+ "name": "Anderson D. Rosa",
16
+ "url": "https://github.com/andersondrosa"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/andersondrosa/loom-forge.git",
21
+ "directory": "packages/runtime"
22
+ },
23
+ "homepage": "https://github.com/andersondrosa/loom-forge/tree/main/packages/runtime#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/andersondrosa/loom-forge/issues"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "sideEffects": false,
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "dependencies": {
41
+ "@loom-forge/core": "0.1.0",
42
+ "@loom-forge/forge-react": "0.1.0",
43
+ "@loom-forge/tslite": "0.1.0",
44
+ "@loom-forge/forge": "0.1.0",
45
+ "@loom-forge/react": "0.1.0"
46
+ },
47
+ "peerDependencies": {
48
+ "react": "^18 || ^19"
49
+ },
50
+ "devDependencies": {
51
+ "@types/react": "^19.0.0",
52
+ "@types/react-dom": "^19.0.0",
53
+ "jsdom": "^29.1.1",
54
+ "react": "^19.0.0",
55
+ "react-dom": "^19.0.0",
56
+ "tsup": "^8.0.0",
57
+ "typescript": "^5.9.3"
58
+ },
59
+ "scripts": {
60
+ "build": "tsup",
61
+ "dev": "tsup --watch",
62
+ "test": "vitest run",
63
+ "test:watch": "vitest",
64
+ "typecheck": "tsc --noEmit",
65
+ "clean": "rm -rf dist"
66
+ }
67
+ }