solid-tag-runtime 0.0.12 → 0.0.13
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/ARCHITECTURE.md +147 -37
- package/README.md +76 -899
- package/docs/README.md +23 -0
- package/docs/api/compile-cache.md +103 -0
- package/docs/api/html.md +83 -0
- package/docs/api/runtime.md +87 -0
- package/docs/compile-cache.md +271 -0
- package/docs/getting-started.md +105 -0
- package/docs/html-runtime.md +112 -0
- package/docs/lifecycle-events.md +77 -0
- package/docs/modules.md +93 -0
- package/docs/rendering.md +65 -0
- package/docs/solid-render.md +111 -0
- package/examples/basic.js +41 -0
- package/examples/compile-cache.js +35 -0
- package/examples/main.tsx +318 -0
- package/examples/render.html +79 -0
- package/index.d.ts +183 -27
- package/package.json +5 -3
- package/src/compile-cache.js +340 -0
- package/src/compiler.js +86 -38
- package/src/index.js +6 -0
- package/src/runtime.js +656 -74
package/src/runtime.js
CHANGED
|
@@ -6,8 +6,11 @@ import {
|
|
|
6
6
|
} from "./errors.js";
|
|
7
7
|
import { createDefaultModuleUrlBackend } from "./url-backend.js";
|
|
8
8
|
import { createEventDispatcher } from "./events.js";
|
|
9
|
+
import { approximateByteSize, sha256, stableSerialize } from "./compile-cache.js";
|
|
9
10
|
|
|
10
11
|
const HOST_REGISTRY_KEY = "__solidTagRuntimeHostModules__";
|
|
12
|
+
const COMPILED_ARTIFACT_ABI = "solid-tag-runtime:compiled-artifact:v1";
|
|
13
|
+
const COMPILED_ARTIFACT_SCHEMA = 1;
|
|
11
14
|
let runtimeSequence = 0;
|
|
12
15
|
|
|
13
16
|
export function createRuntime(options = {}) {
|
|
@@ -18,10 +21,11 @@ export function createRuntime(options = {}) {
|
|
|
18
21
|
allowNativeImports = true,
|
|
19
22
|
resolve: customResolve,
|
|
20
23
|
moduleUrlBackend = createDefaultModuleUrlBackend(),
|
|
24
|
+
compileCache: compileCacheOptions,
|
|
21
25
|
} = options;
|
|
22
26
|
|
|
23
|
-
if (!compiler || typeof compiler.analyze !== "function" || typeof compiler.transform !== "function") {
|
|
24
|
-
throw new TypeError("createRuntime() requires
|
|
27
|
+
if (!compiler || (typeof compiler.compile !== "function" && (typeof compiler.analyze !== "function" || typeof compiler.transform !== "function"))) {
|
|
28
|
+
throw new TypeError("createRuntime() requires compile(), or analyze() + transform(), on its compiler.");
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
const runtimeId = `solid-tag-runtime-${++runtimeSequence}-${randomId()}`;
|
|
@@ -35,6 +39,10 @@ export function createRuntime(options = {}) {
|
|
|
35
39
|
});
|
|
36
40
|
let eventBufferDepth = 0;
|
|
37
41
|
const eventBuffer = [];
|
|
42
|
+
const memoryCompileArtifacts = new Map();
|
|
43
|
+
const inFlightCompiles = new Map();
|
|
44
|
+
const compileCacheConfig = normalizeCompileCacheOptions(compileCacheOptions);
|
|
45
|
+
let warnedMissingCompilerFingerprint = false;
|
|
38
46
|
|
|
39
47
|
const globalRegistry = getHostRegistry();
|
|
40
48
|
const hostNamespaces = new Map();
|
|
@@ -61,6 +69,7 @@ export function createRuntime(options = {}) {
|
|
|
61
69
|
getModuleInfo,
|
|
62
70
|
dispose,
|
|
63
71
|
resolve: resolvePublic,
|
|
72
|
+
compileCache: createCompileCacheManager(),
|
|
64
73
|
};
|
|
65
74
|
|
|
66
75
|
for (const [id, namespace] of Object.entries(modules)) defineModule(id, namespace);
|
|
@@ -141,6 +150,10 @@ export function createRuntime(options = {}) {
|
|
|
141
150
|
dependencies: new Set(),
|
|
142
151
|
dependents: existing?.dependents ?? new Set(),
|
|
143
152
|
compiledCode: undefined,
|
|
153
|
+
compiledArtifact: undefined,
|
|
154
|
+
compiledArtifactKey: undefined,
|
|
155
|
+
compileCacheInfo: createInitialCompileCacheInfo(),
|
|
156
|
+
forceCompileRefresh: false,
|
|
144
157
|
diagnostics: [],
|
|
145
158
|
url: undefined,
|
|
146
159
|
importPromise: undefined,
|
|
@@ -302,7 +315,7 @@ export function createRuntime(options = {}) {
|
|
|
302
315
|
return record.importPromise;
|
|
303
316
|
}
|
|
304
317
|
|
|
305
|
-
async function compile(id) {
|
|
318
|
+
async function compile(id, compileOptions = {}) {
|
|
306
319
|
assertActive();
|
|
307
320
|
const resolved = resolveSpecifier(String(id), null);
|
|
308
321
|
const record = records.get(resolved);
|
|
@@ -318,7 +331,14 @@ export function createRuntime(options = {}) {
|
|
|
318
331
|
};
|
|
319
332
|
}
|
|
320
333
|
|
|
321
|
-
const
|
|
334
|
+
const cacheMode = normalizeCompileCacheMode(compileOptions.cache);
|
|
335
|
+
if (cacheMode !== "use") {
|
|
336
|
+
// A fresh linked URL for the requested module makes any already-linked
|
|
337
|
+
// dependents stale as well. Invalidate the executable graph while
|
|
338
|
+
// preserving their reusable compiler artifacts.
|
|
339
|
+
invalidate(record.id, { dependents: true, reason: `compile-cache-${cacheMode}` });
|
|
340
|
+
}
|
|
341
|
+
const url = await ensureSourceUrl(record, [], { cacheMode });
|
|
322
342
|
return {
|
|
323
343
|
id: record.id,
|
|
324
344
|
kind: record.kind,
|
|
@@ -365,28 +385,40 @@ export function createRuntime(options = {}) {
|
|
|
365
385
|
const normalized = normalizeLookupId(id);
|
|
366
386
|
const includeDependents = invalidateOptions.dependents ?? true;
|
|
367
387
|
const reason = invalidateOptions.reason ?? "explicit";
|
|
388
|
+
const forceCompile = invalidateOptions.compile === true;
|
|
368
389
|
const seen = new Set();
|
|
369
390
|
|
|
370
|
-
invalidateOne(normalized);
|
|
391
|
+
invalidateOne(normalized, true);
|
|
371
392
|
return [...seen];
|
|
372
393
|
|
|
373
|
-
function invalidateOne(targetId) {
|
|
394
|
+
function invalidateOne(targetId, isRequestedModule = false) {
|
|
374
395
|
if (seen.has(targetId)) return;
|
|
375
396
|
seen.add(targetId);
|
|
376
397
|
const record = records.get(targetId);
|
|
377
398
|
if (!record) return;
|
|
378
399
|
|
|
379
400
|
if (includeDependents) {
|
|
380
|
-
for (const dependent of record.dependents) invalidateOne(dependent);
|
|
401
|
+
for (const dependent of record.dependents) invalidateOne(dependent, false);
|
|
381
402
|
}
|
|
382
403
|
|
|
383
404
|
if (record.kind === "source") {
|
|
384
405
|
revokeRecordUrl(record);
|
|
385
406
|
record.compiledCode = undefined;
|
|
386
|
-
record.diagnostics = [];
|
|
387
407
|
record.importPromise = undefined;
|
|
388
408
|
record.namespace = undefined;
|
|
389
409
|
record.state = "defined";
|
|
410
|
+
|
|
411
|
+
// Normal graph invalidation preserves the pre-link compiler artifact.
|
|
412
|
+
// compile:true only applies to the explicitly requested module, never
|
|
413
|
+
// to its transitive dependents.
|
|
414
|
+
if (forceCompile && isRequestedModule) {
|
|
415
|
+
if (record.compiledArtifactKey) memoryCompileArtifacts.delete(record.compiledArtifactKey);
|
|
416
|
+
record.compiledArtifact = undefined;
|
|
417
|
+
record.compiledArtifactKey = undefined;
|
|
418
|
+
record.forceCompileRefresh = true;
|
|
419
|
+
record.compileCacheInfo = createInitialCompileCacheInfo();
|
|
420
|
+
record.diagnostics = [];
|
|
421
|
+
}
|
|
390
422
|
} else if (record.kind === "host") {
|
|
391
423
|
revokeRecordUrl(record);
|
|
392
424
|
record.state = "ready";
|
|
@@ -493,7 +525,539 @@ export function createRuntime(options = {}) {
|
|
|
493
525
|
return resolveSpecifier(specifier, importer ?? null);
|
|
494
526
|
}
|
|
495
527
|
|
|
496
|
-
|
|
528
|
+
function createInitialCompileCacheInfo() {
|
|
529
|
+
return {
|
|
530
|
+
enabled: Boolean(compileCacheConfig),
|
|
531
|
+
status: "none",
|
|
532
|
+
source: undefined,
|
|
533
|
+
key: undefined,
|
|
534
|
+
compilerFingerprint: undefined,
|
|
535
|
+
sourceHash: undefined,
|
|
536
|
+
compileContextHash: undefined,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function createCompileCacheManager() {
|
|
541
|
+
return {
|
|
542
|
+
enabled: Boolean(compileCacheConfig),
|
|
543
|
+
namespace: compileCacheConfig?.namespace,
|
|
544
|
+
version: compileCacheConfig?.version,
|
|
545
|
+
store: compileCacheConfig?.store?.kind ?? (compileCacheConfig ? "custom" : undefined),
|
|
546
|
+
async invalidate(id) {
|
|
547
|
+
assertActive();
|
|
548
|
+
if (!compileCacheConfig) return false;
|
|
549
|
+
const record = records.get(normalizeLookupId(id));
|
|
550
|
+
if (!record || record.kind !== "source") return false;
|
|
551
|
+
|
|
552
|
+
const identity = await createCompileIdentity(record);
|
|
553
|
+
if (!identity?.key) {
|
|
554
|
+
record.compiledArtifact = undefined;
|
|
555
|
+
record.compiledArtifactKey = undefined;
|
|
556
|
+
record.forceCompileRefresh = true;
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
memoryCompileArtifacts.delete(identity.key);
|
|
561
|
+
if (record.compiledArtifactKey === identity.key) {
|
|
562
|
+
record.compiledArtifact = undefined;
|
|
563
|
+
record.compiledArtifactKey = undefined;
|
|
564
|
+
}
|
|
565
|
+
record.forceCompileRefresh = true;
|
|
566
|
+
record.compileCacheInfo = createInitialCompileCacheInfo();
|
|
567
|
+
|
|
568
|
+
if (!compileCacheConfig || !identity.persistable) return false;
|
|
569
|
+
const physicalKey = compileCacheStorageKey(identity.key);
|
|
570
|
+
try {
|
|
571
|
+
const deleted = await compileCacheConfig.store.delete(physicalKey);
|
|
572
|
+
return deleted !== false;
|
|
573
|
+
} catch (error) {
|
|
574
|
+
emitCompileCacheError(record, "delete", error, identity);
|
|
575
|
+
throw error;
|
|
576
|
+
}
|
|
577
|
+
},
|
|
578
|
+
async clear(clearOptions = {}) {
|
|
579
|
+
assertActive();
|
|
580
|
+
if (!compileCacheConfig) return 0;
|
|
581
|
+
memoryCompileArtifacts.clear();
|
|
582
|
+
for (const record of records.values()) {
|
|
583
|
+
if (record.kind !== "source") continue;
|
|
584
|
+
record.compiledArtifact = undefined;
|
|
585
|
+
record.compiledArtifactKey = undefined;
|
|
586
|
+
record.forceCompileRefresh = true;
|
|
587
|
+
record.compileCacheInfo = createInitialCompileCacheInfo();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const prefix = clearOptions.allVersions === true
|
|
591
|
+
? compileCacheNamespacePrefix()
|
|
592
|
+
: compileCacheVersionPrefix();
|
|
593
|
+
try {
|
|
594
|
+
if (typeof compileCacheConfig.store.clear === "function") {
|
|
595
|
+
return Number(await compileCacheConfig.store.clear({ prefix })) || 0;
|
|
596
|
+
}
|
|
597
|
+
if (typeof compileCacheConfig.store.entries === "function") {
|
|
598
|
+
const entries = await compileCacheConfig.store.entries({ prefix });
|
|
599
|
+
let count = 0;
|
|
600
|
+
for (const [key] of entries) {
|
|
601
|
+
await compileCacheConfig.store.delete(key);
|
|
602
|
+
count += 1;
|
|
603
|
+
}
|
|
604
|
+
return count;
|
|
605
|
+
}
|
|
606
|
+
throw new Error("Compile cache store does not support clear() or entries().");
|
|
607
|
+
} catch (error) {
|
|
608
|
+
emitCompileCacheError(undefined, "clear", error);
|
|
609
|
+
throw error;
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
async function ensureCompiledArtifact(record, requestedMode = "use") {
|
|
616
|
+
let mode = normalizeCompileCacheMode(requestedMode);
|
|
617
|
+
if (record.forceCompileRefresh && mode === "use") mode = "refresh";
|
|
618
|
+
|
|
619
|
+
if (mode === "use" && record.compiledArtifact) {
|
|
620
|
+
record.compileCacheInfo = {
|
|
621
|
+
...record.compileCacheInfo,
|
|
622
|
+
status: "hit",
|
|
623
|
+
source: "record",
|
|
624
|
+
};
|
|
625
|
+
return record.compiledArtifact;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
let identity;
|
|
629
|
+
let identityFailed = false;
|
|
630
|
+
if (compileCacheConfig) {
|
|
631
|
+
try {
|
|
632
|
+
identity = await createCompileIdentity(record);
|
|
633
|
+
} catch (error) {
|
|
634
|
+
identityFailed = true;
|
|
635
|
+
emitCompileCacheError(record, "identity", error);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
const canUseSharedCache = Boolean(identity?.key && identity.persistable);
|
|
639
|
+
|
|
640
|
+
if (compileCacheConfig && identity && !identity.persistable) {
|
|
641
|
+
if (identity.reason === "missing-compiler-fingerprint" && !warnedMissingCompilerFingerprint) {
|
|
642
|
+
warnedMissingCompilerFingerprint = true;
|
|
643
|
+
globalThis.console?.warn?.(
|
|
644
|
+
"solid-tag-runtime: persistent compile caching is disabled for this compiler because it does not provide a stable fingerprint.",
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
emit({
|
|
648
|
+
type: "compile-cache-bypass",
|
|
649
|
+
id: record.id,
|
|
650
|
+
key: identity?.key,
|
|
651
|
+
reason: identity.reason ?? "missing-compiler-fingerprint",
|
|
652
|
+
source: "compiler",
|
|
653
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
654
|
+
});
|
|
655
|
+
} else if (compileCacheConfig && identityFailed) {
|
|
656
|
+
emit({
|
|
657
|
+
type: "compile-cache-bypass",
|
|
658
|
+
id: record.id,
|
|
659
|
+
reason: "identity-error",
|
|
660
|
+
source: "compiler",
|
|
661
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (mode === "bypass") {
|
|
666
|
+
emit({
|
|
667
|
+
type: "compile-cache-bypass",
|
|
668
|
+
id: record.id,
|
|
669
|
+
key: identity?.key,
|
|
670
|
+
reason: "requested",
|
|
671
|
+
source: "compiler",
|
|
672
|
+
store: compileCacheConfig?.store?.kind,
|
|
673
|
+
});
|
|
674
|
+
const artifact = await compileFreshArtifact(record);
|
|
675
|
+
record.compiledArtifact = artifact;
|
|
676
|
+
record.compiledArtifactKey = undefined;
|
|
677
|
+
record.forceCompileRefresh = false;
|
|
678
|
+
record.diagnostics = [...(artifact.diagnostics ?? [])];
|
|
679
|
+
record.compileCacheInfo = compileCacheInfoFrom(identity, "bypass", "compiler");
|
|
680
|
+
return artifact;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (mode === "refresh") {
|
|
684
|
+
emit({
|
|
685
|
+
type: "compile-cache-refresh",
|
|
686
|
+
id: record.id,
|
|
687
|
+
key: identity?.key,
|
|
688
|
+
source: "compiler",
|
|
689
|
+
store: compileCacheConfig?.store?.kind,
|
|
690
|
+
});
|
|
691
|
+
} else if (canUseSharedCache) {
|
|
692
|
+
const memoryArtifact = memoryCompileArtifacts.get(identity.key);
|
|
693
|
+
if (memoryArtifact) {
|
|
694
|
+
record.compiledArtifact = memoryArtifact;
|
|
695
|
+
record.compiledArtifactKey = identity.key;
|
|
696
|
+
record.forceCompileRefresh = false;
|
|
697
|
+
record.diagnostics = [...(memoryArtifact.diagnostics ?? [])];
|
|
698
|
+
record.compileCacheInfo = compileCacheInfoFrom(identity, "hit", "memory");
|
|
699
|
+
emit({
|
|
700
|
+
type: "compile-cache-hit",
|
|
701
|
+
id: record.id,
|
|
702
|
+
key: identity.key,
|
|
703
|
+
source: "memory",
|
|
704
|
+
store: "memory",
|
|
705
|
+
byteSize: approximateByteSize(memoryArtifact),
|
|
706
|
+
});
|
|
707
|
+
return memoryArtifact;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const started = now();
|
|
711
|
+
try {
|
|
712
|
+
const cachedRecord = await compileCacheConfig.store.get(compileCacheStorageKey(identity.key));
|
|
713
|
+
const durationMs = now() - started;
|
|
714
|
+
if (cachedRecord !== undefined) {
|
|
715
|
+
const validation = validateCachedCompileRecord(cachedRecord, identity);
|
|
716
|
+
if (validation.valid && !isCacheRecordExpired(cachedRecord)) {
|
|
717
|
+
const artifact = normalizeCompiledArtifact(cachedRecord.artifact);
|
|
718
|
+
memoryCompileArtifacts.set(identity.key, artifact);
|
|
719
|
+
record.compiledArtifact = artifact;
|
|
720
|
+
record.compiledArtifactKey = identity.key;
|
|
721
|
+
record.forceCompileRefresh = false;
|
|
722
|
+
record.diagnostics = [...(artifact.diagnostics ?? [])];
|
|
723
|
+
record.compileCacheInfo = compileCacheInfoFrom(identity, "hit", compileCacheConfig.store.kind ?? "persistent");
|
|
724
|
+
cachedRecord.lastUsedAt = Date.now();
|
|
725
|
+
emit({
|
|
726
|
+
type: "compile-cache-hit",
|
|
727
|
+
id: record.id,
|
|
728
|
+
key: identity.key,
|
|
729
|
+
source: compileCacheConfig.store.kind ?? "persistent",
|
|
730
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
731
|
+
durationMs,
|
|
732
|
+
byteSize: cachedRecord.byteSize,
|
|
733
|
+
});
|
|
734
|
+
return artifact;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
await compileCacheConfig.store.delete(compileCacheStorageKey(identity.key));
|
|
739
|
+
emit({
|
|
740
|
+
type: "compile-cache-evict",
|
|
741
|
+
id: record.id,
|
|
742
|
+
key: identity.key,
|
|
743
|
+
reason: validation.valid ? "maxAge" : "invalid",
|
|
744
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
745
|
+
});
|
|
746
|
+
} catch (error) {
|
|
747
|
+
emitCompileCacheError(record, "delete", error, identity);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
emit({
|
|
752
|
+
type: "compile-cache-miss",
|
|
753
|
+
id: record.id,
|
|
754
|
+
key: identity.key,
|
|
755
|
+
source: compileCacheConfig.store.kind ?? "persistent",
|
|
756
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
757
|
+
durationMs,
|
|
758
|
+
});
|
|
759
|
+
} catch (error) {
|
|
760
|
+
emitCompileCacheError(record, "read", error, identity);
|
|
761
|
+
try { await compileCacheConfig.store.delete(compileCacheStorageKey(identity.key)); } catch {}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const inFlightKey = canUseSharedCache ? identity.key : undefined;
|
|
766
|
+
if (mode === "use" && inFlightKey && inFlightCompiles.has(inFlightKey)) {
|
|
767
|
+
const artifact = await inFlightCompiles.get(inFlightKey);
|
|
768
|
+
record.compiledArtifact = artifact;
|
|
769
|
+
record.compiledArtifactKey = inFlightKey;
|
|
770
|
+
record.forceCompileRefresh = false;
|
|
771
|
+
record.diagnostics = [...(artifact.diagnostics ?? [])];
|
|
772
|
+
record.compileCacheInfo = compileCacheInfoFrom(identity, "hit", "inflight");
|
|
773
|
+
return artifact;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const compilePromise = compileFreshArtifact(record);
|
|
777
|
+
if (inFlightKey) inFlightCompiles.set(inFlightKey, compilePromise);
|
|
778
|
+
|
|
779
|
+
let artifact;
|
|
780
|
+
try {
|
|
781
|
+
artifact = await compilePromise;
|
|
782
|
+
} finally {
|
|
783
|
+
if (inFlightKey && inFlightCompiles.get(inFlightKey) === compilePromise) inFlightCompiles.delete(inFlightKey);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
record.compiledArtifact = artifact;
|
|
787
|
+
record.compiledArtifactKey = canUseSharedCache ? identity.key : undefined;
|
|
788
|
+
record.forceCompileRefresh = false;
|
|
789
|
+
record.diagnostics = [...(artifact.diagnostics ?? [])];
|
|
790
|
+
record.compileCacheInfo = compileCacheInfoFrom(identity, mode === "refresh" ? "refresh" : "miss", "compiler");
|
|
791
|
+
|
|
792
|
+
if (canUseSharedCache) {
|
|
793
|
+
memoryCompileArtifacts.set(identity.key, artifact);
|
|
794
|
+
const persistentRecord = {
|
|
795
|
+
schema: COMPILED_ARTIFACT_SCHEMA,
|
|
796
|
+
key: identity.key,
|
|
797
|
+
artifactAbi: COMPILED_ARTIFACT_ABI,
|
|
798
|
+
sourceHash: identity.sourceHash,
|
|
799
|
+
format: record.format,
|
|
800
|
+
compilerFingerprint: identity.compilerFingerprint,
|
|
801
|
+
compileContextHash: identity.compileContextHash,
|
|
802
|
+
artifact,
|
|
803
|
+
createdAt: Date.now(),
|
|
804
|
+
lastUsedAt: Date.now(),
|
|
805
|
+
};
|
|
806
|
+
persistentRecord.byteSize = approximateByteSize(persistentRecord);
|
|
807
|
+
|
|
808
|
+
await writePersistentCompileRecord(record, identity, persistentRecord);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return artifact;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function compileFreshArtifact(record) {
|
|
815
|
+
try {
|
|
816
|
+
let artifact;
|
|
817
|
+
const context = { id: record.id, format: record.format };
|
|
818
|
+
if (typeof compiler.compile === "function") {
|
|
819
|
+
artifact = await compiler.compile(record.source, context);
|
|
820
|
+
} else {
|
|
821
|
+
let code = record.source;
|
|
822
|
+
let diagnostics = [];
|
|
823
|
+
let sourceMap;
|
|
824
|
+
if (record.format !== "js") {
|
|
825
|
+
const transformed = await compiler.transform(record.source, {
|
|
826
|
+
...context,
|
|
827
|
+
htmlImportSource: "@solidjs/html",
|
|
828
|
+
});
|
|
829
|
+
code = transformed.code;
|
|
830
|
+
diagnostics = [...(transformed.diagnostics ?? [])];
|
|
831
|
+
sourceMap = transformed.sourceMap;
|
|
832
|
+
}
|
|
833
|
+
const analysis = await compiler.analyze(code, context);
|
|
834
|
+
artifact = {
|
|
835
|
+
schema: COMPILED_ARTIFACT_SCHEMA,
|
|
836
|
+
code,
|
|
837
|
+
imports: analysis.imports ?? [],
|
|
838
|
+
diagnostics,
|
|
839
|
+
...(sourceMap == null ? {} : { sourceMap }),
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
return normalizeCompiledArtifact(artifact);
|
|
843
|
+
} catch (error) {
|
|
844
|
+
record.state = "error";
|
|
845
|
+
const wrapped = error instanceof ModuleCompileError ? error : new ModuleCompileError(record.id, error);
|
|
846
|
+
emitError(record, "compile", wrapped);
|
|
847
|
+
throw wrapped;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
async function createCompileIdentity(record) {
|
|
852
|
+
const context = { id: record.id, format: record.format };
|
|
853
|
+
let compilerFingerprint;
|
|
854
|
+
try {
|
|
855
|
+
const fingerprint = typeof compiler.fingerprint === "function"
|
|
856
|
+
? await compiler.fingerprint(context)
|
|
857
|
+
: compiler.fingerprint;
|
|
858
|
+
if (typeof fingerprint === "string" && fingerprint) compilerFingerprint = fingerprint;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
emitCompileCacheError(record, "fingerprint", error);
|
|
861
|
+
return {
|
|
862
|
+
key: undefined,
|
|
863
|
+
persistable: false,
|
|
864
|
+
reason: "fingerprint-error",
|
|
865
|
+
compilerFingerprint: undefined,
|
|
866
|
+
format: record.format,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const sourceHash = await sha256(record.source);
|
|
871
|
+
const cacheContext = typeof compiler.cacheContext === "function"
|
|
872
|
+
? await compiler.cacheContext(context)
|
|
873
|
+
: { format: record.format };
|
|
874
|
+
const compileContextHash = await sha256(stableSerialize(cacheContext ?? {}));
|
|
875
|
+
|
|
876
|
+
const persistable = Boolean(compilerFingerprint);
|
|
877
|
+
if (!persistable) {
|
|
878
|
+
return {
|
|
879
|
+
key: undefined,
|
|
880
|
+
persistable: false,
|
|
881
|
+
reason: "missing-compiler-fingerprint",
|
|
882
|
+
compilerFingerprint: undefined,
|
|
883
|
+
sourceHash,
|
|
884
|
+
compileContextHash,
|
|
885
|
+
format: record.format,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
const key = await sha256(stableSerialize({
|
|
890
|
+
artifactAbi: COMPILED_ARTIFACT_ABI,
|
|
891
|
+
compilerFingerprint,
|
|
892
|
+
format: record.format,
|
|
893
|
+
compileContextHash,
|
|
894
|
+
sourceHash,
|
|
895
|
+
}));
|
|
896
|
+
|
|
897
|
+
return {
|
|
898
|
+
key,
|
|
899
|
+
persistable: true,
|
|
900
|
+
compilerFingerprint,
|
|
901
|
+
sourceHash,
|
|
902
|
+
compileContextHash,
|
|
903
|
+
format: record.format,
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function compileCacheInfoFrom(identity, status, source) {
|
|
908
|
+
return {
|
|
909
|
+
enabled: Boolean(compileCacheConfig),
|
|
910
|
+
status,
|
|
911
|
+
source,
|
|
912
|
+
key: identity?.key,
|
|
913
|
+
compilerFingerprint: identity?.compilerFingerprint,
|
|
914
|
+
sourceHash: identity?.sourceHash,
|
|
915
|
+
compileContextHash: identity?.compileContextHash,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
function validateCachedCompileRecord(cachedRecord, identity) {
|
|
920
|
+
if (!cachedRecord || typeof cachedRecord !== "object") return { valid: false, reason: "record" };
|
|
921
|
+
if (cachedRecord.schema !== COMPILED_ARTIFACT_SCHEMA) return { valid: false, reason: "schema" };
|
|
922
|
+
if (cachedRecord.artifactAbi !== COMPILED_ARTIFACT_ABI) return { valid: false, reason: "artifact-abi" };
|
|
923
|
+
if (cachedRecord.key !== identity.key) return { valid: false, reason: "key" };
|
|
924
|
+
if (cachedRecord.sourceHash !== identity.sourceHash) return { valid: false, reason: "source-hash" };
|
|
925
|
+
if (cachedRecord.format !== identity.format) return { valid: false, reason: "format" };
|
|
926
|
+
if (cachedRecord.compilerFingerprint !== identity.compilerFingerprint) return { valid: false, reason: "compiler-fingerprint" };
|
|
927
|
+
if (cachedRecord.compileContextHash !== identity.compileContextHash) return { valid: false, reason: "compile-context" };
|
|
928
|
+
try {
|
|
929
|
+
normalizeCompiledArtifact(cachedRecord.artifact);
|
|
930
|
+
} catch {
|
|
931
|
+
return { valid: false, reason: "artifact" };
|
|
932
|
+
}
|
|
933
|
+
return { valid: true };
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function isCacheRecordExpired(cachedRecord) {
|
|
937
|
+
const maxAge = compileCacheConfig?.maxAge;
|
|
938
|
+
return Number.isFinite(maxAge) && maxAge >= 0 && Date.now() - Number(cachedRecord.createdAt ?? 0) > maxAge;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function writePersistentCompileRecord(record, identity, persistentRecord) {
|
|
942
|
+
const storageKey = compileCacheStorageKey(identity.key);
|
|
943
|
+
try {
|
|
944
|
+
await compileCacheConfig.store.set(storageKey, persistentRecord);
|
|
945
|
+
} catch (error) {
|
|
946
|
+
if (!isQuotaError(error)) {
|
|
947
|
+
emitCompileCacheError(record, "write", error, identity);
|
|
948
|
+
return false;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
await pruneCompileCache(record, { quotaRecovery: true });
|
|
952
|
+
try {
|
|
953
|
+
await compileCacheConfig.store.set(storageKey, persistentRecord);
|
|
954
|
+
} catch (retryError) {
|
|
955
|
+
emitCompileCacheError(record, "write", retryError, identity);
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
emit({
|
|
961
|
+
type: "compile-cache-write",
|
|
962
|
+
id: record.id,
|
|
963
|
+
key: identity.key,
|
|
964
|
+
source: "compiler",
|
|
965
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
966
|
+
byteSize: persistentRecord.byteSize,
|
|
967
|
+
});
|
|
968
|
+
await pruneCompileCache(record);
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function pruneCompileCache(record, pruneOptions = {}) {
|
|
973
|
+
if (!compileCacheConfig || typeof compileCacheConfig.store.entries !== "function") return 0;
|
|
974
|
+
const { maxEntries, maxBytes, maxAge } = compileCacheConfig;
|
|
975
|
+
const quotaRecovery = pruneOptions.quotaRecovery === true;
|
|
976
|
+
if (!quotaRecovery && !Number.isFinite(maxEntries) && !Number.isFinite(maxBytes) && !Number.isFinite(maxAge)) return 0;
|
|
977
|
+
|
|
978
|
+
try {
|
|
979
|
+
const entries = await compileCacheConfig.store.entries({ prefix: compileCacheVersionPrefix() });
|
|
980
|
+
const nowValue = Date.now();
|
|
981
|
+
const live = [];
|
|
982
|
+
let evicted = 0;
|
|
983
|
+
for (const [key, value] of entries) {
|
|
984
|
+
if (Number.isFinite(maxAge) && maxAge >= 0 && nowValue - Number(value?.createdAt ?? 0) > maxAge) {
|
|
985
|
+
await compileCacheConfig.store.delete(key);
|
|
986
|
+
evicted += 1;
|
|
987
|
+
emit({ type: "compile-cache-evict", id: record?.id, key: value?.key, reason: "maxAge", store: compileCacheConfig.store.kind ?? "custom" });
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
live.push([key, value]);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
live.sort((a, b) => Number(a[1]?.lastUsedAt ?? a[1]?.createdAt ?? 0) - Number(b[1]?.lastUsedAt ?? b[1]?.createdAt ?? 0));
|
|
994
|
+
let totalBytes = live.reduce((sum, [, value]) => sum + Number(value?.byteSize ?? 0), 0);
|
|
995
|
+
|
|
996
|
+
while (
|
|
997
|
+
(Number.isFinite(maxEntries) && live.length > maxEntries) ||
|
|
998
|
+
(Number.isFinite(maxBytes) && totalBytes > maxBytes)
|
|
999
|
+
) {
|
|
1000
|
+
const [key, value] = live.shift();
|
|
1001
|
+
await compileCacheConfig.store.delete(key);
|
|
1002
|
+
totalBytes -= Number(value?.byteSize ?? 0);
|
|
1003
|
+
evicted += 1;
|
|
1004
|
+
emit({
|
|
1005
|
+
type: "compile-cache-evict",
|
|
1006
|
+
id: record?.id,
|
|
1007
|
+
key: value?.key,
|
|
1008
|
+
reason: Number.isFinite(maxEntries) && live.length + 1 > maxEntries ? "maxEntries" : "maxBytes",
|
|
1009
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
1010
|
+
byteSize: value?.byteSize,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (quotaRecovery && evicted === 0 && live.length > 0) {
|
|
1015
|
+
const removeCount = Math.max(1, Math.ceil(live.length * 0.1));
|
|
1016
|
+
for (let index = 0; index < removeCount && live.length > 0; index += 1) {
|
|
1017
|
+
const [key, value] = live.shift();
|
|
1018
|
+
await compileCacheConfig.store.delete(key);
|
|
1019
|
+
evicted += 1;
|
|
1020
|
+
emit({
|
|
1021
|
+
type: "compile-cache-evict",
|
|
1022
|
+
id: record?.id,
|
|
1023
|
+
key: value?.key,
|
|
1024
|
+
reason: "quota",
|
|
1025
|
+
store: compileCacheConfig.store.kind ?? "custom",
|
|
1026
|
+
byteSize: value?.byteSize,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
return evicted;
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
emitCompileCacheError(record, "prune", error);
|
|
1033
|
+
return 0;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function emitCompileCacheError(record, operation, error, identity) {
|
|
1038
|
+
emit({
|
|
1039
|
+
type: "compile-cache-error",
|
|
1040
|
+
id: record?.id,
|
|
1041
|
+
key: identity?.key,
|
|
1042
|
+
operation,
|
|
1043
|
+
store: compileCacheConfig?.store?.kind ?? (compileCacheConfig ? "custom" : undefined),
|
|
1044
|
+
error,
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function compileCacheNamespacePrefix() {
|
|
1049
|
+
return `solid-tag-runtime/compile/${encodeURIComponent(compileCacheConfig.namespace)}/`;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
function compileCacheVersionPrefix() {
|
|
1053
|
+
return `${compileCacheNamespacePrefix()}${encodeURIComponent(compileCacheConfig.version)}/`;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function compileCacheStorageKey(key) {
|
|
1057
|
+
return `${compileCacheVersionPrefix()}${key}`;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
async function ensureSourceUrl(record, stack, linkOptions = {}) {
|
|
497
1061
|
if (record.url) return record.url;
|
|
498
1062
|
|
|
499
1063
|
if (stack.includes(record.id)) {
|
|
@@ -512,21 +1076,12 @@ export function createRuntime(options = {}) {
|
|
|
512
1076
|
version: record.version,
|
|
513
1077
|
});
|
|
514
1078
|
|
|
515
|
-
|
|
516
|
-
try {
|
|
517
|
-
analysis = compiler.analyze(record.source, { id: record.id, format: record.format });
|
|
518
|
-
} catch (error) {
|
|
519
|
-
record.state = "error";
|
|
520
|
-
const wrapped = new ModuleCompileError(record.id, error);
|
|
521
|
-
emitError(record, "analyze", wrapped);
|
|
522
|
-
throw wrapped;
|
|
523
|
-
}
|
|
524
|
-
|
|
1079
|
+
const artifact = await ensureCompiledArtifact(record, linkOptions.cacheMode ?? "use");
|
|
525
1080
|
const resolvedImports = [];
|
|
526
1081
|
const linkedDependencies = [];
|
|
527
1082
|
const nextDependencies = new Set();
|
|
528
1083
|
|
|
529
|
-
for (const reference of
|
|
1084
|
+
for (const reference of artifact.imports ?? []) {
|
|
530
1085
|
let resolved;
|
|
531
1086
|
try {
|
|
532
1087
|
resolved = resolveSpecifier(reference.specifier, record.id);
|
|
@@ -544,7 +1099,7 @@ export function createRuntime(options = {}) {
|
|
|
544
1099
|
nextDependencies.add(dependencyRecord.id);
|
|
545
1100
|
|
|
546
1101
|
if (dependencyRecord.kind === "source") {
|
|
547
|
-
replacement = await ensureSourceUrl(dependencyRecord, nextStack);
|
|
1102
|
+
replacement = await ensureSourceUrl(dependencyRecord, nextStack, { cacheMode: "use" });
|
|
548
1103
|
} else if (dependencyRecord.kind === "host") {
|
|
549
1104
|
replacement = await ensureHostUrl(dependencyRecord);
|
|
550
1105
|
} else {
|
|
@@ -570,35 +1125,7 @@ export function createRuntime(options = {}) {
|
|
|
570
1125
|
|
|
571
1126
|
let linkedSource;
|
|
572
1127
|
try {
|
|
573
|
-
linkedSource = applyImportReplacements(
|
|
574
|
-
|
|
575
|
-
// JSX produced by solid-tag needs @solidjs/html. Resolve it through the
|
|
576
|
-
// same module system so host applications can share their existing runtime.
|
|
577
|
-
let htmlImportSource;
|
|
578
|
-
const needsHtmlRuntime = record.format !== "js" && analysis.needsHtmlRuntime !== false;
|
|
579
|
-
if (needsHtmlRuntime) {
|
|
580
|
-
const htmlResolved = resolveSpecifier("@solidjs/html", record.id);
|
|
581
|
-
const htmlRecord = records.get(htmlResolved);
|
|
582
|
-
|
|
583
|
-
if (htmlRecord) {
|
|
584
|
-
nextDependencies.add(htmlRecord.id);
|
|
585
|
-
htmlImportSource = htmlRecord.kind === "source"
|
|
586
|
-
? await ensureSourceUrl(htmlRecord, nextStack)
|
|
587
|
-
: htmlRecord.kind === "host"
|
|
588
|
-
? await ensureHostUrl(htmlRecord)
|
|
589
|
-
: htmlRecord.externalUrl;
|
|
590
|
-
} else if (allowNativeImports) {
|
|
591
|
-
htmlImportSource = htmlResolved;
|
|
592
|
-
} else {
|
|
593
|
-
throw new ModuleResolutionError("@solidjs/html", record.id);
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
linkedDependencies.push({
|
|
597
|
-
specifier: "@solidjs/html",
|
|
598
|
-
resolvedId: htmlRecord?.id ?? htmlResolved,
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
1128
|
+
linkedSource = applyImportReplacements(artifact.code, resolvedImports);
|
|
602
1129
|
syncDependencyEdges(record, nextDependencies);
|
|
603
1130
|
emit({
|
|
604
1131
|
type: "module-linked",
|
|
@@ -608,31 +1135,11 @@ export function createRuntime(options = {}) {
|
|
|
608
1135
|
dependencies: linkedDependencies,
|
|
609
1136
|
module: toModuleInfo(record),
|
|
610
1137
|
});
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
if (record.format === "js") {
|
|
614
|
-
record.compiledCode = linkedSource;
|
|
615
|
-
record.diagnostics = [];
|
|
616
|
-
} else {
|
|
617
|
-
const transformed = compiler.transform(linkedSource, {
|
|
618
|
-
id: record.id,
|
|
619
|
-
format: record.format,
|
|
620
|
-
htmlImportSource,
|
|
621
|
-
});
|
|
622
|
-
record.compiledCode = transformed.code;
|
|
623
|
-
record.diagnostics = [...(transformed.diagnostics ?? [])];
|
|
624
|
-
}
|
|
625
|
-
} catch (error) {
|
|
626
|
-
record.state = "error";
|
|
627
|
-
const wrapped = new ModuleCompileError(record.id, error);
|
|
628
|
-
emitError(record, "compile", wrapped);
|
|
629
|
-
throw wrapped;
|
|
630
|
-
}
|
|
1138
|
+
record.compiledCode = linkedSource;
|
|
1139
|
+
record.diagnostics = [...(artifact.diagnostics ?? [])];
|
|
631
1140
|
} catch (error) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
|
|
635
|
-
}
|
|
1141
|
+
record.state = "error";
|
|
1142
|
+
emitError(record, error instanceof ModuleResolutionError ? "resolve" : "link", error);
|
|
636
1143
|
throw error;
|
|
637
1144
|
}
|
|
638
1145
|
|
|
@@ -805,6 +1312,7 @@ export function createRuntime(options = {}) {
|
|
|
805
1312
|
dependents: [...record.dependents],
|
|
806
1313
|
url: record.kind === "url" ? record.externalUrl : record.url,
|
|
807
1314
|
diagnostics: [...(record.diagnostics ?? [])],
|
|
1315
|
+
...(record.kind === "source" ? { compileCache: { ...record.compileCacheInfo } } : {}),
|
|
808
1316
|
};
|
|
809
1317
|
}
|
|
810
1318
|
}
|
|
@@ -911,3 +1419,77 @@ function randomId() {
|
|
|
911
1419
|
if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
|
|
912
1420
|
return Math.random().toString(36).slice(2);
|
|
913
1421
|
}
|
|
1422
|
+
|
|
1423
|
+
function normalizeCompileCacheOptions(options) {
|
|
1424
|
+
if (options == null || options === false || options.enabled === false) return undefined;
|
|
1425
|
+
if (typeof options !== "object") {
|
|
1426
|
+
throw new TypeError("compileCache must be an options object with a store.");
|
|
1427
|
+
}
|
|
1428
|
+
const store = options.store;
|
|
1429
|
+
if (!store || typeof store.get !== "function" || typeof store.set !== "function" || typeof store.delete !== "function") {
|
|
1430
|
+
throw new TypeError("compileCache.store must provide async get(), set(), and delete() methods.");
|
|
1431
|
+
}
|
|
1432
|
+
return {
|
|
1433
|
+
store,
|
|
1434
|
+
namespace: String(options.namespace ?? "default"),
|
|
1435
|
+
version: String(options.version ?? "1"),
|
|
1436
|
+
maxEntries: normalizeLimit(options.maxEntries),
|
|
1437
|
+
maxBytes: normalizeLimit(options.maxBytes),
|
|
1438
|
+
maxAge: normalizeLimit(options.maxAge),
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
function normalizeCompileCacheMode(mode) {
|
|
1443
|
+
if (mode == null) return "use";
|
|
1444
|
+
if (mode === "use" || mode === "refresh" || mode === "bypass") return mode;
|
|
1445
|
+
throw new TypeError(`Unknown compile cache mode ${JSON.stringify(mode)}. Expected "use", "refresh", or "bypass".`);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
function normalizeCompiledArtifact(artifact) {
|
|
1449
|
+
if (!artifact || typeof artifact !== "object") {
|
|
1450
|
+
throw new TypeError("Compiler returned an invalid compiled artifact.");
|
|
1451
|
+
}
|
|
1452
|
+
const code = String(artifact.code ?? "");
|
|
1453
|
+
const imports = Array.isArray(artifact.imports) ? artifact.imports.map((reference, index) => {
|
|
1454
|
+
if (!reference || typeof reference !== "object") {
|
|
1455
|
+
throw new TypeError(`Compiled artifact import ${index} is invalid.`);
|
|
1456
|
+
}
|
|
1457
|
+
const start = Number(reference.start);
|
|
1458
|
+
const end = Number(reference.end);
|
|
1459
|
+
const specifier = String(reference.specifier ?? "");
|
|
1460
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end <= start || end > code.length) {
|
|
1461
|
+
throw new TypeError(`Compiled artifact import ${JSON.stringify(specifier)} has invalid source offsets.`);
|
|
1462
|
+
}
|
|
1463
|
+
return {
|
|
1464
|
+
specifier,
|
|
1465
|
+
start,
|
|
1466
|
+
end,
|
|
1467
|
+
kind: reference.kind ?? "static",
|
|
1468
|
+
};
|
|
1469
|
+
}) : [];
|
|
1470
|
+
|
|
1471
|
+
return {
|
|
1472
|
+
schema: COMPILED_ARTIFACT_SCHEMA,
|
|
1473
|
+
code,
|
|
1474
|
+
imports,
|
|
1475
|
+
diagnostics: Array.isArray(artifact.diagnostics) ? [...artifact.diagnostics] : [],
|
|
1476
|
+
...(artifact.sourceMap == null ? {} : { sourceMap: String(artifact.sourceMap) }),
|
|
1477
|
+
...(artifact.changed == null ? {} : { changed: Boolean(artifact.changed) }),
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
function isQuotaError(error) {
|
|
1482
|
+
const name = error?.name;
|
|
1483
|
+
const code = error?.code;
|
|
1484
|
+
return name === "QuotaExceededError" || name === "NS_ERROR_DOM_QUOTA_REACHED" || code === 22 || code === 1014;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function normalizeLimit(value) {
|
|
1488
|
+
if (value == null) return undefined;
|
|
1489
|
+
const number = Number(value);
|
|
1490
|
+
return Number.isFinite(number) && number >= 0 ? number : undefined;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function now() {
|
|
1494
|
+
return globalThis.performance?.now?.() ?? Date.now();
|
|
1495
|
+
}
|