vitral 0.3.0 → 0.5.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.
Files changed (70) hide show
  1. package/dist/angular.d.ts +2 -2
  2. package/dist/angular.js +7 -7
  3. package/dist/auto.js +7 -7
  4. package/dist/{capture-JYFERAPL.js → capture-TT5WBLJ6.js} +1 -1
  5. package/dist/channel-2YPYGX3J.js +95 -0
  6. package/dist/{chunk-AKNNHQ2H.js → chunk-463H6B56.js} +5 -0
  7. package/dist/{chunk-IHTUM6LT.js → chunk-4Z77XNBR.js} +2 -2
  8. package/dist/chunk-54CBIZES.js +105 -0
  9. package/dist/{chunk-TD4S3IHA.js → chunk-5JNV45BB.js} +10 -8
  10. package/dist/{chunk-SDB5AZEI.js → chunk-C3QOBAUE.js} +2 -2
  11. package/dist/chunk-DAESKNQ6.js +207 -0
  12. package/dist/{chunk-3YXI3G34.js → chunk-K6EG7DMO.js} +16 -7
  13. package/dist/{chunk-ZAC6QQI6.js → chunk-KU6HF2EW.js} +4 -4
  14. package/dist/{chunk-I3VSKSAZ.js → chunk-KZBH5MPD.js} +29 -27
  15. package/dist/chunk-NZEQGRBL.js +93 -0
  16. package/dist/chunk-PY7P5BF4.js +404 -0
  17. package/dist/{chunk-GNHCZRQP.js → chunk-TW7KYJQ6.js} +1 -1
  18. package/dist/{chunk-AH7KNMBW.js → chunk-UYOPJ4XQ.js} +1 -1
  19. package/dist/cli.js +1522 -37
  20. package/dist/devtools.d.ts +32 -2
  21. package/dist/devtools.js +27 -12
  22. package/dist/dict.d.ts +69 -0
  23. package/dist/dict.js +8 -0
  24. package/dist/edge.d.ts +118 -0
  25. package/dist/edge.js +398 -0
  26. package/dist/element.d.ts +12 -3
  27. package/dist/element.js +7 -7
  28. package/dist/embed.js +2 -0
  29. package/dist/{events-D5gs7rff.d.ts → events-0rxFlWJJ.d.ts} +1 -1
  30. package/dist/expose-IAKYHJXF.js +15 -0
  31. package/dist/federation.d.ts +89 -0
  32. package/dist/federation.js +101 -0
  33. package/dist/hydrate-YVZDJ2V7.js +168 -0
  34. package/dist/iframe.js +7 -7
  35. package/dist/index.d.ts +4 -4
  36. package/dist/index.js +5 -5
  37. package/dist/otel.d.ts +29 -1
  38. package/dist/otel.js +13 -1
  39. package/dist/overrides-NP754HY7.js +27 -0
  40. package/dist/preact.d.ts +4 -4
  41. package/dist/preact.js +6 -6
  42. package/dist/prefetch.d.ts +25 -0
  43. package/dist/prefetch.js +178 -0
  44. package/dist/react.d.ts +3 -3
  45. package/dist/react.js +6 -6
  46. package/dist/reframe-LQL4DZYP.js +251 -0
  47. package/dist/routes.d.ts +2 -2
  48. package/dist/routes.js +5 -5
  49. package/dist/{shadow-6HELCHCJ.js → shadow-S4SKHTA7.js} +66 -5
  50. package/dist/single-spa.d.ts +40 -0
  51. package/dist/single-spa.js +52 -0
  52. package/dist/{slot-DqXTwGbs.d.ts → slot-D49L1rdU.d.ts} +1 -1
  53. package/dist/solid.d.ts +3 -3
  54. package/dist/solid.js +6 -6
  55. package/dist/{sri-JMX6JLQP.js → sri-576GTDIA.js} +41 -10
  56. package/dist/svelte.d.ts +4 -4
  57. package/dist/svelte.js +6 -6
  58. package/dist/testing.d.ts +171 -3
  59. package/dist/testing.js +193 -6
  60. package/dist/{types-Czvm-siZ.d.ts → types-DUtcF3HO.d.ts} +124 -5
  61. package/dist/vitals.d.ts +29 -0
  62. package/dist/vitals.js +152 -0
  63. package/dist/vite.d.ts +18 -0
  64. package/dist/vite.js +329 -417
  65. package/dist/vue.d.ts +3 -3
  66. package/dist/vue.js +6 -6
  67. package/package.json +170 -21
  68. package/dist/chunk-DXORSF4X.js +0 -42
  69. package/dist/expose-JUOUN5N5.js +0 -15
  70. package/dist/overrides-NZAIUGK5.js +0 -15
