@microverse.ts/host-surface 0.2.0 → 0.3.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 (40) hide show
  1. package/README.md +45 -15
  2. package/dist/application/ports/ScriptReferenceResolverPort.d.ts +13 -0
  3. package/dist/application/ports/ScriptReferenceResolverPort.d.ts.map +1 -0
  4. package/dist/application/useCases/compileBridgeDeclarationsFromHostSurfaceSpec.d.ts +3 -3
  5. package/dist/application/useCases/compileBridgeDeclarationsFromHostSurfaceSpec.d.ts.map +1 -1
  6. package/dist/application/useCases/compileHostSurface.d.ts +4 -3
  7. package/dist/application/useCases/compileHostSurface.d.ts.map +1 -1
  8. package/dist/domain/componentSlotPrelude.d.ts +8 -0
  9. package/dist/domain/componentSlotPrelude.d.ts.map +1 -1
  10. package/dist/domain/componentTypeSpec.d.ts +12 -0
  11. package/dist/domain/componentTypeSpec.d.ts.map +1 -0
  12. package/dist/domain/hostSurfaceManifest.d.ts +3 -2
  13. package/dist/domain/hostSurfaceManifest.d.ts.map +1 -1
  14. package/dist/domain/hostSurfaceSpecTypes.d.ts +89 -0
  15. package/dist/domain/hostSurfaceSpecTypes.d.ts.map +1 -0
  16. package/dist/domain/hostSurfaceTypes.d.ts +11 -90
  17. package/dist/domain/hostSurfaceTypes.d.ts.map +1 -1
  18. package/dist/domain/safeObjectKey.d.ts +1 -1
  19. package/dist/domain/safeObjectKey.d.ts.map +1 -1
  20. package/dist/domain/scriptCatalogManifest.d.ts +14 -0
  21. package/dist/domain/scriptCatalogManifest.d.ts.map +1 -0
  22. package/dist/domain/scriptProfileSpec.d.ts +31 -0
  23. package/dist/domain/scriptProfileSpec.d.ts.map +1 -0
  24. package/dist/domain/surfaceCapabilities.d.ts +1 -1
  25. package/dist/domain/surfaceCapabilities.d.ts.map +1 -1
  26. package/dist/domain/surfaceMethodDef.d.ts +1 -1
  27. package/dist/domain/surfaceMethodDef.d.ts.map +1 -1
  28. package/dist/index.d.ts +13 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +616 -296
  31. package/dist/index.js.map +1 -1
  32. package/dist/infrastructure/builders/bridgeMergeEnv.d.ts +5 -10
  33. package/dist/infrastructure/builders/bridgeMergeEnv.d.ts.map +1 -1
  34. package/dist/infrastructure/builders/filterBridgeDeclarations.d.ts +11 -0
  35. package/dist/infrastructure/builders/filterBridgeDeclarations.d.ts.map +1 -0
  36. package/dist/infrastructure/builders/surfaceBuilder.d.ts +7 -2
  37. package/dist/infrastructure/builders/surfaceBuilder.d.ts.map +1 -1
  38. package/dist/infrastructure/components/hostScriptSession.d.ts +20 -5
  39. package/dist/infrastructure/components/hostScriptSession.d.ts.map +1 -1
  40. package/package.json +8 -8
package/dist/index.js CHANGED
@@ -1,22 +1,404 @@
1
1
  import { buildDeclarativeBridgeTable } from "@microverse.ts/runtime-bridge";
2
- import { validateWithZodSchema } from "@microverse.ts/runtime-zod";
3
- import { z } from "zod";
4
2
  import { applyScriptPropertyChanges, assertValidScriptPropertyBag, cloneScriptPropertyBag, createMicroverseScript, createScriptInstanceContext, diffScriptProperties } from "@microverse.ts/runtime-core";
5
- import { InMemoryCapabilityRegistry, createAllowlist, createCapabilityId } from "@microverse.ts/runtime-capabilities";
3
+ import { createCapabilityId } from "@microverse.ts/runtime-capabilities";
4
+ import { z } from "zod";
5
+ import { validateWithZodSchema } from "@microverse.ts/runtime-zod";
6
+ //#region src/domain/scriptContextSymbol.ts
7
+ var MICROVERSE_SCRIPT_CONTEXT = Symbol("microverse.scriptContext");
8
+ //#endregion
9
+ //#region src/application/useCases/compileBridgeDeclarationsFromHostSurfaceSpec.ts
10
+ function isThenable(value) {
11
+ if (value === null || value === void 0) return false;
12
+ if (typeof value !== "object" && typeof value !== "function") return false;
13
+ return typeof value.then === "function";
14
+ }
15
+ /**
16
+ * Builds declarative bridge declarations from a host surface spec, using the schema validation port for Lua ↔ host payloads.
17
+ */
18
+ function createBridgeDeclarationsFromHostSurfaceSpec(schemaValidation, spec) {
19
+ const out = [];
20
+ for (const bridgeName of Object.keys(spec)) {
21
+ const methods = spec[bridgeName];
22
+ out.push({
23
+ name: bridgeName,
24
+ perEntity: true,
25
+ createApi: (host, slotKey) => {
26
+ const api = {};
27
+ for (const methodName of Object.keys(methods)) {
28
+ const entry = methods[methodName];
29
+ api[methodName] = (...args) => {
30
+ const payload = args.length >= 2 ? args[1] : args[0];
31
+ const parsedIn = schemaValidation.validateWithZodSchema(entry.input, payload);
32
+ if (parsedIn._tag === "err") throw new Error(parsedIn.error);
33
+ const script = host[MICROVERSE_SCRIPT_CONTEXT] ?? createScriptInstanceContext({
34
+ instanceId: String(slotKey),
35
+ scriptId: "unknown",
36
+ slotKey: String(slotKey)
37
+ });
38
+ const raw = entry.handler({
39
+ host,
40
+ slotKey: String(slotKey),
41
+ script
42
+ }, parsedIn.value);
43
+ if (isThenable(raw)) return raw.then((resolved) => {
44
+ const parsedOut = schemaValidation.validateWithZodSchema(entry.output, resolved);
45
+ if (parsedOut._tag === "err") throw new Error(parsedOut.error);
46
+ return parsedOut.value;
47
+ });
48
+ const parsedOut = schemaValidation.validateWithZodSchema(entry.output, raw);
49
+ if (parsedOut._tag === "err") throw new Error(parsedOut.error);
50
+ return parsedOut.value;
51
+ };
52
+ }
53
+ return Object.freeze(api);
54
+ }
55
+ });
56
+ }
57
+ return out;
58
+ }
59
+ //#endregion
60
+ //#region src/infrastructure/builders/filterBridgeDeclarations.ts
61
+ /**
62
+ * Keeps bridge declarations (and methods) whose capability is in the allow set.
63
+ */
64
+ function filterBridgeDeclarationsByCapabilities(declarations, spec, capabilities) {
65
+ const allowed = new Set(capabilities.map((c) => String(c)));
66
+ const bridgeNames = /* @__PURE__ */ new Set();
67
+ for (const bridgeName of Object.keys(spec)) {
68
+ const methods = spec[bridgeName];
69
+ for (const methodName of Object.keys(methods)) {
70
+ const entry = methods[methodName];
71
+ if (allowed.has(String(entry.capability))) {
72
+ bridgeNames.add(bridgeName);
73
+ break;
74
+ }
75
+ }
76
+ }
77
+ return declarations.filter((d) => bridgeNames.has(d.name)).map((d) => ({
78
+ ...d,
79
+ createApi: (host, slotKey) => {
80
+ const api = d.createApi(host, slotKey);
81
+ const methods = spec[d.name];
82
+ const filtered = {};
83
+ for (const methodName of Object.keys(api)) {
84
+ const entry = methods[methodName];
85
+ if (entry !== void 0 && allowed.has(String(entry.capability))) filtered[methodName] = api[methodName];
86
+ }
87
+ return Object.freeze(filtered);
88
+ }
89
+ }));
90
+ }
91
+ function createFilteredBridgeDeclarations(schemaValidation, spec, capabilities) {
92
+ return filterBridgeDeclarationsByCapabilities(createBridgeDeclarationsFromHostSurfaceSpec(schemaValidation, spec), spec, capabilities);
93
+ }
94
+ //#endregion
6
95
  //#region src/infrastructure/builders/bridgeMergeEnv.ts
