octane 0.1.24 → 0.1.26

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 CHANGED
@@ -17,6 +17,10 @@ wrapper is narrower: the wrapper must be locally declared in a fully compiled
17
17
  parameter to a supported hook. This package ships both the runtime and compiler,
18
18
  with the compiler exposed at `octane/compiler`.
19
19
 
20
+ Direct Node or Bun server scripts can preload `octane/compiler/register` to
21
+ compile imported Octane components without going through Vite. See the
22
+ [SSR guide](https://github.com/octanejs/octane/blob/main/docs/ssr.md#run-an-ssg-script-directly).
23
+
20
24
  For the full story, see the
21
25
  [main README](https://github.com/octanejs/octane#readme).
22
26
 
@@ -31,6 +31,7 @@ import { findLeadingJsxImportSourcePragma } from './pragma.js';
31
31
  import { normalizeUniversalRuntime } from './universal-runtime.js';
32
32
  import { formatCompileDiagnostic } from './native-change-diagnostics.js';
33
33
  import { findVoidComponentImports, findVoidRootImports, slotHooks } from './slot-hooks.js';
34
+ import { rewriteServerRuntimeRequests } from './runtime-requests.js';
34
35
  import { assertStrongMode } from './strong-mode.js';
35
36
  import {
36
37
  assertNoLiveClientOnlyImports,
@@ -848,6 +849,20 @@ class OctaneBundlerCompiler {
848
849
  options.universalRuntime ?? this.defaults.universalRuntime,
849
850
  );
850
851
  const filename = this._canonicalModuleId(file);
852
+ const targetRuntimeRequests = (source, kind) => {
853
+ if (environment !== 'server' || options.explicitRuntimeRequests !== true) return null;
854
+ const runtimeResult = rewriteServerRuntimeRequests(source, filename);
855
+ if (runtimeResult === null) return null;
856
+ return {
857
+ code: runtimeResult.code,
858
+ map: runtimeResult.map,
859
+ kind,
860
+ ...finishMetadata(collected),
861
+ };
862
+ };
863
+ const passThrough = () => {
864
+ return targetRuntimeRequests(code, 'runtime-requests') ?? this._passThrough(code, collected);
865
+ };
851
866
  const clientOnlyImports =
852
867
  environment === 'server' && Array.isArray(options.clientOnlyImports)
853
868
  ? options.clientOnlyImports
@@ -974,22 +989,22 @@ class OctaneBundlerCompiler {
974
989
  if (this.requireDirective && !pragmaOwned && this._isProjectOwnedSource(file)) {
975
990
  this._warnUnmarkedOctaneImport(code, filename);
976
991
  }
977
- return this._passThrough(code, collected);
992
+ return passThrough();
978
993
  }
979
994
 
980
995
  if (plainHelperSource) {
981
- if (/\/\/\s*octane-no-slot\b/.test(code)) return null;
996
+ if (/\/\/\s*octane-no-slot\b/.test(code)) return passThrough();
982
997
  if (this.exclude.some((path) => file.includes(path))) {
983
998
  // Same conflict diagnostic as the full-compile gate: an ownership
984
999
  // pragma inside an excluded path must not fail silent.
985
1000
  if (this.requireDirective) {
986
1001
  this._warnExcludedPragmaConflict(file, filename, pragmaOwned);
987
1002
  }
988
- return null;
1003
+ return passThrough();
989
1004
  }
990
- if (!/from\s*['"]octane['"]/.test(code)) return null;
1005
+ if (!/from\s*['"]octane['"]/.test(code)) return passThrough();
991
1006
  if (!this._isInstalledOctaneSource(file, collected)) {
992
- return this._passThrough(code, collected);
1007
+ return passThrough();
993
1008
  }
994
1009
  // Hook slotting is an Octane-ownership rewrite, so the ownership
995
1010
  // gate applies to it exactly as to full compilation: an unmarked
@@ -997,7 +1012,7 @@ class OctaneBundlerCompiler {
997
1012
  // pragma diagnostic), a pragma-marked one gets its hook slots.
998
1013
  if (this.requireDirective && !pragmaOwned && this._isProjectOwnedSource(file)) {
999
1014
  this._warnUnmarkedOctaneImport(code, filename);
1000
- return this._passThrough(code, collected);
1015
+ return passThrough();
1001
1016
  }
1002
1017
  if (this._hasManualHookSlots(file, collected)) {
1003
1018
  // Hand-slotted bindings still own their authored policy. Opting one
@@ -1008,7 +1023,7 @@ class OctaneBundlerCompiler {
1008
1023
  strong,
1009
1024
  });
1010
1025
  }
1011
- return this._passThrough(code, collected);
1026
+ return passThrough();
1012
1027
  }
1013
1028
  const profileFilename = profile ? this._profileModuleId(file, collected) : undefined;
1014
1029
  const specializeVoidRoot =
@@ -1025,16 +1040,18 @@ class OctaneBundlerCompiler {
1025
1040
  }
1026
1041
  : {}),
1027
1042
  });
1028
- if (out === null) return this._passThrough(code, collected);
1029
- return {
1030
- code: out.code,
1031
- map: out.map,
1032
- kind: 'slots',
1033
- ...finishMetadata(collected),
1034
- };
1043
+ if (out === null) return passThrough();
1044
+ return (
1045
+ targetRuntimeRequests(out.code, 'slots') ?? {
1046
+ code: out.code,
1047
+ map: out.map,
1048
+ kind: 'slots',
1049
+ ...finishMetadata(collected),
1050
+ }
1051
+ );
1035
1052
  }
1036
1053
 
1037
- return null;
1054
+ return passThrough();
1038
1055
  }
1039
1056
  }
1040
1057
 
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Synchronous server-script compiler registration.
3
+ *
4
+ * Import this module before a Node or Bun entry point to compile authored
5
+ * `.tsrx`/`.tsx` modules and slot Octane hooks in plain `.ts`/`.js` helpers.
6
+ * Direct execution is a server pipeline, so authored `octane` runtime imports
7
+ * resolve to `octane/server`, matching the server code the compiler emits.
8
+ */
9
+ import * as nodeFs from 'node:fs';
10
+ import * as nodeModule from 'node:module';
11
+ import * as nodePath from 'node:path';
12
+ import * as nodeUrl from 'node:url';
13
+ import { createOctaneCompiler } from './bundler.js';
14
+
15
+ const compiler = createOctaneCompiler({
16
+ root: process.cwd(),
17
+ environment: 'server',
18
+ hmr: false,
19
+ dev: false,
20
+ });
21
+
22
+ const extensionlessCandidates = ['.tsrx', '.tsx', '.ts', '.mts', '.mjs', '.js'];
23
+
24
+ function isFile(path) {
25
+ try {
26
+ return nodeFs.statSync(path).isFile();
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function candidateUrl(specifier, parentURL) {
33
+ if (parentURL === undefined || !parentURL.startsWith('file:')) return null;
34
+ if (!specifier.startsWith('.') && !specifier.startsWith('/') && !specifier.startsWith('file:')) {
35
+ return null;
36
+ }
37
+
38
+ let requested;
39
+ try {
40
+ requested = new URL(specifier, parentURL);
41
+ } catch {
42
+ return null;
43
+ }
44
+ if (requested.protocol !== 'file:') return null;
45
+
46
+ const requestedPath = nodeUrl.fileURLToPath(requested);
47
+ const extension = nodePath.extname(requestedPath);
48
+ const candidates = [];
49
+ if (extension === '') {
50
+ for (const candidateExtension of extensionlessCandidates) {
51
+ candidates.push(requestedPath + candidateExtension);
52
+ }
53
+ for (const candidateExtension of extensionlessCandidates) {
54
+ candidates.push(nodePath.join(requestedPath, 'index' + candidateExtension));
55
+ }
56
+ } else if (extension === '.js') {
57
+ const stem = requestedPath.slice(0, -extension.length);
58
+ candidates.push(stem + '.ts', stem + '.tsx');
59
+ } else if (extension === '.mjs') {
60
+ candidates.push(requestedPath.slice(0, -extension.length) + '.mts');
61
+ } else if (extension === '.cjs') {
62
+ candidates.push(requestedPath.slice(0, -extension.length) + '.cts');
63
+ }
64
+
65
+ for (const candidate of candidates) {
66
+ if (!isFile(candidate)) continue;
67
+ const resolved = nodeUrl.pathToFileURL(candidate);
68
+ resolved.search = requested.search;
69
+ resolved.hash = requested.hash;
70
+ return resolved.href;
71
+ }
72
+ return null;
73
+ }
74
+
75
+ function sourceText(source) {
76
+ if (typeof source === 'string') return source;
77
+ if (source === undefined) return null;
78
+ return new TextDecoder().decode(source);
79
+ }
80
+
81
+ function resolve(specifier, context, nextResolve) {
82
+ const runtimeRequest = compiler.resolveRuntimeRequest(specifier, 'server');
83
+ if (runtimeRequest !== null) return nextResolve(runtimeRequest, context);
84
+
85
+ try {
86
+ return nextResolve(specifier, context);
87
+ } catch (error) {
88
+ const url = candidateUrl(specifier, context.parentURL);
89
+ if (url !== null) return { url, shortCircuit: true };
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ function load(url, context, nextLoad) {
95
+ if (!url.startsWith('file:')) return nextLoad(url, context);
96
+ const filename = nodeUrl.fileURLToPath(url);
97
+
98
+ if (filename.endsWith('.tsrx') || filename.endsWith('.tsx')) {
99
+ const source = nodeFs.readFileSync(filename, 'utf8');
100
+ const result = compiler.transform(source, filename, { environment: 'server' });
101
+ if (result === null || result.kind === 'none') return nextLoad(url, context);
102
+ return { format: 'module', source: result.code, shortCircuit: true };
103
+ }
104
+
105
+ if ((filename.endsWith('.ts') && !filename.endsWith('.d.ts')) || filename.endsWith('.js')) {
106
+ const loaded = nextLoad(url, context);
107
+ const source = sourceText(loaded.source);
108
+ if (source === null) return loaded;
109
+ const result = compiler.transform(source, filename, { environment: 'server' });
110
+ if (result === null || result.kind === 'none') return loaded;
111
+ return { ...loaded, source: result.code, shortCircuit: true };
112
+ }
113
+
114
+ return nextLoad(url, context);
115
+ }
116
+
117
+ function registerBunPlugin() {
118
+ globalThis.Bun.plugin({
119
+ name: 'octane-compiler',
120
+ setup(build) {
121
+ build.onLoad({ filter: /\.(?:tsrx|tsx|ts|js)$/, namespace: 'file' }, ({ path }) => {
122
+ const source = nodeFs.readFileSync(path, 'utf8');
123
+ const result = compiler.transform(source, path, {
124
+ environment: 'server',
125
+ explicitRuntimeRequests: true,
126
+ });
127
+ const loader =
128
+ path.endsWith('.tsx') || path.endsWith('.tsrx')
129
+ ? 'tsx'
130
+ : path.endsWith('.ts')
131
+ ? 'ts'
132
+ : 'js';
133
+ if (result === null || result.kind === 'none') return { contents: source, loader };
134
+ return {
135
+ contents: result.code,
136
+ loader: result.kind === 'slots' || result.kind === 'runtime-requests' ? loader : 'js',
137
+ };
138
+ });
139
+ },
140
+ });
141
+ }
142
+
143
+ if (typeof globalThis.Bun === 'object') registerBunPlugin();
144
+ else nodeModule.registerHooks({ resolve, load });
@@ -0,0 +1,36 @@
1
+ import { initSync as initModuleLexer, parse as lexModule } from 'es-module-lexer';
2
+
3
+ let moduleLexerInitialized = false;
4
+
5
+ /** Target bare Octane ESM requests at the explicit server runtime. */
6
+ export function rewriteServerRuntimeRequests(source, id) {
7
+ if (!source.includes('octane')) return null;
8
+ if (!moduleLexerInitialized) {
9
+ initModuleLexer();
10
+ moduleLexerInitialized = true;
11
+ }
12
+
13
+ let imports;
14
+ try {
15
+ [imports] = lexModule(source, id);
16
+ } catch {
17
+ return null;
18
+ }
19
+
20
+ let code = source;
21
+ let changed = false;
22
+ for (let index = imports.length - 1; index >= 0; index--) {
23
+ const request = imports[index];
24
+ if (request.n !== 'octane') continue;
25
+ if (request.d === -1) {
26
+ code = code.slice(0, request.s) + 'octane/server' + code.slice(request.e);
27
+ changed = true;
28
+ continue;
29
+ }
30
+ const quote = source[request.s];
31
+ if (quote !== "'" && quote !== '"' && quote !== '`') continue;
32
+ code = code.slice(0, request.s) + `${quote}octane/server${quote}` + code.slice(request.e);
33
+ changed = true;
34
+ }
35
+ return changed ? { code, map: null } : null;
36
+ }
@@ -27,6 +27,13 @@ function octaneHookLocals(ast) {
27
27
  let importsHook = false;
28
28
  let hasOctaneImport = false;
29
29
  for (const node of ast.body || []) {
30
+ if (
31
+ (node.type === 'ExportNamedDeclaration' || node.type === 'ExportAllDeclaration') &&
32
+ node.source?.value === 'octane'
33
+ ) {
34
+ hasOctaneImport = true;
35
+ continue;
36
+ }
30
37
  if (node.type !== 'ImportDeclaration' || node.source?.value !== 'octane') continue;
31
38
  hasOctaneImport = true;
32
39
  for (const sp of node.specifiers || []) {
@@ -1025,7 +1032,9 @@ export function slotHooks(source, id, options) {
1025
1032
  !options?.profile &&
1026
1033
  typeof options?.isVoidComponentImport === 'function' &&
1027
1034
  importInfo.hasOctaneImport;
1028
- if (!importInfo.importsHook && !canSpecializeRoot) return null;
1035
+ if (!importInfo.importsHook && !canSpecializeRoot) {
1036
+ return null;
1037
+ }
1029
1038
  // The parsed tree is never mutated: annotateHookCalls returns a COW-rebuilt
1030
1039
  // module whose hook calls carry their `_octane*` props (start/end offsets are
1031
1040
  // preserved, so the text edits below stay valid), with the dependency
@@ -1072,8 +1081,11 @@ export function slotHooks(source, id, options) {
1072
1081
  // Apply insertions right-to-left so earlier offsets stay valid.
1073
1082
  st.edits.sort((a, b) => b.pos - a.pos);
1074
1083
  let code = source;
1075
- for (const e of st.edits) {
1076
- code = code.slice(0, e.pos) + e.text + code.slice(e.end === undefined ? e.pos : e.end);
1084
+ for (const edit of st.edits) {
1085
+ code =
1086
+ code.slice(0, edit.pos) +
1087
+ edit.text +
1088
+ code.slice(edit.end === undefined ? edit.pos : edit.end);
1077
1089
  }
1078
1090
 
1079
1091
  // APPEND the slot consts (rather than prepend) so every original line number
package/dist/runtime.js CHANGED
@@ -100,6 +100,14 @@ function profilePortalComponent(rawBody) {
100
100
  function ensureHooks(scope) {
101
101
  return scope.hooks ?? (scope.hooks = /* @__PURE__ */ new Map());
102
102
  }
103
+ function registerHookCleanup(scope, cleanup) {
104
+ if (process.env.NODE_ENV !== "production") {
105
+ if (scope.block.body[HMR] !== void 0) {
106
+ cleanup[HMR] = true;
107
+ }
108
+ }
109
+ (scope.cleanups ??= []).push(cleanup);
110
+ }
103
111
  let nextHookSlot = 0;
104
112
  function hookSlots(count) {
105
113
  const base = nextHookSlot;
@@ -2446,7 +2454,7 @@ function unmountScope(scope, detachDom = true) {
2446
2454
  unmountScopeChildrenAndSlots(scope, detachDom);
2447
2455
  });
2448
2456
  }
2449
- function unmountScopeChildrenAndSlots(scope, detachDom) {
2457
+ function runScopeCleanups(scope) {
2450
2458
  const c = scope.cleanups;
2451
2459
  if (c !== null)
2452
2460
  for (let i = c.length - 1; i >= 0; i--) {
@@ -2456,6 +2464,12 @@ function unmountScopeChildrenAndSlots(scope, detachDom) {
2456
2464
  reportTeardownError(err);
2457
2465
  }
2458
2466
  }
2467
+ }
2468
+ function unmountScopeChildrenAndSlots(scope, detachDom) {
2469
+ runScopeCleanups(scope);
2470
+ unmountScopeChildrenAndSlotsOnly(scope, detachDom);
2471
+ }
2472
+ function unmountScopeChildrenAndSlotsOnly(scope, detachDom) {
2459
2473
  const children = scope.children;
2460
2474
  if (children !== null)
2461
2475
  for (let i = 0, n = children.length; i < n; i++) unmountScope(children[i].scope, detachDom);
@@ -3123,7 +3137,7 @@ function useEffectEvent(fn, slot) {
3123
3137
  s = { impl: fn, active: true };
3124
3138
  ensureHooks(scope).set(slot, s);
3125
3139
  const cell2 = s;
3126
- (scope.cleanups ??= []).push(() => {
3140
+ registerHookCleanup(scope, () => {
3127
3141
  cell2.active = false;
3128
3142
  });
3129
3143
  } else {
@@ -3205,6 +3219,87 @@ function scopedChildrenAsBody(props) {
3205
3219
  return (_props, scope, extra) => childrenAsBody(props.children)(void 0, scope, extra);
3206
3220
  }
3207
3221
  const CHILDREN_DIALECT_SLOT = /* @__PURE__ */ Symbol("octane.childrenDialect");
3222
+ function hasResettableHmrRange(block) {
3223
+ const start = block.startMarker;
3224
+ const end = block.endMarker;
3225
+ if (start === void 0 || end === void 0) return false;
3226
+ if (start === null || end === null) {
3227
+ return start === null && end === null && (block.kind === "root" || block.exclusiveMarkers === true);
3228
+ }
3229
+ if (start !== end) {
3230
+ return start.parentNode === block.parentNode && end.parentNode === block.parentNode;
3231
+ }
3232
+ for (let parent = block.parentBlock; parent !== null; parent = parent.parentBlock) {
3233
+ if (parent.startMarker === start && parent.endMarker === end) return false;
3234
+ }
3235
+ return start.parentNode === block.parentNode;
3236
+ }
3237
+ function promoteHmrBlockRange(block) {
3238
+ const root = block.startMarker;
3239
+ if (root === null || root !== block.endMarker) return;
3240
+ const parent = block.parentNode;
3241
+ const rangeStart = document.createComment("hmr");
3242
+ const rangeEnd = document.createComment("/hmr");
3243
+ parent.insertBefore(rangeStart, root);
3244
+ parent.insertBefore(rangeEnd, root.nextSibling);
3245
+ block.startMarker = rangeStart;
3246
+ block.endMarker = rangeEnd;
3247
+ block.exclusiveMarkers = false;
3248
+ }
3249
+ function resetHmrBlock(block) {
3250
+ if (TEARDOWN_DEPTH === 0) {
3251
+ TEARDOWN_HANDLER = findTryHandler(block.parentBlock) ?? rendererRegionTryHandler(block);
3252
+ }
3253
+ TEARDOWN_DEPTH++;
3254
+ let abortedRefs = null;
3255
+ if (!block.mounted) {
3256
+ abortedRefs = [];
3257
+ collectVisibleSubtreeRefs(block, abortedRefs);
3258
+ }
3259
+ try {
3260
+ withRefDetachSuppression(abortedRefs, () => {
3261
+ if (block.deoptNode !== null) detachDeoptTreeRefs(block.deoptNode, null);
3262
+ const cleanups = block.cleanups;
3263
+ let preserved = null;
3264
+ if (cleanups !== null) {
3265
+ for (let i = cleanups.length - 1; i >= 0; i--) {
3266
+ const cleanup = cleanups[i];
3267
+ if (cleanup[HMR] === true) continue;
3268
+ try {
3269
+ runEffectLifecycleCallback(cleanup);
3270
+ } catch (err) {
3271
+ reportTeardownError(err);
3272
+ }
3273
+ }
3274
+ for (let i = 0; i < cleanups.length; i++) {
3275
+ const cleanup = cleanups[i];
3276
+ if (cleanup[HMR] === true) (preserved ??= []).push(cleanup);
3277
+ }
3278
+ }
3279
+ unmountScopeChildrenAndSlotsOnly(block, true);
3280
+ removeRange(
3281
+ block.startMarker !== null ? block.startMarker.nextSibling : block.parentNode.firstChild,
3282
+ block.endMarker
3283
+ );
3284
+ block.children = null;
3285
+ block.cleanups = preserved;
3286
+ block._slots = null;
3287
+ block.refFields = null;
3288
+ block.slots = [];
3289
+ block.deoptNode = null;
3290
+ });
3291
+ const hooks = block.hooks;
3292
+ if (hooks !== null) {
3293
+ for (const value of hooks.values()) {
3294
+ if (value !== null && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, "deps")) {
3295
+ value.deps = void 0;
3296
+ }
3297
+ }
3298
+ }
3299
+ } finally {
3300
+ if (--TEARDOWN_DEPTH === 0) dispatchTeardownErrors();
3301
+ }
3302
+ }
3208
3303
  function resetScopeChildren(scope) {
3209
3304
  const block = scope.block;
3210
3305
  if (TEARDOWN_DEPTH === 0) {
@@ -10559,6 +10654,13 @@ function hmr(fn) {
10559
10654
  const nextFn = incomingMeta ? incomingMeta.fn : incoming;
10560
10655
  if (meta.fn.__octaneReturnedOutput !== nextFn.__octaneReturnedOutput)
10561
10656
  return false;
10657
+ for (const b of meta.liveBlocks) {
10658
+ if (b.disposed) {
10659
+ meta.liveBlocks.delete(b);
10660
+ continue;
10661
+ }
10662
+ if (!hasResettableHmrRange(b)) return false;
10663
+ }
10562
10664
  meta.fn = nextFn;
10563
10665
  if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__)
10564
10666
  __profileComponentSource(wrapper, meta.fn);
@@ -10573,6 +10675,8 @@ function hmr(fn) {
10573
10675
  b.body = wrapper;
10574
10676
  if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__)
10575
10677
  __profileSchedule(b, "hmr");
10678
+ promoteHmrBlockRange(b);
10679
+ resetHmrBlock(b);
10576
10680
  scheduleRender(b);
10577
10681
  }
10578
10682
  return true;
@@ -11684,7 +11788,7 @@ function useTransition(slot) {
11684
11788
  }
11685
11789
  };
11686
11790
  TRANSITION_LISTENERS.add(listener);
11687
- (scope.cleanups ??= []).push(() => TRANSITION_LISTENERS.delete(listener));
11791
+ registerHookCleanup(scope, () => TRANSITION_LISTENERS.delete(listener));
11688
11792
  }
11689
11793
  return [s.isPending, s.start];
11690
11794
  }
@@ -11786,7 +11890,7 @@ function useFormStatus(slot) {
11786
11890
  s = { form: null, listener: null };
11787
11891
  const slotRef = s;
11788
11892
  ensureHooks(scope).set(slot, slotRef);
11789
- (scope.cleanups ??= []).push(() => {
11893
+ registerHookCleanup(scope, () => {
11790
11894
  if (slotRef.form && slotRef.listener)
11791
11895
  FORM_STATUS_LISTENERS.get(slotRef.form)?.delete(slotRef.listener);
11792
11896
  });
@@ -11861,7 +11965,7 @@ function useOptimistic(passthrough, updateFnOrSlot, slot) {
11861
11965
  if (TRANSITION_PENDING_COUNT === 0 && slotRef.armed) clear();
11862
11966
  };
11863
11967
  TRANSITION_LISTENERS.add(listener);
11864
- (scope.cleanups ??= []).push(() => TRANSITION_LISTENERS.delete(listener));
11968
+ registerHookCleanup(scope, () => TRANSITION_LISTENERS.delete(listener));
11865
11969
  }
11866
11970
  s.updateFn = updateFn;
11867
11971
  let optimistic = passthrough;
@@ -611,6 +611,7 @@ export interface ObjectHostInstance {
611
611
  }
612
612
  interface ObjectDriverState {
613
613
  instances: Map<number, ObjectHostInstance>;
614
+ parents: Map<number, number | null>;
614
615
  events: Map<number, Map<string, UniversalEventListenerDescriptor>>;
615
616
  lifecycles: Map<number, Map<string, UniversalListenerDescriptor>>;
616
617
  localCallbacks: Map<number, Map<string, UniversalListenerDescriptor>>;