package/dist/svelte.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  hostSlot
3
- } from "./chunk-IHTUM6LT.js";
3
+ } from "./chunk-4Z77XNBR.js";
4
4
  import {
5
5
  defineRemote,
6
6
  events,
7
7
  navigate
8
- } from "./chunk-I3VSKSAZ.js";
9
- import "./chunk-TD4S3IHA.js";
10
- import "./chunk-ZAC6QQI6.js";
11
- import "./chunk-GNHCZRQP.js";
12
- import "./chunk-AKNNHQ2H.js";
8
+ } from "./chunk-KZBH5MPD.js";
9
+ import "./chunk-5JNV45BB.js";
10
+ import "./chunk-KU6HF2EW.js";
11
+ import "./chunk-TW7KYJQ6.js";
12
+ import "./chunk-463H6B56.js";
13
13
  import "./chunk-GNPFWISJ.js";
14
14
 
15
15
  // src/svelte.ts
package/dist/testing.d.ts CHANGED
@@ -1,9 +1,177 @@
1
- import { P as Props, R as RemoteContext } from './types-Czvm-siZ.js';
1
+ import { P as Props, R as RemoteContext, a as RemoteModule } from './types-DUtcF3HO.js';
2
2
  export { r as resetVitral } from './state-BDdCwc21.js';
3
3
  import './errors-kjBv6IH_.js';
4
4
 
