@loom-forge/forge 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +278 -0
- package/dist/index.d.ts +746 -0
- package/dist/index.js +1078 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1078 @@
|
|
|
1
|
+
import { edgesOf, detectCycles, invertEdges, propagateUnion, reachable } from '@tslite/graph';
|
|
2
|
+
import { EMPTY_COMMANDS, aliasOf, intentsOf, identityBinder, resolveTree, commandScope, TREE, resolveIntent } from '@loom-forge/core';
|
|
3
|
+
export { createDescriptorRegistry } from '@loom-forge/core';
|
|
4
|
+
|
|
5
|
+
// src/facts.ts
|
|
6
|
+
var SLOT_TYPE = "$slot";
|
|
7
|
+
var slotName = (node) => {
|
|
8
|
+
const name = node.props?.name;
|
|
9
|
+
return typeof name === "string" ? name : "children";
|
|
10
|
+
};
|
|
11
|
+
var EMPTY_MOUNT = {
|
|
12
|
+
contexts: /* @__PURE__ */ new Set(),
|
|
13
|
+
commands: EMPTY_COMMANDS
|
|
14
|
+
};
|
|
15
|
+
var installed = (scope, node) => {
|
|
16
|
+
const provided2 = Object.keys(node.provides ?? {});
|
|
17
|
+
if (provided2.length === 0 && node.commands === void 0) return scope;
|
|
18
|
+
return {
|
|
19
|
+
contexts: provided2.length ? /* @__PURE__ */ new Set([...scope.contexts, ...provided2]) : scope.contexts,
|
|
20
|
+
commands: commandScope(scope.commands, node)
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
var nested = (outer, inner) => ({
|
|
24
|
+
contexts: /* @__PURE__ */ new Set([...outer.contexts, ...inner.contexts]),
|
|
25
|
+
commands: { ...outer.commands, ...inner.commands }
|
|
26
|
+
});
|
|
27
|
+
function walkMounted(root, shape, mounting, visit) {
|
|
28
|
+
const go = (node, scopes) => {
|
|
29
|
+
visit(node, scopes);
|
|
30
|
+
const below = scopes.map((s) => installed(s, shape.installs(node)));
|
|
31
|
+
const callee = shape.callee(node);
|
|
32
|
+
const slots = callee === void 0 ? void 0 : mounting.slotScopesOf(callee);
|
|
33
|
+
const childScopes = slots === void 0 ? below : below.flatMap((b) => slots.map((s) => nested(b, s)));
|
|
34
|
+
for (const child of shape.children(node)) go(child, childScopes);
|
|
35
|
+
};
|
|
36
|
+
go(root, [EMPTY_MOUNT]);
|
|
37
|
+
}
|
|
38
|
+
var irShape = (ir) => ({
|
|
39
|
+
children: (node) => node.children.map((address) => ir.nodes.get(address)).filter((n) => n !== void 0),
|
|
40
|
+
callee: (node) => node.kind === "component" ? node.type : void 0,
|
|
41
|
+
installs: (node) => node.address === ir.root ? {
|
|
42
|
+
...ir.provides || node.provides ? { provides: { ...ir.provides, ...node.provides } } : {},
|
|
43
|
+
...ir.commands || node.commands ? { commands: { ...ir.commands, ...node.commands } } : {}
|
|
44
|
+
} : node
|
|
45
|
+
});
|
|
46
|
+
var defShape = {
|
|
47
|
+
children: (node) => node.children ?? [],
|
|
48
|
+
callee: (node) => node.el == null ? node.type : void 0,
|
|
49
|
+
installs: (node) => node
|
|
50
|
+
};
|
|
51
|
+
var NEVER = [];
|
|
52
|
+
function mountingOf(components, opts = {}) {
|
|
53
|
+
const irs = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const c of components) irs.set(c.id, c);
|
|
55
|
+
const memo = /* @__PURE__ */ new Map();
|
|
56
|
+
const open = /* @__PURE__ */ new Set();
|
|
57
|
+
const mounting = {
|
|
58
|
+
slotScopesOf(component) {
|
|
59
|
+
const ir = irs.get(component);
|
|
60
|
+
if (!ir) return void 0;
|
|
61
|
+
const known = memo.get(component);
|
|
62
|
+
if (known) return known;
|
|
63
|
+
if (open.has(component)) return NEVER;
|
|
64
|
+
open.add(component);
|
|
65
|
+
const found = [];
|
|
66
|
+
const root = ir.nodes.get(ir.root);
|
|
67
|
+
if (root)
|
|
68
|
+
walkMounted(root, irShape(ir), mounting, (node, scopes) => {
|
|
69
|
+
if (node.type === SLOT_TYPE && slotName(node) === "children")
|
|
70
|
+
found.push(...scopes);
|
|
71
|
+
});
|
|
72
|
+
open.delete(component);
|
|
73
|
+
memo.set(component, found);
|
|
74
|
+
return found;
|
|
75
|
+
},
|
|
76
|
+
vocabulary: vocabularyOf(irs.values(), opts.host)
|
|
77
|
+
};
|
|
78
|
+
return mounting;
|
|
79
|
+
}
|
|
80
|
+
var vocabularyOf = (irs, host) => {
|
|
81
|
+
const names = new Set(Object.keys(host ?? {}));
|
|
82
|
+
for (const ir of irs) {
|
|
83
|
+
for (const name of Object.keys(ir.commands ?? {})) names.add(name);
|
|
84
|
+
for (const node of ir.nodes.values())
|
|
85
|
+
for (const name of Object.keys(node.commands ?? {})) names.add(name);
|
|
86
|
+
}
|
|
87
|
+
return names;
|
|
88
|
+
};
|
|
89
|
+
function mountScopesOf(root, mounting) {
|
|
90
|
+
const out = /* @__PURE__ */ new Map();
|
|
91
|
+
walkMounted(
|
|
92
|
+
root,
|
|
93
|
+
defShape,
|
|
94
|
+
mounting,
|
|
95
|
+
(node, scopes) => out.set(node, scopes)
|
|
96
|
+
);
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function propagateObligations(own, sitesOf) {
|
|
100
|
+
const ids = [...own.keys()];
|
|
101
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
102
|
+
for (const id of ids) {
|
|
103
|
+
const callees = /* @__PURE__ */ new Set();
|
|
104
|
+
for (const site of sitesOf.get(id) ?? [])
|
|
105
|
+
if (own.has(site.callee)) callees.add(site.callee);
|
|
106
|
+
outgoing.set(id, callees);
|
|
107
|
+
}
|
|
108
|
+
const incoming = invertEdges(ids, edgesOf(outgoing));
|
|
109
|
+
const required = new Map(
|
|
110
|
+
ids.map((id) => [id, new Set(own.get(id))])
|
|
111
|
+
);
|
|
112
|
+
const queue = [...ids];
|
|
113
|
+
const queued = new Set(queue);
|
|
114
|
+
while (queue.length > 0) {
|
|
115
|
+
const id = queue.shift();
|
|
116
|
+
queued.delete(id);
|
|
117
|
+
const next = new Set(own.get(id));
|
|
118
|
+
for (const site of sitesOf.get(id) ?? [])
|
|
119
|
+
for (const name of required.get(site.callee) ?? [])
|
|
120
|
+
if (!site.discharges(name)) next.add(name);
|
|
121
|
+
if (sameSet(next, required.get(id))) continue;
|
|
122
|
+
required.set(id, next);
|
|
123
|
+
for (const dependent of incoming.get(id) ?? [])
|
|
124
|
+
if (!queued.has(dependent)) {
|
|
125
|
+
queue.push(dependent);
|
|
126
|
+
queued.add(dependent);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return required;
|
|
130
|
+
}
|
|
131
|
+
var sameSet = (a, b) => a.size === b.size && [...a].every((x) => b.has(x));
|
|
132
|
+
|
|
133
|
+
// src/commands.ts
|
|
134
|
+
function routeIntent(action, scope, ctx) {
|
|
135
|
+
if (ctx.updates.has(action)) return { kind: "update", action };
|
|
136
|
+
const alias = scope.commands[action];
|
|
137
|
+
if (alias !== void 0) {
|
|
138
|
+
const { action: target, params } = aliasOf(alias);
|
|
139
|
+
const bound = params === void 0 ? {} : { bound: params };
|
|
140
|
+
return ctx.updates.has(target) ? { kind: "update", action: target, ...bound } : { kind: "action", action: target, ...bound };
|
|
141
|
+
}
|
|
142
|
+
return ctx.vocabulary.has(action) ? { kind: "command", name: action } : { kind: "action", action };
|
|
143
|
+
}
|
|
144
|
+
function settledRoute(action, scopes, ctx) {
|
|
145
|
+
let settled;
|
|
146
|
+
for (const scope of scopes) {
|
|
147
|
+
const route = routeIntent(action, scope, ctx);
|
|
148
|
+
if (settled === void 0) settled = route;
|
|
149
|
+
else if (!sameRoute(settled, route)) return void 0;
|
|
150
|
+
}
|
|
151
|
+
return settled;
|
|
152
|
+
}
|
|
153
|
+
var sameRoute = (a, b) => {
|
|
154
|
+
if (a.kind === "command") return b.kind === "command" && a.name === b.name;
|
|
155
|
+
return b.kind !== "command" && a.kind === b.kind && a.action === b.action && a.bound === b.bound;
|
|
156
|
+
};
|
|
157
|
+
function deriveCommands(components, mounting, host = EMPTY_COMMANDS) {
|
|
158
|
+
const irs = /* @__PURE__ */ new Map();
|
|
159
|
+
for (const c of components) irs.set(c.id, c);
|
|
160
|
+
const intentsAt = /* @__PURE__ */ new Map();
|
|
161
|
+
const actions = /* @__PURE__ */ new Map();
|
|
162
|
+
const own = /* @__PURE__ */ new Map();
|
|
163
|
+
const sitesOf = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const ir of irs.values()) {
|
|
165
|
+
const ctx = {
|
|
166
|
+
updates: new Set(Object.keys(ir.updates ?? {})),
|
|
167
|
+
vocabulary: mounting.vocabulary
|
|
168
|
+
};
|
|
169
|
+
const routed2 = /* @__PURE__ */ new Set();
|
|
170
|
+
const free = /* @__PURE__ */ new Set();
|
|
171
|
+
actions.set(ir.id, routed2);
|
|
172
|
+
own.set(ir.id, free);
|
|
173
|
+
const route = (action, scope) => {
|
|
174
|
+
const found = routeIntent(action, scope, ctx);
|
|
175
|
+
switch (found.kind) {
|
|
176
|
+
case "update":
|
|
177
|
+
return { to: found.action, rel: "update" };
|
|
178
|
+
case "action":
|
|
179
|
+
routed2.add(found.action);
|
|
180
|
+
return { to: found.action, rel: "action" };
|
|
181
|
+
case "command":
|
|
182
|
+
free.add(found.name);
|
|
183
|
+
return { to: found.name, rel: "command" };
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
for (const effect of Object.values(ir.effects ?? {})) {
|
|
187
|
+
for (const link of intentsOf(effect)) route(link.action, EMPTY_MOUNT);
|
|
188
|
+
if (effect.cleanup)
|
|
189
|
+
for (const link of intentsOf(effect.cleanup))
|
|
190
|
+
route(link.action, EMPTY_MOUNT);
|
|
191
|
+
}
|
|
192
|
+
const sites = [];
|
|
193
|
+
const shape = irShape(ir);
|
|
194
|
+
const root = ir.nodes.get(ir.root);
|
|
195
|
+
if (root)
|
|
196
|
+
walkMounted(root, shape, mounting, (node, scopes) => {
|
|
197
|
+
const edges = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const binding of Object.values(node.events ?? {}))
|
|
199
|
+
for (const link of intentsOf(binding))
|
|
200
|
+
for (const scope of scopes) {
|
|
201
|
+
const edge = route(link.action, scope);
|
|
202
|
+
edges.set(`${edge.rel} ${edge.to}`, edge);
|
|
203
|
+
}
|
|
204
|
+
if (edges.size) intentsAt.set(node.address, [...edges.values()]);
|
|
205
|
+
if (node.kind === "component")
|
|
206
|
+
for (const scope of scopes) {
|
|
207
|
+
const commands = installed(scope, shape.installs(node)).commands;
|
|
208
|
+
sites.push({
|
|
209
|
+
callee: node.type,
|
|
210
|
+
commands,
|
|
211
|
+
discharges: (name) => commands[name] !== void 0
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
sitesOf.set(ir.id, sites);
|
|
216
|
+
}
|
|
217
|
+
const required = propagateObligations(own, sitesOf);
|
|
218
|
+
const called = /* @__PURE__ */ new Set();
|
|
219
|
+
for (const [id, sites] of sitesOf)
|
|
220
|
+
for (const site of sites) {
|
|
221
|
+
called.add(site.callee);
|
|
222
|
+
for (const name of required.get(site.callee) ?? []) {
|
|
223
|
+
const alias = site.commands[name];
|
|
224
|
+
if (alias !== void 0) actions.get(id).add(aliasOf(alias).action);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
for (const ir of irs.values()) {
|
|
228
|
+
if (!ir.entry && called.has(ir.id)) continue;
|
|
229
|
+
for (const name of required.get(ir.id) ?? []) {
|
|
230
|
+
const alias = host[name];
|
|
231
|
+
actions.get(ir.id).add(alias === void 0 ? name : aliasOf(alias).action);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
intentsAt,
|
|
236
|
+
actions,
|
|
237
|
+
free: new Map([...required].map(([id, s]) => [id, [...s].sort()]))
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// src/facts.ts
|
|
242
|
+
var factsOf = (facts, componentId) => facts.components.get(componentId);
|
|
243
|
+
var nodeFactsOf = (facts, address) => facts.nodes.get(address);
|
|
244
|
+
var effectiveTags = (facts, id) => facts.tags?.get(id) ?? EMPTY;
|
|
245
|
+
var EMPTY = [];
|
|
246
|
+
var readBehavior = (d) => {
|
|
247
|
+
const b = d?.meta?.behavior;
|
|
248
|
+
return Array.isArray(b) ? b : void 0;
|
|
249
|
+
};
|
|
250
|
+
var structuralEdgesOf = (node) => (
|
|
251
|
+
// `el` já foi normalizado para `kind: "element"`, e elemento nunca cria aresta — mesmo que
|
|
252
|
+
// a tag colida com um componente conhecido (é intrínseco explícito).
|
|
253
|
+
node.kind === "component" || node.kind === "registered" ? [{ to: node.type, rel: "uses" }] : []
|
|
254
|
+
);
|
|
255
|
+
var mkError = (componentId, target, axis, message) => ({
|
|
256
|
+
componentId,
|
|
257
|
+
code: "UNRESOLVED_TARGET",
|
|
258
|
+
target,
|
|
259
|
+
axis,
|
|
260
|
+
message
|
|
261
|
+
});
|
|
262
|
+
function deriveFacts(components, opts = {}) {
|
|
263
|
+
const irs = /* @__PURE__ */ new Map();
|
|
264
|
+
for (const c of components) irs.set(c.id, c);
|
|
265
|
+
const componentFacts = /* @__PURE__ */ new Map();
|
|
266
|
+
const nodeFacts = /* @__PURE__ */ new Map();
|
|
267
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
268
|
+
const nodeIds = new Set(irs.keys());
|
|
269
|
+
const errors = [];
|
|
270
|
+
const routing = deriveCommands(
|
|
271
|
+
irs.values(),
|
|
272
|
+
mountingOf(irs.values(), { host: opts.commands }),
|
|
273
|
+
opts.commands
|
|
274
|
+
);
|
|
275
|
+
for (const ir of irs.values()) {
|
|
276
|
+
const deps = {
|
|
277
|
+
uses: /* @__PURE__ */ new Set(),
|
|
278
|
+
usesRegistered: /* @__PURE__ */ new Set(),
|
|
279
|
+
actions: new Set(routing.actions.get(ir.id)),
|
|
280
|
+
dataRefs: /* @__PURE__ */ new Set(),
|
|
281
|
+
contains: /* @__PURE__ */ new Set()
|
|
282
|
+
};
|
|
283
|
+
for (const node of ir.nodes.values()) {
|
|
284
|
+
const edges2 = [
|
|
285
|
+
...structuralEdgesOf(node),
|
|
286
|
+
...routing.intentsAt.get(node.address) ?? []
|
|
287
|
+
];
|
|
288
|
+
const tags = node.events && Object.keys(node.events).length ? ["interactive"] : void 0;
|
|
289
|
+
nodeFacts.set(node.address, { edges: edges2, ...tags ? { tags } : {} });
|
|
290
|
+
if (node.kind === "component") deps.uses.add(node.type);
|
|
291
|
+
else if (node.kind === "registered") deps.usesRegistered.add(node.type);
|
|
292
|
+
}
|
|
293
|
+
const inferred = [];
|
|
294
|
+
if (Object.keys(ir.state).length) inferred.push("stateful");
|
|
295
|
+
if (Object.keys(ir.effects ?? {}).length) inferred.push("effectful");
|
|
296
|
+
componentFacts.set(ir.id, {
|
|
297
|
+
ownTags: { declared: ir.tags ?? EMPTY, inferred },
|
|
298
|
+
deps: {
|
|
299
|
+
uses: [...deps.uses],
|
|
300
|
+
usesRegistered: [...deps.usesRegistered],
|
|
301
|
+
actions: [...deps.actions],
|
|
302
|
+
dataRefs: [...deps.dataRefs],
|
|
303
|
+
contains: [...deps.contains]
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
const out = /* @__PURE__ */ new Set();
|
|
307
|
+
const add = (targets, axis, known, message) => {
|
|
308
|
+
for (const t of targets) {
|
|
309
|
+
out.add(t);
|
|
310
|
+
nodeIds.add(t);
|
|
311
|
+
if (known && !known(t))
|
|
312
|
+
errors.push(mkError(ir.id, t, axis, message(t)));
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
add(
|
|
316
|
+
deps.uses,
|
|
317
|
+
"uses",
|
|
318
|
+
(t) => irs.has(t),
|
|
319
|
+
(t) => `component "${ir.id}" uses unregistered component "${t}"`
|
|
320
|
+
);
|
|
321
|
+
add(
|
|
322
|
+
deps.usesRegistered,
|
|
323
|
+
"usesRegistered",
|
|
324
|
+
opts.registry ? (t) => opts.registry.has(t) : void 0,
|
|
325
|
+
(t) => `component "${ir.id}" uses unregistered element "${t}"`
|
|
326
|
+
);
|
|
327
|
+
add(
|
|
328
|
+
deps.actions,
|
|
329
|
+
"actions",
|
|
330
|
+
opts.knownActions ? (t) => opts.knownActions.has(t) : void 0,
|
|
331
|
+
(t) => `component "${ir.id}" invokes unknown action "${t}"`
|
|
332
|
+
);
|
|
333
|
+
add(deps.dataRefs, "dataRefs", void 0, () => "");
|
|
334
|
+
outgoing.set(ir.id, out);
|
|
335
|
+
}
|
|
336
|
+
const facts = {
|
|
337
|
+
components: componentFacts,
|
|
338
|
+
nodes: nodeFacts,
|
|
339
|
+
outgoing,
|
|
340
|
+
errors
|
|
341
|
+
};
|
|
342
|
+
const on = opts.overlays ?? { tags: true, incoming: true, cycles: true };
|
|
343
|
+
if (!on.tags && !on.incoming && !on.cycles) return facts;
|
|
344
|
+
const edges = edgesOf(outgoing);
|
|
345
|
+
const ownOf = (id) => {
|
|
346
|
+
const own = componentFacts.get(id);
|
|
347
|
+
if (own) return [...own.ownTags.declared, ...own.ownTags.inferred];
|
|
348
|
+
const behavior = readBehavior(opts.registry?.get(id));
|
|
349
|
+
if (behavior) return behavior;
|
|
350
|
+
if (opts.actionTags) return opts.actionTags(id);
|
|
351
|
+
return EMPTY;
|
|
352
|
+
};
|
|
353
|
+
return {
|
|
354
|
+
...facts,
|
|
355
|
+
...on.tags ? { tags: propagateUnion(nodeIds, edges, ownOf) } : {},
|
|
356
|
+
...on.incoming ? { incoming: invertEdges(nodeIds, edges) } : {},
|
|
357
|
+
...on.cycles ? { cycles: detectCycles(nodeIds, edges) } : {}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// src/ir.ts
|
|
362
|
+
var componentAddress = (component) => component;
|
|
363
|
+
var nodeAddress = (component, indexPath) => `${component}#${indexPath.join(".")}`;
|
|
364
|
+
var parseAddress = (addr) => {
|
|
365
|
+
const i = addr.indexOf("#");
|
|
366
|
+
if (i < 0) return { component: addr, path: null };
|
|
367
|
+
const p = addr.slice(i + 1);
|
|
368
|
+
return {
|
|
369
|
+
component: addr.slice(0, i),
|
|
370
|
+
path: p === "" ? [] : p.split(".").map(Number)
|
|
371
|
+
};
|
|
372
|
+
};
|
|
373
|
+
function createComponentDefRegistry(initial) {
|
|
374
|
+
const map = /* @__PURE__ */ new Map();
|
|
375
|
+
const registry = {
|
|
376
|
+
register(def) {
|
|
377
|
+
if (!def.component)
|
|
378
|
+
throw new Error(
|
|
379
|
+
"componentDefRegistry.register: `component` \xE9 obrigat\xF3rio."
|
|
380
|
+
);
|
|
381
|
+
map.set(def.component, def);
|
|
382
|
+
return registry;
|
|
383
|
+
},
|
|
384
|
+
get: (name) => map.get(name),
|
|
385
|
+
has: (name) => map.has(name),
|
|
386
|
+
list: () => [...map.values()],
|
|
387
|
+
names: () => new Set(map.keys())
|
|
388
|
+
};
|
|
389
|
+
for (const d of initial ?? []) registry.register(d);
|
|
390
|
+
return registry;
|
|
391
|
+
}
|
|
392
|
+
var readKey = (node, key) => node[key];
|
|
393
|
+
var templateOf = (node) => {
|
|
394
|
+
const data = readKey(node, "each");
|
|
395
|
+
const when = readKey(node, "when");
|
|
396
|
+
if (data === void 0 && when === void 0) return void 0;
|
|
397
|
+
const t = {};
|
|
398
|
+
if (data !== void 0) t.data = data;
|
|
399
|
+
if (when !== void 0) t.when = when;
|
|
400
|
+
return t;
|
|
401
|
+
};
|
|
402
|
+
function compileComponent(def, ctx) {
|
|
403
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
404
|
+
const classify = (type) => ctx.components.has(type) ? "component" : ctx.registry?.has(type) ? "registered" : "element";
|
|
405
|
+
const flatten = (node, indexPath) => {
|
|
406
|
+
const address = nodeAddress(def.component, indexPath);
|
|
407
|
+
const isEl = node.el != null;
|
|
408
|
+
const tag = node.el ?? node.type ?? "";
|
|
409
|
+
const kind = isEl || !node.type ? "element" : classify(node.type);
|
|
410
|
+
const children = (node.children ?? []).map(
|
|
411
|
+
(c, i) => flatten(c, [...indexPath, i])
|
|
412
|
+
);
|
|
413
|
+
const template = templateOf(node);
|
|
414
|
+
const ir = {
|
|
415
|
+
address,
|
|
416
|
+
id: node.id,
|
|
417
|
+
type: tag,
|
|
418
|
+
kind,
|
|
419
|
+
children,
|
|
420
|
+
...node.props ? { props: node.props } : {},
|
|
421
|
+
...node.events ? { events: node.events } : {},
|
|
422
|
+
...node.layout ? { layout: node.layout } : {},
|
|
423
|
+
...node.placement !== void 0 ? { placement: node.placement } : {},
|
|
424
|
+
...template ? { template } : {},
|
|
425
|
+
...node.provides ? { provides: node.provides } : {},
|
|
426
|
+
...node.commands ? { commands: node.commands } : {},
|
|
427
|
+
...node.fallback === "drop" ? { fallback: "drop" } : {},
|
|
428
|
+
...node.requires ? { requires: node.requires } : {}
|
|
429
|
+
};
|
|
430
|
+
nodes.set(address, ir);
|
|
431
|
+
return address;
|
|
432
|
+
};
|
|
433
|
+
const root = flatten(def.body, [0]);
|
|
434
|
+
return {
|
|
435
|
+
id: def.component,
|
|
436
|
+
entry: def.entry ?? false,
|
|
437
|
+
context: { uses: def.context?.uses ?? [] },
|
|
438
|
+
...def.provides ? { provides: def.provides } : {},
|
|
439
|
+
...def.commands ? { commands: def.commands } : {},
|
|
440
|
+
...def.effects ? { effects: def.effects } : {},
|
|
441
|
+
...def.updates ? { updates: def.updates } : {},
|
|
442
|
+
state: def.state ?? {},
|
|
443
|
+
requires: def.requires ?? {},
|
|
444
|
+
root,
|
|
445
|
+
nodes,
|
|
446
|
+
...def.category ? { category: def.category } : {},
|
|
447
|
+
...def.slots ? { slots: def.slots } : {},
|
|
448
|
+
...def.props ? { props: def.props } : {},
|
|
449
|
+
...def.tags ? { tags: def.tags } : {},
|
|
450
|
+
...def.version ? { version: def.version } : {}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
function buildSystemIR(components, opts = {}) {
|
|
454
|
+
const map = /* @__PURE__ */ new Map();
|
|
455
|
+
for (const c of components) map.set(c.id, c);
|
|
456
|
+
return { components: map, facts: deriveFacts(map.values(), opts) };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/compile.ts
|
|
460
|
+
var COMPONENT_FIELDS = {
|
|
461
|
+
component: "identity",
|
|
462
|
+
entry: "data",
|
|
463
|
+
category: "data",
|
|
464
|
+
version: "data",
|
|
465
|
+
tags: "data",
|
|
466
|
+
requires: "data",
|
|
467
|
+
/** só NOMES do catálogo: o que entra no escopo, não o que ele vale. */
|
|
468
|
+
context: "data",
|
|
469
|
+
props: "decl",
|
|
470
|
+
slots: "decl",
|
|
471
|
+
/** `type` é DECLARAÇÃO; o `init` é EXPRESSÃO sobre `props` e `ctx`, avaliada no mount. */
|
|
472
|
+
state: "slot",
|
|
473
|
+
provides: "slot",
|
|
474
|
+
/** o alias é roteador (nome → nome), mas os `params` da aplicação parcial são expressão. */
|
|
475
|
+
commands: "slot",
|
|
476
|
+
updates: "slot",
|
|
477
|
+
effects: "slot",
|
|
478
|
+
body: "tree"
|
|
479
|
+
};
|
|
480
|
+
var addressOf = (path) => typeof path[0] === "number" ? `#${path.join(".")}` : path.join(".");
|
|
481
|
+
function componentSlotsOf(def) {
|
|
482
|
+
const slots = [];
|
|
483
|
+
if (def.provides) slots.push({ path: ["provides"], value: def.provides });
|
|
484
|
+
for (const [name, alias] of Object.entries(def.commands ?? {}))
|
|
485
|
+
if (typeof alias === "object" && alias.params !== void 0)
|
|
486
|
+
slots.push({ path: ["commands", name, "params"], value: alias.params });
|
|
487
|
+
for (const [key, decl] of Object.entries(def.state ?? {}))
|
|
488
|
+
if (decl.init !== void 0)
|
|
489
|
+
slots.push({ path: ["state", key, "init"], value: decl.init });
|
|
490
|
+
for (const [name, update] of Object.entries(def.updates ?? {}))
|
|
491
|
+
if (update.set !== void 0)
|
|
492
|
+
slots.push({ path: ["updates", name, "set"], value: update.set });
|
|
493
|
+
const intentSlots = (intent, path) => {
|
|
494
|
+
if (intent.params !== void 0)
|
|
495
|
+
slots.push({ path: [...path, "params"], value: intent.params });
|
|
496
|
+
if (intent.then) intentSlots(intent.then, [...path, "then"]);
|
|
497
|
+
if (intent.catch) intentSlots(intent.catch, [...path, "catch"]);
|
|
498
|
+
};
|
|
499
|
+
for (const [name, effect] of Object.entries(def.effects ?? {})) {
|
|
500
|
+
intentSlots(effect, ["effects", name]);
|
|
501
|
+
if (effect.cleanup)
|
|
502
|
+
intentSlots(effect.cleanup, ["effects", name, "cleanup"]);
|
|
503
|
+
}
|
|
504
|
+
return slots;
|
|
505
|
+
}
|
|
506
|
+
var setIn = (target, path, value) => {
|
|
507
|
+
if (path.length === 0) return value;
|
|
508
|
+
const [head, ...rest] = path;
|
|
509
|
+
const current = target?.[head];
|
|
510
|
+
return {
|
|
511
|
+
...target,
|
|
512
|
+
[head]: setIn(current, rest, value)
|
|
513
|
+
};
|
|
514
|
+
};
|
|
515
|
+
var GHOST_ID = "$slot";
|
|
516
|
+
var GHOST_PATH = [0, "props", "value"];
|
|
517
|
+
var reanchor = (item, slot) => {
|
|
518
|
+
if (item.path.length < GHOST_PATH.length || GHOST_PATH.some((seg, i) => item.path[i] !== seg))
|
|
519
|
+
return void 0;
|
|
520
|
+
const path = [...slot.path, ...item.path.slice(GHOST_PATH.length)];
|
|
521
|
+
return { ...item, path, address: addressOf(path) };
|
|
522
|
+
};
|
|
523
|
+
var compileValue = (slot, compile) => {
|
|
524
|
+
const ghost = {
|
|
525
|
+
id: GHOST_ID,
|
|
526
|
+
type: GHOST_ID,
|
|
527
|
+
props: { value: slot.value }
|
|
528
|
+
};
|
|
529
|
+
const out = compile(ghost);
|
|
530
|
+
const back = (items) => (items ?? []).flatMap((item) => {
|
|
531
|
+
const moved = reanchor(item, slot);
|
|
532
|
+
return moved === void 0 ? [] : [moved];
|
|
533
|
+
});
|
|
534
|
+
return {
|
|
535
|
+
value: out.def.props?.value,
|
|
536
|
+
diagnostics: back(out.diagnostics),
|
|
537
|
+
islands: back(out.islands)
|
|
538
|
+
};
|
|
539
|
+
};
|
|
540
|
+
var compiled = /* @__PURE__ */ new WeakMap();
|
|
541
|
+
var adapters = /* @__PURE__ */ new WeakMap();
|
|
542
|
+
function compileNodeOf(binder) {
|
|
543
|
+
let adapter = adapters.get(binder);
|
|
544
|
+
if (adapter === void 0) {
|
|
545
|
+
adapter = (node) => ({ def: binder.compile?.(node) ?? node });
|
|
546
|
+
adapters.set(binder, adapter);
|
|
547
|
+
}
|
|
548
|
+
return adapter;
|
|
549
|
+
}
|
|
550
|
+
function compileDefinition(def, compile) {
|
|
551
|
+
let byDef = compiled.get(compile);
|
|
552
|
+
if (byDef === void 0) {
|
|
553
|
+
byDef = /* @__PURE__ */ new WeakMap();
|
|
554
|
+
compiled.set(compile, byDef);
|
|
555
|
+
}
|
|
556
|
+
const hit = byDef.get(def);
|
|
557
|
+
if (hit) return hit;
|
|
558
|
+
const body = compile(def.body);
|
|
559
|
+
const diagnostics = [...body.diagnostics ?? []];
|
|
560
|
+
const islands = [...body.islands ?? []];
|
|
561
|
+
let out = { ...def, body: body.def };
|
|
562
|
+
for (const slot of componentSlotsOf(def)) {
|
|
563
|
+
const value = compileValue(slot, compile);
|
|
564
|
+
out = setIn(out, slot.path, value.value);
|
|
565
|
+
diagnostics.push(...value.diagnostics);
|
|
566
|
+
islands.push(...value.islands);
|
|
567
|
+
}
|
|
568
|
+
const result = { def: out, diagnostics, islands };
|
|
569
|
+
byDef.set(def, result);
|
|
570
|
+
byDef.set(out, result);
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// src/state.ts
|
|
575
|
+
var STATEFUL_TYPE = "$stateful";
|
|
576
|
+
var isLive = (def) => Object.keys(def.updates ?? {}).length > 0 || Object.keys(def.effects ?? {}).length > 0;
|
|
577
|
+
var initState = (def, scope, binder) => {
|
|
578
|
+
const inits = {};
|
|
579
|
+
for (const [key, decl] of Object.entries(def.state ?? {}))
|
|
580
|
+
inits[key] = decl.init;
|
|
581
|
+
if (Object.keys(inits).length === 0) return {};
|
|
582
|
+
const ghost = { id: "$init", type: "$init", props: inits };
|
|
583
|
+
return binder.bind(ghost, scope).props ?? {};
|
|
584
|
+
};
|
|
585
|
+
var bindPayload = (params, scope, binder) => {
|
|
586
|
+
if (params === void 0) return void 0;
|
|
587
|
+
const ghost = {
|
|
588
|
+
id: "$payload",
|
|
589
|
+
type: "$payload",
|
|
590
|
+
props: { params }
|
|
591
|
+
};
|
|
592
|
+
const bound = binder.bind(ghost, scope);
|
|
593
|
+
return bound.props?.params;
|
|
594
|
+
};
|
|
595
|
+
var applyUpdate = (prev, update, scope, binder) => {
|
|
596
|
+
if (!update.set) return prev;
|
|
597
|
+
const ghost = { id: "$set", type: "$set", props: update.set };
|
|
598
|
+
const bound = binder.bind(ghost, scope);
|
|
599
|
+
return { ...prev, ...bound.props ?? {} };
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// src/slots.ts
|
|
603
|
+
var metaStr = (d, key) => {
|
|
604
|
+
const v = d?.meta?.[key];
|
|
605
|
+
return typeof v === "string" ? v : void 0;
|
|
606
|
+
};
|
|
607
|
+
var metaArr = (d, key) => Array.isArray(d?.meta?.[key]) ? d.meta[key] : [];
|
|
608
|
+
function checkSlots(sys, opts = {}) {
|
|
609
|
+
const registry = opts.registry;
|
|
610
|
+
const violations = [];
|
|
611
|
+
const slotsOf = (type) => {
|
|
612
|
+
const c = sys.components.get(type);
|
|
613
|
+
if (c?.slots) return c.slots;
|
|
614
|
+
const ms = registry?.get(type)?.meta?.slots;
|
|
615
|
+
return ms && typeof ms === "object" ? ms : void 0;
|
|
616
|
+
};
|
|
617
|
+
const infoOf = (type) => {
|
|
618
|
+
const c = sys.components.get(type);
|
|
619
|
+
if (c)
|
|
620
|
+
return {
|
|
621
|
+
category: c.category,
|
|
622
|
+
tags: new Set(effectiveTags(sys.facts, type))
|
|
623
|
+
};
|
|
624
|
+
const d = registry?.get(type);
|
|
625
|
+
if (d)
|
|
626
|
+
return {
|
|
627
|
+
category: metaStr(d, "category"),
|
|
628
|
+
tags: new Set(metaArr(d, "behavior"))
|
|
629
|
+
};
|
|
630
|
+
return { category: void 0, tags: EMPTY2 };
|
|
631
|
+
};
|
|
632
|
+
const checkChild = (comp, parentType, contract, child) => {
|
|
633
|
+
const push = (rule, message) => {
|
|
634
|
+
violations.push({
|
|
635
|
+
rule,
|
|
636
|
+
componentId: comp.id,
|
|
637
|
+
parent: parentType,
|
|
638
|
+
slot: "children",
|
|
639
|
+
child: child.type,
|
|
640
|
+
message
|
|
641
|
+
});
|
|
642
|
+
};
|
|
643
|
+
if (contract.leaf && child.children.length)
|
|
644
|
+
push(
|
|
645
|
+
"slot-leaf",
|
|
646
|
+
`slot de "${parentType}" exige folha, mas "${child.type}" tem filhos`
|
|
647
|
+
);
|
|
648
|
+
const info = infoOf(child.type);
|
|
649
|
+
if (contract.category) {
|
|
650
|
+
const allowed = Array.isArray(contract.category) ? contract.category : [contract.category];
|
|
651
|
+
if (info.category === void 0 || !allowed.includes(info.category))
|
|
652
|
+
push(
|
|
653
|
+
"slot-category",
|
|
654
|
+
`"${child.type}" (categoria ${info.category ?? "\u2014"}) n\xE3o \xE9 aceito no slot de "${parentType}" (exige ${allowed.join("|")})`
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (contract.tags && !contract.tags.every((t) => info.tags.has(t)))
|
|
658
|
+
push(
|
|
659
|
+
"slot-tags",
|
|
660
|
+
`"${child.type}" n\xE3o tem as tags exigidas pelo slot de "${parentType}" (${contract.tags.join(", ")})`
|
|
661
|
+
);
|
|
662
|
+
};
|
|
663
|
+
for (const comp of sys.components.values()) {
|
|
664
|
+
for (const node of comp.nodes.values()) {
|
|
665
|
+
if (!node.children.length) continue;
|
|
666
|
+
const contract = slotsOf(node.type)?.children;
|
|
667
|
+
if (!contract) continue;
|
|
668
|
+
const children = node.children.map((a) => comp.nodes.get(a)).filter((n2) => n2 !== void 0);
|
|
669
|
+
const n = children.length;
|
|
670
|
+
if (contract.single && n > 1 || contract.max !== void 0 && n > contract.max)
|
|
671
|
+
violations.push({
|
|
672
|
+
rule: "slot-cardinality",
|
|
673
|
+
componentId: comp.id,
|
|
674
|
+
parent: node.type,
|
|
675
|
+
slot: "children",
|
|
676
|
+
message: `slot de "${node.type}" aceita no m\xE1ximo ${contract.single ? 1 : contract.max} filho(s), recebeu ${n}`
|
|
677
|
+
});
|
|
678
|
+
if (contract.min !== void 0 && n < contract.min)
|
|
679
|
+
violations.push({
|
|
680
|
+
rule: "slot-cardinality",
|
|
681
|
+
componentId: comp.id,
|
|
682
|
+
parent: node.type,
|
|
683
|
+
slot: "children",
|
|
684
|
+
message: `slot de "${node.type}" exige ao menos ${contract.min} filho(s), recebeu ${n}`
|
|
685
|
+
});
|
|
686
|
+
for (const child of children)
|
|
687
|
+
checkChild(comp, node.type, contract, child);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return violations;
|
|
691
|
+
}
|
|
692
|
+
var EMPTY2 = /* @__PURE__ */ new Set();
|
|
693
|
+
|
|
694
|
+
// src/context.ts
|
|
695
|
+
var sorted = (set) => [...set].sort();
|
|
696
|
+
var common = (sets) => new Set([...sets[0] ?? []].filter((n) => sets.every((s) => s.has(n))));
|
|
697
|
+
function deriveContext(components) {
|
|
698
|
+
const irs = /* @__PURE__ */ new Map();
|
|
699
|
+
for (const c of components) irs.set(c.id, c);
|
|
700
|
+
const mounting = mountingOf(irs.values());
|
|
701
|
+
const providedAt = /* @__PURE__ */ new Map();
|
|
702
|
+
const sitesOf = /* @__PURE__ */ new Map();
|
|
703
|
+
const own = /* @__PURE__ */ new Map();
|
|
704
|
+
for (const ir of irs.values()) {
|
|
705
|
+
const provides = new Set(Object.keys(ir.provides ?? {}));
|
|
706
|
+
own.set(
|
|
707
|
+
ir.id,
|
|
708
|
+
new Set(ir.context.uses.filter((name) => !provides.has(name)))
|
|
709
|
+
);
|
|
710
|
+
const sites = [];
|
|
711
|
+
const shape = irShape(ir);
|
|
712
|
+
const root = ir.nodes.get(ir.root);
|
|
713
|
+
if (root)
|
|
714
|
+
walkMounted(root, shape, mounting, (node, scopes) => {
|
|
715
|
+
const here = scopes.map(
|
|
716
|
+
(s) => installed(s, shape.installs(node)).contexts
|
|
717
|
+
);
|
|
718
|
+
if (here.length) providedAt.set(node.address, sorted(common(here)));
|
|
719
|
+
if (node.kind === "component")
|
|
720
|
+
for (const provided2 of here)
|
|
721
|
+
sites.push({
|
|
722
|
+
callee: node.type,
|
|
723
|
+
address: node.address,
|
|
724
|
+
provided: provided2,
|
|
725
|
+
discharges: (name) => provided2.has(name)
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
sitesOf.set(ir.id, sites);
|
|
729
|
+
}
|
|
730
|
+
const required = propagateObligations(own, sitesOf);
|
|
731
|
+
const unsatisfiedAt = /* @__PURE__ */ new Map();
|
|
732
|
+
for (const sites of sitesOf.values())
|
|
733
|
+
for (const site of sites)
|
|
734
|
+
for (const name of required.get(site.callee) ?? [])
|
|
735
|
+
if (!site.provided.has(name)) {
|
|
736
|
+
const missing = unsatisfiedAt.get(site.address) ?? /* @__PURE__ */ new Set();
|
|
737
|
+
missing.add(name);
|
|
738
|
+
unsatisfiedAt.set(site.address, missing);
|
|
739
|
+
}
|
|
740
|
+
return {
|
|
741
|
+
required: new Map([...required].map(([id, s]) => [id, sorted(s)])),
|
|
742
|
+
providedAt,
|
|
743
|
+
unsatisfiedAt: new Map(
|
|
744
|
+
[...unsatisfiedAt].map(([address, s]) => [address, sorted(s)])
|
|
745
|
+
)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function categoryOf(sys, registry) {
|
|
749
|
+
return (id) => {
|
|
750
|
+
const c = sys.components.get(id);
|
|
751
|
+
if (c) return c.category;
|
|
752
|
+
const meta = registry?.get(id)?.meta?.category;
|
|
753
|
+
return typeof meta === "string" ? meta : void 0;
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function checkCategoryPolicy(sys, policy) {
|
|
757
|
+
const violations = [];
|
|
758
|
+
const dflt = policy.defaultPolicy ?? "allow";
|
|
759
|
+
for (const [from, targets] of sys.facts.outgoing) {
|
|
760
|
+
const fromCat = policy.categoryOf(from);
|
|
761
|
+
if (fromCat === void 0) continue;
|
|
762
|
+
for (const to of targets) {
|
|
763
|
+
const toCat = policy.categoryOf(to);
|
|
764
|
+
if (toCat === void 0) continue;
|
|
765
|
+
const decision = policy.matrix[fromCat]?.[toCat] ?? dflt;
|
|
766
|
+
if (decision === "deny")
|
|
767
|
+
violations.push({
|
|
768
|
+
rule: "category-policy",
|
|
769
|
+
componentId: from,
|
|
770
|
+
message: `"${from}" (${fromCat}) n\xE3o pode depender de "${to}" (${toCat})`,
|
|
771
|
+
data: { to, fromCategory: fromCat, toCategory: toCat }
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return violations;
|
|
776
|
+
}
|
|
777
|
+
function computeUnable(sys, off) {
|
|
778
|
+
const sources = new Set(off.components ?? []);
|
|
779
|
+
for (const c of sys.components.values()) {
|
|
780
|
+
if (off.capabilities && Object.keys(c.requires).some((cap) => off.capabilities.has(cap)))
|
|
781
|
+
sources.add(c.id);
|
|
782
|
+
const deps = sys.facts.components.get(c.id)?.deps;
|
|
783
|
+
if (deps?.actions.some(
|
|
784
|
+
(a) => off.actions?.has(a) || off.port?.isAvailable?.(a) === false
|
|
785
|
+
))
|
|
786
|
+
sources.add(c.id);
|
|
787
|
+
}
|
|
788
|
+
return reachable(
|
|
789
|
+
sys.facts.incoming?.keys() ?? sys.components.keys(),
|
|
790
|
+
edgesOf(sys.facts.outgoing),
|
|
791
|
+
sources
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
function checkIdentity(sys) {
|
|
795
|
+
const violations = [];
|
|
796
|
+
for (const c of sys.components.values()) {
|
|
797
|
+
const byId = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const node of c.nodes.values()) {
|
|
799
|
+
const addresses = byId.get(node.id) ?? [];
|
|
800
|
+
addresses.push(node.address);
|
|
801
|
+
byId.set(node.id, addresses);
|
|
802
|
+
}
|
|
803
|
+
for (const [id, nodes] of byId)
|
|
804
|
+
if (nodes.length > 1)
|
|
805
|
+
violations.push({
|
|
806
|
+
rule: "duplicate-id",
|
|
807
|
+
componentId: c.id,
|
|
808
|
+
message: `component "${c.id}" has ${nodes.length} nodes with id "${id}" \u2014 the editor, the patch and the focus would all point to the first one`,
|
|
809
|
+
data: { id, nodes }
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
return violations;
|
|
813
|
+
}
|
|
814
|
+
function checkForbiddenTags(sys, forbidden, scope) {
|
|
815
|
+
const banned = new Set(forbidden);
|
|
816
|
+
const violations = [];
|
|
817
|
+
for (const c of sys.components.values()) {
|
|
818
|
+
if (scope && !scope(c)) continue;
|
|
819
|
+
const tags = sys.facts.tags?.get(c.id);
|
|
820
|
+
if (!tags) continue;
|
|
821
|
+
for (const t of tags)
|
|
822
|
+
if (banned.has(t))
|
|
823
|
+
violations.push({
|
|
824
|
+
rule: "forbidden-tag",
|
|
825
|
+
componentId: c.id,
|
|
826
|
+
message: `"${c.id}" tem a tag proibida "${t}" (efetiva/propagada)`,
|
|
827
|
+
data: { tag: t }
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
return violations;
|
|
831
|
+
}
|
|
832
|
+
function checkContexts(sys, ctx, opts = {}) {
|
|
833
|
+
const issues = [];
|
|
834
|
+
const ambient = new Set(opts.ambient ?? []);
|
|
835
|
+
for (const c of sys.components.values()) {
|
|
836
|
+
const declared = /* @__PURE__ */ new Set([
|
|
837
|
+
...c.context.uses,
|
|
838
|
+
...Object.keys(c.provides ?? {})
|
|
839
|
+
]);
|
|
840
|
+
const required = ctx.required.get(c.id) ?? [];
|
|
841
|
+
const undeclared = required.filter((name) => !declared.has(name));
|
|
842
|
+
if (undeclared.length) {
|
|
843
|
+
const at = [...ctx.unsatisfiedAt].find(
|
|
844
|
+
([address, names]) => address.startsWith(`${c.id}#`) && names.some((n) => undeclared.includes(n))
|
|
845
|
+
);
|
|
846
|
+
issues.push({
|
|
847
|
+
rule: "context-undeclared",
|
|
848
|
+
componentId: c.id,
|
|
849
|
+
message: `"${c.id}" exige o contexto ${undeclared.map((n) => `"${n}"`).join(", ")} e n\xE3o o declara \u2014 quem o comp\xF5e n\xE3o tem como saber`,
|
|
850
|
+
data: { contexts: undeclared, ...at ? { node: at[0] } : {} }
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
if (c.entry && opts.ambient !== void 0) {
|
|
854
|
+
const missing = required.filter((name) => !ambient.has(name));
|
|
855
|
+
if (missing.length)
|
|
856
|
+
issues.push({
|
|
857
|
+
rule: "context-unsatisfied",
|
|
858
|
+
componentId: c.id,
|
|
859
|
+
message: `"${c.id}" \xE9 entry e exige ${missing.map((n) => `"${n}"`).join(", ")}, que o host n\xE3o prov\xEA`,
|
|
860
|
+
data: { contexts: missing }
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
const provided2 = Object.keys(c.provides ?? {});
|
|
864
|
+
if (provided2.length) {
|
|
865
|
+
const wanted = /* @__PURE__ */ new Set();
|
|
866
|
+
for (const node of c.nodes.values())
|
|
867
|
+
if (node.kind === "component")
|
|
868
|
+
for (const n of ctx.required.get(node.type) ?? []) wanted.add(n);
|
|
869
|
+
for (const n of c.context.uses) wanted.add(n);
|
|
870
|
+
const unused = provided2.filter((name) => !wanted.has(name));
|
|
871
|
+
if (unused.length)
|
|
872
|
+
issues.push({
|
|
873
|
+
rule: "context-unused",
|
|
874
|
+
componentId: c.id,
|
|
875
|
+
message: `"${c.id}" prov\xEA ${unused.map((n) => `"${n}"`).join(", ")} e ningu\xE9m abaixo usa`,
|
|
876
|
+
data: { contexts: unused }
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return issues;
|
|
881
|
+
}
|
|
882
|
+
function checkEffects(sys) {
|
|
883
|
+
const issues = [];
|
|
884
|
+
for (const c of sys.components.values()) {
|
|
885
|
+
for (const [name, effect] of Object.entries(c.effects ?? {})) {
|
|
886
|
+
const local = c.updates?.[effect.action];
|
|
887
|
+
if (!local?.set) continue;
|
|
888
|
+
const written = new Set(
|
|
889
|
+
Object.keys(local.set).map((key) => `state.${key}`)
|
|
890
|
+
);
|
|
891
|
+
const watched = effect.on.filter((path) => written.has(path));
|
|
892
|
+
if (watched.length)
|
|
893
|
+
issues.push({
|
|
894
|
+
rule: "effect-loop",
|
|
895
|
+
componentId: c.id,
|
|
896
|
+
message: `o efeito "${name}" observa ${watched.map((p) => `"${p}"`).join(", ")} e a a\xE7\xE3o "${effect.action}" escreve ali \u2014 reexecutaria para sempre`,
|
|
897
|
+
data: { effect: name, action: effect.action, paths: watched }
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return issues;
|
|
902
|
+
}
|
|
903
|
+
var MAX_DEPTH = 100;
|
|
904
|
+
var EMPTY_UPDATES = /* @__PURE__ */ new Set();
|
|
905
|
+
var EMPTY_DEFS = createComponentDefRegistry();
|
|
906
|
+
var stamped = (node, component) => {
|
|
907
|
+
const out = { ...node, origins: [{ component, id: node.id }] };
|
|
908
|
+
const fields = out;
|
|
909
|
+
for (const [field, shape] of Object.entries(TREE)) {
|
|
910
|
+
const value = fields[field];
|
|
911
|
+
if (shape === "list" && Array.isArray(value))
|
|
912
|
+
fields[field] = value.map((child) => stamped(child, component));
|
|
913
|
+
else if (shape === "node" && value !== null && typeof value === "object")
|
|
914
|
+
fields[field] = stamped(value, component);
|
|
915
|
+
}
|
|
916
|
+
return out;
|
|
917
|
+
};
|
|
918
|
+
var bodies = /* @__PURE__ */ new WeakMap();
|
|
919
|
+
var bodyOf = (def) => {
|
|
920
|
+
const cached = bodies.get(def);
|
|
921
|
+
if (cached) return cached;
|
|
922
|
+
const sugared = def.provides || def.commands ? {
|
|
923
|
+
...def.body,
|
|
924
|
+
...def.provides ? { provides: { ...def.provides, ...def.body.provides } } : {},
|
|
925
|
+
...def.commands ? { commands: { ...def.commands, ...def.body.commands } } : {}
|
|
926
|
+
} : def.body;
|
|
927
|
+
const body = stamped(sugared, def.component);
|
|
928
|
+
bodies.set(def, body);
|
|
929
|
+
return body;
|
|
930
|
+
};
|
|
931
|
+
var occupying = (call, ctx, inner = []) => {
|
|
932
|
+
const origins = [
|
|
933
|
+
...ctx.component !== void 0 ? call.origins ?? [] : [],
|
|
934
|
+
...inner
|
|
935
|
+
];
|
|
936
|
+
return origins.length ? { origins } : {};
|
|
937
|
+
};
|
|
938
|
+
var componentScope = (def, props, contexts, binder) => {
|
|
939
|
+
const outer = {
|
|
940
|
+
props: props ?? {},
|
|
941
|
+
ctx: pick(contexts, def.context?.uses)
|
|
942
|
+
};
|
|
943
|
+
return { ...outer, state: initState(def, outer, binder) };
|
|
944
|
+
};
|
|
945
|
+
var pick = (contexts, uses) => {
|
|
946
|
+
const out = {};
|
|
947
|
+
for (const name of uses ?? [])
|
|
948
|
+
if (name in contexts) out[name] = contexts[name];
|
|
949
|
+
return out;
|
|
950
|
+
};
|
|
951
|
+
var routed = (node, ctx) => {
|
|
952
|
+
if (node.events === void 0) return void 0;
|
|
953
|
+
const out = {};
|
|
954
|
+
for (const [name, binding] of Object.entries(node.events))
|
|
955
|
+
out[name] = resolveIntent(binding, ctx.commands, ctx.updates);
|
|
956
|
+
return out;
|
|
957
|
+
};
|
|
958
|
+
var provided = (inherited, node) => node.provides ? { ...inherited, ...node.provides } : inherited;
|
|
959
|
+
function expandComponents(root, system, binder, opts = {}) {
|
|
960
|
+
if (opts.requireEntryRoot && root.type != null && system.has(root.type)) {
|
|
961
|
+
if (!system.get(root.type).entry) return { id: root.id, type: root.type };
|
|
962
|
+
}
|
|
963
|
+
const expandChildren = (children, ctx) => {
|
|
964
|
+
const out = [];
|
|
965
|
+
for (const c of children ?? []) {
|
|
966
|
+
if (c.type === SLOT_TYPE) {
|
|
967
|
+
const pending = ctx.slots?.[slotName(c)];
|
|
968
|
+
if (pending)
|
|
969
|
+
out.push(
|
|
970
|
+
...expandChildren(pending.nodes, {
|
|
971
|
+
...pending.written,
|
|
972
|
+
// dado vem de onde você foi ESCRITO; contexto e COMANDO vêm de onde você foi
|
|
973
|
+
// MONTADO. É o que faz o mesmo botão executar o `submeter` da sessão que o recebe.
|
|
974
|
+
contexts: ctx.contexts,
|
|
975
|
+
commands: ctx.commands
|
|
976
|
+
})
|
|
977
|
+
);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
out.push(expand(c, ctx.prefix + c.id, ctx));
|
|
981
|
+
}
|
|
982
|
+
return out;
|
|
983
|
+
};
|
|
984
|
+
const expand = (node, instanceId, ctx) => {
|
|
985
|
+
if (node.el == null && node.type != null && system.has(node.type)) {
|
|
986
|
+
if (ctx.expanding.has(node.type) || ctx.depth >= MAX_DEPTH)
|
|
987
|
+
return { id: instanceId, type: node.type };
|
|
988
|
+
const cdef = system.get(node.type);
|
|
989
|
+
if (isLive(cdef))
|
|
990
|
+
return {
|
|
991
|
+
id: instanceId,
|
|
992
|
+
type: STATEFUL_TYPE,
|
|
993
|
+
placement: node.placement,
|
|
994
|
+
// os contextos em vigor AQUI viajam com o marcador: o boundary expande o corpo ao
|
|
995
|
+
// vivo, e ele precisa do mesmo registro que um componente puro receberia.
|
|
996
|
+
// o marcador responde pela CHAMADA; a origem do corpo vem de dentro do boundary
|
|
997
|
+
...occupying(node, ctx),
|
|
998
|
+
props: {
|
|
999
|
+
def: cdef,
|
|
1000
|
+
callProps: node.props ?? {},
|
|
1001
|
+
contexts: provided(ctx.contexts, node),
|
|
1002
|
+
// o vocabulário de comando viaja igual: o boundary expande o corpo ao vivo, e ele
|
|
1003
|
+
// precisa do mesmo escopo que um componente puro receberia.
|
|
1004
|
+
commands: commandScope(ctx.commands, node)
|
|
1005
|
+
}
|
|
1006
|
+
};
|
|
1007
|
+
const inner = provided(ctx.contexts, node);
|
|
1008
|
+
const innerCommands = commandScope(ctx.commands, node);
|
|
1009
|
+
const authored = bodyOf(cdef);
|
|
1010
|
+
const tree = binder.compile?.(authored) ?? authored;
|
|
1011
|
+
const body = binder.bind(
|
|
1012
|
+
tree,
|
|
1013
|
+
componentScope(cdef, node.props, inner, binder)
|
|
1014
|
+
);
|
|
1015
|
+
const expanded = expand(body, instanceId, {
|
|
1016
|
+
prefix: `${instanceId}/`,
|
|
1017
|
+
expanding: new Set(ctx.expanding).add(node.type),
|
|
1018
|
+
depth: ctx.depth + 1,
|
|
1019
|
+
// os children do call-site vão CRUS, com o escopo de quem os escreveu; quem os expande
|
|
1020
|
+
// é o `$slot`, já dentro do corpo.
|
|
1021
|
+
slots: { children: { nodes: node.children ?? [], written: ctx } },
|
|
1022
|
+
contexts: inner,
|
|
1023
|
+
commands: innerCommands,
|
|
1024
|
+
// o corpo passa a ser do CHAMADO: os updates dele é que resolvem antes do escopo.
|
|
1025
|
+
updates: new Set(Object.keys(cdef.updates ?? {})),
|
|
1026
|
+
component: cdef.component
|
|
1027
|
+
});
|
|
1028
|
+
return {
|
|
1029
|
+
...expanded,
|
|
1030
|
+
placement: node.placement,
|
|
1031
|
+
...occupying(node, ctx, expanded.origins)
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
const next = { ...node, id: instanceId };
|
|
1035
|
+
delete next.provides;
|
|
1036
|
+
delete next.commands;
|
|
1037
|
+
if (ctx.component === void 0) delete next.origins;
|
|
1038
|
+
const events = routed(node, ctx);
|
|
1039
|
+
if (events !== void 0) next.events = events;
|
|
1040
|
+
if (node.children !== void 0)
|
|
1041
|
+
next.children = expandChildren(node.children, {
|
|
1042
|
+
...ctx,
|
|
1043
|
+
contexts: provided(ctx.contexts, node),
|
|
1044
|
+
commands: commandScope(ctx.commands, node)
|
|
1045
|
+
});
|
|
1046
|
+
return next;
|
|
1047
|
+
};
|
|
1048
|
+
return expand(root, root.id, {
|
|
1049
|
+
prefix: "",
|
|
1050
|
+
expanding: /* @__PURE__ */ new Set(),
|
|
1051
|
+
depth: 0,
|
|
1052
|
+
contexts: opts.contexts ?? {},
|
|
1053
|
+
commands: opts.commands ?? EMPTY_COMMANDS,
|
|
1054
|
+
updates: opts.updates ?? EMPTY_UPDATES,
|
|
1055
|
+
...opts.component !== void 0 ? { component: opts.component } : {}
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
function resolveComponentTree(node, opts, data) {
|
|
1059
|
+
const binder = opts.binder ?? identityBinder;
|
|
1060
|
+
const bound = binder.bind(binder.compile?.(node) ?? node, data);
|
|
1061
|
+
const expanded = expandComponents(
|
|
1062
|
+
bound,
|
|
1063
|
+
opts.componentDefs ?? EMPTY_DEFS,
|
|
1064
|
+
binder,
|
|
1065
|
+
{
|
|
1066
|
+
requireEntryRoot: opts.requireEntryRoot,
|
|
1067
|
+
...opts.contexts ? { contexts: opts.contexts } : {},
|
|
1068
|
+
...opts.commands ? { commands: opts.commands } : {},
|
|
1069
|
+
...opts.updates ? { updates: opts.updates } : {},
|
|
1070
|
+
...opts.component !== void 0 ? { component: opts.component } : {}
|
|
1071
|
+
}
|
|
1072
|
+
);
|
|
1073
|
+
return resolveTree(expanded, { ...opts, binder: identityBinder });
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
export { COMPONENT_FIELDS, EMPTY_MOUNT, STATEFUL_TYPE, addressOf, applyUpdate, bindPayload, bodyOf, buildSystemIR, categoryOf, checkCategoryPolicy, checkContexts, checkEffects, checkForbiddenTags, checkIdentity, checkSlots, compileComponent, compileDefinition, compileNodeOf, componentAddress, componentSlotsOf, computeUnable, createComponentDefRegistry, deriveCommands, deriveContext, deriveFacts, effectiveTags, expandComponents, factsOf, initState, isLive, mountScopesOf, mountingOf, nodeAddress, nodeFactsOf, parseAddress, resolveComponentTree, routeIntent, settledRoute };
|
|
1077
|
+
//# sourceMappingURL=index.js.map
|
|
1078
|
+
//# sourceMappingURL=index.js.map
|