7
96
  /**
8
- * Builds a frozen `mergeEnv` table: bridge name API object, ready for `MicroverseSlot.run({ mergeEnv })`.
9
- *
10
- * @param host - Your host context, already extended with the capability registry symbol from `@microverse.ts/host-surface`.
11
- * @param slotKey - Same slot key passed to `buildDeclarativeBridgeTable` (string form of `MicroverseId` is fine).
12
- * @param surface - Result of {@link defineHostSurface} (implements {@link HostSurfaceCore}).
97
+ * Builds a frozen bridge table for a component profile capability set.
98
+ */
99
+ function buildBridgeMergeEnvForProfile(schemaValidation, host, slotKey, spec, capabilities) {
100
+ return buildDeclarativeBridgeTable(host, slotKey, [...createFilteredBridgeDeclarations(schemaValidation, spec, capabilities)]);
101
+ }
102
+ //#endregion
103
+ //#region src/domain/safeObjectKey.ts
104
+ /** Keys that must not be used as dynamic property names on ordinary objects. */
105
+ var FORBIDDEN_OBJECT_KEYS = new Set([
106
+ "__proto__",
107
+ "constructor",
108
+ "prototype"
109
+ ]);
110
+ /**
111
+ * Rejects bridge/method names that could trigger prototype pollution when used as object keys.
13
112
  */
