@loom-forge/tslite 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,192 @@
1
+ # @loom-forge/tslite
2
+
3
+ **loom** · a linguagem de expressão da definição. Duas fases, e elas são dois relógios: a
4
+ **compilação** traduz o texto autorado (`"{{ props.nome }}"`) numa ilha `#expr`, e o **bind**
5
+ executa o que ela produziu contra um escopo. Implementa o port `BindingEngine` do
6
+ [`@loom-forge/core`](../core/README.md).
7
+
8
+ ```bash
9
+ pnpm add @loom-forge/tslite
10
+ ```
11
+
12
+ ```ts
13
+ import { compile, createTsliteBinder } from "@loom-forge/tslite";
14
+
15
+ const { def, diagnostics } = compile(autorada); // texto → ilha #expr. NÃO avalia.
16
+ const engine = createEngine({
17
+ binder: createTsliteBinder({ globals: { upper } }),
18
+ });
19
+ ```
20
+
21
+ Quem só **consolida** (um servidor que compila a definição no save) importa o subpath, que não
22
+ carrega o `check` nem a stdlib de runtime — só o que interpreta a string e a lista do que o deploy
23
+ recusaria:
24
+
25
+ ```ts
26
+ import { compile } from "@loom-forge/tslite/compile";
27
+ ```
28
+
29
+ ---
30
+
31
+ ## `compile(def)` — a fase de compilação
32
+
33
+ ```ts
34
+ compile(root: DefNode, opts?: { profile?: CapabilityProfile }):
35
+ { def: DefNode; diagnostics: CompileDiagnostic[]; islands: CompiledIsland[] }
36
+ ```
37
+
38
+ A definição **autorada** carrega a expressão como texto e é o SoT — é o que o autor edita, o
39
+ canvas desenha e o editor diffa. A **compilada** carrega o AST como ilha `#expr` no lugar, e é
40
+ derivada. A forma é a mesma dos dois lados, então o endereço (`#0.1.props.title`) vale para ambos
41
+ e não há source map.
42
+
43
+ - **não avalia** — não tem dados e não computa valor;
44
+ - **não lança** — o campo que não compila fica com o texto original e sai um diagnóstico, com o
45
+ **endereço** do campo, o caminho como lista (`path`, índice e chave distintos), o texto do campo
46
+ (`source`) e o trecho culpado nele (`span`). Os códigos são os do TSLite: `parse-error` ·
47
+ `lex-error` · `island-not-a-value` · `unserializable-expression` · `node-not-allowed` ·
48
+ `forbidden-key` · `forbidden-identifier` · `dynamic-computed`; e o nosso `missing-binding`;
49
+ - **quem interpreta a string é o `parseValue` do `@tslite/template`**, com o `directives` que o
50
+ slot declara (`SLOTS` do core; hoje `false` em todos — `{%` e `{#` são texto). Só string é
51
+ ambígua; `{{ e }}` ocupando a string inteira preserva o tipo de `e`; texto com ilha rende
52
+ string; o que não parseia, não rende valor (`{{ }}`) ou não volta pelo `fromJson` do codec fica
53
+ texto. A string é derivada **uma** vez: a forma serializada vai para `def` — a ilha é o nó como
54
+ está, **normalizada** pelo `@tslite/normalize` (sem posição e sem o type-node sintático; o
55
+ `Schema` de cada anotação fica) —, e as `islands` saem em memória, com `loc` no texto do campo
56
+ — é o que o `check` tipa com span, sem reparsear;
57
+ - **com o `profile` de quem executa, acusa o que o bind recusaria** — pela mesma lista que o
58
+ `compile` do `@tslite/interpret` usa para recusar (`compileViolations`): nó fora do profile e o
59
+ que o sanitize barra, com a precedência de lá. Sem `profile`, isso não é julgado: quem só traduz
60
+ ou tipa não executa. Uma bancada compila com o `DEFAULT_PROFILE`, que é o do binder, e a
61
+ recusa aparece no campo, e não no console;
62
+ - **onde cabe expressão** é a tabela de slots (`FIELDS`/`SLOTS`/`TREE`, exportada pelo core):
63
+ `props`, `events[*].params`, `placement`, `each`, `when`. Campo fora dela não tem porta.
64
+
65
+ A marca da ilha (`EXPR_MARKER`) e o predicado (`isExprMarker`) são do `@tslite/core`, e é de lá
66
+ que o [`@loom-forge/check`](../check/README.md) e o `eject` os importam — a pergunta "isto é uma
67
+ ilha?" tem uma resposta só, e ela é do dono do marcador.
68
+
69
+ ---
70
+
71
+ ## `createTsliteBinder(opts?)` — o bind
72
+
73
+ ```ts
74
+ import { createTsliteBinder } from "@loom-forge/tslite";
75
+ const engine = createEngine({
76
+ binder: createTsliteBinder({ globals: { upper } }),
77
+ });
78
+ ```
79
+
80
+ Expande a definição contra `data`: `{{expr}}` em strings (parser TS-like) e ilhas `#expr` (serializável).
81
+ Pipeline **100% TSLite, sem `new Function`**: a fase `compile()` traduz o texto em ilha `#expr`;
82
+ o bind faz `fromJson → compile (gateado pelo
83
+ CapabilityProfile + sanitize) → runner`.
84
+
85
+ **Os `params` de uma intenção não são avaliados no render.** Um evento (`events.onX`), um efeito
86
+ e cada elo da cadeia `then`/`catch` (ADR-018 do root) saem do `bind` com o `params` **compilado**
87
+ e o escopo do render guardado no binding; quem avalia é o despacho, sobre esse escopo mais a raiz
88
+ `event` — o valor emitido pelo widget, o `data` de um `then`, o `error` de um `catch`. É o
89
+ `intentParams` do `@loom-forge/core`, ligando um nó-fantasma pelo mesmo binder. A compilação
90
+ endereça cada elo pelo caminho dele (`#0.events.onClick.then.params.valido`).
91
+
92
+ ```ts
93
+ interface TsliteBinderOptions {
94
+ globals?: Record<string, unknown>; // helpers no escopo
95
+ profile?: CapabilityProfile; // o que a definição pode programar (default: DEFAULT_PROFILE)
96
+ boundary?: { runtime }; // a metade de runtime de um `createBoundary` do @tslite/validate
97
+ budget?: Budget | false; // custo por avaliação: { steps?, depth?, size? } (default: EXPRESSION_BUDGET)
98
+ onError?(error, source); // falhou ao EXECUTAR (unknown-name, host lançou, orçamento estourado)
99
+ onReject?(error, source); // recusada na COMPILAÇÃO (node-not-allowed, sanitize)
100
+ }
101
+ ```
102
+
103
+ **Seguro por construção:** o `compile` só vê o escopo passado — globais (`fetch`/`process`) não
104
+ existem. **Política = dado:** o `CapabilityProfile` é serializável e viaja com a definição. O
105
+ default, `DEFAULT_PROFILE` (`PRESETS["pure-fp"]`), libera expressão, chamada ao host e lambda —
106
+ inclusive com corpo em bloco — e fecha mutação, laço, `throw` e `async`.
107
+
108
+ **Com lambda, uma expressão consegue não terminar**, e por isso o bind vem com orçamento: o
109
+ `EXPRESSION_BUDGET` do `@tslite/capability` (`steps` 1 milhão · `depth` 256 · `size` 10 milhões)
110
+ entra quando nem a opção nem o profile trazem um. Os números foram medidos aqui (ADR-114) e
111
+ provados lá: um `filter` sobre 10 mil linhas custa ~90 mil passos, e uma recursão infinita para
112
+ em `depth`. Apertar ou afrouxar é trocar o
113
+ profile ou o orçamento, não ligar flags:
114
+
115
+ ```ts
116
+ import { PRESETS } from "@tslite/capability";
117
+ createTsliteBinder({ profile: PRESETS.data }); // fecha a lambda: "JSON com lógica"
118
+ createTsliteBinder({ budget: { steps: 10_000, depth: 32 } }); // orçamento mais apertado
119
+ createTsliteBinder({ budget: false }); // sem contagem, e sem proteção contra recursão sem fim
120
+ ```
121
+
122
+ O `compile` também roda o **sanitize fail-closed** (substrato `__proto__`/`constructor` + autoridade
123
+ ambiente) por default. Ver [docs/ARCHITECTURE](./docs/ARCHITECTURE.md).
124
+
125
+ ### Diretivas de template (`each` / `when`)
126
+
127
+ Estruturais (mudam a árvore, não só valores) — tornam listas/condicionais **declarativas no JSON**:
128
+
129
+ ```jsonc
130
+ {
131
+ "type": "List",
132
+ "children": [
133
+ // each array → repete o nó por item; o item ganha o nome do `as` (+ `$index`)
134
+ {
135
+ "type": "Row",
136
+ "each": "{{ data.users }}",
137
+ "as": "u",
138
+ "props": { "name": "{{ u.name }}" },
139
+ // when → dropa se falsy (por item dentro do each)
140
+ "when": "{{ u.active }}",
141
+ },
142
+ ],
143
+ }
144
+ // each objeto re-escopa a subárvore: { "each": "{{ data.user }}", "as": "u", "props": { "n": "{{ u.name }}" } }
145
+ ```
146
+
147
+ - ids dos repetidos são estáveis: `row.${item.id ?? $index}`.
148
+ - **`as` é obrigatório** — sem ele o item não teria nome, e a compilação acusa `missing-binding`.
149
+ - as diretivas são **consumidas** (não aparecem na saída resolvida) e **guardadas** (mesma Linguagem
150
+ do bind);
151
+ - o escopo do item **estende** o do pai — um `each` aninhado enxerga o item de fora, e a raiz
152
+ `data` continua alcançável de qualquer profundidade.
153
+
154
+ > A camada de **tipos** não está aqui — vive em [`@loom-forge/check`](../check/README.md), para o
155
+ > bind de runtime nunca arrastar `@tslite/checker`/`jsonschema`/`infer`. Ali é que se prova que um
156
+ > `when` rende `boolean` e que `{{ item.nomee }}` não existe na coleção.
157
+
158
+ ---
159
+
160
+ ## Stdlib
161
+
162
+ O escopo carrega os **53 operadores** do `@tslite/operators` por default (`length`, `map`,
163
+ `filter`, `prop`, `pick`…). Não é conveniência: a Linguagem é FP e **não tem protótipo**, então
164
+ `xs.length` é erro e a forma é `length(xs)`. Sob o profile default a composição point-free
165
+ funciona inteira — `map(prop("nome"), xs)`, `filter(propEq("ativo", true), xs)` —, e a lambda
166
+ também: `map((p) => p.nome, xs)`. A ordem é a do Ramda, com a função primeiro.
167
+
168
+ O mesmo vocabulário aparece tipado no [`@loom-forge/check`](../check/README.md) (`vanilla`, as
169
+ assinaturas) — é o que faz o checker provar o que o executor vai rodar.
170
+
171
+ ## O asserter, quando há um
172
+
173
+ O `check` pode tipar `{{ parseAs(Address, props.bruto) }}` contra um catálogo de tipos nomeados.
174
+ Tipar é metade; **provar** é runtime, e runtime é aqui. As duas metades saem de **uma**
175
+ declaração — o `createBoundary` do `@tslite/validate` monta, do mesmo catálogo, o que o checker
176
+ precisa e o que o interpret precisa —, e o host entrega a metade certa a cada lado:
177
+
178
+ ```ts
179
+ import { createBoundary } from "@tslite/validate";
180
+ import { vanilla } from "@tslite/operators";
181
+
182
+ const boundary = createBoundary({
183
+ types: { Address },
184
+ reserved: Object.keys(vanilla),
185
+ });
186
+ createTsliteBinder({ boundary }); // a metade de runtime: o catálogo e o `parseAs`
187
+ buildEnv(def, { boundary }); // no `check`: a metade de tipos, do mesmo objeto
188
+ ```
189
+
190
+ Não vem embutido: quem não usa asserter não deve pagar o validador, e este package não o importa
191
+ — a opção recebe só o `runtime` da fronteira. Ver a fronteira do `unknown` no
192
+ [`@loom-forge/check`](../check/README.md).
@@ -0,0 +1,135 @@
1
+ import { parseValue } from '@tslite/template';
2
+ import { compileViolations } from '@tslite/interpret';
3
+ import { PRESETS } from '@tslite/capability';
4
+ import { valueAt, createPositionMap } from '@tslite/language-service';
5
+ import { expr } from '@tslite/core/builders';
6
+ import { normalize } from '@tslite/normalize';
7
+ import { FIELDS, slotOf, TREE } from '@loom-forge/core';
8
+
9
+ // src/compile.ts
10
+ var DEFAULT_PROFILE = PRESETS["pure-fp"];
11
+ var addressOf = (path) => `#${path.join(".")}`;
12
+ var spanOf = (text, loc) => {
13
+ if (!loc?.start) return void 0;
14
+ const map = createPositionMap(text);
15
+ const at = (p) => map.offsetAt({ line: p.line - 1, column: p.column });
16
+ const from = at(loc.start);
17
+ return { from, to: loc.end ? at(loc.end) : from };
18
+ };
19
+ var setIn = (target, path, value) => {
20
+ if (path.length === 0) return value;
21
+ const [head, ...rest] = path;
22
+ if (Array.isArray(target)) {
23
+ const out = target.slice();
24
+ out[head] = setIn(out[head], rest, value);
25
+ return out;
26
+ }
27
+ const record = target;
28
+ return { ...record, [head]: setIn(record[head], rest, value) };
29
+ };
30
+ var normalized = (node) => expr(normalize(node));
31
+ function compile(root, opts = {}) {
32
+ const diagnostics = [];
33
+ const islands = [];
34
+ const gate = opts.profile ? { profile: opts.profile } : void 0;
35
+ const fail = (code, message, path, extra = {}) => {
36
+ diagnostics.push({
37
+ code,
38
+ message,
39
+ address: addressOf(path),
40
+ path,
41
+ severity: extra.severity ?? "error",
42
+ ...extra.source === void 0 ? {} : { source: extra.source },
43
+ ...extra.span === void 0 ? {} : { span: extra.span }
44
+ });
45
+ };
46
+ const refused = (island, path) => {
47
+ if (!gate) return false;
48
+ const violations = compileViolations(island.node, gate);
49
+ for (const v of violations)
50
+ fail(v.code, v.message, path, {
51
+ source: island.text,
52
+ span: spanOf(island.text, v.node.loc)
53
+ });
54
+ return violations.length > 0;
55
+ };
56
+ const compileValue = (value, path, directives) => {
57
+ const parsed = parseValue(value, { directives });
58
+ let out = parsed.value;
59
+ for (const d of parsed.diagnostics) {
60
+ const text = valueAt(value, d.path);
61
+ fail(d.code, d.message, [...path, ...d.path], {
62
+ severity: d.severity,
63
+ ...typeof text === "string" ? { source: text } : {},
64
+ ...d.span === void 0 ? {} : { span: d.span }
65
+ });
66
+ }
67
+ for (const island of parsed.islands) {
68
+ const at = [...path, ...island.path];
69
+ if (refused(island, at)) {
70
+ out = setIn(out, island.path, island.text);
71
+ continue;
72
+ }
73
+ out = setIn(out, island.path, normalized(island.node));
74
+ islands.push({
75
+ address: addressOf(at),
76
+ path: at,
77
+ text: island.text,
78
+ node: island.node
79
+ });
80
+ }
81
+ return out;
82
+ };
83
+ const compileIntent = (intent, path, directives) => ({
84
+ ...intent,
85
+ ...intent.params === void 0 ? {} : {
86
+ params: compileValue(intent.params, [...path, "params"], directives)
87
+ },
88
+ ...intent.then ? { then: compileIntent(intent.then, [...path, "then"], directives) } : {},
89
+ ...intent.catch ? { catch: compileIntent(intent.catch, [...path, "catch"], directives) } : {}
90
+ });
91
+ const compileIntents = (intents, path, directives) => {
92
+ const out = {};
93
+ for (const [name, intent] of Object.entries(intents))
94
+ out[name] = typeof intent === "string" ? intent : compileIntent(intent, [...path, name], directives);
95
+ return out;
96
+ };
97
+ const compileNode = (node, path) => {
98
+ const out = { ...node };
99
+ const read = (field) => node[field];
100
+ for (const [field, role] of Object.entries(FIELDS)) {
101
+ if (role !== "slot") continue;
102
+ const value = read(field);
103
+ if (value === void 0) continue;
104
+ const decl = slotOf(field);
105
+ if (!decl) continue;
106
+ out[field] = decl.shape === "intentParams" ? compileIntents(
107
+ value,
108
+ [...path, field],
109
+ decl.directives
110
+ ) : compileValue(value, [...path, field], decl.directives);
111
+ }
112
+ if (node.each !== void 0 && typeof node.as !== "string")
113
+ fail(
114
+ "missing-binding",
115
+ "`each` exige `as` \u2014 o nome pelo qual o item entra no escopo",
116
+ [...path, "each"]
117
+ );
118
+ for (const [field, shape] of Object.entries(TREE)) {
119
+ const value = read(field);
120
+ if (value === void 0) continue;
121
+ if (shape === "list" && Array.isArray(value))
122
+ out[field] = value.map(
123
+ (c, i) => compileNode(c, [...path, i])
124
+ );
125
+ else if (shape === "node" && value && typeof value === "object")
126
+ out[field] = compileNode(value, [...path, field]);
127
+ }
128
+ return out;
129
+ };
130
+ return { def: compileNode(root, [0]), diagnostics, islands };
131
+ }
132
+
133
+ export { DEFAULT_PROFILE, addressOf, compile };
134
+ //# sourceMappingURL=chunk-7C76HX6B.js.map
135
+ //# sourceMappingURL=chunk-7C76HX6B.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/compile.ts"],"names":[],"mappings":"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","file":"chunk-7C76HX6B.js","sourcesContent":["import { parseValue, type ValueIsland } from \"@tslite/template\";\nimport { compileViolations } from \"@tslite/interpret\";\nimport { PRESETS, type CapabilityProfile } from \"@tslite/capability\";\nimport {\n createPositionMap,\n valueAt,\n type DocumentSegment,\n type Span,\n} from \"@tslite/language-service\";\nimport type { Expression } from \"@tslite/core\";\nimport { expr } from \"@tslite/core/builders\";\nimport { normalize } from \"@tslite/normalize\";\nimport type { ViolationKind } from \"@tslite/sanitize\";\nimport { FIELDS, slotOf, TREE } from \"@loom-forge/core\";\nimport type { CommandAlias, DefNode, EventBinding } from \"@loom-forge/core\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n COMPILAÇÃO — definição AUTORADA (expressão em texto) → definição COMPILADA\n (expressão em AST, como ilha `#expr`). Mesma forma dos dois lados: nada é\n re-parenteado, absorvido ou podado, então o endereço vale para ambos e não há\n source map.\n\n COMPILAR NÃO AVALIA. Não há dados aqui. `{{ x }}` vira a PERGUNTA em forma de\n árvore; quem responde é o bind, a cada render, contra o escopo vivo. Dobrar uma\n expressão constante aqui apagaria a reatividade em silêncio.\n\n E NUNCA LANÇA. Um `{{` recém-digitado é o estado normal de quem está escrevendo:\n o campo degrada para o texto e sai um diagnóstico. O autor tem o direito de\n escrever errado e ver o erro; o que ele não pode é escrever pela metade e ver a\n tela cair.\n\n QUEM INTERPRETA A STRING É O TSLITE. O `parseValue` do `@tslite/template` aplica a\n regra — só string é ambígua; `{{ e }}` inteira preserva o tipo; texto com ilha rende\n string; o que não parseia ou não volta pelo codec fica texto e sai como diagnóstico —\n e devolve as duas coisas de que este repo precisa: a forma SERIALIZADA (o que o bind\n executa e o que se persiste) e os nós EM MEMÓRIA, com posição no texto do campo (o\n que o `check` tipa com span). A string é derivada UMA vez.\n ────────────────────────────────────────────────────────────────────────────── */\n\nexport type CompileCode =\n /** a expressão não está na gramática da Linguagem (os códigos do parser). */\n | \"parse-error\"\n | \"lex-error\"\n /** a ilha parseia mas não rende um VALOR — o caso comum é a ilha vazia, `{{ }}`. */\n | \"island-not-a-value\"\n /** a expressão parseia, mas a ilha que ela vira não é lida de volta pelo codec — o bind a\n * recusaria, e o campo renderia sem ela. */\n | \"unserializable-expression\"\n /** a expressão usa um nó que o `CapabilityProfile` do deploy fecha (o código é o do TSLite). */\n | \"node-not-allowed\"\n /** o sanitize recusa a expressão — os códigos são os `ViolationKind` do `@tslite/sanitize`. */\n | ViolationKind\n /** `each` sem `as`: o item não teria nome, e nada dentro do laço o alcançaria. */\n | \"missing-binding\";\n\n/**\n * O profile que o bind usa quando ninguém passa outro: **`pure-fp`** — lambda (inclusive com corpo\n * em bloco), ternário, template, spread, `?.`/`??`, sem mutação, laço, `throw` nem `async`.\n *\n * Mora AQUI, e não no binder, porque a pergunta \"o deploy deixa isto rodar?\" também é da autoria: a\n * bancada compila com o mesmo profile que o palco executa, e as duas respostas não podem divergir.\n */\nexport const DEFAULT_PROFILE: CapabilityProfile = PRESETS[\"pure-fp\"];\n\nexport interface CompileOptions {\n /**\n * O `CapabilityProfile` de quem vai EXECUTAR. Com ele, a compilação acusa o que o bind recusaria\n * — nó fora do profile e o que o sanitize barra, pela mesma lista que o `compile` do interpret\n * usa para recusar (`compileViolations`) —, e o campo fica com o texto. Sem ele, nada disso é\n * julgado: quem só traduz (eject) ou só tipa (check) não executa.\n */\n profile?: CapabilityProfile;\n}\n\n/**\n * O caminho de um campo a partir da raiz: `[0, 1, \"props\", \"title\"]`. Índice e chave são\n * distintos no tipo — `2` anda em array, `\"2\"` em objeto —, e o `address` é só a exibição dele.\n */\nexport type CompilePath = readonly DocumentSegment[];\n\n/** `#0.1.props.title` — o caminho como o autor o lê. Derivado; ninguém o lê de volta. */\nexport const addressOf = (path: CompilePath): string => `#${path.join(\".\")}`;\n\nexport interface CompileDiagnostic {\n code: CompileCode;\n message: string;\n address: string;\n path: CompilePath;\n severity: \"error\" | \"warning\";\n /** o texto do campo, como o autor o escreveu, quando há um. */\n source?: string;\n /** offsets em `source` — o trecho culpado. Ausente quando o problema é do campo inteiro. */\n span?: Span;\n}\n\n/**\n * Uma ilha compilada, em memória: o texto do campo e o nó com `loc` relativo a ele. É o que o\n * `check` tipa — com o span no texto que o autor vê, sem reparsear a string.\n */\nexport interface CompiledIsland {\n address: string;\n path: CompilePath;\n text: string;\n node: Expression;\n}\n\nexport interface CompileResult {\n /** a definição compilada. Em erro, o campo culpado fica com o texto original. */\n def: DefNode;\n diagnostics: CompileDiagnostic[];\n /** as ilhas que entraram em `def`, na ordem em que foram encontradas. */\n islands: CompiledIsland[];\n}\n\n/**\n * `loc` do nó (linha 1-based, coluna 0-based) → offsets no texto do campo. É a mesma conversão do\n * `spanOf` do `@tslite/checker`; ela é refeita aqui porque o checker é package de dev, e este\n * subpath é o que um servidor de consolidação carrega sem o executor de tipos.\n */\nconst spanOf = (\n text: string,\n loc: Expression[\"loc\"] | null | undefined,\n): Span | undefined => {\n if (!loc?.start) return undefined;\n const map = createPositionMap(text);\n const at = (p: { line: number; column: number }): number =>\n map.offsetAt({ line: p.line - 1, column: p.column });\n const from = at(loc.start);\n return { from, to: loc.end ? at(loc.end) : from };\n};\n\n/** escreve `value` no caminho, sem tocar no resto — a ilha recusada volta a ser o texto. */\nconst setIn = (target: unknown, path: CompilePath, value: unknown): unknown => {\n if (path.length === 0) return value;\n const [head, ...rest] = path;\n if (Array.isArray(target)) {\n const out = target.slice();\n out[head as number] = setIn(out[head as number], rest, value);\n return out;\n }\n const record = target as Record<string, unknown>;\n return { ...record, [head!]: setIn(record[head!], rest, value) };\n};\n\n/**\n * A ilha NORMALIZADA, num clone — a que vai para a DEFINIÇÃO.\n *\n * O codec entrega a ilha como o nó está: `start`/`end`/`loc` em cada nó no referencial do texto\n * do campo, e o type-node sintático ao lado do `Schema` de cada anotação. Na definição compilada\n * o texto não existe mais (o bind não usa posição, e o `check` a tem pelas ilhas em memória —\n * `islands[].node`, que ficam inteiras), e o type-node é lido só por quem imprime fonte a partir\n * dele — o `@tslite/emit` cai para o `Schema` quando ele não está. Guardar os dois seria persistir\n * um número relativo a um texto ausente e duas árvores do mesmo tipo. Quem sabe a lista do que\n * é artefato é o `@tslite/normalize` (a `ERASABLE_TYPE_NODE_KEYS` do core, D47): um filtro\n * próprio divergiria dela no primeiro campo novo. O `normalize` clona — a ilha da definição e a\n * ilha em memória compartilham nós.\n */\nconst normalized = (node: Expression): unknown => expr(normalize(node));\n\nexport function compile(\n root: DefNode,\n opts: CompileOptions = {},\n): CompileResult {\n const diagnostics: CompileDiagnostic[] = [];\n const islands: CompiledIsland[] = [];\n const gate = opts.profile ? { profile: opts.profile } : undefined;\n\n const fail = (\n code: CompileCode,\n message: string,\n path: CompilePath,\n extra: {\n source?: string;\n span?: Span;\n severity?: \"error\" | \"warning\";\n } = {},\n ): void => {\n diagnostics.push({\n code,\n message,\n address: addressOf(path),\n path,\n severity: extra.severity ?? \"error\",\n ...(extra.source === undefined ? {} : { source: extra.source }),\n ...(extra.span === undefined ? {} : { span: extra.span }),\n });\n };\n\n /**\n * O que o bind deste deploy recusaria ao compilar a ilha — um diagnóstico por violação, pela\n * MESMA lista que o `compile` do interpret usa para recusar. Aqui não há lista de nós proibidos\n * nem precedência de sanitize para envelhecer.\n */\n const refused = (island: ValueIsland, path: CompilePath): boolean => {\n if (!gate) return false;\n const violations = compileViolations(island.node, gate);\n for (const v of violations)\n fail(v.code, v.message, path, {\n source: island.text,\n span: spanOf(island.text, (v.node as Expression).loc),\n });\n return violations.length > 0;\n };\n\n /**\n * O valor de um slot. Só string é ambígua, e quem decide o que uma string é — dado, expressão\n * crua, template — é o `parseValue`, com o `directives` que o SLOT declara. O que ele recusa\n * fica texto; o que o deploy recusaria idem.\n */\n const compileValue = (\n value: unknown,\n path: CompilePath,\n directives: boolean,\n ): unknown => {\n const parsed = parseValue(value, { directives });\n let out = parsed.value;\n for (const d of parsed.diagnostics) {\n const text = valueAt(value, d.path);\n fail(d.code as CompileCode, d.message, [...path, ...d.path], {\n severity: d.severity,\n ...(typeof text === \"string\" ? { source: text } : {}),\n ...(d.span === undefined ? {} : { span: d.span }),\n });\n }\n for (const island of parsed.islands) {\n const at = [...path, ...island.path];\n if (refused(island, at)) {\n out = setIn(out, island.path, island.text);\n continue;\n }\n // a definição leva a ilha NORMALIZADA; a memória leva o nó inteiro, com `loc`\n out = setIn(out, island.path, normalized(island.node));\n islands.push({\n address: addressOf(at),\n path: at,\n text: island.text,\n node: island.node,\n });\n }\n return out;\n };\n\n /** UMA intenção: os `params` dela, e os da cadeia (`then`/`catch`), cada elo no caminho dele. */\n const compileIntent = (\n intent: EventBinding,\n path: CompilePath,\n directives: boolean,\n ): EventBinding => ({\n ...intent,\n ...(intent.params === undefined\n ? {}\n : {\n params: compileValue(intent.params, [...path, \"params\"], directives),\n }),\n ...(intent.then\n ? { then: compileIntent(intent.then, [...path, \"then\"], directives) }\n : {}),\n ...(intent.catch\n ? { catch: compileIntent(intent.catch, [...path, \"catch\"], directives) }\n : {}),\n });\n\n /** um mapa de INTENÇÕES (`events`, `commands`): o compilável é o `params` de cada entrada — e,\n * num evento, os da cadeia. A forma curta de um alias (`\"limpar\": \"form/clear\"`) é só um nome,\n * e atravessa intacta. */\n const compileIntents = (\n intents: Record<string, EventBinding | CommandAlias>,\n path: CompilePath,\n directives: boolean,\n ): Record<string, EventBinding | CommandAlias> => {\n const out: Record<string, EventBinding | CommandAlias> = {};\n for (const [name, intent] of Object.entries(intents))\n out[name] =\n typeof intent === \"string\"\n ? intent\n : compileIntent(intent as EventBinding, [...path, name], directives);\n return out;\n };\n\n const compileNode = (node: DefNode, path: CompilePath): DefNode => {\n const out: Record<string, unknown> = { ...node };\n const read = (field: string): unknown =>\n (node as unknown as Record<string, unknown>)[field];\n\n // os SLOTS — onde expressão pode morar, e o que cada um declara que uma string contém\n for (const [field, role] of Object.entries(FIELDS)) {\n if (role !== \"slot\") continue;\n const value = read(field);\n if (value === undefined) continue;\n const decl = slotOf(field);\n if (!decl) continue; // FIELDS e SLOTS discordaram — há teste cravando que não discordam\n out[field] =\n decl.shape === \"intentParams\"\n ? compileIntents(\n value as Record<string, EventBinding | CommandAlias>,\n [...path, field],\n decl.directives,\n )\n : compileValue(value, [...path, field], decl.directives);\n }\n\n // `each` liga um nome, e o nome é obrigatório: sem ele o item fica inalcançável —\n // o laço repetiria o nó e nada dentro dele veria o item, sem erro nenhum.\n if (node.each !== undefined && typeof node.as !== \"string\")\n fail(\n \"missing-binding\",\n \"`each` exige `as` — o nome pelo qual o item entra no escopo\",\n [...path, \"each\"],\n );\n\n // a ÁRVORE — dirigida pela tabela, não por `children`/`fallback` na mão\n for (const [field, shape] of Object.entries(TREE)) {\n const value = read(field);\n if (value === undefined) continue;\n if (shape === \"list\" && Array.isArray(value))\n out[field] = value.map((c, i) =>\n compileNode(c as DefNode, [...path, i]),\n );\n else if (shape === \"node\" && value && typeof value === \"object\")\n out[field] = compileNode(value as DefNode, [...path, field]);\n }\n\n return out as unknown as DefNode;\n };\n\n return { def: compileNode(root, [0]), diagnostics, islands };\n}\n"]}
@@ -0,0 +1,75 @@
1
+ import { CapabilityProfile } from '@tslite/capability';
2
+ import { DocumentSegment, Span } from '@tslite/language-service';
3
+ import { Expression } from '@tslite/core';
4
+ import { ViolationKind } from '@tslite/sanitize';
5
+ import { DefNode } from '@loom-forge/core';
6
+
7
+ type CompileCode =
8
+ /** a expressão não está na gramática da Linguagem (os códigos do parser). */
9
+ "parse-error" | "lex-error"
10
+ /** a ilha parseia mas não rende um VALOR — o caso comum é a ilha vazia, `{{ }}`. */
11
+ | "island-not-a-value"
12
+ /** a expressão parseia, mas a ilha que ela vira não é lida de volta pelo codec — o bind a
13
+ * recusaria, e o campo renderia sem ela. */
14
+ | "unserializable-expression"
15
+ /** a expressão usa um nó que o `CapabilityProfile` do deploy fecha (o código é o do TSLite). */
16
+ | "node-not-allowed"
17
+ /** o sanitize recusa a expressão — os códigos são os `ViolationKind` do `@tslite/sanitize`. */
18
+ | ViolationKind
19
+ /** `each` sem `as`: o item não teria nome, e nada dentro do laço o alcançaria. */
20
+ | "missing-binding";
21
+ /**
22
+ * O profile que o bind usa quando ninguém passa outro: **`pure-fp`** — lambda (inclusive com corpo
23
+ * em bloco), ternário, template, spread, `?.`/`??`, sem mutação, laço, `throw` nem `async`.
24
+ *
25
+ * Mora AQUI, e não no binder, porque a pergunta "o deploy deixa isto rodar?" também é da autoria: a
26
+ * bancada compila com o mesmo profile que o palco executa, e as duas respostas não podem divergir.
27
+ */
28
+ declare const DEFAULT_PROFILE: CapabilityProfile;
29
+ interface CompileOptions {
30
+ /**
31
+ * O `CapabilityProfile` de quem vai EXECUTAR. Com ele, a compilação acusa o que o bind recusaria
32
+ * — nó fora do profile e o que o sanitize barra, pela mesma lista que o `compile` do interpret
33
+ * usa para recusar (`compileViolations`) —, e o campo fica com o texto. Sem ele, nada disso é
34
+ * julgado: quem só traduz (eject) ou só tipa (check) não executa.
35
+ */
36
+ profile?: CapabilityProfile;
37
+ }
38
+ /**
39
+ * O caminho de um campo a partir da raiz: `[0, 1, "props", "title"]`. Índice e chave são
40
+ * distintos no tipo — `2` anda em array, `"2"` em objeto —, e o `address` é só a exibição dele.
41
+ */
42
+ type CompilePath = readonly DocumentSegment[];
43
+ /** `#0.1.props.title` — o caminho como o autor o lê. Derivado; ninguém o lê de volta. */
44
+ declare const addressOf: (path: CompilePath) => string;
45
+ interface CompileDiagnostic {
46
+ code: CompileCode;
47
+ message: string;
48
+ address: string;
49
+ path: CompilePath;
50
+ severity: "error" | "warning";
51
+ /** o texto do campo, como o autor o escreveu, quando há um. */
52
+ source?: string;
53
+ /** offsets em `source` — o trecho culpado. Ausente quando o problema é do campo inteiro. */
54
+ span?: Span;
55
+ }
56
+ /**
57
+ * Uma ilha compilada, em memória: o texto do campo e o nó com `loc` relativo a ele. É o que o
58
+ * `check` tipa — com o span no texto que o autor vê, sem reparsear a string.
59
+ */
60
+ interface CompiledIsland {
61
+ address: string;
62
+ path: CompilePath;
63
+ text: string;
64
+ node: Expression;
65
+ }
66
+ interface CompileResult {
67
+ /** a definição compilada. Em erro, o campo culpado fica com o texto original. */
68
+ def: DefNode;
69
+ diagnostics: CompileDiagnostic[];
70
+ /** as ilhas que entraram em `def`, na ordem em que foram encontradas. */
71
+ islands: CompiledIsland[];
72
+ }
73
+ declare function compile(root: DefNode, opts?: CompileOptions): CompileResult;
74
+
75
+ export { type CompileCode, type CompileDiagnostic, type CompileOptions, type CompilePath, type CompileResult, type CompiledIsland, DEFAULT_PROFILE, addressOf, compile };
@@ -0,0 +1,3 @@
1
+ export { DEFAULT_PROFILE, addressOf, compile } from './chunk-7C76HX6B.js';
2
+ //# sourceMappingURL=compile.js.map
3
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"compile.js"}
@@ -0,0 +1,46 @@
1
+ import { CapabilityProfile, Budget } from '@tslite/capability';
2
+ import { BindingEngine } from '@loom-forge/core';
3
+ export { CompileCode, CompileDiagnostic, CompileOptions, CompilePath, CompileResult, CompiledIsland, DEFAULT_PROFILE, addressOf, compile } from './compile.js';
4
+ import '@tslite/language-service';
5
+ import '@tslite/core';
6
+ import '@tslite/sanitize';
7
+
8
+ interface TsliteBinderOptions {
9
+ /**
10
+ * A STDLIB e os helpers do host, planos no escopo. Default: `vanillaRuntime` do
11
+ * `@tslite/operators` — os 53 operadores (`length`, `map`, `filter`, `pick`…).
12
+ *
13
+ * Não é conveniência: o TSLite é FP e **não tem protótipo**, então `xs.length` é erro e a
14
+ * forma é `length(xs)`. Sem os operadores, uma expressão não consegue fazer quase nada.
15
+ * Passar `globals` SUBSTITUI a stdlib; para acrescentar, espalhe: `{ ...vanillaRuntime, meu }`.
16
+ */
17
+ globals?: Record<string, unknown>;
18
+ /**
19
+ * A metade de RUNTIME da fronteira de tipos — o `runtime` de um `createBoundary` do
20
+ * `@tslite/validate`: o catálogo de tipos nomeados como valor e o asserter (`parseAs`) que
21
+ * prova. O host monta a fronteira UMA vez, do mesmo catálogo que dá ao `check`, e entrega a
22
+ * metade certa a cada lado; quem não usa asserter não instala o validador.
23
+ */
24
+ boundary?: {
25
+ readonly runtime: Readonly<Record<string, unknown>>;
26
+ };
27
+ /** o que a definição pode programar — `CapabilityProfile` (dado puro, serializável).
28
+ * Default: `DEFAULT_PROFILE` (`PRESETS["pure-fp"]`: lambda, sem mutação/laço/`throw`/async).
29
+ * Apertar ou afrouxar = passar outro preset (`PRESETS.data` fecha a lambda) ou um profile
30
+ * próprio. */
31
+ profile?: CapabilityProfile;
32
+ /** orçamento de EXECUÇÃO por avaliação (`steps`/`depth`/`size`, em unidades da Linguagem).
33
+ * Vence o do profile; sem nenhum dos dois, vale o `EXPRESSION_BUDGET` do `@tslite/capability`
34
+ * — o orçamento MEDIDO para uma expressão vinda de um documento. `false` desliga a contagem:
35
+ * o runner fica idêntico ao sem instrumentação, e uma recursão sem fim só para quando a pilha
36
+ * do JS estoura. */
37
+ budget?: Budget | false;
38
+ /** erro de execução (ex.: nome desconhecido). Default: warn em dev + `undefined`. */
39
+ onError?(error: unknown, source: string): void;
40
+ /** expressão recusada na COMPILAÇÃO — fora do profile (`node-not-allowed`) ou barrada pelo
41
+ * sanitize (`__proto__`/`constructor`, autoridade ambiente). Default: warn em dev + `undefined`. */
42
+ onReject?(error: unknown, source: string): void;
43
+ }
44
+ declare function createTsliteBinder(opts?: TsliteBinderOptions): BindingEngine;
45
+
46
+ export { type TsliteBinderOptions, createTsliteBinder };
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ import { DEFAULT_PROFILE, compile } from './chunk-7C76HX6B.js';
2
+ export { DEFAULT_PROFILE, addressOf, compile } from './chunk-7C76HX6B.js';
3
+ import { fromJson } from '@tslite/codec';
4
+ import { isExprMarker, EXPR_MARKER } from '@tslite/core';
5
+ import { vanillaRuntime } from '@tslite/operators';
6
+ import { createBaseScope, compile as compile$1 } from '@tslite/interpret';
7
+ import { hasIslands } from '@tslite/template';
8
+ import { compileProfile, EXPRESSION_BUDGET } from '@tslite/capability';
9
+ import { SLOTS } from '@loom-forge/core';
10
+
11
+ var marker = (v) => `${EXPR_MARKER}:${String(v[EXPR_MARKER])}`;
12
+ var isTsliteError = (e) => !!e && typeof e === "object" && "code" in e;
13
+ var isDev = () => {
14
+ const g = globalThis;
15
+ return g.process?.env?.NODE_ENV !== "production";
16
+ };
17
+ function createTsliteBinder(opts = {}) {
18
+ const globals = {
19
+ ...opts.globals ?? vanillaRuntime,
20
+ ...opts.boundary?.runtime ?? {}
21
+ };
22
+ const profile = opts.profile ?? DEFAULT_PROFILE;
23
+ const budget = opts.budget === false ? void 0 : opts.budget ?? (compileProfile(profile).budget === null ? EXPRESSION_BUDGET : void 0);
24
+ const base = createBaseScope(globals);
25
+ const compileOpts = budget ? { profile, budget, base } : { profile, base };
26
+ const onError = opts.onError ?? ((error, source) => {
27
+ if (isDev())
28
+ console.warn(`[loom/tslite] erro avaliando "${source}":`, error);
29
+ });
30
+ const onReject = opts.onReject ?? ((error, source) => {
31
+ if (isDev())
32
+ console.warn(`[loom/tslite] express\xE3o recusada "${source}":`, error);
33
+ });
34
+ const islands = /* @__PURE__ */ new WeakMap();
35
+ const exec = (entry, scope, source) => {
36
+ if (!entry.ok) {
37
+ onReject(entry.error, source);
38
+ return void 0;
39
+ }
40
+ try {
41
+ return entry.run(scope);
42
+ } catch (error) {
43
+ onError(error, source);
44
+ return void 0;
45
+ }
46
+ };
47
+ const evalIsland = (value, scope) => {
48
+ let entry = islands.get(value);
49
+ if (entry === void 0) {
50
+ try {
51
+ entry = {
52
+ ok: true,
53
+ run: compile$1(fromJson(value), compileOpts)
54
+ };
55
+ } catch (error) {
56
+ entry = {
57
+ ok: false,
58
+ phase: isTsliteError(error) ? "compile" : "decode",
59
+ error
60
+ };
61
+ }
62
+ islands.set(value, entry);
63
+ }
64
+ return exec(entry, scope, marker(value));
65
+ };
66
+ const bindValue = (v, scope) => {
67
+ if (isExprMarker(v)) return evalIsland(v, scope);
68
+ if (Array.isArray(v)) return v.map((x) => bindValue(x, scope));
69
+ if (v && typeof v === "object") {
70
+ const out = {};
71
+ for (const [k, val] of Object.entries(v)) out[k] = bindValue(val, scope);
72
+ return out;
73
+ }
74
+ return v;
75
+ };
76
+ const isObj = (v) => !!v && typeof v === "object" && !Array.isArray(v);
77
+ const dir = (node, key) => node[key];
78
+ const evalDirective = (v, scope) => isExprMarker(v) ? evalIsland(v, scope) : v;
79
+ const rootScope = (scope) => isObj(scope) ? scope : {};
80
+ const childScope = (parent, binding, item, index) => ({
81
+ ...parent,
82
+ ...binding ? { [binding]: item } : {},
83
+ ...index != null ? { $index: index } : {}
84
+ });
85
+ const repeatId = (baseId, item, index) => `${baseId}.${isObj(item) && "id" in item ? String(item.id) : index}`;
86
+ const stripDir = (node) => {
87
+ if (dir(node, "when") === void 0 && dir(node, "each") === void 0 && node.as === void 0)
88
+ return node;
89
+ const n = { ...node };
90
+ delete n.when;
91
+ delete n.each;
92
+ delete n.as;
93
+ return n;
94
+ };
95
+ const VALUE_SLOTS = Object.entries(SLOTS).filter(
96
+ ([, decl]) => decl.stage === "value"
97
+ );
98
+ const slotDyn = (node, field, shape) => {
99
+ const v = dir(node, field);
100
+ if (v === void 0) return false;
101
+ if (shape === "intentParams")
102
+ return Object.values(
103
+ v
104
+ ).some((b) => typeof b !== "string" && intentHasIsland(b));
105
+ return containsIsland(v);
106
+ };
107
+ const intentHasIsland = (b) => b.params !== void 0 && containsIsland(b.params) || b.then !== void 0 && intentHasIsland(b.then) || b.catch !== void 0 && intentHasIsland(b.catch);
108
+ const bindValues = (node, scope) => {
109
+ const hasDir = dir(node, "when") !== void 0 || dir(node, "each") !== void 0 || node.as !== void 0;
110
+ const dyn = VALUE_SLOTS.filter(
111
+ ([field, decl]) => slotDyn(node, field, decl.shape)
112
+ );
113
+ if (!hasDir && dyn.length === 0) return node;
114
+ const out = { ...node };
115
+ delete out.when;
116
+ delete out.each;
117
+ delete out.as;
118
+ for (const [field, decl] of dyn) {
119
+ const value = dir(node, field);
120
+ if (decl.shape === "intentParams") {
121
+ const intents = {};
122
+ for (const [k, b] of Object.entries(
123
+ value
124
+ ))
125
+ intents[k] = typeof b !== "string" && intentHasIsland(b) ? { ...b, scope } : b;
126
+ out[field] = intents;
127
+ } else out[field] = bindValue(value, scope);
128
+ }
129
+ return out;
130
+ };
131
+ const renderResolved = (node, scope) => {
132
+ const next = bindValues(node, scope);
133
+ if (!node.children) return next;
134
+ const kids = node.children.flatMap((c) => expandChild(c, scope));
135
+ return next.children === kids ? next : { ...next, children: kids };
136
+ };
137
+ const expandChild = (child, parentScope) => {
138
+ const dataDir = dir(child, "each");
139
+ const when = dir(child, "when");
140
+ const binding = typeof child.as === "string" ? child.as : void 0;
141
+ const dataVal = dataDir !== void 0 ? evalDirective(dataDir, parentScope) : void 0;
142
+ if (Array.isArray(dataVal)) {
143
+ const out = [];
144
+ dataVal.forEach((item, i) => {
145
+ const s2 = childScope(parentScope, binding, item, i);
146
+ if (when !== void 0 && !evalDirective(when, s2)) return;
147
+ out.push(
148
+ renderResolved(
149
+ stripDir({ ...child, id: repeatId(child.id, item, i) }),
150
+ s2
151
+ )
152
+ );
153
+ });
154
+ return out;
155
+ }
156
+ const s = dataVal !== void 0 ? childScope(parentScope, binding, dataVal) : parentScope;
157
+ if (when !== void 0 && !evalDirective(when, s)) return [];
158
+ return [renderResolved(stripDir(child), s)];
159
+ };
160
+ const compiled = /* @__PURE__ */ new WeakMap();
161
+ return {
162
+ compile(node) {
163
+ const hit = compiled.get(node);
164
+ if (hit !== void 0) return hit;
165
+ const { def, diagnostics } = compile(node, { profile });
166
+ for (const d of diagnostics)
167
+ if (d.severity === "error") onReject(d.message, d.source ?? d.address);
168
+ compiled.set(node, def);
169
+ compiled.set(def, def);
170
+ return def;
171
+ },
172
+ bind(node, data) {
173
+ if (isDev()) warnIfAuthored(node);
174
+ const out = expandChild(node, rootScope(data));
175
+ return out[0] ?? stripDir(node);
176
+ }
177
+ };
178
+ }
179
+ var warned = /* @__PURE__ */ new WeakSet();
180
+ function warnIfAuthored(node) {
181
+ if (warned.has(node)) return;
182
+ warned.add(node);
183
+ const found = findAuthoredSlot(node);
184
+ if (found)
185
+ console.warn(
186
+ `[loom/tslite] defini\xE7\xE3o N\xC3O COMPILADA: ${found} tem "{{ }}" em texto, e o bind trata string como dado. Passe a defini\xE7\xE3o por compile() antes \u2014 o ponto de entrada (resolveTree / a expans\xE3o do forge) faz isso pelo port.`
187
+ );
188
+ }
189
+ function findAuthoredSlot(node, path = "#0") {
190
+ for (const [field, decl] of Object.entries(SLOTS)) {
191
+ const value = node[field];
192
+ if (hasAuthoredText(value, decl.directives)) return `${path}.${field}`;
193
+ }
194
+ for (const [i, child] of (node.children ?? []).entries()) {
195
+ const found = findAuthoredSlot(child, `${path}.${i}`);
196
+ if (found) return found;
197
+ }
198
+ return void 0;
199
+ }
200
+ function hasAuthoredText(v, directives) {
201
+ if (typeof v === "string") return hasIslands(v, { directives });
202
+ if (Array.isArray(v)) return v.some((x) => hasAuthoredText(x, directives));
203
+ if (v && typeof v === "object")
204
+ return Object.values(v).some((x) => hasAuthoredText(x, directives));
205
+ return false;
206
+ }
207
+ function scan(v) {
208
+ if (isExprMarker(v)) return true;
209
+ if (Array.isArray(v)) return v.some(containsIsland);
210
+ if (v && typeof v === "object") return Object.values(v).some(containsIsland);
211
+ return false;
212
+ }
213
+ var scanned = /* @__PURE__ */ new WeakMap();
214
+ function containsIsland(v) {
215
+ if (v === null || typeof v !== "object") return false;
216
+ const hit = scanned.get(v);
217
+ if (hit !== void 0) return hit;
218
+ const r = scan(v);
219
+ scanned.set(v, r);
220
+ return r;
221
+ }
222
+
223
+ export { createTsliteBinder };
224
+ //# sourceMappingURL=index.js.map
225
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/bind.ts"],"names":["compile","s"],"mappings":"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","file":"index.js","sourcesContent":["import { fromJson } from \"@tslite/codec\";\nimport { EXPR_MARKER, isExprMarker } from \"@tslite/core\";\nimport { vanillaRuntime } from \"@tslite/operators\";\nimport { compile, createBaseScope } from \"@tslite/interpret\";\nimport { hasIslands } from \"@tslite/template\";\nimport {\n compileProfile,\n EXPRESSION_BUDGET,\n type Budget,\n type CapabilityProfile,\n} from \"@tslite/capability\";\nimport type {\n BindingEngine,\n CommandAlias,\n DefNode,\n EventBinding,\n} from \"@loom-forge/core\";\nimport { compile as compileDefinition, DEFAULT_PROFILE } from \"./compile\";\nimport { SLOTS } from \"@loom-forge/core\";\n\n/* ──────────────────────────────────────────────────────────────────────────────\n Adapter de BIND via TSLite — implementa o `BindingEngine` (port). Expande a definição\n contra `data` em DOIS estágios, sobre uma cadeia de escopos léxicos (não global):\n\n ESTRUTURAL (when/each) → VALOR ({{}} / #expr)\n muda a ÁRVORE (expansão 1:N) liga VALORES (1:1)\n\n • ESTRUTURAL: `when` dropa o nó se falsy (por item dentro de `each`); `each` array repete\n o nó por item (o item entra pelo nome do `as`, + `$index`); `each` objeto re-escopa a\n subárvore. Ids dos repetidos = `id.${item.id ?? índice}` (estáveis). Diretivas consumidas.\n • VALOR: `{{expr}}` em strings (parser, TS-like) e ilha `#expr` (JsonExpression).\n • EXECUÇÃO (valor E diretivas): parseExpression/decode → compile (eval-free, GATEADO pelo\n CapabilityProfile) → runner. O profile é DADO serializável: diz o que a definição pode\n programar, e o `compile` recusa (hard) o nó fora dele. NÃO há `new Function`;\n identificadores só resolvem pelo escopo — globais (`fetch`/`process`) não existem.\n ────────────────────────────────────────────────────────────────────────────── */\n\nexport interface TsliteBinderOptions {\n /**\n * A STDLIB e os helpers do host, planos no escopo. Default: `vanillaRuntime` do\n * `@tslite/operators` — os 53 operadores (`length`, `map`, `filter`, `pick`…).\n *\n * Não é conveniência: o TSLite é FP e **não tem protótipo**, então `xs.length` é erro e a\n * forma é `length(xs)`. Sem os operadores, uma expressão não consegue fazer quase nada.\n * Passar `globals` SUBSTITUI a stdlib; para acrescentar, espalhe: `{ ...vanillaRuntime, meu }`.\n */\n globals?: Record<string, unknown>;\n /**\n * A metade de RUNTIME da fronteira de tipos — o `runtime` de um `createBoundary` do\n * `@tslite/validate`: o catálogo de tipos nomeados como valor e o asserter (`parseAs`) que\n * prova. O host monta a fronteira UMA vez, do mesmo catálogo que dá ao `check`, e entrega a\n * metade certa a cada lado; quem não usa asserter não instala o validador.\n */\n boundary?: { readonly runtime: Readonly<Record<string, unknown>> };\n /** o que a definição pode programar — `CapabilityProfile` (dado puro, serializável).\n * Default: `DEFAULT_PROFILE` (`PRESETS[\"pure-fp\"]`: lambda, sem mutação/laço/`throw`/async).\n * Apertar ou afrouxar = passar outro preset (`PRESETS.data` fecha a lambda) ou um profile\n * próprio. */\n profile?: CapabilityProfile;\n /** orçamento de EXECUÇÃO por avaliação (`steps`/`depth`/`size`, em unidades da Linguagem).\n * Vence o do profile; sem nenhum dos dois, vale o `EXPRESSION_BUDGET` do `@tslite/capability`\n * — o orçamento MEDIDO para uma expressão vinda de um documento. `false` desliga a contagem:\n * o runner fica idêntico ao sem instrumentação, e uma recursão sem fim só para quando a pilha\n * do JS estoura. */\n budget?: Budget | false;\n /** erro de execução (ex.: nome desconhecido). Default: warn em dev + `undefined`. */\n onError?(error: unknown, source: string): void;\n /** expressão recusada na COMPILAÇÃO — fora do profile (`node-not-allowed`) ou barrada pelo\n * sanitize (`__proto__`/`constructor`, autoridade ambiente). Default: warn em dev + `undefined`. */\n onReject?(error: unknown, source: string): void;\n}\n\n/** rótulo da ilha para o diagnóstico: o tipo do nó raiz. */\nconst marker = (v: object): string =>\n `${EXPR_MARKER}:${String((v as Record<string, unknown>)[EXPR_MARKER])}`;\n\nconst isTsliteError = (e: unknown): boolean =>\n !!e && typeof e === \"object\" && \"code\" in (e as object);\n\nconst isDev = (): boolean => {\n const g = globalThis as { process?: { env?: { NODE_ENV?: string } } };\n return g.process?.env?.NODE_ENV !== \"production\";\n};\n\nexport function createTsliteBinder(\n opts: TsliteBinderOptions = {},\n): BindingEngine {\n const globals = {\n ...(opts.globals ?? vanillaRuntime),\n ...(opts.boundary?.runtime ?? {}),\n };\n const profile = opts.profile ?? DEFAULT_PROFILE;\n // a precedência do `interpret`: o `budget` explícito vence o do profile. O default só entra\n // quando nenhum dos dois declara — um profile com orçamento próprio (`server`) segue o dele.\n const budget =\n opts.budget === false\n ? undefined\n : (opts.budget ??\n (compileProfile(profile).budget === null\n ? EXPRESSION_BUDGET\n : undefined));\n // a BASE do escopo: os `globals` montados UMA vez, e cada avaliação abre o próprio root como\n // filho dela — em vez de copiar os 53 operadores a cada render e a cada item de um `each`.\n // Ela é só leitura: um programa que reatribuísse `map` mudaria todas as execuções seguintes.\n const base = createBaseScope(globals);\n const compileOpts = budget ? { profile, budget, base } : { profile, base };\n const onError =\n opts.onError ??\n ((error, source) => {\n if (isDev())\n console.warn(`[loom/tslite] erro avaliando \"${source}\":`, error);\n });\n const onReject =\n opts.onReject ??\n ((error, source) => {\n if (isDev())\n console.warn(`[loom/tslite] expressão recusada \"${source}\":`, error);\n });\n\n /* ── TABELA DE COMPILAÇÃO ────────────────────────────────────────────────────────\n O binder consome a definição COMPILADA: expressão já é ÁRVORE (ilha `#expr`), nunca\n texto. Aqui não há parser — só `fromJson` (valor com ilhas → AST) e o `compile` do\n TSLite, cada ilha uma vez. String é DADO, sempre.\n\n A tabela é chaveada pela IDENTIDADE da ilha (WeakMap): a ilha é um objeto da definição,\n estável entre renders. E é POR BINDER porque o runner carrega o gating do `profile` —\n dois binders com profiles diferentes não podem compartilhar runner.\n\n As falhas são memoizadas (não o efeito: os callbacks seguem disparando a cada avaliação)\n — uma ilha recusada não deve ser recompilada a cada render só pra ser recusada de novo. */\n type Compiled =\n | { ok: true; run: ReturnType<typeof compile> }\n | { ok: false; phase: \"decode\" | \"compile\"; error: unknown };\n\n const islands = new WeakMap<object, Compiled>();\n\n /** Executa uma entrada da tabela. Recusa (compilação/decode) → `onReject`; falha de\n * execução (escopo sem o nome, host lançou, orçamento estourado) → `onError`. Nunca lança. */\n const exec = (\n entry: Compiled,\n scope: Record<string, unknown>,\n source: string,\n ): unknown => {\n if (!entry.ok) {\n onReject(entry.error, source);\n return undefined;\n }\n try {\n return entry.run(scope);\n } catch (error) {\n onError(error, source);\n return undefined;\n }\n };\n\n const evalIsland = (\n value: object,\n scope: Record<string, unknown>,\n ): unknown => {\n let entry = islands.get(value);\n if (entry === undefined) {\n try {\n entry = {\n ok: true,\n run: compile(fromJson(value as never), compileOpts),\n };\n } catch (error) {\n entry = {\n ok: false,\n phase: isTsliteError(error) ? \"compile\" : \"decode\",\n error,\n };\n }\n islands.set(value, entry);\n }\n return exec(entry, scope, marker(value));\n };\n /**\n * Valor compilado → valor ligado. **String é DADO** — na definição compilada não existe\n * expressão em texto, então não há o que farejar. Só a ilha computa, e o estático desce\n * intacto: o custo é proporcional ao dinamismo.\n */\n const bindValue = (v: unknown, scope: Record<string, unknown>): unknown => {\n if (isExprMarker(v)) return evalIsland(v as object, scope);\n if (Array.isArray(v)) return v.map((x) => bindValue(x, scope));\n if (v && typeof v === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, val] of Object.entries(v)) out[k] = bindValue(val, scope);\n return out;\n }\n return v;\n };\n\n /* ── estágio estrutural: when (drop) + each (repeat/re-scope) ──────────────── */\n\n const isObj = (v: unknown): v is Record<string, unknown> =>\n !!v && typeof v === \"object\" && !Array.isArray(v);\n\n const dir = (node: DefNode, key: string): unknown =>\n (node as unknown as Record<string, unknown>)[key];\n\n /** avalia `when`/`each`: ilha → executa; literal → o próprio valor. */\n const evalDirective = (\n v: unknown,\n scope: Record<string, unknown>,\n ): unknown => (isExprMarker(v) ? evalIsland(v as object, scope) : v);\n\n /* ── ESCOPO: raízes nomeadas, nada implícito ──────────────────────────────────────\n Todo nome ou é RAIZ (o mapa que o chamador passa: `data` no render, `props`/`state`\n no corpo de um componente) ou foi DECLARADO — o `as` de um `each`. Não há nome\n achatado: prop e state não disputam espaço, e o checker consegue montar o `env` como\n projeção direta do que o `ComponentDef` declara.\n\n Os `globals` ficam planos e moram na BASE do escopo: são o vocabulário da Linguagem\n (`length`, `map`), não dado do programa — e um nome do escopo os sombreia.\n\n O escopo do item ESTENDE o do pai — `each` aninhado enxerga o item de fora, que é o\n que \"escopo léxico\" quer dizer. */\n const rootScope = (scope: unknown): Record<string, unknown> =>\n isObj(scope) ? scope : {};\n\n const childScope = (\n parent: Record<string, unknown>,\n binding: string | undefined,\n item: unknown,\n index?: number,\n ): Record<string, unknown> => ({\n ...parent,\n ...(binding ? { [binding]: item } : {}),\n ...(index != null ? { $index: index } : {}),\n });\n\n const repeatId = (baseId: string, item: unknown, index: number): string =>\n `${baseId}.${isObj(item) && \"id\" in item ? String(item.id) : index}`;\n\n /** as diretivas são CONSUMIDAS: não aparecem na saída resolvida. */\n const stripDir = (node: DefNode): DefNode => {\n if (\n dir(node, \"when\") === undefined &&\n dir(node, \"each\") === undefined &&\n node.as === undefined\n )\n return node;\n const n = { ...node } as unknown as Record<string, unknown>;\n delete n.when;\n delete n.each;\n delete n.as;\n return n as unknown as DefNode;\n };\n\n /** os campos que este estágio liga — os de VALOR. Os estruturais (`each`/`when`) são do\n * outro estágio, e quem diz qual é qual é a TABELA, não uma lista aqui: campo novo passa a\n * ser coberto sem ninguém lembrar. É para isso que ela existe. */\n const VALUE_SLOTS = Object.entries(SLOTS).filter(\n ([, decl]) => decl.stage === \"value\",\n );\n\n /** o campo tem alguma ilha para avaliar? */\n const slotDyn = (node: DefNode, field: string, shape: string): boolean => {\n const v = dir(node, field);\n if (v === undefined) return false;\n if (shape === \"intentParams\")\n return Object.values(\n v as Record<string, EventBinding | CommandAlias>,\n ).some((b) => typeof b !== \"string\" && intentHasIsland(b));\n return containsIsland(v);\n };\n\n /** a intenção — ou algum elo da cadeia dela — tem ilha nos `params`? */\n const intentHasIsland = (b: EventBinding): boolean =>\n (b.params !== undefined && containsIsland(b.params)) ||\n (b.then !== undefined && intentHasIsland(b.then)) ||\n (b.catch !== undefined && intentHasIsland(b.catch));\n\n /** constrói o nó final: remove as diretivas estruturais + liga os slots de VALOR. Mantém a\n * ref se nada muda. NÃO recursa filhos. */\n const bindValues = (\n node: DefNode,\n scope: Record<string, unknown>,\n ): DefNode => {\n const hasDir =\n dir(node, \"when\") !== undefined ||\n dir(node, \"each\") !== undefined ||\n node.as !== undefined;\n const dyn = VALUE_SLOTS.filter(([field, decl]) =>\n slotDyn(node, field, decl.shape),\n );\n if (!hasDir && dyn.length === 0) return node;\n const out = { ...node } as unknown as Record<string, unknown>;\n delete out.when;\n delete out.each;\n delete out.as;\n for (const [field, decl] of dyn) {\n const value = dir(node, field);\n if (decl.shape === \"intentParams\") {\n // `events` ou `commands`: os `params` de uma intenção NÃO são avaliados aqui — ficam\n // compilados, e o binding guarda o escopo do render. Quem avalia é o despacho, sobre\n // esse escopo mais a raiz `event` (ADR-018). A forma curta de um alias é só um nome, e\n // atravessa intacta.\n const intents: Record<string, EventBinding | CommandAlias> = {};\n for (const [k, b] of Object.entries(\n value as Record<string, EventBinding | CommandAlias>,\n ))\n intents[k] =\n typeof b !== \"string\" && intentHasIsland(b) ? { ...b, scope } : b;\n out[field] = intents;\n } else out[field] = bindValue(value, scope);\n }\n return out as unknown as DefNode;\n };\n\n /** renderiza um nó que VAI aparecer (escopo já decidido pelo caller): bind de valores +\n * expande os filhos via `expandChild`. */\n const renderResolved = (\n node: DefNode,\n scope: Record<string, unknown>,\n ): DefNode => {\n const next = bindValues(node, scope);\n if (!node.children) return next;\n const kids = node.children.flatMap((c) => expandChild(c, scope));\n return next.children === kids ? next : { ...next, children: kids };\n };\n\n /** expande UM filho → 0+ nós: `when` (drop), `each` array (repeat por item), objeto\n * (re-escopa a subárvore). O item entra no escopo pelo nome que o `as` declara. */\n const expandChild = (\n child: DefNode,\n parentScope: Record<string, unknown>,\n ): DefNode[] => {\n const dataDir = dir(child, \"each\");\n const when = dir(child, \"when\");\n const binding = typeof child.as === \"string\" ? child.as : undefined;\n const dataVal =\n dataDir !== undefined ? evalDirective(dataDir, parentScope) : undefined;\n\n if (Array.isArray(dataVal)) {\n const out: DefNode[] = [];\n dataVal.forEach((item, i) => {\n const s = childScope(parentScope, binding, item, i);\n if (when !== undefined && !evalDirective(when, s)) return; // when por item\n out.push(\n renderResolved(\n stripDir({ ...child, id: repeatId(child.id, item, i) }),\n s,\n ),\n );\n });\n return out;\n }\n\n const s =\n dataVal !== undefined\n ? childScope(parentScope, binding, dataVal)\n : parentScope;\n if (when !== undefined && !evalDirective(when, s)) return []; // drop\n return [renderResolved(stripDir(child), s)];\n };\n\n /* ── A FASE DE COMPILAÇÃO ────────────────────────────────────────────────────────\n Memoizada por identidade do nó e idempotente: chamar de novo é barato, e chamar\n sobre já-compilado é no-op. Os diagnósticos vão pro `onReject` — recusar é da\n compilação, que é exatamente o que eles são. */\n const compiled = new WeakMap<DefNode, DefNode>();\n\n return {\n compile(node: DefNode): DefNode {\n const hit = compiled.get(node);\n if (hit !== undefined) return hit;\n // com o profile deste binder: o que ele recusaria sai aqui, uma vez, com endereço — e não\n // a cada avaliação, sem ele\n const { def, diagnostics } = compileDefinition(node, { profile });\n for (const d of diagnostics)\n if (d.severity === \"error\") onReject(d.message, d.source ?? d.address);\n compiled.set(node, def);\n compiled.set(def, def); // recompilar a saída é no-op\n return def;\n },\n\n bind(node: DefNode, data: unknown): DefNode {\n if (isDev()) warnIfAuthored(node);\n // a raiz é tratada como qualquer nó (suporta each-objeto/when); raiz nunca é dropada.\n const out = expandChild(node, rootScope(data));\n return out[0] ?? stripDir(node);\n },\n };\n}\n\n/* ── O guarda ────────────────────────────────────────────────────────────────────\n O `bind` consome COMPILADO. Se chegar definição autorada, a expressão em texto seria\n tratada como dado e o nó renderizaria `\"olá {{ nome }}\"` literalmente — o código parece\n certo, não levanta erro, e custa horas. O guarda roda só em dev e só olha os SLOTS. */\nconst warned = new WeakSet<object>();\n\nfunction warnIfAuthored(node: DefNode): void {\n if (warned.has(node)) return;\n warned.add(node);\n const found = findAuthoredSlot(node);\n if (found)\n console.warn(\n `[loom/tslite] definição NÃO COMPILADA: ${found} tem \"{{ }}\" em texto, e o bind ` +\n `trata string como dado. Passe a definição por compile() antes — o ponto de entrada ` +\n `(resolveTree / a expansão do forge) faz isso pelo port.`,\n );\n}\n\n/** o primeiro slot com expressão em texto, ou `undefined`. */\nfunction findAuthoredSlot(node: DefNode, path = \"#0\"): string | undefined {\n for (const [field, decl] of Object.entries(SLOTS)) {\n const value = (node as unknown as Record<string, unknown>)[field];\n if (hasAuthoredText(value, decl.directives)) return `${path}.${field}`;\n }\n for (const [i, child] of (node.children ?? []).entries()) {\n const found = findAuthoredSlot(child, `${path}.${i}`);\n if (found) return found;\n }\n return undefined;\n}\n\n/** há marcação em alguma string daqui? Pelo scanner do TSLite, com o `directives` do slot — um\n * `includes(\"{{\")` erraria num `{{` dentro de uma string literal. */\nfunction hasAuthoredText(v: unknown, directives: boolean): boolean {\n if (typeof v === \"string\") return hasIslands(v, { directives });\n if (Array.isArray(v)) return v.some((x) => hasAuthoredText(x, directives));\n if (v && typeof v === \"object\")\n return Object.values(v).some((x) => hasAuthoredText(x, directives));\n return false;\n}\n\n/** Há ilha na subárvore? Evita rebuild de props 100% estáticas (preserva a referência → memo\n * do React). Na definição compilada a pergunta é ESTRUTURAL: procura o marcador, não texto. */\nfunction scan(v: unknown): boolean {\n if (isExprMarker(v)) return true;\n if (Array.isArray(v)) return v.some(containsIsland);\n if (v && typeof v === \"object\") return Object.values(v).some(containsIsland);\n return false;\n}\n\n/* Memoizado por IDENTIDADE do objeto: \"tem ilha?\" é propriedade da DEFINIÇÃO, não do dado —\n a resposta não muda entre renders, e um `each` de N itens reusa o mesmo objeto de props N\n vezes. A definição é imutável (todo o desenho é dado imutável), então a identidade é chave\n legítima. Global e sem profile: a pergunta é estrutural. */\nconst scanned = new WeakMap<object, boolean>();\n\nfunction containsIsland(v: unknown): boolean {\n if (v === null || typeof v !== \"object\") return false;\n const hit = scanned.get(v);\n if (hit !== undefined) return hit;\n const r = scan(v);\n scanned.set(v, r);\n return r;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@loom-forge/tslite",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "loom · o adapter de BIND via TSLite: compile() (expressão em texto → ilha #expr) e o binder que a executa, gateado por CapabilityProfile, eval-free, com a stdlib do @tslite/operators no escopo.",
6
+ "keywords": [
7
+ "tslite",
8
+ "expression",
9
+ "template",
10
+ "json",
11
+ "dsl",
12
+ "compile"
13
+ ],
14
+ "license": "MIT",
15
+ "author": {
16
+ "name": "Anderson D. Rosa",
17
+ "url": "https://github.com/andersondrosa"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/andersondrosa/loom-forge.git",
22
+ "directory": "packages/tslite"
23
+ },
24
+ "homepage": "https://github.com/andersondrosa/loom-forge/tree/main/packages/tslite#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/andersondrosa/loom-forge/issues"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "sideEffects": false,
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./compile": {
38
+ "types": "./dist/compile.d.ts",
39
+ "import": "./dist/compile.js"
40
+ }
41
+ },
42
+ "files": [
43
+ "dist"
44
+ ],
45
+ "dependencies": {
46
+ "@tslite/capability": "^0.6.0",
47
+ "@tslite/codec": "^0.8.1",
48
+ "@tslite/core": "^0.11.0",
49
+ "@tslite/interpret": "^0.5.2",
50
+ "@tslite/language-service": "^0.4.0",
51
+ "@tslite/normalize": "^0.2.0",
52
+ "@tslite/operators": "^1.0.4",
53
+ "@tslite/parser": "^1.4.0",
54
+ "@tslite/sanitize": "^0.1.12",
55
+ "@tslite/template": "^0.3.1",
56
+ "@loom-forge/core": "0.1.0"
57
+ },
58
+ "devDependencies": {
59
+ "tsup": "^8.0.0",
60
+ "typescript": "^5.9.3"
61
+ },
62
+ "scripts": {
63
+ "build": "tsup",
64
+ "dev": "tsup --watch",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "typecheck": "tsc --noEmit",
68
+ "clean": "rm -rf dist"
69
+ }
70
+ }