5
+ /** Endereço do JSON Schema do contrato. Vai no `$schema` do arquivo gravado. */
6
+ declare const CONTRACT_SCHEMA = "https://joaogabriel15.github.io/vitral/schema/vitral-contract.json";
5
7
  /**
6
- * Utilidades de teste: monte o host sem subir nenhum remote de verdade.
8
+ * Contrato do consumidor: o que um host passou para um remote e o que ele
9
+ * espera de volta, gravado com `record()`. Arquivo `<remote>.contract.json`.
10
+ */
11
+ interface Contract {
12
+ $schema?: string;
13
+ /** Nome com que o host monta o remote. */
14
+ remote: string;
15
+ /** Quem gravou (o host). */
16
+ host?: string;
17
+ /** `version` do manifesto do remote na gravação. */
18
+ version?: string;
19
+ /** Uma montagem por caso, sem repetições. */
20
+ cases: ContractCase[];
21
+ }
22
+ /** Quando o remote emitiu: no `mount`, num `update`, ao receber um evento do host, ou depois (clique, timer). */
23
+ type EmitPhase = 'mount' | 'update' | 'input' | 'later';
24
+ /**
25
+ * Uma montagem gravada. Valores que o JSON não guarda viram marcadores:
26
+ * `{ "$fn": true }` (função), `{ "$date": "…" }` e `{ "$unknown": "Classe" }`.
27
+ */
28
+ interface ContractCase {
29
+ /** Props do mount. */
30
+ props: Props;
31
+ /** Props de cada `update`, em ordem. */
32
+ updates?: Props[];
33
+ /** Eventos que o host emitiu e o remote escutava, em ordem. */
34
+ inputs?: {
35
+ type: string;
36
+ payload?: unknown;
37
+ }[];
38
+ /** Valores que o remote leu com `ctx.events.last`. */
39
+ last?: Record<string, unknown>;
40
+ /** Chaves de `ctx.shared` que o remote leu, com o primeiro valor lido. */
41
+ shared?: Record<string, unknown>;
42
+ /** Eventos que o remote emitiu e o host escuta. */
43
+ emits?: {
44
+ type: string;
45
+ phase: EmitPhase;
46
+ }[];
47
+ /** Eventos que o remote assinou. */
48
+ listens?: string[];
49
+ }
50
+ interface HarnessOptions<P extends Props = Props> {
51
+ props: P;
52
+ /** Nome do remote em `ctx.name`. Padrão `'remote'`. */
53
+ name?: string;
54
+ /** Monta dentro de um shadow root (`ctx.shadowRoot` e `ctx.portalContainer` ficam nele). */
55
+ shadow?: boolean;
56
+ /** Valores iniciais de `ctx.shared`. */
57
+ shared?: Record<string, unknown>;
58
+ /** Valores iniciais de `ctx.events.last`. */
59
+ last?: Record<string, unknown>;
60
+ /** Campos que substituem os do `ctx` simulado. */
61
+ ctx?: Partial<RemoteContext>;
62
+ /** Onde montar. Padrão: um `<div>` novo no `document.body`. */
63
+ target?: HTMLElement;
64
+ }
65
+ interface Harness<P extends Props = Props> {
66
+ /** Elemento onde o remote montou. */
67
+ readonly el: HTMLElement;
68
+ /** O `ctx` que o remote recebeu. */
69
+ readonly ctx: RemoteContext;
70
+ /** Eventos que o remote emitiu por `ctx.events`, em ordem. */
71
+ readonly events: {
72
+ type: string;
73
+ payload: unknown;
74
+ }[];
75
+ /** Erros de `ctx.reportError` e de handlers de eventos. */
76
+ readonly errors: unknown[];
77
+ /** Chamadas de `ctx.navigate`. */
78
+ readonly navigations: {
79
+ url: string;
80
+ options?: {
81
+ replace?: boolean;
82
+ };
83
+ }[];
84
+ /** Tipos de evento que o remote escuta agora. */
85
+ listens(): string[];
86
+ /** Emite um evento como se fosse o host. */
87
+ emit(type: string, payload?: unknown): void;
88
+ update(props: P): void;
89
+ /** Dispara `ctx.onActivate` (keep-alive). */
90
+ activate(): void;
91
+ /** Dispara `ctx.onDeactivate` (keep-alive). */
92
+ deactivate(): void;
93
+ unmount(): void;
94
+ }
95
+ /**
96
+ * Monta `mod` com um `ctx` simulado: barramento de eventos próprio, `shared`
97
+ * em memória, `portalContainer`, `customElements`, `navigate` e
98
+ * `reportError` gravados.
99
+ *
100
+ * const h = await harness(remote, { props: { itens: 2 } })
101
+ * expect(h.el.textContent).toContain('2 itens')
102
+ * h.emit('tema', 'escuro')
103
+ * expect(h.events).toContainEqual({ type: 'carrinho:aberto', payload: undefined })
104
+ */
105
+ declare function harness<P extends Props = Props>(mod: RemoteModule<P> | {
106
+ default: RemoteModule<P>;
107
+ }, options: HarnessOptions<P>): Promise<Harness<P>>;
108
+ interface VerifyOptions {
109
+ /**
110
+ * Código do remote (o texto dos arquivos JS). Um evento que o host escuta e
111
+ * que o remote só emite depois (clique, timer) é procurado aqui pelo nome.
112
+ */
113
+ sources?: string[];
114
+ /** Quanto esperar depois de cada etapa, em ms. Padrão 0. */
115
+ wait?: number;
116
+ }
117
+ interface VerifyResult {
118
+ /** Casos reproduzidos. */
119
+ cases: number;
120
+ /** O que quebra o host. Vazio quando o contrato passa. */
121
+ failures: string[];
122
+ /** O que mudou sem quebrar. */
123
+ warnings: string[];
124
+ }
125
+ /**
126
+ * Reproduz cada caso de `contract` em `mod` (props, updates, eventos do host,
127
+ * `shared` e `last`) e confere: mount, update e unmount sem erro, nenhum
128
+ * `reportError`, e cada evento que o host escuta ainda emitido (ou, se só sai
129
+ * depois de um clique, ainda presente no código).
130
+ */
131
+ declare function verify(contract: Contract, mod: RemoteModule<any> | {
132
+ default: RemoteModule<any>;
133
+ }, options?: VerifyOptions): Promise<VerifyResult>;
134
+
135
+ /**
136
+ * Gravação do contrato do consumidor, no host: para cada remote montado,
137
+ * guarda as props, os updates, os eventos que o host emitiu para ele, o que
138
+ * ele leu de `ctx.shared` e `ctx.events.last`, e os eventos dele que o host
139
+ * escuta. O remote reproduz o arquivo no CI com `vitral test --contract`.
140
+ *
141
+ * Funciona trocando métodos do protocolo global (`load` e o barramento), sem
142
+ * nada no núcleo: o host só paga por isto quando importa `vitral/testing`.
143
+ */
144
+
145
+ interface RecordOptions {
146
+ /** Nome do host, gravado no contrato. */
147
+ host?: string;
148
+ /** Máximo de casos diferentes por remote. Padrão 50. */
149
+ max?: number;
150
+ }
151
+ interface Recorder {
152
+ /** Contratos gravados até agora, por nome do remote. */
153
+ contracts(): Record<string, Contract>;
154
+ /** Grava `<remote>.contract.json` em `dir` (só no Node). Devolve os caminhos. */
155
+ save(dir?: string): Promise<string[]>;
156
+ /** Para de gravar e devolve o protocolo como estava. */
157
+ stop(): void;
158
+ }
159
+ /**
160
+ * Começa a gravar. Chame antes de o host montar os remotes e de assinar
161
+ * eventos (o recorder só vê as assinaturas feitas depois dele).
162
+ *
163
+ * const rec = record({ host: 'loja' })
164
+ * // ...testes do host montam os remotes...
165
+ * await rec.save('contracts')
166
+ *
167
+ * Remotes em `isolate: 'iframe'` e partes (`carrinho/Mini`) não são gravados.
168
+ */
169
+ declare function record(options?: RecordOptions): Recorder;
170
+
171
+ /**
172
+ * Utilidades de teste: monte o host sem subir nenhum remote de verdade, monte
173
+ * um remote com `ctx` simulado (`harness`) e grave o contrato do consumidor
174
+ * (`record`) que o remote reproduz no CI com `vitral test --contract`.
7
175
  *
8
176
  * import { mockRemote } from 'vitral/testing'
9
177
  * const carrinho = mockRemote('carrinho', (el, props) => (el.textContent = `itens: ${props.itens}`))
@@ -26,4 +194,4 @@ declare function mockRemote<P extends Props = Props>(name: string, render?: (el:
26
194
  /** Remote que sempre falha ao montar, para testar telas de erro. */