14
- function buildBridgeMergeEnvForHost(host, slotKey, surface) {
15
- return buildDeclarativeBridgeTable(host, slotKey, [...surface.toBridgeDeclarations()]);
113
+ function assertSafeObjectKey(kind, name) {
114
+ if (FORBIDDEN_OBJECT_KEYS.has(name)) throw new Error(`Invalid surface ${kind} name "${name}": reserved key`);
115
+ }
116
+ /** Record with no inherited prototype — safe for dynamic string keys at runtime. */
117
+ function createNullPrototypeRecord() {
118
+ return Object.create(null);
119
+ }
120
+ //#endregion
121
+ //#region src/domain/surfaceCapabilities.ts
122
+ /** Runtime list of capability ids declared on a compiled surface spec. */
123
+ function collectCapabilitiesFromHostSurfaceSpec(spec) {
124
+ const out = [];
125
+ const seen = /* @__PURE__ */ new Set();
126
+ for (const bridgeName of Object.keys(spec)) {
127
+ const methods = spec[bridgeName];
128
+ for (const methodName of Object.keys(methods)) {
129
+ const id = methods[methodName].capability;
130
+ const key = String(id);
131
+ if (!seen.has(key)) {
132
+ seen.add(key);
133
+ out.push(id);
134
+ }
135
+ }
136
+ }
137
+ return out;
138
+ }
139
+ /** Narrows `capabilities` to those declared on the surface (runtime check). */
140
+ function pickSurfaceCapabilities(surfaceCapabilities, ...capabilities) {
141
+ const allowed = new Set(surfaceCapabilities.map((c) => String(c)));
142
+ for (const id of capabilities) if (!allowed.has(String(id))) throw new Error(`capability not declared on host surface: ${String(id)} (surface has: ${[...allowed].join(", ")})`);
143
+ return capabilities;
144
+ }
145
+ z.object({});
146
+ var EMPTY_STATE = z.object({});
147
+ function scriptProfileComponentClassName(profileName) {
148
+ return `${profileName}Component`;
149
+ }
150
+ function scriptProfilePropsAlias(profileName) {
151
+ return `${profileName}Props`;
152
+ }
153
+ function scriptProfileStateAlias(profileName) {
154
+ return `${profileName}State`;
155
+ }
156
+ function scriptProfileBridgesClassName(profileName) {
157
+ return `${profileName}Bridges`;
158
+ }
159
+ /** Bridge table names whose methods include at least one capability from the profile. */
160
+ function bridgeNamesForCapabilities(spec, capabilities) {
161
+ const allowed = new Set(capabilities.map((c) => String(c)));
162
+ const names = [];
163
+ for (const bridgeName of Object.keys(spec)) {
164
+ const methods = spec[bridgeName];
165
+ for (const methodName of Object.keys(methods)) {
166
+ const entry = methods[methodName];
167
+ if (allowed.has(String(entry.capability))) {
168
+ names.push(bridgeName);
169
+ break;
170
+ }
171
+ }
172
+ }
173
+ return names.sort((a, b) => a.localeCompare(b));
174
+ }
175
+ function resolveScriptProfile(registry, name, spec) {
176
+ const def = registry[name];
177
+ if (def === void 0) throw new Error(`unknown script profile: ${name}`);
178
+ const capabilities = def.capabilities.map((c) => createCapabilityId(c));
179
+ let props = def.props;
180
+ let state = def.state ?? EMPTY_STATE;
181
+ const hooks = [...def.hooks ?? []];
182
+ let parentName = def.extends;
183
+ const visited = new Set([name]);
184
+ while (parentName !== void 0) {
185
+ if (visited.has(parentName)) throw new Error(`script profile inheritance cycle: ${name}`);
186
+ visited.add(parentName);
187
+ const parent = registry[parentName];
188
+ if (parent === void 0) throw new Error(`script profile "${name}" extends unknown profile "${parentName}"`);
189
+ const parentCaps = parent.capabilities.map((c) => createCapabilityId(c));
190
+ for (const cap of parentCaps) if (!capabilities.some((c) => String(c) === String(cap))) capabilities.push(cap);
191
+ props = parent.props.merge(props);
192
+ state = (parent.state ?? EMPTY_STATE).merge(state);
193
+ const parentHooks = parent.hooks ?? [];
194
+ for (const h of parentHooks) if (!hooks.includes(h)) hooks.push(h);
195
+ parentName = parent.extends;
196
+ }
197
+ hooks.sort((a, b) => a.localeCompare(b));
198
+ const bridgeNames = bridgeNamesForCapabilities(spec, capabilities);
199
+ return {
200
+ name,
201
+ extends: def.extends,
202
+ capabilities,
203
+ props,
204
+ state,
205
+ hooks,
206
+ bridgeNames,
207
+ references: def.references
208
+ };
209
+ }
210
+ function buildResolvedScriptProfileRegistry(registry, spec) {
211
+ const out = {};
212
+ for (const name of Object.keys(registry)) out[name] = resolveScriptProfile(registry, name, spec);
213
+ return out;
214
+ }
215
+ function validateScriptProfileRegistry(registry, spec, componentHooks, opts) {
216
+ if ((opts?.requireAtLeastOne ?? false) && Object.keys(registry).length === 0) throw new Error("host surface: at least one script profile or .componentType() is required");
217
+ const surfaceCaps = new Set(collectCapabilitiesFromHostSurfaceSpec(spec).map((c) => String(c)));
218
+ const hookKinds = componentHooks !== void 0 ? new Set(Object.keys(componentHooks)) : /* @__PURE__ */ new Set();
219
+ for (const name of Object.keys(registry)) {
220
+ assertSafeObjectKey("componentType", name);
221
+ const def = registry[name];
222
+ if (def.extends !== void 0) assertSafeObjectKey("componentType", def.extends);
223
+ for (const cap of def.capabilities) {
224
+ const id = createCapabilityId(cap);
225
+ if (!surfaceCaps.has(String(id))) throw new Error(`script profile "${name}": capability not declared on surface: ${String(cap)}`);
226
+ }
227
+ for (const hook of def.hooks ?? []) if (!hookKinds.has(hook)) throw new Error(`script profile "${name}": hook "${hook}" not declared in .componentHooks()`);
228
+ if (def.extends !== void 0 && registry[def.extends] === void 0) throw new Error(`script profile "${name}" extends unknown profile "${def.extends}"`);
229
+ }
230
+ for (const name of Object.keys(registry)) resolveScriptProfile(registry, name, spec);
231
+ }
232
+ //#endregion
233
+ //#region src/domain/scriptCatalogManifest.ts
234
+ /** LuaCATS aliases per catalog scriptId → resolved component class (for `---@type` in `.lua` files). */
235
+ function buildScriptCatalogLuaDefManifest(entries) {
236
+ return {
237
+ schemaVersion: 1,
238
+ output: "generated/scriptCatalog.d.lua",
239
+ classes: [],
240
+ aliases: entries.filter((entry) => entry.localComponentClass !== true).slice().sort((a, b) => a.scriptId.localeCompare(b.scriptId)).map((entry) => {
241
+ const componentClass = scriptProfileComponentClassName(entry.profileId);
242
+ return {
243
+ name: scriptCatalogComponentAlias(entry.scriptId),
244
+ definition: componentClass
245
+ };
246
+ }),
247
+ globals: [],
248
+ luaHooks: []
249
+ };
250
+ }
251
+ function scriptCatalogComponentAlias(scriptId) {
252
+ return `${scriptId.replace(/[^A-Za-z0-9_]/g, "_")}ScriptComponent`;
253
+ }
254
+ //#endregion
255
+ //#region src/domain/componentSlotPrelude.ts
256
+ /** @see MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE */
257
+ var MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE = `
258
+ rawset(_ENV, "__microverse_component_rawProps", {})
259
+ rawset(_ENV, "__microverse_component_dirty", {})
260
+
261
+ local function rawProps()
262
+ return rawget(_ENV, "__microverse_component_rawProps")
263
+ end
264
+
265
+ local function dirty()
266
+ return rawget(_ENV, "__microverse_component_dirty")
267
+ end
268
+
269
+ local PropertiesMT = {
270
+ __index = function(t, k)
271
+ return rawget(t, "__raw")[k]
272
+ end,
273
+ __newindex = function(t, k, v)
274
+ rawget(t, "__raw")[k] = v
275
+ local d = dirty()
276
+ d[k] = v
277
+ end,
278
+ }
279
+
280
+ local ReferencesMT = {
281
+ __index = function(t, k)
282
+ local wrap = rawget(_ENV, "__microverse_reference_wrap")
283
+ local raw = rawget(t, "__raw")
284
+ local val = raw[k]
285
+ if type(wrap) == "function" then
286
+ return wrap(k, val)
287
+ end
288
+ return val
289
+ end,
290
+ }
291
+
292
+ function __microverse_lua_build_component_impl(bridges)
293
+ local impl = { state = {}, bridges = type(bridges) == "table" and bridges or {} }
294
+ local proxy = { __raw = rawProps() }
295
+ setmetatable(proxy, PropertiesMT)
296
+ impl.properties = proxy
297
+ local refProxy = { __raw = rawProps() }
298
+ setmetatable(refProxy, ReferencesMT)
299
+ impl.references = refProxy
300
+ local base = rawget(_ENV, "__microverse_component_hook_base")
301
+ if type(base) == "table" then
302
+ setmetatable(impl, { __index = base })
303
+ end
304
+ rawset(_ENV, "__microverse_lua_ComponentImpl", impl)
305
+ return impl
306
+ end
307
+
308
+ rawset(_ENV, "__microverse_lua_build_component_impl", __microverse_lua_build_component_impl)
309
+
310
+ rawset(_ENV, "__microverse_lua_component_apply_incoming", function()
311
+ local incoming = rawget(_ENV, "__microverseIncomingProps")
312
+ if type(incoming) ~= "table" then
313
+ return
314
+ end
315
+ local rp = rawProps()
316
+ local impl = rawget(_ENV, "__microverse_lua_ComponentImpl")
317
+ if type(impl) ~= "table" or type(impl.properties) ~= "table" then
318
+ for k, v in pairs(incoming) do
319
+ rp[k] = v
320
+ end
321
+ return
322
+ end
323
+ for k, v in pairs(incoming) do
324
+ impl.properties[k] = v
325
+ end
326
+ end)
327
+
328
+ rawset(_ENV, "__microverse_lua_component_flush_to_sink", function()
329
+ local push = rawget(_ENV, "__microverseFlushPush")
330
+ local d = dirty()
331
+ for k, v in pairs(d) do
332
+ if type(push) == "function" then
333
+ push(k, v)
334
+ end
335
+ d[k] = nil
336
+ end
337
+ end)
338
+ `.trim();
339
+ /** Lua prelude that registers `TypeName:extend()` singletons for each component type. */
340
+ function profileBridgeSlotKey(typeName, bridgeName) {
341
+ return `__mv_${typeName}_${bridgeName}`;
16
342
  }
17
- /** Bridge table names for `__microverse_bridge_names` in the slot env. */
18
- function bridgeNamesFromSurface(surface) {
19
- return surface.toBridgeDeclarations().map((d) => d.name);
343
+ function profileBridgeNamesMergeEnvKey(typeName) {
344
+ return `__microverse_profile_bridge_names_${typeName}`;
345
+ }
346
+ function buildComponentTypeBridgeNamesPreludeLua(componentTypes) {
347
+ const lines = [];
348
+ for (const typeName of Object.keys(componentTypes).sort((a, b) => a.localeCompare(b))) {
349
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(typeName)) throw new Error(`unsafe component type name for bridge names prelude: ${typeName}`);
350
+ const entries = componentTypes[typeName].bridgeNames.map((n) => JSON.stringify(n)).join(", ");
351
+ lines.push(`rawset(_ENV, ${JSON.stringify(profileBridgeNamesMergeEnvKey(typeName))}, { ${entries} })`);
352
+ }
353
+ return lines.join("\n");
354
+ }
355
+ function buildComponentTypeSingletonsPreludeLua(typeNames) {
356
+ const lines = [
357
+ "local function __microverse_collect_profile_bridges(typeName)",
358
+ " local names = rawget(_ENV, \"__microverse_profile_bridge_names_\" .. typeName)",
359
+ " local bridges = {}",
360
+ " if type(names) ~= \"table\" then",
361
+ " return bridges",
362
+ " end",
363
+ " for _, name in ipairs(names) do",
364
+ " if type(name) == \"string\" then",
365
+ " local b = rawget(_ENV, \"__mv_\" .. typeName .. \"_\" .. name)",
366
+ " if type(b) == \"table\" then",
367
+ " bridges[name] = b",
368
+ " end",
369
+ " end",
370
+ " end",
371
+ " return bridges",
372
+ "end",
373
+ "local function __microverse_make_type_extend(typeName)",
374
+ " return function(self)",
375
+ " local ext = rawget(_ENV, \"__microverse_lua_extend_component\")",
376
+ " if type(ext) == \"function\" then",
377
+ " ext(typeName)",
378
+ " end",
379
+ " local build = rawget(_ENV, \"__microverse_lua_build_component_impl\")",
380
+ " if type(build) ~= \"function\" then",
381
+ " error(\"component type extend: build impl missing\")",
382
+ " end",
383
+ " return build(__microverse_collect_profile_bridges(typeName))",
384
+ " end",
385
+ "end"
386
+ ];
387
+ for (const name of typeNames) {
388
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`unsafe component type name for Lua singleton: ${name}`);
389
+ lines.push(`rawset(_ENV, ${JSON.stringify(name)}, { extend = __microverse_make_type_extend(${JSON.stringify(name)}) })`);
390
+ }
391
+ return lines.join("\n");
392
+ }
393
+ /** Applies host-selected profile (same as `Type:extend()` without requiring Lua to call it). */
394
+ function buildApplyHostScriptProfileChunkLua(profileName) {
395
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(profileName)) throw new Error(`unsafe script profile name: ${profileName}`);
396
+ return [
397
+ `local T = ${profileName}`,
398
+ "if type(T) == \"table\" and type(T.extend) == \"function\" then",
399
+ " T:extend()",
400
+ "end"
401
+ ].join("\n");
20
402
  }
