solid-tag-runtime 0.0.6 → 0.0.8

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/src/runtime.js CHANGED
@@ -5,6 +5,7 @@ import {
5
5
  SolidTagRuntimeError,
6
6
  } from "./errors.js";
7
7
  import { createDefaultModuleUrlBackend } from "./url-backend.js";
8
+ import { createEventDispatcher } from "./events.js";
8
9
 
9
10
  const HOST_REGISTRY_KEY = "__solidTagRuntimeHostModules__";
10
11
  let runtimeSequence = 0;
@@ -27,6 +28,13 @@ export function createRuntime(options = {}) {
27
28
  const records = new Map();
28
29
  let anonymousSequence = 0;
29
30
  let disposed = false;
31
+ const events = createEventDispatcher({
32
+ onListenerError(error) {
33
+ globalThis.console?.error?.("solid-tag-runtime lifecycle subscriber error", error);
34
+ },
35
+ });
36
+ let eventBufferDepth = 0;
37
+ const eventBuffer = [];
30
38
 
31
39
  const globalRegistry = getHostRegistry();
32
40
  const hostNamespaces = new Map();
@@ -34,6 +42,7 @@ export function createRuntime(options = {}) {
34
42
 
35
43
  const api = {
36
44
  define,
45
+ defineMany,
37
46
  defineModule,
38
47
  defineUrl,
39
48
  update,
@@ -43,6 +52,9 @@ export function createRuntime(options = {}) {
43
52
  toComponent,
44
53
  has,
45
54
  invalidate,
55
+ remove,
56
+ clear,
57
+ subscribe,
46
58
  modules: listModules,
47
59
  dependencies,
48
60
  dependents,
@@ -61,11 +73,63 @@ export function createRuntime(options = {}) {
61
73
  }
62
74
 
63
75
  function define(id, source, defineOptions = {}) {
76
+ return defineSource(id, source, defineOptions, "define");
77
+ }
78
+
79
+ function defineMany(definitions) {
80
+ assertActive();
81
+ if (!Array.isArray(definitions)) {
82
+ throw new TypeError("defineMany(definitions) expects an array of source module definitions.");
83
+ }
84
+
85
+ const prepared = definitions.map((definition, index) => {
86
+ if (!definition || typeof definition !== "object") {
87
+ throw new TypeError(`defineMany() item ${index} must be an object with id and source.`);
88
+ }
89
+ if (definition.id == null) {
90
+ throw new TypeError(`defineMany() item ${index} is missing id.`);
91
+ }
92
+ const id = normalizeDefinedId(definition.id);
93
+ return {
94
+ id,
95
+ source: String(definition.source ?? ""),
96
+ format: definition.format ?? "jsx",
97
+ };
98
+ });
99
+
100
+ const seen = new Set();
101
+ for (const item of prepared) {
102
+ if (seen.has(item.id)) {
103
+ throw new SolidTagRuntimeError(`defineMany() contains duplicate module id ${JSON.stringify(item.id)}.`);
104
+ }
105
+ seen.add(item.id);
106
+ }
107
+
108
+ eventBufferDepth += 1;
109
+ try {
110
+ const ids = prepared.map(item => defineSource(item.id, item.source, { format: item.format }, "defineMany"));
111
+ emit({
112
+ type: "modules-defined",
113
+ ids: [...ids],
114
+ modules: ids.map(id => toModuleInfo(records.get(id))),
115
+ });
116
+ return ids;
117
+ } finally {
118
+ eventBufferDepth -= 1;
119
+ if (eventBufferDepth === 0) flushEventBuffer();
120
+ }
121
+ }
122
+
123
+ function update(id, source, updateOptions = {}) {
124
+ return defineSource(id, source, updateOptions, "update");
125
+ }
126
+
127
+ function defineSource(id, source, defineOptions = {}, operation = "define") {
64
128
  assertActive();
65
129
  const normalized = normalizeDefinedId(id);
66
130
  const existing = records.get(normalized);
67
131
 
68
- if (existing) invalidate(normalized, { dependents: true });
132
+ if (existing) invalidate(normalized, { dependents: true, reason: operation });
69
133
 
70
134
  const record = {
71
135
  id: normalized,
@@ -84,18 +148,34 @@ export function createRuntime(options = {}) {
84
148
  };
85
149
 
86
150
  records.set(normalized, record);
151
+ emit({
152
+ type: "module-defined",
153
+ id: normalized,
154
+ kind: record.kind,
155
+ version: record.version,
156
+ operation,
157
+ replaced: Boolean(existing),
158
+ module: toModuleInfo(record),
159
+ });
160
+ if (operation === "update") {
161
+ emit({
162
+ type: "module-updated",
163
+ id: normalized,
164
+ kind: record.kind,
165
+ previousVersion: existing?.version ?? 0,
166
+ version: record.version,
167
+ format: record.format,
168
+ module: toModuleInfo(record),
169
+ });
170
+ }
87
171
  return normalized;
88
172
  }
89
173
 
90
- function update(id, source, updateOptions = {}) {
91
- return define(id, source, updateOptions);
92
- }
93
-
94
174
  function defineModule(id, namespace) {
95
175
  assertActive();
96
176
  const normalized = normalizeDefinedId(id);
97
177
  const existing = records.get(normalized);
98
- if (existing) invalidate(normalized, { dependents: true });
178
+ if (existing) invalidate(normalized, { dependents: true, reason: "defineModule" });
99
179
 
100
180
  if ((typeof namespace !== "object" || namespace === null) && typeof namespace !== "function") {
101
181
  throw new TypeError("defineModule(id, namespace) expects an object, function, or ES module namespace-like value.");
@@ -103,7 +183,7 @@ export function createRuntime(options = {}) {
103
183
 
104
184
  hostNamespaces.set(normalized, namespace);
105
185
 
106
- records.set(normalized, {
186
+ const record = {
107
187
  id: normalized,
108
188
  kind: "host",
109
189
  namespace,
@@ -112,6 +192,16 @@ export function createRuntime(options = {}) {
112
192
  dependencies: new Set(),
113
193
  dependents: existing?.dependents ?? new Set(),
114
194
  url: undefined,
195
+ };
196
+ records.set(normalized, record);
197
+ emit({
198
+ type: "module-defined",
199
+ id: normalized,
200
+ kind: record.kind,
201
+ version: record.version,
202
+ operation: "defineModule",
203
+ replaced: Boolean(existing),
204
+ module: toModuleInfo(record),
115
205
  });
116
206
 
117
207
  return normalized;
@@ -121,10 +211,10 @@ export function createRuntime(options = {}) {
121
211
  assertActive();
122
212
  const normalized = normalizeDefinedId(id);
123
213
  const existing = records.get(normalized);
124
- if (existing) invalidate(normalized, { dependents: true });
214
+ if (existing) invalidate(normalized, { dependents: true, reason: "defineUrl" });
125
215
 
126
216
  const href = String(url);
127
- records.set(normalized, {
217
+ const record = {
128
218
  id: normalized,
129
219
  kind: "url",
130
220
  externalUrl: href,
@@ -134,6 +224,16 @@ export function createRuntime(options = {}) {
134
224
  dependents: existing?.dependents ?? new Set(),
135
225
  importPromise: undefined,
136
226
  namespace: undefined,
227
+ };
228
+ records.set(normalized, record);
229
+ emit({
230
+ type: "module-defined",
231
+ id: normalized,
232
+ kind: record.kind,
233
+ version: record.version,
234
+ operation: "defineUrl",
235
+ replaced: Boolean(existing),
236
+ module: toModuleInfo(record),
137
237
  });
138
238
 
139
239
  return normalized;
@@ -155,23 +255,38 @@ export function createRuntime(options = {}) {
155
255
 
156
256
  if (record.kind === "url") {
157
257
  if (!record.importPromise) {
258
+ record.state = "evaluating";
259
+ emit({ type: "module-evaluating", id: record.id, kind: record.kind, version: record.version });
158
260
  record.importPromise = import(record.externalUrl).then(namespace => {
159
261
  record.namespace = namespace;
160
262
  record.state = "ready";
263
+ emit({ type: "module-evaluated", id: record.id, kind: record.kind, version: record.version, module: toModuleInfo(record) });
161
264
  return namespace;
265
+ }).catch(error => {
266
+ record.importPromise = undefined;
267
+ record.state = "error";
268
+ emitError(record, "evaluate", error);
269
+ throw error;
162
270
  });
163
271
  }
164
272
  return record.importPromise;
165
273
  }
166
274
 
167
275
  if (!record.importPromise) {
168
- record.importPromise = ensureSourceUrl(record, []).then(url => import(url)).then(namespace => {
276
+ record.importPromise = ensureSourceUrl(record, []).then(url => {
277
+ record.state = "evaluating";
278
+ emit({ type: "module-evaluating", id: record.id, kind: record.kind, version: record.version });
279
+ return import(url);
280
+ }).then(namespace => {
169
281
  record.namespace = namespace;
170
282
  record.state = "ready";
283
+ emit({ type: "module-evaluated", id: record.id, kind: record.kind, version: record.version, module: toModuleInfo(record) });
171
284
  return namespace;
172
285
  }).catch(error => {
173
286
  record.importPromise = undefined;
287
+ const alreadyReported = record.state === "error";
174
288
  record.state = "error";
289
+ if (!alreadyReported) emitError(record, "evaluate", error);
175
290
  throw error;
176
291
  });
177
292
  }
@@ -241,6 +356,7 @@ export function createRuntime(options = {}) {
241
356
  assertActive();
242
357
  const normalized = normalizeLookupId(id);
243
358
  const includeDependents = invalidateOptions.dependents ?? true;
359
+ const reason = invalidateOptions.reason ?? "explicit";
244
360
  const seen = new Set();
245
361
 
246
362
  invalidateOne(normalized);
@@ -271,7 +387,67 @@ export function createRuntime(options = {}) {
271
387
  record.namespace = undefined;
272
388
  record.state = "defined";
273
389
  }
390
+
391
+ emit({
392
+ type: "module-invalidated",
393
+ id: record.id,
394
+ kind: record.kind,
395
+ version: record.version,
396
+ reason,
397
+ });
398
+ }
399
+ }
400
+
401
+ function remove(id, removeOptions = {}) {
402
+ assertActive();
403
+ const normalized = normalizeLookupId(id);
404
+ const record = records.get(normalized);
405
+ if (!record) return false;
406
+
407
+ if (removeOptions.invalidateDependents !== false) {
408
+ for (const dependent of [...record.dependents]) {
409
+ invalidate(dependent, { dependents: true, reason: `dependency-removed:${normalized}` });
410
+ }
274
411
  }
412
+
413
+ for (const dependency of record.dependencies) {
414
+ records.get(dependency)?.dependents.delete(normalized);
415
+ }
416
+
417
+ revokeRecordUrl(record);
418
+ if (record.kind === "host") hostNamespaces.delete(normalized);
419
+ records.delete(normalized);
420
+
421
+ emit({
422
+ type: "module-removed",
423
+ id: normalized,
424
+ kind: record.kind,
425
+ version: record.version,
426
+ invalidateDependents: removeOptions.invalidateDependents !== false,
427
+ });
428
+ return true;
429
+ }
430
+
431
+ function clear(clearOptions = {}) {
432
+ assertActive();
433
+ const preserveHostModules = clearOptions.preserveHostModules !== false;
434
+ const ids = [...records.values()]
435
+ .filter(record => !(preserveHostModules && record.kind === "host"))
436
+ .map(record => record.id);
437
+
438
+ for (const id of ids) remove(id, { invalidateDependents: false });
439
+
440
+ emit({
441
+ type: "runtime-cleared",
442
+ ids: [...ids],
443
+ preserveHostModules,
444
+ });
445
+ return ids;
446
+ }
447
+
448
+ function subscribe(typesOrListener, maybeListener) {
449
+ assertActive();
450
+ return events.subscribe(typesOrListener, maybeListener);
275
451
  }
276
452
 
277
453
  function listModules() {
@@ -296,11 +472,13 @@ export function createRuntime(options = {}) {
296
472
  function dispose() {
297
473
  if (disposed) return;
298
474
  disposed = true;
475
+ emit({ type: "runtime-disposed" });
299
476
 
300
477
  for (const record of records.values()) revokeRecordUrl(record);
301
478
  records.clear();
302
479
  hostNamespaces.clear();
303
480
  globalRegistry.delete(runtimeId);
481
+ events.clear();
304
482
  }
305
483
 
306
484
  function resolvePublic(specifier, importer) {
@@ -312,93 +490,159 @@ export function createRuntime(options = {}) {
312
490
 
313
491
  if (stack.includes(record.id)) {
314
492
  const cycleStart = stack.indexOf(record.id);
315
- throw new CircularDependencyError([...stack.slice(cycleStart), record.id]);
493
+ const error = new CircularDependencyError([...stack.slice(cycleStart), record.id]);
494
+ emitError(record, "link", error);
495
+ throw error;
316
496
  }
317
497
 
318
498
  const nextStack = [...stack, record.id];
319
499
  record.state = "linking";
500
+ emit({
501
+ type: "module-linking",
502
+ id: record.id,
503
+ kind: record.kind,
504
+ version: record.version,
505
+ });
320
506
 
321
507
  let analysis;
322
508
  try {
323
509
  analysis = compiler.analyze(record.source, { id: record.id, format: record.format });
324
510
  } catch (error) {
325
511
  record.state = "error";
326
- throw new ModuleCompileError(record.id, error);
512
+ const wrapped = new ModuleCompileError(record.id, error);
513
+ emitError(record, "analyze", wrapped);
514
+ throw wrapped;
327
515
  }
328
516
 
329
517
  const resolvedImports = [];
518
+ const linkedDependencies = [];
330
519
  const nextDependencies = new Set();
331
520
 
332
521
  for (const reference of analysis.imports ?? []) {
333
- const resolved = resolveSpecifier(reference.specifier, record.id);
522
+ let resolved;
523
+ try {
524
+ resolved = resolveSpecifier(reference.specifier, record.id);
525
+ } catch (error) {
526
+ record.state = "error";
527
+ emitError(record, "resolve", error);
528
+ throw error;
529
+ }
530
+
334
531
  const dependencyRecord = records.get(resolved);
335
532
  let replacement = resolved;
336
533
 
337
- if (dependencyRecord) {
338
- nextDependencies.add(dependencyRecord.id);
339
-
340
- if (dependencyRecord.kind === "source") {
341
- replacement = await ensureSourceUrl(dependencyRecord, nextStack);
342
- } else if (dependencyRecord.kind === "host") {
343
- replacement = await ensureHostUrl(dependencyRecord);
344
- } else {
345
- replacement = dependencyRecord.externalUrl;
534
+ try {
535
+ if (dependencyRecord) {
536
+ nextDependencies.add(dependencyRecord.id);
537
+
538
+ if (dependencyRecord.kind === "source") {
539
+ replacement = await ensureSourceUrl(dependencyRecord, nextStack);
540
+ } else if (dependencyRecord.kind === "host") {
541
+ replacement = await ensureHostUrl(dependencyRecord);
542
+ } else {
543
+ replacement = dependencyRecord.externalUrl;
544
+ }
545
+ } else if (isRelativeSpecifier(reference.specifier) || reference.specifier.startsWith("/")) {
546
+ throw new ModuleResolutionError(reference.specifier, record.id);
547
+ } else if (!allowNativeImports && !isAbsoluteUrl(resolved)) {
548
+ throw new ModuleResolutionError(reference.specifier, record.id);
346
549
  }
347
- } else if (isRelativeSpecifier(reference.specifier) || reference.specifier.startsWith("/")) {
348
- throw new ModuleResolutionError(reference.specifier, record.id);
349
- } else if (!allowNativeImports && !isAbsoluteUrl(resolved)) {
350
- throw new ModuleResolutionError(reference.specifier, record.id);
550
+ } catch (error) {
551
+ record.state = "error";
552
+ emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
553
+ throw error;
351
554
  }
352
555
 
353
556
  resolvedImports.push({ ...reference, replacement });
557
+ linkedDependencies.push({
558
+ specifier: reference.specifier,
559
+ resolvedId: dependencyRecord?.id ?? resolved,
560
+ });
354
561
  }
355
562
 
356
- let linkedSource = applyImportReplacements(record.source, resolvedImports);
357
-
358
- // JSX produced by solid-tag needs @solidjs/html. Resolve it through the
359
- // same module system so host applications can share their existing runtime.
360
- let htmlImportSource;
361
- const needsHtmlRuntime = record.format !== "js" && analysis.needsHtmlRuntime !== false;
362
- if (needsHtmlRuntime) {
363
- const htmlResolved = resolveSpecifier("@solidjs/html", record.id);
364
- const htmlRecord = records.get(htmlResolved);
365
-
366
- if (htmlRecord) {
367
- nextDependencies.add(htmlRecord.id);
368
- htmlImportSource = htmlRecord.kind === "source"
369
- ? await ensureSourceUrl(htmlRecord, nextStack)
370
- : htmlRecord.kind === "host"
371
- ? await ensureHostUrl(htmlRecord)
372
- : htmlRecord.externalUrl;
373
- } else if (allowNativeImports) {
374
- htmlImportSource = htmlResolved;
375
- } else {
376
- throw new ModuleResolutionError("@solidjs/html", record.id);
563
+ let linkedSource;
564
+ try {
565
+ linkedSource = applyImportReplacements(record.source, resolvedImports);
566
+
567
+ // JSX produced by solid-tag needs @solidjs/html. Resolve it through the
568
+ // same module system so host applications can share their existing runtime.
569
+ let htmlImportSource;
570
+ const needsHtmlRuntime = record.format !== "js" && analysis.needsHtmlRuntime !== false;
571
+ if (needsHtmlRuntime) {
572
+ const htmlResolved = resolveSpecifier("@solidjs/html", record.id);
573
+ const htmlRecord = records.get(htmlResolved);
574
+
575
+ if (htmlRecord) {
576
+ nextDependencies.add(htmlRecord.id);
577
+ htmlImportSource = htmlRecord.kind === "source"
578
+ ? await ensureSourceUrl(htmlRecord, nextStack)
579
+ : htmlRecord.kind === "host"
580
+ ? await ensureHostUrl(htmlRecord)
581
+ : htmlRecord.externalUrl;
582
+ } else if (allowNativeImports) {
583
+ htmlImportSource = htmlResolved;
584
+ } else {
585
+ throw new ModuleResolutionError("@solidjs/html", record.id);
586
+ }
587
+
588
+ linkedDependencies.push({
589
+ specifier: "@solidjs/html",
590
+ resolvedId: htmlRecord?.id ?? htmlResolved,
591
+ });
377
592
  }
378
- }
379
593
 
380
- syncDependencyEdges(record, nextDependencies);
594
+ syncDependencyEdges(record, nextDependencies);
595
+ emit({
596
+ type: "module-linked",
597
+ id: record.id,
598
+ kind: record.kind,
599
+ version: record.version,
600
+ dependencies: linkedDependencies,
601
+ module: toModuleInfo(record),
602
+ });
381
603
 
382
- try {
383
- if (record.format === "js") {
384
- record.compiledCode = linkedSource;
385
- record.diagnostics = [];
386
- } else {
387
- const transformed = compiler.transform(linkedSource, {
388
- id: record.id,
389
- format: record.format,
390
- htmlImportSource,
391
- });
392
- record.compiledCode = transformed.code;
393
- record.diagnostics = [...(transformed.diagnostics ?? [])];
604
+ try {
605
+ if (record.format === "js") {
606
+ record.compiledCode = linkedSource;
607
+ record.diagnostics = [];
608
+ } else {
609
+ const transformed = compiler.transform(linkedSource, {
610
+ id: record.id,
611
+ format: record.format,
612
+ htmlImportSource,
613
+ });
614
+ record.compiledCode = transformed.code;
615
+ record.diagnostics = [...(transformed.diagnostics ?? [])];
616
+ }
617
+ } catch (error) {
618
+ record.state = "error";
619
+ const wrapped = new ModuleCompileError(record.id, error);
620
+ emitError(record, "compile", wrapped);
621
+ throw wrapped;
394
622
  }
395
623
  } catch (error) {
396
- record.state = "error";
397
- throw new ModuleCompileError(record.id, error);
624
+ if (record.state !== "error") {
625
+ record.state = "error";
626
+ emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
627
+ }
628
+ throw error;
398
629
  }
399
630
 
400
- record.url = moduleUrlBackend.create(appendSourceUrl(record.compiledCode, record.id));
631
+ try {
632
+ record.url = moduleUrlBackend.create(appendSourceUrl(record.compiledCode, record.id));
633
+ } catch (error) {
634
+ record.state = "error";
635
+ emitError(record, "url-create", error);
636
+ throw error;
637
+ }
401
638
  record.state = "compiled";
639
+ emit({
640
+ type: "module-compiled",
641
+ id: record.id,
642
+ kind: record.kind,
643
+ version: record.version,
644
+ module: toModuleInfo(record),
645
+ });
402
646
  return record.url;
403
647
  }
404
648
 
@@ -428,30 +672,69 @@ export function createRuntime(options = {}) {
428
672
  );
429
673
  }
430
674
 
431
- record.url = moduleUrlBackend.create(appendSourceUrl(lines.join("\n"), `host:${record.id}`));
675
+ try {
676
+ record.url = moduleUrlBackend.create(appendSourceUrl(lines.join("\n"), `host:${record.id}`));
677
+ } catch (error) {
678
+ emitError(record, "host-bridge", error);
679
+ throw error;
680
+ }
432
681
  return record.url;
433
682
  }
434
683
 
435
684
  function resolveSpecifier(specifier, importer) {
436
685
  const raw = String(specifier);
686
+ emit({
687
+ type: "module-resolving",
688
+ specifier: raw,
689
+ importer: importer ?? null,
690
+ });
437
691
 
438
- if (typeof customResolve === "function") {
439
- const custom = customResolve(raw, importer, api);
440
- if (typeof custom === "string" && custom) return normalizeResolved(custom, importer);
441
- }
442
-
443
- if (records.has(raw)) return raw;
444
- if (isAbsoluteUrl(raw)) return raw;
692
+ let resolved;
693
+ let resolutionKind;
445
694
 
446
- if (raw.startsWith("/")) return normalizePath(raw);
695
+ try {
696
+ if (typeof customResolve === "function") {
697
+ const custom = customResolve(raw, importer, api);
698
+ if (typeof custom === "string" && custom) {
699
+ resolved = normalizeResolved(custom, importer);
700
+ resolutionKind = "custom";
701
+ }
702
+ }
447
703
 
448
- if (isRelativeSpecifier(raw)) {
449
- if (!importer) return normalizePath(`/${raw}`);
450
- if (isAbsoluteUrl(importer)) return new URL(raw, importer).href;
451
- return normalizePath(`${dirname(importer)}/${raw}`);
704
+ if (resolved == null && records.has(raw)) {
705
+ resolved = raw;
706
+ resolutionKind = "registered";
707
+ }
708
+ if (resolved == null && isAbsoluteUrl(raw)) {
709
+ resolved = raw;
710
+ resolutionKind = "url";
711
+ }
712
+ if (resolved == null && raw.startsWith("/")) {
713
+ resolved = normalizePath(raw);
714
+ resolutionKind = "absolute";
715
+ }
716
+ if (resolved == null && isRelativeSpecifier(raw)) {
717
+ if (!importer) resolved = normalizePath(`/${raw}`);
718
+ else if (isAbsoluteUrl(importer)) resolved = new URL(raw, importer).href;
719
+ else resolved = normalizePath(`${dirname(importer)}/${raw}`);
720
+ resolutionKind = "relative";
721
+ }
722
+ if (resolved == null) {
723
+ resolved = raw;
724
+ resolutionKind = "native";
725
+ }
726
+ } catch (error) {
727
+ throw error;
452
728
  }
453
729
 
454
- return raw;
730
+ emit({
731
+ type: "module-resolved",
732
+ specifier: raw,
733
+ importer: importer ?? null,
734
+ resolvedId: resolved,
735
+ resolutionKind,
736
+ });
737
+ return resolved;
455
738
  }
456
739
 
457
740
  function syncDependencyEdges(record, nextDependencies) {
@@ -472,6 +755,37 @@ export function createRuntime(options = {}) {
472
755
  }
473
756
  }
474
757
 
758
+ function emit(event) {
759
+ if (events.size === 0) return;
760
+ const payload = {
761
+ runtimeId,
762
+ timestamp: Date.now(),
763
+ ...event,
764
+ };
765
+ if (eventBufferDepth > 0) {
766
+ eventBuffer.push(payload);
767
+ return;
768
+ }
769
+ events.emit(payload);
770
+ }
771
+
772
+ function flushEventBuffer() {
773
+ if (eventBuffer.length === 0) return;
774
+ const pending = eventBuffer.splice(0, eventBuffer.length);
775
+ for (const event of pending) events.emit(event);
776
+ }
777
+
778
+ function emitError(record, phase, error) {
779
+ emit({
780
+ type: "module-error",
781
+ id: record?.id,
782
+ kind: record?.kind,
783
+ version: record?.version,
784
+ phase,
785
+ error,
786
+ });
787
+ }
788
+
475
789
  function toModuleInfo(record) {
476
790
  return {
477
791
  id: record.id,