27
195
  declare function failingRemote(name: string, message?: string): void;
28
196
 
29
- export { type MockRemote, failingRemote, mockRemote };
197
+ export { CONTRACT_SCHEMA, type Contract, type ContractCase, type EmitPhase, type Harness, type HarnessOptions, type MockRemote, type RecordOptions, type Recorder, type VerifyOptions, type VerifyResult, failingRemote, harness, mockRemote, record, verify };
package/dist/testing.js CHANGED
@@ -1,14 +1,197 @@
1
- import "./chunk-I3VSKSAZ.js";
1
+ import {
2
+ CONTRACT_SCHEMA,
3
+ encode,
4
+ harness,
5
+ verify
6
+ } from "./chunk-DAESKNQ6.js";
7
+ import "./chunk-KZBH5MPD.js";
2
8
  import {
3
9
  register
4
- } from "./chunk-TD4S3IHA.js";
5
- import "./chunk-ZAC6QQI6.js";
6
- import "./chunk-GNHCZRQP.js";
7
- import "./chunk-AKNNHQ2H.js";
10
+ } from "./chunk-5JNV45BB.js";
11
+ import "./chunk-KU6HF2EW.js";
12
+ import "./chunk-TW7KYJQ6.js";
13
+ import "./chunk-463H6B56.js";
8
14
  import {
15
+ protocol,
9
16
  resetProtocol
10
17
  } from "./chunk-GNPFWISJ.js";
11
18
 