21
403
  //#endregion
22
404
  //#region src/infrastructure/adapters/zodSchemaValidationAdapter.ts
@@ -167,19 +549,7 @@ function asyncHandleClassName(bridgeName, methodName) {
167
549
  function bridgeLuaClassName(bridgeTableName) {
168
550
  return bridgeTableName.charAt(0).toUpperCase() + bridgeTableName.slice(1);
169
551
  }
170
- function pushMicroverseBridgesClass(bridgeNames, classes) {
171
- if (bridgeNames.length === 0) return;
172
- classes.push({
173
- name: "MicroverseBridges",
174
- description: "Capability-scoped host bridges for this component (`self.bridges` after `component:extend()`).",
175
- fields: bridgeNames.map((name) => ({
176
- name,
177
- luaType: bridgeLuaClassName(name)
178
- })),
179
- emitSingleton: false
180
- });
181
- }
182
- function buildComponentEventManifestFields(kinds, componentHooks) {
552
+ function buildComponentEventManifestFields(kinds, componentClassName, componentHooks) {
183
553
  const out = [];
184
554
  for (const kind of kinds) {
185
555
  if (!(componentHooks[kind] instanceof z.ZodObject)) throw new Error(`defineHostSurface componentHooks: "${kind}" must be a z.object(...)`);
@@ -188,7 +558,7 @@ function buildComponentEventManifestFields(kinds, componentHooks) {
188
558
  out.push({
189
559
  name: hookName,
190
560
  description: `Host invokes when \`${kind}\` is emitted. Payload: \`${payloadName}\`.`,
191
- luaType: `fun(self: Component, evt: ${payloadName})`
561
+ luaType: `fun(self: ${componentClassName}, evt: ${payloadName})`
192
562
  });
193
563
  }
194
564
  return out;
@@ -210,60 +580,112 @@ function pushComponentEventPayloadClasses(kinds, componentHooks, classes) {
210
580
  });
211
581
  }
212
582
  }
213
- function pushComponentManifestClasses(classes, bridgeNames, componentHooks) {
214
- const eventKinds = componentHooks !== void 0 ? Object.keys(componentHooks).sort((a, b) => a.localeCompare(b)) : [];
215
- if (componentHooks !== void 0) pushComponentEventPayloadClasses(eventKinds, componentHooks, classes);
216
- const lifecycleFields = [
583
+ function pushBaseComponentClass(classes) {
584
+ classes.push({
585
+ name: "Component",
586
+ description: "Base component lifecycle (extended by typed component profiles).",
587
+ fields: [
588
+ {
589
+ name: "init",
590
+ luaType: "fun(self: Component)",
591
+ description: "Called once after mount and initial props are applied."
592
+ },
593
+ {
594
+ name: "onPropsChanged",
595
+ luaType: "fun(self: Component, key: string, newValue: any)",
596
+ description: "Called when the host patches a property key."
597
+ },
598
+ {
599
+ name: "onDestroy",
600
+ luaType: "fun(self: Component)",
601
+ description: "Called before the script instance slot is disposed."
602
+ }
603
+ ],
604
+ emitSingleton: false
605
+ });
606
+ }
607
+ function pushProfileBridgesClass(typeName, bridgeNames, classes) {
608
+ if (bridgeNames.length === 0) return;
609
+ const bridgesName = scriptProfileBridgesClassName(typeName);
610
+ classes.push({
611
+ name: bridgesName,
612
+ description: `Host bridges for \`${typeName}\` components (\`self.bridges\` after \`${typeName}:extend()\`).`,
613
+ fields: bridgeNames.map((name) => ({
614
+ name,
615
+ luaType: bridgeLuaClassName(name)
616
+ })),
617
+ emitSingleton: false
618
+ });
619
+ }
620
+ function pushComponentTypeManifest(classes, aliases, typeName, profile, componentHooks) {
621
+ const propsAlias = scriptProfilePropsAlias(typeName);
622
+ const stateAlias = scriptProfileStateAlias(typeName);
623
+ aliases.set(propsAlias, zodToLuaTypeRef(profile.props));
624
+ aliases.set(stateAlias, zodToLuaTypeRef(profile.state));
625
+ pushProfileBridgesClass(typeName, profile.bridgeNames, classes);
626
+ const componentClass = scriptProfileComponentClassName(typeName);
627
+ const bridgesClass = profile.bridgeNames.length > 0 ? scriptProfileBridgesClassName(typeName) : "table";
628
+ const extendsClass = profile.extends !== void 0 ? scriptProfileComponentClassName(profile.extends) : "Component";
629
+ const fields = [
217
630
  {
218
631
  name: "properties",
219
- luaType: "table",
632
+ luaType: propsAlias,
220
633
  description: "Host-synced props (proxy)."
221
634
  },
222
635
  {
223
636
  name: "state",
224
- luaType: "table",
637
+ luaType: stateAlias,
225
638
  description: "Lua-local state."
226
639
  },
227
640
  {
228
641
  name: "bridges",
229
- luaType: bridgeNames.length > 0 ? "MicroverseBridges" : "table",
230
- description: "Host bridges allowed for this instance (not global in the slot)."
642
+ luaType: bridgesClass,
643
+ description: "Host bridges allowed for this component type."
231
644
  },
232
645
  {
233
646
  name: "init",
234
- luaType: "fun(self: Component)",
647
+ luaType: `fun(self: ${componentClass})`,
235
648
  description: "Called once after mount and initial props are applied."
236
649
  },
237
650
  {
238
651
  name: "onPropsChanged",
239
- luaType: "fun(self: Component, key: string, newValue: any)",
652
+ luaType: `fun(self: ${componentClass}, key: string, newValue: any)`,
240
653
  description: "Called when the host patches a property key."
241
654
  },
242
655
  {
243
656
  name: "onDestroy",
244
- luaType: "fun(self: Component)",
657
+ luaType: `fun(self: ${componentClass})`,
245
658
  description: "Called before the script instance slot is disposed."
246
659
  }
247
660
  ];
248
- if (componentHooks !== void 0) lifecycleFields.push(...buildComponentEventManifestFields(eventKinds, componentHooks));
661
+ if (componentHooks !== void 0 && profile.hooks.length > 0) fields.push(...buildComponentEventManifestFields(profile.hooks, componentClass, componentHooks));
249
662
  classes.push({
250
- name: "Component",
251
- description: "Component instance from `local C = component:extend()`. Use `self.bridges` for host APIs; define `on*` methods for domain events.",
252
- fields: lifecycleFields,
663
+ name: componentClass,
664
+ extendsClass,
665
+ description: `Component instance from \`local C = ${typeName}:extend()\`.`,
666
+ fields,
253
667
  emitSingleton: false
254
668
  });
255
669
  classes.push({
256
- name: "component",
257
- description: "Per-slot helper injected by the host. Use `component:extend()` to create the active component.",
670
+ name: typeName,
671
+ description: `Factory for \`${componentClass}\` in this slot.`,
258
672
  methods: [{
259
673
  name: "extend",
260
- description: "Returns the component table with `properties`, `state`, and `bridges` wired for this slot.",
674
+ description: "Creates the active component with profile-scoped bridges.",
261
675
  params: [],
262
- returns: "Component"
263
- }]
676
+ returns: componentClass
677
+ }],
678
+ emitSingleton: true
264
679
  });
265
680
  }
266
- function buildLuaDefManifestFromHostSurfaceSpec(spec, opts, componentHooks) {
681
+ function pushComponentManifestClasses(classes, aliases, componentTypes, componentHooks) {
682
+ const eventKinds = componentHooks !== void 0 ? Object.keys(componentHooks).sort((a, b) => a.localeCompare(b)) : [];
683
+ if (componentHooks !== void 0) pushComponentEventPayloadClasses(eventKinds, componentHooks, classes);
684
+ pushBaseComponentClass(classes);
685
+ const typeNames = Object.keys(componentTypes).sort((a, b) => a.localeCompare(b));
686
+ for (const typeName of typeNames) pushComponentTypeManifest(classes, aliases, typeName, componentTypes[typeName], componentHooks);
687
+ }
688
+ function buildLuaDefManifestFromHostSurfaceSpec(spec, opts, componentHooks, componentTypes) {
267
689
  const classes = [];
268
690
  const bridgeNames = Object.keys(spec).sort((a, b) => a.localeCompare(b));
269
691
  for (const bridgeName of bridgeNames) {
@@ -307,12 +729,11 @@ function buildLuaDefManifestFromHostSurfaceSpec(spec, opts, componentHooks) {
307
729
  emitSingleton: false
308
730
  });
309
731
  }
310
- pushMicroverseBridgesClass(bridgeNames, classes);
311
732
  const fromLuaType = collectLuaTypeAliasesFromHostSpec(spec);
312
733
  const fromOverrides = inferLuaTypeAliasesFromHostSpec(spec);
313
734
  const merged = new Map([...fromLuaType.map((a) => [a.name, a.definition]), ...fromOverrides.map((a) => [a.name, a.definition])]);
314
735
  if (opts.luaTypeAliases !== void 0) for (const [k, v] of Object.entries(opts.luaTypeAliases)) merged.set(k, v);
315
- pushComponentManifestClasses(classes, bridgeNames, componentHooks);
736
+ if (componentTypes !== void 0) pushComponentManifestClasses(classes, merged, componentTypes, componentHooks);
316
737
  const aliases = merged.size === 0 ? void 0 : [...merged.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([name, definition]) => ({
317
738
  name,
318
739
  definition
@@ -452,110 +873,28 @@ function unwrapInputSchema(schema) {
452
873
  return cur;
453
874
  }
454
875
  //#endregion
455
- //#region src/domain/surfaceCapabilities.ts
456
- /** Runtime list of capability ids declared on a compiled surface spec. */
457
- function collectCapabilitiesFromHostSurfaceSpec(spec) {
458
- const out = [];
459
- const seen = /* @__PURE__ */ new Set();
460
- for (const bridgeName of Object.keys(spec)) {
461
- const methods = spec[bridgeName];
462
- for (const methodName of Object.keys(methods)) {
463
- const id = methods[methodName].capability;
464
- const key = String(id);
465
- if (!seen.has(key)) {
466
- seen.add(key);
467
- out.push(id);
468
- }
469
- }
470
- }
471
- return out;
472
- }
473
- /** Narrows `capabilities` to those declared on the surface (runtime check). */
474
- function pickSurfaceCapabilities(surfaceCapabilities, ...capabilities) {
475
- const allowed = new Set(surfaceCapabilities.map((c) => String(c)));
476
- for (const id of capabilities) if (!allowed.has(String(id))) throw new Error(`capability not declared on host surface: ${String(id)} (surface has: ${[...allowed].join(", ")})`);
477
- return capabilities;
478
- }
479
- //#endregion
480
- //#region src/domain/capabilityRegistrySymbol.ts
481
- /**
482
- * Well-known symbol key used to attach a {@link CapabilityRegistryPort} on the host object
483
- * while surface bridge methods run. Populated by {@link augmentHostWithCapabilityRegistry} or
484
- * internally by {@link HostScriptSession}.
485
- */
486
- var MICROVERSE_CAPABILITY_REGISTRY = Symbol.for("microverse:capabilityRegistry");
487
- //#endregion
488
- //#region src/domain/scriptContextSymbol.ts
489
- var MICROVERSE_SCRIPT_CONTEXT = Symbol("microverse.scriptContext");
490
- //#endregion
491
- //#region src/application/useCases/compileBridgeDeclarationsFromHostSurfaceSpec.ts
492
- function isThenable(value) {
493
- if (value === null || value === void 0) return false;
494
- if (typeof value !== "object" && typeof value !== "function") return false;
495
- return typeof value.then === "function";
496
- }
497
- /**
498
- * Builds declarative bridge declarations from a host surface spec, using the schema validation port for Lua ↔ host payloads.
499
- */
500
- function createBridgeDeclarationsFromHostSurfaceSpec(schemaValidation, spec) {
501
- const out = [];
502
- for (const bridgeName of Object.keys(spec)) {
503
- const methods = spec[bridgeName];
504
- out.push({
505
- name: bridgeName,
506
- perEntity: true,
507
- createApi: (host, slotKey) => {
508
- const hostWithScript = host;
509
- const api = {};
510
- for (const methodName of Object.keys(methods)) {
511
- const entry = methods[methodName];
512
- api[methodName] = (...args) => {
513
- const payload = args.length >= 2 ? args[1] : args[0];
514
- const registry = hostWithScript[MICROVERSE_CAPABILITY_REGISTRY];
515
- const capability = entry.capability;
516
- if (!registry.isAllowed(capability)) throw new Error(`capability denied: ${String(capability)}`);
517
- const parsedIn = schemaValidation.validateWithZodSchema(entry.input, payload);
518
- if (parsedIn._tag === "err") throw new Error(parsedIn.error);
519
- const script = hostWithScript[MICROVERSE_SCRIPT_CONTEXT] ?? createScriptInstanceContext({
520
- instanceId: String(slotKey),
521
- scriptId: "unknown",
522
- slotKey: String(slotKey)
523
- });
524
- const raw = entry.handler({
525
- host: hostWithScript,
526
- slotKey: String(slotKey),
527
- script
528
- }, parsedIn.value);
529
- if (isThenable(raw)) return raw.then((resolved) => {
530
- const parsedOut = schemaValidation.validateWithZodSchema(entry.output, resolved);
531
- if (parsedOut._tag === "err") throw new Error(parsedOut.error);
532
- return parsedOut.value;
533
- });
534
- const parsedOut = schemaValidation.validateWithZodSchema(entry.output, raw);
535
- if (parsedOut._tag === "err") throw new Error(parsedOut.error);
536
- return parsedOut.value;
537
- };
538
- }
539
- return Object.freeze(api);
540
- }
541
- });
542
- }
543
- return out;
544
- }
545
- //#endregion
546
876
  //#region src/application/useCases/compileHostSurface.ts
547
- function buildHostSurfaceCore(schemaValidation, spec, componentHooks) {
877
+ function buildHostSurfaceCore(schemaValidation, spec, componentTypeRegistry, componentHooks) {
878
+ validateScriptProfileRegistry(componentTypeRegistry, spec, componentHooks, { requireAtLeastOne: false });
879
+ const componentTypes = buildResolvedScriptProfileRegistry(componentTypeRegistry, spec);
548
880
  const capabilities = collectCapabilitiesFromHostSurfaceSpec(spec);
549
881
  return {
882
+ getHostSurfaceSpec: () => spec,
550
883
  toBridgeDeclarations: () => createBridgeDeclarationsFromHostSurfaceSpec(schemaValidation, spec),
551
- toLuaDefManifest: (opts) => buildLuaDefManifestFromHostSurfaceSpec(spec, opts, componentHooks),
884
+ toLuaDefManifest: (opts) => buildLuaDefManifestFromHostSurfaceSpec(spec, opts, componentHooks, componentTypes),
552
885
  capabilities,
886
+ componentTypes,
887
+ getComponentType: (name) => {
888
+ const profile = componentTypes[name];
889
+ if (profile === void 0) throw new Error(`unknown component type: ${name}`);
890
+ return profile;
891
+ },
553
892
  pickCapabilities: (...picked) => pickSurfaceCapabilities(capabilities, ...picked)
554
893
  };
555
894
  }
556
- function compileHostSurface(ports, spec, componentHooks) {
895
+ function compileHostSurface(ports, spec, componentTypeRegistry, componentHooks) {
557
896
  const [schemaValidation] = ports;
558
- const core = buildHostSurfaceCore(schemaValidation, spec, componentHooks);
897
+ const core = buildHostSurfaceCore(schemaValidation, spec, componentTypeRegistry, componentHooks);
559
898
  if (componentHooks === void 0) return core;
560
899
  return {
561
900
  ...core,
@@ -565,8 +904,8 @@ function compileHostSurface(ports, spec, componentHooks) {
565
904
  /**
566
905
  * Same as {@link compileHostSurface}, but requires every bridge method to be typed with the same `THost`.
567
906
  */
568
- function compileHostSurfaceFor(ports, spec, componentHooks) {
569
- return componentHooks === void 0 ? compileHostSurface(ports, spec) : compileHostSurface(ports, spec, componentHooks);
907
+ function compileHostSurfaceFor(ports, spec, componentTypeRegistry, componentHooks) {
908
+ return componentHooks === void 0 ? compileHostSurface(ports, spec, componentTypeRegistry) : compileHostSurface(ports, spec, componentTypeRegistry, componentHooks);
570
909
  }
571
910
  //#endregion
572
911
  //#region src/domain/inferMethodAsync.ts
@@ -593,33 +932,16 @@ function normalizeMethodDef(def) {
593
932
  };
594
933
  }
595
934
  //#endregion
596
- //#region src/domain/safeObjectKey.ts
597
- /** Keys that must not be used as dynamic property names on ordinary objects. */
598
- var FORBIDDEN_OBJECT_KEYS = new Set([
599
- "__proto__",
600
- "constructor",
601
- "prototype"
602
- ]);
603
- /**
604
- * Rejects bridge/method names that could trigger prototype pollution when used as object keys.
605
- */
606
- function assertSafeObjectKey(kind, name) {
607
- if (FORBIDDEN_OBJECT_KEYS.has(name)) throw new Error(`Invalid surface ${kind} name "${name}": reserved key`);
608
- }
609
- /** Record with no inherited prototype — safe for dynamic string keys at runtime. */
610
- function createNullPrototypeRecord() {
611
- return Object.create(null);
612
- }
613
- //#endregion
614
935
  //#region src/infrastructure/builders/surfaceBuilder.ts
615
936
  /**
616
937
  * Fluent builder for a host surface. Created via {@link defineHostSurfaceFor} / {@link defineHostSurface} factory overloads.
617
938
  */
618
939
  var SurfaceBuilder = class SurfaceBuilder {
619
940
  spec = createNullPrototypeRecord();
941
+ componentTypeRegistry = createNullPrototypeRecord();
620
942
  componentHooksSpec;
621
943
  ports;
622
- constructor(ports, componentHooks, initialSpec) {
944
+ constructor(ports, componentHooks, initialSpec, initialComponentTypes) {
623
945
  this.ports = ports;
624
946
  this.componentHooksSpec = componentHooks;
625
947
  if (initialSpec !== void 0) for (const bridgeName of Object.keys(initialSpec)) {
@@ -632,19 +954,27 @@ var SurfaceBuilder = class SurfaceBuilder {
632
954
  }
633
955
  this.spec[bridgeName] = bridge;
634
956
  }
957
+ if (initialComponentTypes !== void 0) for (const name of Object.keys(initialComponentTypes)) this.componentTypeRegistry[name] = initialComponentTypes[name];
958
+ }
959
+ /** Declares a typed Lua component profile (props, state, capabilities, hooks). */
960
+ componentType(name, def) {
961
+ assertSafeObjectKey("componentType", name);
962
+ this.componentTypeRegistry[name] = def;
963
+ return this;
635
964
  }
636
965
  /** Opens a Lua bridge table (e.g. `orders`, `greet`). */
637
966
  bridge(name) {
638
967
  return new BridgeBuilder(this, name);
639
968
  }
640
- /** Attaches component domain-event Zod schemas (emitted into `.d.lua` as `on*` methods on `Component`). */
969
+ /** Attaches component domain-event Zod schemas (emitted into `.d.lua` as `on*` methods on component types). */
641
970
  componentHooks(hooks) {
642
- return new SurfaceBuilder(this.ports, hooks, this.spec);
971
+ return new SurfaceBuilder(this.ports, hooks, this.spec, this.componentTypeRegistry);
643
972
  }
644
973
  /** Compiles the accumulated spec into a {@link HostSurface}. */
645
974
  build() {
646
975
  const spec = this.spec;
647
- return compileHostSurfaceFor(this.ports, spec, this.componentHooksSpec);
976
+ const registry = this.componentTypeRegistry;
977
+ return compileHostSurfaceFor(this.ports, spec, registry, this.componentHooksSpec);
648
978
  }
649
979
  /** @internal */
650
980
  addMethod(bridgeName, methodName, entry) {
@@ -720,99 +1050,6 @@ function defineHostSurfaceFor() {
720
1050
  return createSurfaceBuilder();
721
1051
  }
722
1052
  //#endregion
723
- //#region src/domain/componentSlotPrelude.ts
724
- /** @see MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE */
725
- var MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE = `
726
- rawset(_ENV, "__microverse_component_rawProps", {})
727
- rawset(_ENV, "__microverse_component_dirty", {})
728
-
729
- local function rawProps()
730
- return rawget(_ENV, "__microverse_component_rawProps")
731
- end
732
-
733
- local function dirty()
734
- return rawget(_ENV, "__microverse_component_dirty")
735
- end
736
-
737
- local PropertiesMT = {
738
- __index = function(t, k)
739
- return rawget(t, "__raw")[k]
740
- end,
741
- __newindex = function(t, k, v)
742
- rawget(t, "__raw")[k] = v
743
- local d = dirty()
744
- d[k] = v
745
- end,
746
- }
747
-
748
- function __microverse_lua_attach_bridges(impl)
749
- if type(impl) ~= "table" then
750
- return
751
- end
752
- impl.bridges = impl.bridges or {}
753
- local names = rawget(_ENV, "__microverse_bridge_names")
754
- if type(names) ~= "table" then
755
- return
756
- end
757
- for _, name in ipairs(names) do
758
- if type(name) == "string" then
759
- local g = rawget(_ENV, name)
760
- if type(g) == "table" then
761
- impl.bridges[name] = g
762
- rawset(_ENV, name, nil)
763
- end
764
- end
765
- end
766
- end
767
-
768
- rawset(_ENV, "__microverse_lua_attach_bridges", __microverse_lua_attach_bridges)
769
-
770
- rawset(_ENV, "component", {
771
- extend = function()
772
- local impl = { state = {}, bridges = {} }
773
- local proxy = { __raw = rawProps() }
774
- setmetatable(proxy, PropertiesMT)
775
- impl.properties = proxy
776
- local base = rawget(_ENV, "__microverse_component_hook_base")
777
- if type(base) == "table" then
778
- setmetatable(impl, { __index = base })
779
- end
780
- __microverse_lua_attach_bridges(impl)
781
- rawset(_ENV, "__microverse_lua_ComponentImpl", impl)
782
- return impl
783
- end,
784
- })
785
-
786
- rawset(_ENV, "__microverse_lua_component_apply_incoming", function()
787
- local incoming = rawget(_ENV, "__microverseIncomingProps")
788
- if type(incoming) ~= "table" then
789
- return
790
- end
791
- local rp = rawProps()
792
- local impl = rawget(_ENV, "__microverse_lua_ComponentImpl")
793
- if type(impl) ~= "table" or type(impl.properties) ~= "table" then
794
- for k, v in pairs(incoming) do
795
- rp[k] = v
796
- end
797
- return
798
- end
799
- for k, v in pairs(incoming) do
800
- impl.properties[k] = v
801
- end
802
- end)
803
-
804
- rawset(_ENV, "__microverse_lua_component_flush_to_sink", function()
805
- local push = rawget(_ENV, "__microverseFlushPush")
806
- local d = dirty()
807
- for k, v in pairs(d) do
808
- if type(push) == "function" then
809
- push(k, v)
810
- end
811
- d[k] = nil
812
- end
813
- end)
814
- `.trim();
815
- //#endregion
816
1053
  //#region src/domain/scriptPropertyMergeEnv.ts
817
1054
  function scriptPropertyBagToMergeEnv(bag) {
818
1055
  const out = {};
@@ -857,34 +1094,27 @@ function plainToScriptPropertyValue(value) {
857
1094
  }
858
1095
  }
859
1096
  //#endregion
860
- //#region src/infrastructure/adapters/augmentHostWithCapabilityRegistry.ts
861
- /**
862
- * Returns a shallow copy of `host` with {@link MICROVERSE_CAPABILITY_REGISTRY} set to `registry`.
863
- * Bridge handlers read the registry to enforce per-session capability allowlists.
864
- *
865
- * @param host - Your engine / service context passed into `buildDeclarativeBridgeTable`.
866
- * @param registry - Typically an {@link InMemoryCapabilityRegistry} from `@microverse.ts/runtime-capabilities`.
867
- */
868
- function augmentHostWithCapabilityRegistry(host, registry) {
869
- return Object.assign(host, { [MICROVERSE_CAPABILITY_REGISTRY]: registry });
870
- }
871
- //#endregion
872
1097
  //#region src/infrastructure/adapters/augmentHostWithScriptContext.ts
873
1098
  function augmentHostWithScriptContext(host, script) {
874
1099
  return Object.assign(host, { [MICROVERSE_SCRIPT_CONTEXT]: script });
875
1100
  }
876
1101
  //#endregion
877
1102
  //#region src/infrastructure/components/hostScriptSession.ts
1103
+ var defaultSchemaValidation = createZodSchemaValidationPort();
878
1104
  var HostScriptSession = class {
879
1105
  opts;
880
1106
  sandbox;
881
1107
  hostProps = {};
882
- registry;
1108
+ schemaValidation;
1109
+ activeComponentType;
1110
+ hostProfileApplied = false;
883
1111
  context;
884
1112
  constructor(opts) {
885
1113
  this.opts = opts;
886
- this.registry = new InMemoryCapabilityRegistry(createAllowlist([...opts.allowedCapabilities]));
1114
+ this.schemaValidation = opts.schemaValidation ?? defaultSchemaValidation;
887
1115
  this.context = opts.script;
1116
+ if (opts.resolvedProfile !== void 0) this.activeComponentType = opts.resolvedProfile.name;
1117
+ else if (opts.profileId !== void 0) this.activeComponentType = opts.profileId;
888
1118
  }
889
1119
  openSession = async () => {
890
1120
  this.sandbox = await this.opts.runtime.createMicroverse({ slotKey: this.opts.slotKey });
@@ -895,11 +1125,25 @@ var HostScriptSession = class {
895
1125
  mergeEnv: this.mergeEnv(),
896
1126
  timeout: this.opts.defaultTimeout
897
1127
  });
898
- await sb.run({
899
- script: createMicroverseScript(buildBridgeNamesPreludeLua(bridgeNamesFromSurface(this.opts.surface))),
900
- mergeEnv: this.mergeEnv(),
901
- timeout: this.opts.defaultTimeout
902
- });
1128
+ const activeProfile = this.tryGetActiveProfile();
1129
+ const singletonName = this.profileSingletonName();
1130
+ const typeNames = this.singletonTypeNames();
1131
+ if (typeNames.length > 0) {
1132
+ const bridgeRegistry = activeProfile !== void 0 ? { [activeProfile.name]: activeProfile } : this.opts.surface.componentTypes;
1133
+ await sb.run({
1134
+ script: createMicroverseScript([buildComponentTypeBridgeNamesPreludeLua(bridgeRegistry), buildComponentTypeSingletonsPreludeLua(typeNames)].join("\n")),
1135
+ mergeEnv: this.mergeEnv(),
1136
+ timeout: this.opts.defaultTimeout
1137
+ });
1138
+ if (singletonName !== void 0) {
1139
+ await sb.run({
1140
+ script: createMicroverseScript(buildApplyHostScriptProfileChunkLua(singletonName)),
1141
+ mergeEnv: this.mergeEnv(),
1142
+ timeout: this.opts.defaultTimeout
1143
+ });
1144
+ this.hostProfileApplied = true;
1145
+ }
1146
+ }
903
1147
  const hooks = readComponentHooks(this.opts.surface);
904
1148
  if (hooks !== void 0) {
905
1149
  const prelude = buildComponentEventStubPreludeLua(hooks);
@@ -911,25 +1155,82 @@ var HostScriptSession = class {
911
1155
  }
912
1156
  }
913
1157
  };
914
- getCapabilityRegistry = () => this.registry;
915
1158
  requireMicroverseSlot() {
916
1159
  if (this.sandbox === void 0) throw new Error("HostScriptSession: openSession() was not called");
917
1160
  return this.sandbox;
918
1161
  }
919
1162
  mergeEnv() {
920
- return buildBridgeMergeEnvForHost(augmentHostWithScriptContext(augmentHostWithCapabilityRegistry(this.opts.host, this.registry), this.opts.script), String(this.opts.slotKey), this.opts.surface);
1163
+ const host = augmentHostWithScriptContext(this.opts.host, this.opts.script);
1164
+ const spec = this.opts.surface.getHostSurfaceSpec();
1165
+ const env = { __microverse_lua_extend_component: (typeName) => {
1166
+ if (this.opts.resolvedProfile !== void 0 && typeName === this.profileSingletonName()) {
1167
+ this.activeComponentType = this.opts.resolvedProfile.name;
1168
+ return;
1169
+ }
1170
+ this.opts.surface.getComponentType(typeName);
1171
+ this.activeComponentType = typeName;
1172
+ } };
1173
+ const activeProfile = this.tryGetActiveProfile();
1174
+ const typeNames = activeProfile !== void 0 ? [activeProfile.name] : Object.keys(this.opts.surface.componentTypes);
1175
+ for (const typeName of typeNames) {
1176
+ const profile = activeProfile !== void 0 && typeName === activeProfile.name ? activeProfile : this.opts.surface.getComponentType(typeName);
1177
+ const bridgeTable = buildBridgeMergeEnvForProfile(this.schemaValidation, host, String(this.opts.slotKey), spec, profile.capabilities);
1178
+ for (const bridgeName of profile.bridgeNames) env[profileBridgeSlotKey(typeName, bridgeName)] = bridgeTable[bridgeName];
1179
+ }
1180
+ const profileForRefs = this.tryGetActiveProfile();
1181
+ if (profileForRefs?.references !== void 0 && this.opts.referenceResolver !== void 0) {
1182
+ const profile = profileForRefs;
1183
+ env.__microverse_reference_wrap = (field, raw) => this.wrapReference(field, raw, profile);
1184
+ }
1185
+ return env;
1186
+ }
1187
+ wrapReference(field, raw, profile) {
1188
+ const resolver = this.opts.referenceResolver;
1189
+ const refs = profile.references;
1190
+ if (resolver === void 0 || refs === void 0) return raw;
1191
+ const def = refs[field];
1192
+ if (def === void 0) return raw;
1193
+ const rawId = raw === null || raw === void 0 ? null : typeof raw === "string" ? raw : typeof raw === "number" || typeof raw === "boolean" ? String(raw) : null;
1194
+ return resolver.wrap({
1195
+ slotKey: String(this.opts.slotKey),
1196
+ field,
1197
+ raw: rawId,
1198
+ kind: def.kind,
1199
+ componentType: referenceComponentType(def)
1200
+ });
921
1201
  }
922
1202
  emitAudit(event) {
923
1203
  this.opts.onScriptAudit?.(event);
924
1204
  }
925
- validatePropsBag(bag) {
926
- if (this.opts.propsSchema !== void 0) {
927
- const parsed = this.opts.propsSchema.parse(bag);
928
- assertValidScriptPropertyBag(parsed);
929
- return parsed;
1205
+ getActiveProfileName() {
1206
+ return this.activeComponentType ?? this.opts.profileId;
1207
+ }
1208
+ profileSingletonName() {
1209
+ return this.opts.profileSingleton ?? this.opts.profileId ?? this.opts.resolvedProfile?.name;
1210
+ }
1211
+ singletonTypeNames() {
1212
+ const singleton = this.profileSingletonName();
1213
+ const fromSurface = Object.keys(this.opts.surface.componentTypes);
1214
+ const names = new Set(fromSurface);
1215
+ if (singleton !== void 0) names.add(singleton);
1216
+ return [...names].sort((a, b) => a.localeCompare(b));
1217
+ }
1218
+ tryGetActiveProfile() {
1219
+ if (this.opts.resolvedProfile !== void 0) return this.opts.resolvedProfile;
1220
+ const name = this.getActiveProfileName();
1221
+ if (name === void 0) return;
1222
+ try {
1223
+ return this.opts.surface.getComponentType(name);
1224
+ } catch {
1225
+ return;
930
1226
  }
931
- assertValidScriptPropertyBag(bag);
932
- return bag;
1227
+ }
1228
+ validatePropsBag(bag) {
1229
+ const profile = this.tryGetActiveProfile();
1230
+ if (profile === void 0) throw new Error("HostScriptSession: set profileId or resolvedProfile before props or host sync");
1231
+ const parsed = profile.props.parse(bag);
1232
+ assertValidScriptPropertyBag(parsed);
1233
+ return parsed;
933
1234
  }
934
1235
  getProps = () => ({ ...this.hostProps });
935
1236
  setProps = async (bag) => {
@@ -1009,8 +1310,6 @@ var HostScriptSession = class {
1009
1310
  const src = [
1010
1311
  `local impl = rawget(_ENV, "__microverse_lua_ComponentImpl")`,
1011
1312
  `if type(impl) == "table" then`,
1012
- ` local attach = rawget(_ENV, "__microverse_lua_attach_bridges")`,
1013
- ` if type(attach) == "function" then attach(impl) end`,
1014
1313
  ` local m = rawget(impl, ${JSON.stringify(hookName)})`,
1015
1314
  ` if type(m) == "function" then`,
1016
1315
  argLiterals.length > 0 ? ` m(impl, ${argLiterals})` : ` m(impl)`,
@@ -1058,7 +1357,11 @@ var HostScriptSession = class {
1058
1357
  for (const k of Object.keys(this.hostProps)) delete this.hostProps[k];
1059
1358
  Object.assign(this.hostProps, cloned);
1060
1359
  };
1360
+ getHostProfileApplied = () => this.hostProfileApplied;
1061
1361
  };
1362
+ function referenceComponentType(def) {
1363
+ if (def.kind === "entityComponentRef" || def.kind === "entityComponentRefArray") return def.componentType;
1364
+ }
1062
1365
  function assertSafeLuaGlobalName(name) {
1063
1366
  if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) throw new Error(`unsafe Lua global name: ${name}`);
1064
1367
  }
@@ -1066,9 +1369,6 @@ function readComponentHooks(surface) {
1066
1369
  if (!("componentHooks" in surface)) return;
1067
1370
  return surface.componentHooks;
1068
1371
  }
1069
- function buildBridgeNamesPreludeLua(bridgeNames) {
1070
- return `rawset(_ENV, "__microverse_bridge_names", { ${bridgeNames.map((n) => JSON.stringify(n)).join(", ")} })`;
1071
- }
1072
1372
  function buildComponentEventStubPreludeLua(hooks) {
1073
1373
  return [
1074
1374
  "local Base = {}",
@@ -1111,6 +1411,26 @@ function luaTableLiteralFromUnknownRecord(o) {
1111
1411
  return `{ ${parts.join(", ")} }`;
1112
1412
  }
1113
1413
  //#endregion
1114
- export { BridgeBuilder, HostScriptSession, MICROVERSE_CAPABILITY_REGISTRY, MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE, MICROVERSE_SCRIPT_CONTEXT, SurfaceBuilder, augmentHostWithCapabilityRegistry, augmentHostWithScriptContext, buildBridgeMergeEnvForHost, collectCapabilitiesFromHostSurfaceSpec, compileHostSurface, compileHostSurfaceFor, createBridgeDeclarationsFromHostSurfaceSpec, defineHostSurface, defineHostSurfaceFor, luaGlobalHookName, luaType, mergeEnvSinkToScriptPropertyBag, pickSurfaceCapabilities, scriptPropertyBagToMergeEnv, scriptPropertyValueToPlain, zodToLuaTypeRef };
1414
+ //#region src/domain/capabilityRegistrySymbol.ts
1415
+ /**
1416
+ * Well-known symbol key used to attach a {@link CapabilityRegistryPort} on the host object
1417
+ * while surface bridge methods run. Populated by {@link augmentHostWithCapabilityRegistry} or
1418
+ * internally by {@link HostScriptSession}.
1419
+ */
1420
+ var MICROVERSE_CAPABILITY_REGISTRY = Symbol.for("microverse:capabilityRegistry");
1421
+ //#endregion
1422
+ //#region src/infrastructure/adapters/augmentHostWithCapabilityRegistry.ts
1423
+ /**
1424
+ * Returns a shallow copy of `host` with {@link MICROVERSE_CAPABILITY_REGISTRY} set to `registry`.
1425
+ * Bridge handlers read the registry to enforce per-session capability allowlists.
1426
+ *
1427
+ * @param host - Your engine / service context passed into `buildDeclarativeBridgeTable`.
1428
+ * @param registry - Typically an {@link InMemoryCapabilityRegistry} from `@microverse.ts/runtime-capabilities`.
1429
+ */
1430
+ function augmentHostWithCapabilityRegistry(host, registry) {
1431
+ return Object.assign(host, { [MICROVERSE_CAPABILITY_REGISTRY]: registry });
1432
+ }
1433
+ //#endregion
1434
+ export { BridgeBuilder, HostScriptSession, MICROVERSE_CAPABILITY_REGISTRY, MICROVERSE_LUA_COMPONENT_SLOT_PRELUDE, MICROVERSE_SCRIPT_CONTEXT, SurfaceBuilder, augmentHostWithCapabilityRegistry, augmentHostWithScriptContext, buildBridgeMergeEnvForProfile, buildComponentTypeSingletonsPreludeLua, buildResolvedScriptProfileRegistry, buildScriptCatalogLuaDefManifest, collectCapabilitiesFromHostSurfaceSpec, compileHostSurface, compileHostSurfaceFor, createBridgeDeclarationsFromHostSurfaceSpec, defineHostSurface, defineHostSurfaceFor, luaGlobalHookName, luaType, mergeEnvSinkToScriptPropertyBag, pickSurfaceCapabilities, resolveScriptProfile, scriptCatalogComponentAlias, scriptProfileBridgesClassName, scriptProfileComponentClassName, scriptProfilePropsAlias, scriptProfileStateAlias, scriptPropertyBagToMergeEnv, scriptPropertyValueToPlain, zodToLuaTypeRef };
1115
1435
 
1116
1436
  //# sourceMappingURL=index.js.map