solid-tag-runtime 0.0.1 → 0.0.3

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/html.js ADDED
@@ -0,0 +1,832 @@
1
+ import { SolidTagRuntimeError } from "./errors.js";
2
+
3
+ const DEFAULT_SELECTOR = [
4
+ 'script[type="solid-jsx"]',
5
+ 'script[type="text/solid-jsx"]',
6
+ 'script[type="solid-js"]',
7
+ 'script[type="text/solid-js"]',
8
+ 'script[type="solid-module"]',
9
+ 'script[type="text/solid-module"]',
10
+ ].join(",");
11
+
12
+ const RUNTIME_SCOPE_ATTRIBUTE = "data-solid-runtime";
13
+ const elementOwners = new WeakMap();
14
+ const defaultControllers = new WeakMap();
15
+ let anonymousSequence = 0;
16
+ let controllerSequence = 0;
17
+
18
+ export class HTMLModuleError extends SolidTagRuntimeError {
19
+ constructor(message, options = {}) {
20
+ super(message, options);
21
+ this.name = "HTMLModuleError";
22
+ this.element = options.element;
23
+ this.moduleId = options.moduleId;
24
+ this.sourceUrl = options.sourceUrl;
25
+ }
26
+ }
27
+
28
+ export class HTMLModuleOwnershipError extends HTMLModuleError {
29
+ constructor(message, options = {}) {
30
+ super(message, options);
31
+ this.name = "HTMLModuleOwnershipError";
32
+ this.ownerScope = options.ownerScope;
33
+ this.requestedScope = options.requestedScope;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Create a persistent DOM/HTML adapter for one solid-tag-runtime instance.
39
+ *
40
+ * The controller owns DOM-to-module relationships. All ingestion paths
41
+ * (scan, observer, registerElement, append, addModule) converge on the same
42
+ * module-level WeakMap so an element can only be claimed once.
43
+ */
44
+ export function createHTMLRuntime(runtime, options = {}) {
45
+ assertRuntime(runtime);
46
+
47
+ const controller = {
48
+ id: `solid-tag-html-${++controllerSequence}`,
49
+ runtime,
50
+ scope: normalizeScope(options.scope),
51
+ root: options.root,
52
+ selector: options.selector ?? DEFAULT_SELECTOR,
53
+ acceptUnscoped: options.acceptUnscoped !== false,
54
+ executeEntries: options.executeEntries !== false,
55
+ fetch: options.fetch,
56
+ baseUrl: options.baseUrl,
57
+ MutationObserver: options.MutationObserver,
58
+ subtree: options.subtree !== false,
59
+ createElement: options.createElement,
60
+ appendTo: options.appendTo,
61
+ bindings: new Map(),
62
+ observer: undefined,
63
+ observerRoot: undefined,
64
+ ignoredExisting: new WeakSet(),
65
+ queue: Promise.resolve(),
66
+ lastError: undefined,
67
+ connected: false,
68
+ initial: emptyRegistrationResult(),
69
+ };
70
+
71
+ const api = {
72
+ get runtime() {
73
+ return runtime;
74
+ },
75
+ get scope() {
76
+ return controller.scope;
77
+ },
78
+ get connected() {
79
+ return controller.connected;
80
+ },
81
+ get initial() {
82
+ return controller.initial;
83
+ },
84
+ register,
85
+ observe,
86
+ flush,
87
+ disconnect,
88
+ defineElement,
89
+ registerElement,
90
+ append,
91
+ addModule,
92
+ owns,
93
+ getModuleId,
94
+ getElement,
95
+ };
96
+
97
+ controller.api = api;
98
+ return api;
99
+
100
+ /** One-shot scan of matching scripts. */
101
+ async function register(callOptions = {}) {
102
+ const root = getRoot(mergeOptions(controller, callOptions), "HTML runtime register");
103
+ const selector = callOptions.selector ?? controller.selector;
104
+ const elements = Array.from(root.querySelectorAll(selector)).filter(element =>
105
+ acceptsDiscoveredElement(controller, element, callOptions),
106
+ );
107
+
108
+ return processElements(controller, elements, {
109
+ ...callOptions,
110
+ root,
111
+ origin: callOptions.origin ?? "initial-scan",
112
+ executeEntries: callOptions.executeEntries ?? controller.executeEntries,
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Start MutationObserver-backed discovery. Existing matching scripts are
118
+ * registered by default. The controller itself is returned for chaining.
119
+ */
120
+ async function observe(callOptions = {}) {
121
+ if (controller.connected) return api;
122
+
123
+ const merged = mergeOptions(controller, callOptions);
124
+ const root = getRoot(merged, "HTML runtime observe");
125
+ const MutationObserverImpl = callOptions.MutationObserver ?? controller.MutationObserver ?? globalThis.MutationObserver;
126
+
127
+ if (typeof MutationObserverImpl !== "function") {
128
+ throw new TypeError(
129
+ "observe() requires MutationObserver support or a MutationObserver option.",
130
+ );
131
+ }
132
+
133
+ controller.observerRoot = root;
134
+ controller.connected = true;
135
+ controller.lastError = undefined;
136
+ controller.ignoredExisting = new WeakSet();
137
+
138
+ if (callOptions.registerExisting === false) {
139
+ for (const element of Array.from(root.querySelectorAll(callOptions.selector ?? controller.selector))) {
140
+ if (acceptsDiscoveredElement(controller, element, callOptions)) {
141
+ controller.ignoredExisting.add(element);
142
+ }
143
+ }
144
+ controller.initial = emptyRegistrationResult();
145
+ }
146
+
147
+ const observer = new MutationObserverImpl(() => {
148
+ void enqueueObserverScan(controller, callOptions).catch(() => {});
149
+ });
150
+
151
+ controller.observer = observer;
152
+ observer.observe(root, {
153
+ childList: true,
154
+ subtree: callOptions.subtree ?? controller.subtree,
155
+ });
156
+
157
+ try {
158
+ if (callOptions.registerExisting !== false) {
159
+ controller.initial = await enqueueObserverScan(controller, {
160
+ ...callOptions,
161
+ origin: "observer-initial",
162
+ });
163
+ }
164
+ } catch (error) {
165
+ disconnect();
166
+ throw error;
167
+ }
168
+
169
+ return api;
170
+ }
171
+
172
+ /** Wait for observer work, then synchronously scan for newly discoverable scripts. */
173
+ async function flush(callOptions = {}) {
174
+ await controller.queue;
175
+
176
+ if (controller.lastError) {
177
+ const error = controller.lastError;
178
+ controller.lastError = undefined;
179
+ throw error;
180
+ }
181
+
182
+ if (!controller.connected) return emptyRegistrationResult();
183
+ return enqueueObserverScan(controller, callOptions);
184
+ }
185
+
186
+ function disconnect() {
187
+ if (!controller.connected) return;
188
+ controller.connected = false;
189
+ controller.observer?.disconnect();
190
+ controller.observer = undefined;
191
+ controller.observerRoot = undefined;
192
+ }
193
+
194
+ /** Define one element without automatically executing an entry module. */
195
+ async function defineElement(element, callOptions = {}) {
196
+ const result = await processElements(controller, [element], {
197
+ ...callOptions,
198
+ origin: callOptions.origin ?? "define-element",
199
+ explicit: true,
200
+ executeEntries: false,
201
+ });
202
+ return result.modules[0] ?? definitionFromOwnedElement(controller, element);
203
+ }
204
+
205
+ /** Explicitly claim/register an element for this controller. */
206
+ async function registerElement(element, callOptions = {}) {
207
+ const result = await processElements(controller, [element], {
208
+ ...callOptions,
209
+ origin: callOptions.origin ?? "register-element",
210
+ explicit: true,
211
+ executeEntries: callOptions.executeEntries ?? controller.executeEntries,
212
+ });
213
+ return result.modules[0] ?? definitionFromOwnedElement(controller, element);
214
+ }
215
+
216
+ /**
217
+ * Claim a user-created script before DOM insertion, append it to this
218
+ * controller's target, then register it directly. The observer will see the
219
+ * mutation later but skip it because ownership was already established.
220
+ */
221
+ async function append(element, callOptions = {}) {
222
+ const claim = claimElement(controller, element, {
223
+ ...callOptions,
224
+ origin: callOptions.origin ?? "append",
225
+ explicit: true,
226
+ });
227
+
228
+ const target = getAppendTarget(controller, callOptions);
229
+ appendElement(target, element);
230
+
231
+ const result = await processClaims(controller, claim.fresh ? [claim.record] : [], {
232
+ ...callOptions,
233
+ origin: callOptions.origin ?? "append",
234
+ executeEntries: callOptions.executeEntries ?? controller.executeEntries,
235
+ });
236
+
237
+ if (!claim.fresh) await claim.record.promise;
238
+ return result.modules[0] ?? claim.record.definition;
239
+ }
240
+
241
+ /** Create a runtime script element and add it through append(). */
242
+ async function addModule(moduleOptions = {}) {
243
+ const format = normalizeFormat(moduleOptions.format ?? inferFormat({
244
+ explicitFormat: undefined,
245
+ language: moduleOptions.language,
246
+ type: moduleOptions.type,
247
+ moduleId: moduleOptions.id ?? moduleOptions.module,
248
+ sourceUrl: moduleOptions.src,
249
+ }));
250
+
251
+ const createElement = moduleOptions.createElement
252
+ ?? controller.createElement
253
+ ?? getDocumentForCreation(controller, moduleOptions)?.createElement?.bind(getDocumentForCreation(controller, moduleOptions));
254
+
255
+ if (typeof createElement !== "function") {
256
+ throw new TypeError(
257
+ "addModule() requires document.createElement(), a root ownerDocument, or a createElement option.",
258
+ );
259
+ }
260
+
261
+ const element = createElement("script");
262
+ if (!element || typeof element.setAttribute !== "function") {
263
+ throw new TypeError("addModule() createElement must return a script element-like object with setAttribute().");
264
+ }
265
+
266
+ const type = moduleOptions.type ?? (format === "js" ? "solid-js" : "solid-jsx");
267
+ element.setAttribute("type", type);
268
+
269
+ const id = nonEmpty(moduleOptions.id ?? moduleOptions.module);
270
+ if (id) element.setAttribute("module", id);
271
+ if (moduleOptions.src) element.setAttribute("src", String(moduleOptions.src));
272
+ if (moduleOptions.entry) element.setAttribute("entry", "");
273
+ if (moduleOptions.language) element.setAttribute("language", String(moduleOptions.language));
274
+ if (moduleOptions.source != null) element.textContent = String(moduleOptions.source);
275
+
276
+ const definition = await append(element, {
277
+ ...moduleOptions,
278
+ format,
279
+ origin: "add-module",
280
+ });
281
+
282
+ return {
283
+ ...definition,
284
+ element,
285
+ };
286
+ }
287
+
288
+ function owns(element) {
289
+ return elementOwners.get(element)?.owner === controller;
290
+ }
291
+
292
+ function getModuleId(element) {
293
+ const record = elementOwners.get(element);
294
+ return record?.owner === controller ? record.moduleId : undefined;
295
+ }
296
+
297
+ function getElement(id) {
298
+ const normalized = normalizeModuleId(runtime, id);
299
+ return controller.bindings.get(normalized)?.element;
300
+ }
301
+ }
302
+
303
+ /**
304
+ * Discover runtime module scripts under a Document/Element-like root, define
305
+ * all of them first, then optionally execute scripts marked with `entry`.
306
+ *
307
+ * Backwards-compatible convenience wrapper over the runtime's default HTML
308
+ * controller.
309
+ */
310
+ export async function registerHTML(runtime, options = {}) {
311
+ return getDefaultController(runtime, options).register(options);
312
+ }
313
+
314
+ /** Backwards-compatible observer convenience wrapper. */
315
+ export async function observeHTML(runtime, options = {}) {
316
+ const controller = getDefaultController(runtime, options);
317
+ await controller.observe(options);
318
+ return controller;
319
+ }
320
+
321
+ /** Define one <script> element as a runtime module without executing it. */
322
+ export async function defineScript(runtime, element, options = {}) {
323
+ return getDefaultController(runtime, options).defineElement(element, options);
324
+ }
325
+
326
+ export const htmlModuleSelector = DEFAULT_SELECTOR;
327
+ export const htmlRuntimeScopeAttribute = RUNTIME_SCOPE_ATTRIBUTE;
328
+
329
+ async function enqueueObserverScan(controller, callOptions = {}) {
330
+ const task = controller.queue.then(async () => {
331
+ if (!controller.connected) return emptyRegistrationResult();
332
+
333
+ const root = controller.observerRoot ?? getRoot(mergeOptions(controller, callOptions), "HTML runtime observer");
334
+ const selector = callOptions.selector ?? controller.selector;
335
+ const elements = Array.from(root.querySelectorAll(selector)).filter(element => {
336
+ if (controller.ignoredExisting.has(element)) return false;
337
+ if (!acceptsDiscoveredElement(controller, element, callOptions)) return false;
338
+
339
+ const ownership = elementOwners.get(element);
340
+ if (!ownership) return true;
341
+ if (ownership.owner === controller) return false;
342
+
343
+ throw ownershipError(controller, element, ownership);
344
+ });
345
+
346
+ return processElements(controller, elements, {
347
+ ...callOptions,
348
+ root,
349
+ origin: callOptions.origin ?? "observer",
350
+ executeEntries: callOptions.executeEntries ?? controller.executeEntries,
351
+ });
352
+ });
353
+
354
+ controller.queue = task.then(
355
+ () => undefined,
356
+ error => {
357
+ controller.lastError = error;
358
+ if (typeof callOptions.onError === "function") {
359
+ callOptions.onError(error);
360
+ } else {
361
+ globalThis.console?.error?.("solid-tag-runtime/html observer error", error);
362
+ }
363
+ },
364
+ );
365
+
366
+ return task;
367
+ }
368
+
369
+ async function processElements(controller, elements, options = {}) {
370
+ const freshRecords = [];
371
+ const existingRecords = [];
372
+
373
+ // Claim the entire batch synchronously before any asynchronous source loading.
374
+ // This prevents observer/manual registration races and reserves module IDs.
375
+ for (const element of elements) {
376
+ if (!options.explicit && !acceptsDiscoveredElement(controller, element, options)) continue;
377
+
378
+ const claim = claimElement(controller, element, options);
379
+ if (claim.fresh) freshRecords.push(claim.record);
380
+ else existingRecords.push(claim.record);
381
+ }
382
+
383
+ const result = await processClaims(controller, freshRecords, options);
384
+
385
+ // Same-controller repeated registration is idempotent. Await currently pending
386
+ // registrations, but do not report/re-execute them as newly registered modules.
387
+ await Promise.all(existingRecords.map(record => record.promise));
388
+ return result;
389
+ }
390
+
391
+ async function processClaims(controller, records, options = {}) {
392
+ if (records.length === 0) return emptyRegistrationResult();
393
+
394
+ let prepared;
395
+ try {
396
+ prepared = await Promise.all(records.map(record => prepareRecordSource(controller, record, options)));
397
+ } catch (error) {
398
+ for (const record of records) rejectRecord(record, error);
399
+ throw error;
400
+ }
401
+
402
+ const modules = [];
403
+ try {
404
+ // Preserve the graph invariant: every module in this discovered batch is
405
+ // defined before any entry in the batch executes.
406
+ for (let index = 0; index < records.length; index++) {
407
+ const record = records[index];
408
+ const item = prepared[index];
409
+ controller.runtime.define(record.moduleId, item.source, { format: record.format });
410
+
411
+ record.state = "defined";
412
+ record.definition = definitionFromRecord(record);
413
+ record.resolve(record.definition);
414
+ modules.push(record.definition);
415
+ }
416
+ } catch (error) {
417
+ for (const record of records) {
418
+ if (record.state === "claimed" || record.state === "loading") rejectRecord(record, error);
419
+ }
420
+ throw error;
421
+ }
422
+
423
+ const entries = modules.filter(module => module.entry);
424
+ const executed = [];
425
+
426
+ if (options.executeEntries !== false) {
427
+ for (const entry of entries) {
428
+ const record = elementOwners.get(entry.element);
429
+ if (record) record.state = "executing";
430
+
431
+ try {
432
+ const namespace = await controller.runtime.import(entry.id);
433
+ if (record) record.state = "ready";
434
+ executed.push({ id: entry.id, namespace });
435
+ } catch (error) {
436
+ if (record) {
437
+ record.state = "error";
438
+ record.error = error;
439
+ }
440
+ throw error;
441
+ }
442
+ }
443
+ }
444
+
445
+ return {
446
+ modules,
447
+ entries: entries.map(entry => entry.id),
448
+ executed,
449
+ };
450
+ }
451
+
452
+ function claimElement(controller, element, options = {}) {
453
+ assertElement(element, options.origin ?? "HTML runtime");
454
+
455
+ const existing = elementOwners.get(element);
456
+ if (existing) {
457
+ if (existing.owner === controller) return { fresh: false, record: existing };
458
+ throw ownershipError(controller, element, existing);
459
+ }
460
+
461
+ assertExplicitScopeCompatibility(controller, element, options);
462
+ stampScope(controller, element, options);
463
+
464
+ const metadata = inspectElement(controller, element, options);
465
+ const moduleId = normalizeModuleId(controller.runtime, metadata.id);
466
+ const binding = controller.bindings.get(moduleId);
467
+
468
+ if (binding && binding.element !== element) {
469
+ throw new HTMLModuleError(
470
+ `HTML module ${JSON.stringify(moduleId)} is already bound to another script element in this HTML runtime.`,
471
+ { element, moduleId, sourceUrl: metadata.sourceUrl },
472
+ );
473
+ }
474
+
475
+ if (!binding && controller.runtime.has(moduleId)) {
476
+ throw new HTMLModuleError(
477
+ `Runtime module ${JSON.stringify(moduleId)} already exists and is not owned by this HTML runtime. Use runtime.update() for explicit replacement.`,
478
+ { element, moduleId, sourceUrl: metadata.sourceUrl },
479
+ );
480
+ }
481
+
482
+ let resolve;
483
+ let reject;
484
+ const promise = new Promise((resolvePromise, rejectPromise) => {
485
+ resolve = resolvePromise;
486
+ reject = rejectPromise;
487
+ });
488
+ // A failed background observer registration may not have a caller awaiting
489
+ // this per-element promise. Mark the rejection as observed while preserving
490
+ // the original promise for explicit callers.
491
+ void promise.catch(() => {});
492
+
493
+ const record = {
494
+ owner: controller,
495
+ element,
496
+ moduleId,
497
+ format: metadata.format,
498
+ entry: metadata.entry,
499
+ sourceUrl: metadata.sourceUrl,
500
+ inline: !metadata.sourceUrl,
501
+ state: "claimed",
502
+ origin: options.origin ?? "unknown",
503
+ promise,
504
+ resolve,
505
+ reject,
506
+ definition: undefined,
507
+ error: undefined,
508
+ };
509
+
510
+ elementOwners.set(element, record);
511
+ controller.bindings.set(moduleId, record);
512
+ return { fresh: true, record };
513
+ }
514
+
515
+ async function prepareRecordSource(controller, record, options) {
516
+ record.state = "loading";
517
+
518
+ let source;
519
+ if (record.sourceUrl) {
520
+ source = await fetchSource(
521
+ record.sourceUrl,
522
+ options.fetch ?? controller.fetch ?? globalThis.fetch,
523
+ record.element,
524
+ record.moduleId,
525
+ );
526
+ } else {
527
+ source = record.element.textContent ?? "";
528
+ }
529
+
530
+ return { source };
531
+ }
532
+
533
+ function inspectElement(controller, element, options = {}) {
534
+ const type = normalizeType(element.getAttribute("type"));
535
+ const entry = hasAttribute(element, "entry");
536
+ const srcAttribute = nonEmpty(element.getAttribute("src"));
537
+ const explicitId = nonEmpty(element.getAttribute("module"));
538
+ const baseUrl = options.baseUrl
539
+ ?? controller.baseUrl
540
+ ?? element.baseURI
541
+ ?? options.root?.baseURI
542
+ ?? controller.root?.baseURI;
543
+ const sourceUrl = srcAttribute ? resolveSourceUrl(srcAttribute, baseUrl) : undefined;
544
+
545
+ const format = inferFormat({
546
+ explicitFormat: options.format,
547
+ language: nonEmpty(element.getAttribute("language")),
548
+ type,
549
+ moduleId: explicitId,
550
+ sourceUrl,
551
+ });
552
+
553
+ const id = explicitId ?? sourceUrl ?? (entry ? createAnonymousId(format) : undefined);
554
+ if (!id) {
555
+ throw new HTMLModuleError(
556
+ "Inline solid runtime module scripts require a `module` attribute unless they are marked `entry`.",
557
+ { element },
558
+ );
559
+ }
560
+
561
+ return { id, format, entry, sourceUrl };
562
+ }
563
+
564
+ function definitionFromRecord(record) {
565
+ return {
566
+ id: record.moduleId,
567
+ format: record.format,
568
+ entry: record.entry,
569
+ sourceUrl: record.sourceUrl,
570
+ inline: record.inline,
571
+ element: record.element,
572
+ };
573
+ }
574
+
575
+ function definitionFromOwnedElement(controller, element) {
576
+ const record = elementOwners.get(element);
577
+ if (!record || record.owner !== controller) return undefined;
578
+ return record.definition ?? definitionFromRecord(record);
579
+ }
580
+
581
+ function rejectRecord(record, error) {
582
+ if (record.state === "error") return;
583
+ record.state = "error";
584
+ record.error = error;
585
+ record.reject(error);
586
+ }
587
+
588
+ function acceptsDiscoveredElement(controller, element, options = {}) {
589
+ const elementScope = nonEmpty(element?.getAttribute?.(RUNTIME_SCOPE_ATTRIBUTE));
590
+ const scope = normalizeScope(options.scope ?? controller.scope);
591
+ const acceptUnscoped = options.acceptUnscoped ?? controller.acceptUnscoped;
592
+
593
+ if (scope) {
594
+ if (elementScope) return elementScope === scope;
595
+ return acceptUnscoped !== false;
596
+ }
597
+
598
+ // An unscoped controller never silently steals a declaratively scoped script.
599
+ return !elementScope;
600
+ }
601
+
602
+ function assertExplicitScopeCompatibility(controller, element, options = {}) {
603
+ const requestedScope = normalizeScope(options.scope ?? controller.scope);
604
+ const elementScope = nonEmpty(element.getAttribute(RUNTIME_SCOPE_ATTRIBUTE));
605
+
606
+ if (elementScope && requestedScope !== elementScope) {
607
+ throw new HTMLModuleOwnershipError(
608
+ `Script is scoped to HTML runtime ${JSON.stringify(elementScope)} and cannot be claimed by ${JSON.stringify(requestedScope ?? "(unscoped)")}.`,
609
+ {
610
+ element,
611
+ ownerScope: elementScope,
612
+ requestedScope,
613
+ },
614
+ );
615
+ }
616
+ }
617
+
618
+ function stampScope(controller, element, options = {}) {
619
+ const scope = normalizeScope(options.scope ?? controller.scope);
620
+ if (!scope) return;
621
+ if (nonEmpty(element.getAttribute(RUNTIME_SCOPE_ATTRIBUTE))) return;
622
+
623
+ if (typeof element.setAttribute !== "function") {
624
+ throw new TypeError(
625
+ `Scoped HTML runtimes require script elements with setAttribute() so ${RUNTIME_SCOPE_ATTRIBUTE} can be recorded.`,
626
+ );
627
+ }
628
+
629
+ element.setAttribute(RUNTIME_SCOPE_ATTRIBUTE, scope);
630
+ }
631
+
632
+ function ownershipError(controller, element, ownership) {
633
+ return new HTMLModuleOwnershipError(
634
+ `Runtime script element is already owned by HTML runtime ${JSON.stringify(ownership.owner.scope ?? ownership.owner.id)} and cannot be claimed by ${JSON.stringify(controller.scope ?? controller.id)}.`,
635
+ {
636
+ element,
637
+ moduleId: ownership.moduleId,
638
+ sourceUrl: ownership.sourceUrl,
639
+ ownerScope: ownership.owner.scope,
640
+ requestedScope: controller.scope,
641
+ },
642
+ );
643
+ }
644
+
645
+ function getDefaultController(runtime, options = {}) {
646
+ assertRuntime(runtime);
647
+ let controller = defaultControllers.get(runtime);
648
+ if (!controller) {
649
+ controller = createHTMLRuntime(runtime, {
650
+ root: options.root,
651
+ selector: options.selector,
652
+ executeEntries: options.executeEntries,
653
+ fetch: options.fetch,
654
+ baseUrl: options.baseUrl,
655
+ MutationObserver: options.MutationObserver,
656
+ subtree: options.subtree,
657
+ createElement: options.createElement,
658
+ appendTo: options.appendTo,
659
+ scope: options.scope,
660
+ acceptUnscoped: options.acceptUnscoped,
661
+ });
662
+ defaultControllers.set(runtime, controller);
663
+ }
664
+ return controller;
665
+ }
666
+
667
+ function getAppendTarget(controller, options = {}) {
668
+ const explicit = options.appendTo ?? controller.appendTo;
669
+ if (explicit) return explicit;
670
+
671
+ const root = options.root ?? controller.root ?? globalThis.document;
672
+ if (root?.body) return root.body;
673
+ if (root?.documentElement && root.documentElement !== root) return root.documentElement;
674
+ return root;
675
+ }
676
+
677
+ function appendElement(target, element) {
678
+ if (target && typeof target.append === "function") {
679
+ target.append(element);
680
+ return;
681
+ }
682
+ if (target && typeof target.appendChild === "function") {
683
+ target.appendChild(element);
684
+ return;
685
+ }
686
+ throw new TypeError("append() requires an append target with append() or appendChild().");
687
+ }
688
+
689
+ function getDocumentForCreation(controller, options = {}) {
690
+ return options.document
691
+ ?? controller.root?.ownerDocument
692
+ ?? controller.root
693
+ ?? globalThis.document;
694
+ }
695
+
696
+ function mergeOptions(controller, options) {
697
+ return {
698
+ root: options.root ?? controller.root,
699
+ selector: options.selector ?? controller.selector,
700
+ executeEntries: options.executeEntries ?? controller.executeEntries,
701
+ fetch: options.fetch ?? controller.fetch,
702
+ baseUrl: options.baseUrl ?? controller.baseUrl,
703
+ MutationObserver: options.MutationObserver ?? controller.MutationObserver,
704
+ subtree: options.subtree ?? controller.subtree,
705
+ scope: options.scope ?? controller.scope,
706
+ acceptUnscoped: options.acceptUnscoped ?? controller.acceptUnscoped,
707
+ };
708
+ }
709
+
710
+ function emptyRegistrationResult() {
711
+ return {
712
+ modules: [],
713
+ entries: [],
714
+ executed: [],
715
+ };
716
+ }
717
+
718
+ function getRoot(options, caller) {
719
+ const root = options.root ?? globalThis.document;
720
+ if (!root || typeof root.querySelectorAll !== "function") {
721
+ throw new TypeError(`${caller} requires a DOM root with querySelectorAll().`);
722
+ }
723
+ return root;
724
+ }
725
+
726
+ function assertRuntime(runtime) {
727
+ if (!runtime || typeof runtime.define !== "function" || typeof runtime.import !== "function") {
728
+ throw new TypeError("Expected a solid-tag-runtime instance.");
729
+ }
730
+ }
731
+
732
+ function assertElement(element, caller) {
733
+ if (!element || typeof element.getAttribute !== "function") {
734
+ throw new TypeError(`${caller} expects a script element-like object.`);
735
+ }
736
+ }
737
+
738
+ function normalizeScope(value) {
739
+ return nonEmpty(value);
740
+ }
741
+
742
+ function normalizeModuleId(runtime, id) {
743
+ return runtime.resolve(String(id));
744
+ }
745
+
746
+ function normalizeType(value) {
747
+ return String(value ?? "").trim().toLowerCase();
748
+ }
749
+
750
+ function inferFormat({ explicitFormat, language, type, moduleId, sourceUrl }) {
751
+ if (explicitFormat != null) return normalizeFormat(explicitFormat);
752
+ if (language != null) return normalizeFormat(language);
753
+
754
+ if (type === "solid-js" || type === "text/solid-js") return "js";
755
+ if (type === "solid-jsx" || type === "text/solid-jsx") return "jsx";
756
+
757
+ const target = moduleId ?? sourceUrl ?? "";
758
+ const pathname = stripQueryAndHash(target).toLowerCase();
759
+ if (pathname.endsWith(".js") || pathname.endsWith(".mjs")) return "js";
760
+ return "jsx";
761
+ }
762
+
763
+ function normalizeFormat(value) {
764
+ const format = String(value).trim().toLowerCase();
765
+ if (format === "js" || format === "javascript") return "js";
766
+ if (format === "jsx") return "jsx";
767
+ throw new HTMLModuleError(`Unsupported HTML runtime module format ${JSON.stringify(value)}.`);
768
+ }
769
+
770
+ async function fetchSource(url, fetchImpl, element, moduleId) {
771
+ if (typeof fetchImpl !== "function") {
772
+ throw new HTMLModuleError(
773
+ `Cannot load ${JSON.stringify(url)} because no fetch implementation is available.`,
774
+ { element, moduleId, sourceUrl: url },
775
+ );
776
+ }
777
+
778
+ let response;
779
+ try {
780
+ response = await fetchImpl(url);
781
+ } catch (cause) {
782
+ throw new HTMLModuleError(
783
+ `Failed to fetch runtime module ${JSON.stringify(url)}.`,
784
+ { cause, element, moduleId, sourceUrl: url },
785
+ );
786
+ }
787
+
788
+ if (!response || response.ok === false) {
789
+ const status = response?.status ? ` (${response.status}${response.statusText ? ` ${response.statusText}` : ""})` : "";
790
+ throw new HTMLModuleError(
791
+ `Failed to fetch runtime module ${JSON.stringify(url)}${status}.`,
792
+ { element, moduleId, sourceUrl: url },
793
+ );
794
+ }
795
+
796
+ try {
797
+ return await response.text();
798
+ } catch (cause) {
799
+ throw new HTMLModuleError(
800
+ `Failed to read runtime module ${JSON.stringify(url)} as text.`,
801
+ { cause, element, moduleId, sourceUrl: url },
802
+ );
803
+ }
804
+ }
805
+
806
+ function resolveSourceUrl(src, baseUrl) {
807
+ try {
808
+ if (baseUrl) return new URL(src, baseUrl).href;
809
+ return new URL(src).href;
810
+ } catch {
811
+ return src;
812
+ }
813
+ }
814
+
815
+ function createAnonymousId(format) {
816
+ const extension = format === "js" ? "js" : "jsx";
817
+ return `/__solid_tag_runtime__/html-entry-${++anonymousSequence}.${extension}`;
818
+ }
819
+
820
+ function stripQueryAndHash(value) {
821
+ return String(value).split(/[?#]/, 1)[0];
822
+ }
823
+
824
+ function nonEmpty(value) {
825
+ const text = String(value ?? "").trim();
826
+ return text || undefined;
827
+ }
828
+
829
+ function hasAttribute(element, name) {
830
+ if (typeof element.hasAttribute === "function") return element.hasAttribute(name);
831
+ return element.getAttribute(name) != null;
832
+ }