19
+ // src/recorder.ts
20
+ function record(options = {}) {
21
+ const p = protocol();
22
+ const bus = p.events;
23
+ const { load, emit, on } = { load: p.load, emit: bus.emit, on: bus.on };
24
+ const cases = /* @__PURE__ */ new Map();
25
+ const versions = /* @__PURE__ */ new Map();
26
+ const hostListens = /* @__PURE__ */ new Set();
27
+ const wrapped = /* @__PURE__ */ new WeakMap();
28
+ let viaRemote = 0;
29
+ let hostEmitting = [];
30
+ bus.on = (type, handler) => {
31
+ if (viaRemote) return on.call(bus, type, handler);
32
+ hostListens.add(type);
33
+ return on.call(bus, type, (payload) => {
34
+ const before = viaRemote;
35
+ viaRemote = 0;
36
+ try {
37
+ handler(payload);
38
+ } finally {
39
+ viaRemote = before;
40
+ }
41
+ });
42
+ };
43
+ bus.emit = (type, payload) => {
44
+ hostEmitting.push(!viaRemote);
45
+ try {
46
+ return emit.call(bus, type, payload);
47
+ } finally {
48
+ hostEmitting.pop();
49
+ }
50
+ };
51
+ const scope = (live, events) => ({
52
+ emit(type, payload) {
53
+ live.emits.push({ type, phase: live.phase });
54
+ viaRemote++;
55
+ try {
56
+ events.emit(type, payload);
57
+ } finally {
58
+ viaRemote--;
59
+ }
60
+ },
61
+ on(type, handler) {
62
+ live.listens.add(type);
63
+ viaRemote++;
64
+ try {
65
+ return events.on(type, (payload) => {
66
+ if (!hostEmitting.at(-1)) return handler(payload);
67
+ live.inputs.push({ type, payload: encode(payload) });
68
+ const before = live.phase;
69
+ live.phase = "input";
70
+ try {
71
+ handler(payload);
72
+ } finally {
73
+ live.phase = before;
74
+ }
75
+ });
76
+ } finally {
77
+ viaRemote--;
78
+ }
79
+ },
80
+ last(type) {
81
+ const v = events.last(type);
82
+ if (v !== void 0 && !(type in live.last)) live.last[type] = encode(v);
83
+ return v;
84
+ }
85
+ });
86
+ const sharedOf = (live, api) => {
87
+ const seen = (key, v) => {
88
+ if (v !== void 0 && !(key in live.shared)) live.shared[key] = encode(v);
89
+ return v;
90
+ };
91
+ const wrap = (key) => {
92
+ const h = api(key);
93
+ return { ...h, get: () => seen(key, h.get()), subscribe: (fn) => h.subscribe((v) => fn(seen(key, v))) };
94
+ };
95
+ return Object.assign(wrap, { provide: api.provide, batch: api.batch });
96
+ };
97
+ const wrapModule = (mod) => {
98
+ let w = wrapped.get(mod);
99
+ if (w) return w;
100
+ w = Object.create(mod, {
101
+ mount: {
102
+ value: async (el, props, ctx) => {
103
+ const live = { props: encode(props), updates: [], inputs: [], last: {}, shared: {}, emits: [], listens: /* @__PURE__ */ new Set(), phase: "mount" };
104
+ const list = cases.get(ctx.name) ?? [];
105
+ cases.set(ctx.name, list);
106
+ list.push(live);
107
+ const own = { ...ctx, events: scope(live, ctx.events) };
108
+ if (ctx.shared) own.shared = sharedOf(live, ctx.shared);
109
+ let mounted;
110
+ try {
111
+ mounted = await mod.mount(el, props, own);
112
+ } finally {
113
+ live.phase = "later";
114
+ }
115
+ return {
116
+ update(next) {
117
+ live.updates.push(encode(next));
118
+ live.phase = "update";
119
+ try {
120
+ mounted.update?.(next);
121
+ } finally {
122
+ live.phase = "later";
123
+ }
124
+ },
125
+ unmount: () => mounted.unmount()
126
+ };
127
+ }
128
+ }
129
+ });
130
+ wrapped.set(mod, w);
131
+ return w;
132
+ };
133
+ p.load = (name, fetcher) => load.call(p, name, fetcher).then((r) => {
134
+ if (r.version) versions.set(r.name ?? name, r.version);
135
+ return typeof r.module?.mount === "function" ? { ...r, module: wrapModule(r.module) } : r;
136
+ });
137
+ const contracts = () => {
138
+ const out = {};
139
+ for (const [remote, lives] of cases) {
140
+ const unique = /* @__PURE__ */ new Map();
141
+ for (const live of lives) {
142
+ const c = { props: live.props };
143
+ if (live.updates.length) c.updates = live.updates;
144
+ if (live.inputs.length) c.inputs = live.inputs;
145
+ if (Object.keys(live.last).length) c.last = live.last;
146
+ if (Object.keys(live.shared).length) c.shared = live.shared;
147
+ const emits = /* @__PURE__ */ new Map();
148
+ for (const e of live.emits) if (hostListens.has(e.type) && (!emits.has(e.type) || emits.get(e.type) === "later")) emits.set(e.type, e.phase);
149
+ if (emits.size) c.emits = [...emits].map(([type, phase]) => ({ type, phase }));
150
+ if (live.listens.size) c.listens = [...live.listens].sort();
151
+ const key = JSON.stringify(c);
152
+ if (!unique.has(key) && unique.size < (options.max ?? 50)) unique.set(key, c);
153
+ }
154
+ out[remote] = {
155
+ $schema: CONTRACT_SCHEMA,
156
+ remote,
157
+ ...options.host && { host: options.host },
158
+ ...versions.has(remote) && { version: versions.get(remote) },
159
+ cases: [...unique.values()]
160
+ };
161
+ }
162
+ return out;
163
+ };
164
+ return {
165
+ contracts,
166
+ async save(dir = ".") {
167
+ const fs = "node:fs/promises";
168
+ const path = "node:path";
169
+ const { mkdir, writeFile } = await import(
170
+ /* @vite-ignore */
171
+ fs
172
+ );
173
+ const { join } = await import(
174
+ /* @vite-ignore */
175
+ path
176
+ );
177
+ await mkdir(dir, { recursive: true });
178
+ const files = [];
179
+ for (const [name, c] of Object.entries(contracts())) {
180
+ const file = join(dir, `${name.replace(/[^\w.-]+/g, "_")}.contract.json`);
181
+ await writeFile(file, JSON.stringify(c, null, 2) + "\n");
182
+ files.push(file);
183
+ }
184
+ return files;
185
+ },
186
+ stop() {
187
+ p.load = load;
188
+ bus.emit = emit;
189
+ bus.on = on;
190
+ hostEmitting = [];
191
+ }
192
+ };
193
+ }
194
+
12
195
  // src/testing.ts
13
196
  function mockRemote(name, render = (el) => {
14
197
  el.setAttribute("data-mock-remote", name);
@@ -47,7 +230,11 @@ function failingRemote(name, message = "falha simulada") {
47
230
  });
48
231
  }
49
232
  export {
233
+ CONTRACT_SCHEMA,
50
234
  failingRemote,
235
+ harness,
51
236
  mockRemote,
52
- resetProtocol as resetVitral
237
+ record,
238
+ resetProtocol as resetVitral,
239
+ verify
53
240
  };
@@ -99,6 +99,17 @@ interface RemoteContext {
99
99
  * Hosts antigos não mandam este campo.
100
100
  */
101
101
  portalContainer?: HTMLElement;
102
+ /**
103
+ * Registro de custom elements do remote. No Shadow DOM, onde o navegador
104
+ * tem Scoped Custom Element Registries (Chrome 146+, Safari 26+), é um
105
+ * registro só deste remote, usado pelo shadow root e pelo elemento `el`:
106
+ * dois remotes podem definir `<ds-button>` de versões diferentes. Sem
107
+ * suporte, e fora do Shadow DOM, é o `customElements` global. No Shadow
108
+ * DOM, `define` de uma tag já definida não lança: fica a definição que já
109
+ * existe, com um aviso em dev quando ela veio de outro remote. Hosts
110
+ * antigos não mandam este campo: use `ctx.customElements ?? customElements`.
111
+ */
112
+ customElements?: CustomElementRegistry;
102
113
  /** Avisa o host de um erro em tempo de execução (os adaptadores já chamam). */
103
114
  reportError(error: unknown): void;
104
115
  /** Navega a página inteira (host e todos os roteadores) para `url`. */
@@ -124,6 +135,21 @@ interface RemoteContext {
124
135
  * (`provide`, `shared` ou um hook); senão, importe `shared` de 'vitral'.
125
136
  */
126
137
  shared?: SharedApi;
138
+ /**
139
+ * Só quando o remote roda como MCP App (`vitral({ target: 'mcp-app' })`):
140
+ * fala JSON-RPC direto com o host MCP, para o que `events` não cobre
141
+ * (`tools/call`, `resources/read`, `ui/request-display-mode`…).
142
+ */
143
+ mcp?: McpAppBridge;
144
+ }
145
+ /** `ctx.mcp` de um remote empacotado como MCP App. */
146
+ interface McpAppBridge {
147
+ /** Pedido ao host; resolve com `result` ou rejeita com `error.message`. */
148
+ request<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>;
149
+ /** Notificação ao host (sem resposta). */
150
+ notify(method: string, params?: Record<string, unknown>): void;
151
+ /** O `hostContext` atual (tema, idioma, tamanho do container…), já com as mudanças. */
152
+ hostContext(): Record<string, unknown> | undefined;
127
153
  }
128
154
  interface MountedRemote<P extends Props = Props> {
129
155
  update?(props: P): void;
@@ -131,6 +157,15 @@ interface MountedRemote<P extends Props = Props> {
131
157
  }
132
158
  interface RemoteModule<P extends Props = Props> {
133
159
  mount(el: HTMLElement, props: P, ctx: RemoteContext): MountedRemote<P> | Promise<MountedRemote<P>>;
160
+ /**
161
+ * Opcional. Quando o gateway de `vitral/edge` já renderizou o remote no
162
+ * servidor, o `<vitral-remote>` chama `hydrate` com `el` cheio do HTML de
163
+ * `render` (no Shadow DOM declarativo, se for o caso), em vez de limpar e
164
+ * chamar `mount`. Deve assumir o DOM que existe, sem recriar.
165
+ */
166
+ hydrate?(el: HTMLElement, props: P, ctx: RemoteContext): MountedRemote<P> | Promise<MountedRemote<P>>;
167
+ /** Opcional, só no servidor: o HTML do remote para estas props (SSR pelo gateway). */
168
+ render?(props: P): string | Promise<string>;
134
169
  }
135
170
  interface RemoteEntry {
136
171
  /** URL do módulo JS do remote, ou do `vitral.json` gerado pelo plugin. */
@@ -149,7 +184,7 @@ interface RemoteEntry {
149
184
  /** Isolamento padrão para este remote. */
150
185
  isolate?: Isolation;
151
186
  /**
152
- * O `vitral.json` já baixado (ex.: por `vitral registry`), para não buscá-lo
187
+ * O `vitral.json` já baixado (ex.: no build do host), para não buscá-lo
153
188
  * na hora. Os caminhos dele são relativos a `url`, e os hashes ficam fixos
154
189
  * no host.
155
190
  */
@@ -159,12 +194,68 @@ interface RemoteEntry {
159
194
  * Vence a de `configure`. Veja `VitralConfig.contract`.
160
195
  */
161
196
  contract?: string;
197
+ /**
198
+ * Com `url` apontando para um canal (`canal.json`): escolhe a versão deste
199
+ * usuário. Devolve a versão (`'1.5.0'`) ou a URL de um `vitral.json`;
200
+ * `undefined` usa a regra padrão (`canary.percent`). Veja `Channel`.
201
+ */
202
+ resolve?: (channel: Channel, ctx: ChannelContext) => string | undefined | Promise<string | undefined>;
203
+ /**
204
+ * Volta para a estável, até o fim da sessão, quando a versão que não é a
205
+ * estável gera `errors` erros numa janela de `mounts` montagens. Padrão
206
+ * `{ errors: 3, mounts: 20 }`. `false` desliga.
207
+ */
208
+ rollback?: {
209
+ errors?: number;
210
+ mounts?: number;
211
+ } | false;
212
+ }
213
+ /**
214
+ * Arquivo-ponteiro de um remote (o canal, ex.: `canal.json`): diz que versão
215
+ * cada usuário recebe. Cada versão fica na própria pasta, ao lado do canal
216
+ * (`'1.4.0'` → `1.4.0/vitral.json`), ou é a URL de um `vitral.json`.
217
+ *
218
+ * { "stable": "1.4.0", "canary": { "version": "1.5.0", "percent": 10 } }
219
+ */
220
+ interface Channel {
221
+ /** Versão de todos os usuários fora da canary. */
222
+ stable: string;
223
+ /** Versão nova para `percent`% dos usuários (0 a 100). */
224
+ canary?: {
225
+ version: string;
226
+ percent: number;
227
+ };
228
+ /** Campos próprios, para o `resolve` do host ler (ex.: `"beta": "1.6.0"`). */
229
+ [key: string]: unknown;
230
+ }
231
+ /** O que o `resolve` de um canal recebe além do canal. */
232
+ interface ChannelContext {
233
+ /** Nome do remote. */
234
+ name: string;
235
+ /**
236
+ * Número de 0 a 99, sempre o mesmo para este navegador (ou para
237
+ * `canaryKey`) e este remote. A regra padrão dá a canary a quem tem
238
+ * `bucket < percent`.
239
+ */
240
+ bucket: number;
241
+ }
242
+ /** Aviso de rollback automático, para `configure({ onRollback })`. */
243
+ interface RollbackInfo {
244
+ name: string;
245
+ /** Versão abandonada (como o canal ou o `resolve` a escreveram). */
246
+ from: string;
247
+ /** Versão estável, usada daqui até o fim da sessão. */
248
+ to: string;
249
+ errors: number;
250
+ mounts: number;
162
251
  }
163
252
  /**
164
253
  * `none`: CSS com escopo. `shadow`: Shadow DOM. `iframe`: iframe com
165
254
  * `sandbox`, para código de terceiros (precisa de `enableIframe()`).
255
+ * `reframe`: JS num iframe oculto de mesma origem (globals próprios) e DOM
256
+ * no Shadow DOM da página. Não é fronteira de segurança.
166
257
  */
167
- type Isolation = 'shadow' | 'none' | 'iframe';
258
+ type Isolation = 'shadow' | 'none' | 'iframe' | 'reframe';
168
259
  /** Estilos do host dentro do Shadow DOM. Sem efeito no modo escopo. */
169
260
  interface StyleOptions {
170
261
  /**
@@ -178,6 +269,15 @@ interface StyleOptions {
178
269
  */
179
270
  mirrorTheme?: boolean;
180
271
  }
272
+ /** Opções de `configure({ devOverrides })`. */
273
+ interface DevOverrides {
274
+ /**
275
+ * Modelo da URL de um apelido: `?vitral=checkout@pr-123` com
276
+ * `'https://{name}-{ref}.preview.empresa.com'` vira
277
+ * `https://checkout-pr-123.preview.empresa.com/vitral.json`.
278
+ */
279
+ preview?: string;
280
+ }
181
281
  interface VitralConfig {
182
282
  /** Mapa nome → URL (ou entrada completa), ou a URL de um JSON com esse mapa. */
183
283
  remotes?: Record<string, string | RemoteEntry> | string;
@@ -196,9 +296,10 @@ interface VitralConfig {
196
296
  * Liga os overrides de desenvolvimento (`?vitral=nome@url` e
197
297
  * `localStorage['vitral:overrides']`) fora de localhost. Em localhost já
198
298
  * valem. Nunca ligue em produção: um link com `?vitral=` executaria código
199
- * de qualquer origem.
299
+ * de qualquer origem. O objeto também liga e aceita `preview`, o modelo de
300
+ * URL dos apelidos (`?vitral=checkout@pr-123`).
200
301
  */
201
- devOverrides?: boolean;
302
+ devOverrides?: boolean | DevOverrides;
202
303
  /** Estilos do host no Shadow DOM: `{ inherit: ['/assets/base.css'] }`. */
203
304
  styles?: StyleOptions;
204
305
  /** Recebe o tempo de cada etapa do carregamento (para RUM). */
@@ -232,6 +333,16 @@ interface VitralConfig {
232
333
  * conferido. `'*'` confere só as dependências.
233
334
  */
234
335
  contract?: string;
336
+ /**
337
+ * Chave estável do usuário (ex.: o id dele) para sortear quem recebe a
338
+ * canary de um canal. Padrão: um id aleatório guardado no `localStorage`.
339
+ */
340
+ canaryKey?: string;
341
+ /**
342
+ * Chamado quando a versão canary de um canal passa do limite de erros e o
343
+ * host volta para a estável (veja `RemoteEntry.rollback`).
344
+ */
345
+ onRollback?: (info: RollbackInfo) => void;
235
346
  }
236
347
  /** Módulos de um grupo de `dedupe`, por especificador (`'react-dom/client'`). */
237
348
  type SharedModules = Record<string, unknown>;
@@ -294,6 +405,14 @@ interface RemoteManifest {
294
405
  * antigos), o runtime faz esse trabalho em cada página.
295
406
  */
296
407
  shadow?: ShadowCss;
408
+ /**
409
+ * SBOM CycloneDX do remote (opção `sbom` do plugin): o arquivo, relativo
410
+ * ao manifesto, e o hash dele. O runtime não usa; é para auditoria.
411
+ */
412
+ sbom?: {
413
+ file: string;
414
+ integrity: string;
415
+ };
297
416
  }
298
417
  /** Contrato do remote no `vitral.json`. */
299
418
  interface RemoteContract {
@@ -324,4 +443,4 @@ interface EventBus {
324
443
  last<T = unknown>(type: string): T | undefined;
325
444
  }
326
445
 
327
- export { type CheckedProps as C, type EventBus as E, type Handler as H, type Isolation as I, type MountedRemote as M, type Props as P, type RemoteContext as R, type Shared as S, type VitralConfig as V, type RemoteModule as a, type StyleOptions as b, type RemoteEntry as c, type SharedValue as d, type PropsOf as e, type ProvideOptions as f, type ProvidedValue as g, type RemoteContract as h, type RemoteManifest as i, type RemoteMetric as j, type Remotes as k, type SharedApi as l, type SharedModules as m, type SharedType as n, batch as o, provide as p, shared as s };
446
+ export { type CheckedProps as C, type DevOverrides as D, type EventBus as E, type Handler as H, type Isolation as I, type MountedRemote as M, type Props as P, type RemoteContext as R, type Shared as S, type VitralConfig as V, type RemoteModule as a, type SharedModules as b, type RemoteEntry as c, type StyleOptions as d, type SharedValue as e, type Channel as f, type ChannelContext as g, type McpAppBridge as h, type PropsOf as i, type ProvideOptions as j, type ProvidedValue as k, type RemoteContract as l, type RemoteManifest as m, type RemoteMetric as n, type Remotes as o, type RollbackInfo as p, type SharedApi as q, type SharedType as r, batch as s, provide as t, shared as u };
@@ -0,0 +1,29 @@
1
+ type VitalName = 'LCP' | 'CLS' | 'INP' | 'FCP' | 'TTFB';
2
+ type VitalRating = 'good' | 'needs-improvement' | 'poor';
3
+ interface VitalReport {
4
+ name: VitalName;
5
+ /** Remote dono do elemento. Ausente quando é do host ou da página. */
6
+ remote?: string;
7
+ /** Versão do remote (do manifesto) na hora da medição. */
8
+ version?: string;
9
+ /** Rota: pathname da navegação (soft ou não). */
10
+ route: string;
11
+ /** `navigationId` das entradas, só com Soft Navigations. */
12
+ navigationId?: string;
13
+ /** ms para LCP, INP, FCP e TTFB; sem unidade para CLS. */
14
+ value: number;
15
+ rating: VitalRating;
16
+ }
17
+ interface VitalsOptions {
18
+ onReport: (report: VitalReport) => void;
19
+ }
20
+ declare const rating: (name: VitalName, value: number) => VitalRating;
21
+ /** Dono de `node`: o `data-vitral-root` mais próximo, atravessando shadow roots. `''` é o host. */
22
+ declare function owner(node: Node | null | undefined): string;
23
+ /**
24
+ * Observa as Web Vitals e chama `onReport` com cada uma, por remote e rota.
25
+ * Devolve `stop`, que relata o que falta e desliga os observers.
26
+ */
27
+ declare function vitals({ onReport }: VitalsOptions): () => void;
28
+
29
+ export { type VitalName, type VitalRating, type VitalReport, type VitalsOptions, owner, rating, vitals };